From 8db1ca87846ef82e29b747d27dfc8f657cb7791a Mon Sep 17 00:00:00 2001 From: Valerie Peng Date: Mon, 27 Apr 2026 17:06:47 +0000 Subject: [PATCH 001/169] 8380715: Enhance PKCS5 padding Reviewed-by: jnimeh, pkumaraswamy, rhalade --- .../com/sun/crypto/provider/PKCS5Padding.java | 36 ++++++++------- .../sun/security/pkcs11/P11Cipher.java | 44 ++++++++++++------- 2 files changed, 48 insertions(+), 32 deletions(-) diff --git a/src/java.base/share/classes/com/sun/crypto/provider/PKCS5Padding.java b/src/java.base/share/classes/com/sun/crypto/provider/PKCS5Padding.java index a8ba711d658c..3aee3e633fe4 100644 --- a/src/java.base/share/classes/com/sun/crypto/provider/PKCS5Padding.java +++ b/src/java.base/share/classes/com/sun/crypto/provider/PKCS5Padding.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -91,25 +91,27 @@ public int unpad(byte[] in, int off, int len) { (len == 0)) { // this can happen if input is really a padded buffer return 0; } - int idx = Math.addExact(off, len); - byte lastByte = in[idx - 1]; - int padValue = (int)lastByte & 0x0ff; - if ((padValue < 0x01) - || (padValue > blockSize)) { - return -1; - } - int start = idx - padValue; - if (start < off) { - return -1; - } + int last = Math.addExact(off, len); + int padValue = in[last - 1] & 0x0ff; + // check padValue >= 1; negative if fail + int invalid = (padValue - 1); + // check padValue <= blockSize; negative if fail + invalid |= (blockSize - padValue); - for (int i = start; i < idx; i++) { - if (in[i] != lastByte) { - return -1; - } + // check the last block for the padding bytes + for (int i = last - blockSize; i < last ; i++) { + // mask = 0 for the first (blockSize - padValue) bytes in the block + // otherwise, use 0xffff to preserve the value + int mask = (last - padValue - i - 1) >> 8 ; + int valueCheck = ((in[i] & 0x0ff) ^ padValue) & mask; + // again, assign negative value if fail + invalid |= -valueCheck; } - return start; + // convert 'invalid' to -1 when 'invalid' is not 0 + // right shifting by 8 should be sufficient since value range is small + invalid >>= 8; // 0 or -1 + return (invalid | (last - padValue)); } /** diff --git a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11Cipher.java b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11Cipher.java index 39827af42e54..e349251cb10c 100644 --- a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11Cipher.java +++ b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11Cipher.java @@ -71,9 +71,8 @@ private static interface Padding { int setPaddingBytes(byte[] paddingBuffer, int startOff, int padLen); // DEC: return the length of trailing padding bytes given the specified - // padded data - int unpad(byte[] paddedData, int len) - throws BadPaddingException, IllegalBlockSizeException; + // padded data, returns -1 if invalid + int unpad(byte[] paddedData, int len) throws IllegalBlockSizeException; } private static class PKCS5Padding implements Padding { @@ -95,23 +94,32 @@ public int setPaddingBytes(byte[] paddingBuffer, int startOff, int padLen) { } public int unpad(byte[] paddedData, int len) - throws BadPaddingException, IllegalBlockSizeException { + throws IllegalBlockSizeException { if ((len < 1) || (len % blockSize != 0)) { throw new IllegalBlockSizeException ("Input length must be multiples of " + blockSize); } - byte padValue = paddedData[len - 1]; - if (padValue < 1 || padValue > blockSize) { - throw new BadPaddingException("Invalid pad value!"); - } - // sanity check padding bytes - int padStartIndex = len - padValue; - for (int i = padStartIndex; i < len; i++) { - if (paddedData[i] != padValue) { - throw new BadPaddingException("Invalid pad bytes!"); - } + + int padValue = paddedData[len - 1] & 0x0ff; + // check padValue >= 1; negative if fail + int invalid = (padValue - 1); + // check padValue <= blockSize; negative if fail + invalid |= (blockSize - padValue); + + // check the last block for the padding bytes + for (int i = len - blockSize; i < len; i++) { + // mask = 0 for the first (blockSize - padValue) bytes in the + // block. Otherwise, use 0xffff to preserve the value + int mask = (len - padValue - i - 1) >> 8 ; + int valueCheck = ((paddedData[i] & 0x0ff) ^ padValue) & mask; + // again, assign negative value if fail + invalid |= -valueCheck; } - return padValue; + // convert 'invalid' to -1 when 'invalid' is not 0 + // right shifting by 8 should be sufficient since value range is + // small + invalid >>= 8; // 0 or -1 + return (invalid | padValue); } } @@ -970,6 +978,9 @@ private int implDoFinal(byte[] out, int outOfs, int outLen) padBuffer.length - k); int actualPadLen = paddingObj.unpad(padBuffer, k); + if (actualPadLen < 0) { + throw new BadPaddingException("Invalid pad value!"); + } k -= actualPadLen; System.arraycopy(padBuffer, 0, out, outOfs, k); } else { @@ -1084,6 +1095,9 @@ private int implDoFinal(ByteBuffer outBuffer) 0, padBuffer, k, padBuffer.length - k); int actualPadLen = paddingObj.unpad(padBuffer, k); + if (actualPadLen < 0) { + throw new BadPaddingException("Invalid pad value!"); + } k -= actualPadLen; outArray = padBuffer; outOfs = 0; From 3895fe128e6109f85004ec5f1cf2d00657a7cb50 Mon Sep 17 00:00:00 2001 From: Mark Powers Date: Tue, 28 Apr 2026 14:33:42 +0000 Subject: [PATCH 002/169] 8382178: Enhance TLS certificate handling Reviewed-by: mschoene, hchao, rhalade, mullan, weijun --- .../security/validator/CADistrustPolicy.java | 30 ++++----- .../validator/CamerfirmaTLSPolicy.java | 33 ++-------- .../security/validator/ChunghwaTLSPolicy.java | 15 ++++- .../security/validator/EntrustTLSPolicy.java | 30 ++------- .../security/validator/SymantecTLSPolicy.java | 65 ++----------------- .../share/conf/security/java.security | 17 ++--- .../distrust/Camerfirma.java | 6 +- .../distrust/Chunghwa.java | 9 ++- .../distrust/Entrust.java | 4 +- .../distrust/Symantec.java | 10 +-- 10 files changed, 65 insertions(+), 154 deletions(-) diff --git a/src/java.base/share/classes/sun/security/validator/CADistrustPolicy.java b/src/java.base/share/classes/sun/security/validator/CADistrustPolicy.java index 44c9ed2d0754..c1afe4246125 100644 --- a/src/java.base/share/classes/sun/security/validator/CADistrustPolicy.java +++ b/src/java.base/share/classes/sun/security/validator/CADistrustPolicy.java @@ -36,12 +36,9 @@ */ enum CADistrustPolicy { /** - * Distrust TLS Server certificates anchored by a Symantec root CA and - * issued after April 16, 2019 (with exceptions for a couple of subordinate - * CAs, see the jdk.security.caDistrustPolicies definition in the - * java.security file for more details). If enabled, this policy is - * currently enforced by the PKIX and SunX509 TrustManager implementations - * of the SunJSSE provider implementation. + * Distrust TLS Server certificates anchored by a Symantec root CA. + * If enabled, this policy is currently enforced by the PKIX and SunX509 + * TrustManager implementations of the SunJSSE provider implementation. */ SYMANTEC_TLS { void checkDistrust(String variant, X509Certificate[] chain) @@ -54,10 +51,9 @@ void checkDistrust(String variant, X509Certificate[] chain) }, /** - * Distrust TLS Server certificates anchored by an Entrust root CA and - * issued after November 11, 2024. If enabled, this policy is currently - * enforced by the PKIX and SunX509 TrustManager implementations - * of the SunJSSE provider implementation. + * Distrust TLS Server certificates anchored by an Entrust root CA. + * If enabled, this policy is currently enforced by the PKIX and SunX509 + * TrustManager implementations of the SunJSSE provider implementation. */ ENTRUST_TLS { void checkDistrust(String variant, X509Certificate[] chain) @@ -70,10 +66,9 @@ void checkDistrust(String variant, X509Certificate[] chain) }, /** - * Distrust TLS Server certificates anchored by a CAMERFIRMA root CA and - * issued after April 15, 2025. If enabled, this policy is currently - * enforced by the PKIX and SunX509 TrustManager implementations - * of the SunJSSE provider implementation. + * Distrust TLS Server certificates anchored by a CAMERFIRMA root CA. + * If enabled, this policy is currently enforced by the PKIX and SunX509 + * TrustManager implementations of the SunJSSE provider implementation. */ CAMERFIRMA_TLS { void checkDistrust(String variant, X509Certificate[] chain) @@ -87,9 +82,10 @@ void checkDistrust(String variant, X509Certificate[] chain) /** * Distrust TLS Server certificates anchored by the Chunghwa ePKI root CA - * and issued after March 17, 2026. If enabled, this policy is currently - * enforced by the PKIX and SunX509 TrustManager implementations - * of the SunJSSE provider implementation. + * and issued after March 17, 2026. After APRIL 19, 2027, certificates will + * be distrusted regardless of the issued date in the certificate. + * If enabled, this policy is currently enforced by the PKIX and SunX509 + * TrustManager implementations of the SunJSSE provider implementation. */ CHUNGHWA_TLS { void checkDistrust(String variant, X509Certificate[] chain) diff --git a/src/java.base/share/classes/sun/security/validator/CamerfirmaTLSPolicy.java b/src/java.base/share/classes/sun/security/validator/CamerfirmaTLSPolicy.java index 984f5a0ab47f..fb6db1126893 100644 --- a/src/java.base/share/classes/sun/security/validator/CamerfirmaTLSPolicy.java +++ b/src/java.base/share/classes/sun/security/validator/CamerfirmaTLSPolicy.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,12 +25,6 @@ package sun.security.validator; import java.security.cert.X509Certificate; -import java.time.LocalDate; -import java.time.Month; -import java.time.ZoneOffset; -import java.util.Date; -import java.util.Map; -import java.util.Set; import sun.security.util.Debug; import sun.security.x509.X509CertImpl; @@ -53,9 +47,7 @@ final class CamerfirmaTLSPolicy { "063E4AFAC491DFD332F3089B8542E94617D893D7FE944E10A7937EE29D9693C0"; // Any TLS Server certificate that is anchored by one of the Camerfirma - // roots above and is issued after this date will be distrusted. - private static final LocalDate APRIL_15_2025 = - LocalDate.of(2025, Month.APRIL, 15); + // roots above will be distrusted. /** * This method assumes the eeCert is a TLS Server Cert and chains back to @@ -74,11 +66,10 @@ static void checkDistrust(X509Certificate[] chain) + "trust anchor of TLS server certificate"); } if (FINGERPRINT.equalsIgnoreCase(fp)) { - Date notBefore = chain[0].getNotBefore(); - LocalDate ldNotBefore = LocalDate.ofInstant(notBefore.toInstant(), - ZoneOffset.UTC); - // reject if certificate is issued after April 15, 2025 - checkNotBefore(ldNotBefore, APRIL_15_2025, anchor); + throw new ValidatorException + ("TLS Server certificate anchored by a distrusted legacy " + + "Camerfirma root CA: "+ anchor.getSubjectX500Principal(), + ValidatorException.T_UNTRUSTED_CERT, anchor); } } @@ -86,17 +77,5 @@ private static String fingerprint(X509Certificate cert) { return X509CertImpl.getFingerprint("SHA-256", cert, debug); } - private static void checkNotBefore(LocalDate notBeforeDate, - LocalDate distrustDate, X509Certificate anchor) - throws ValidatorException { - if (notBeforeDate.isAfter(distrustDate)) { - throw new ValidatorException - ("TLS Server certificate issued after " + distrustDate + - " and anchored by a distrusted legacy Camerfirma root CA: " - + anchor.getSubjectX500Principal(), - ValidatorException.T_UNTRUSTED_CERT, anchor); - } - } - private CamerfirmaTLSPolicy() {} } diff --git a/src/java.base/share/classes/sun/security/validator/ChunghwaTLSPolicy.java b/src/java.base/share/classes/sun/security/validator/ChunghwaTLSPolicy.java index 114f5043fbf9..e7f0642f8606 100644 --- a/src/java.base/share/classes/sun/security/validator/ChunghwaTLSPolicy.java +++ b/src/java.base/share/classes/sun/security/validator/ChunghwaTLSPolicy.java @@ -29,8 +29,6 @@ import java.time.Month; import java.time.ZoneOffset; import java.util.Date; -import java.util.Map; -import java.util.Set; import sun.security.util.Debug; import sun.security.x509.X509CertImpl; @@ -55,6 +53,12 @@ final class ChunghwaTLSPolicy { private static final LocalDate MARCH_17_2026 = LocalDate.of(2026, Month.MARCH, 17); + // Any TLS Server certificate that is anchored by the Chunghwa + // root above is distrusted after APRIL_19_2027. + // APRIL_19_2027 is MARCH_17_2026 plus 398 days. + private static final LocalDate APRIL_19_2027 = + LocalDate.of(2027, Month.APRIL, 19); + /** * This method assumes the eeCert is a TLS Server Cert and chains back to * the anchor. @@ -72,6 +76,13 @@ static void checkDistrust(X509Certificate[] chain) + "trust anchor of TLS server certificate"); } if (FINGERPRINT.equalsIgnoreCase(fp)) { + if (LocalDate.now().isAfter(APRIL_19_2027)) { + throw new ValidatorException + ("TLS Server certificate anchored by a distrusted legacy " + + "Chunghwa root CA: " + anchor.getSubjectX500Principal(), + ValidatorException.T_UNTRUSTED_CERT, anchor); + } + Date notBefore = chain[0].getNotBefore(); LocalDate ldNotBefore = LocalDate.ofInstant(notBefore.toInstant(), ZoneOffset.UTC); diff --git a/src/java.base/share/classes/sun/security/validator/EntrustTLSPolicy.java b/src/java.base/share/classes/sun/security/validator/EntrustTLSPolicy.java index 831d1088ed35..efb84ac8bc99 100644 --- a/src/java.base/share/classes/sun/security/validator/EntrustTLSPolicy.java +++ b/src/java.base/share/classes/sun/security/validator/EntrustTLSPolicy.java @@ -25,11 +25,6 @@ package sun.security.validator; import java.security.cert.X509Certificate; -import java.time.LocalDate; -import java.time.Month; -import java.time.ZoneOffset; -import java.util.Date; -import java.util.Map; import java.util.Set; import sun.security.util.Debug; @@ -70,9 +65,7 @@ final class EntrustTLSPolicy { ); // Any TLS Server certificate that is anchored by one of the Entrust - // roots above and is issued after this date will be distrusted. - private static final LocalDate NOVEMBER_11_2024 = - LocalDate.of(2024, Month.NOVEMBER, 11); + // roots above will be distrusted. /** * This method assumes the eeCert is a TLS Server Cert and chains back to @@ -91,11 +84,10 @@ static void checkDistrust(X509Certificate[] chain) + "trust anchor of TLS server certificate"); } if (FINGERPRINTS.contains(fp)) { - Date notBefore = chain[0].getNotBefore(); - LocalDate ldNotBefore = LocalDate.ofInstant(notBefore.toInstant(), - ZoneOffset.UTC); - // reject if certificate is issued after November 11, 2024 - checkNotBefore(ldNotBefore, NOVEMBER_11_2024, anchor); + throw new ValidatorException + ("TLS Server certificate anchored by a distrusted legacy " + + "Entrust root CA: "+ anchor.getSubjectX500Principal(), + ValidatorException.T_UNTRUSTED_CERT, anchor); } } @@ -103,17 +95,5 @@ private static String fingerprint(X509Certificate cert) { return X509CertImpl.getFingerprint("SHA-256", cert, debug); } - private static void checkNotBefore(LocalDate notBeforeDate, - LocalDate distrustDate, X509Certificate anchor) - throws ValidatorException { - if (notBeforeDate.isAfter(distrustDate)) { - throw new ValidatorException - ("TLS Server certificate issued after " + distrustDate + - " and anchored by a distrusted legacy Entrust root CA: " - + anchor.getSubjectX500Principal(), - ValidatorException.T_UNTRUSTED_CERT, anchor); - } - } - private EntrustTLSPolicy() {} } diff --git a/src/java.base/share/classes/sun/security/validator/SymantecTLSPolicy.java b/src/java.base/share/classes/sun/security/validator/SymantecTLSPolicy.java index a481a05c752b..01c090e7eb9f 100644 --- a/src/java.base/share/classes/sun/security/validator/SymantecTLSPolicy.java +++ b/src/java.base/share/classes/sun/security/validator/SymantecTLSPolicy.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,11 +25,6 @@ package sun.security.validator; import java.security.cert.X509Certificate; -import java.time.LocalDate; -import java.time.Month; -import java.time.ZoneOffset; -import java.util.Date; -import java.util.Map; import java.util.Set; import sun.security.util.Debug; @@ -123,28 +118,8 @@ final class SymantecTLSPolicy { "2399561127A57125DE8CEFEA610DDF2FA078B5C8067F4E828290BFB860E84B3C" ); - private static final LocalDate DECEMBER_31_2019 = - LocalDate.of(2019, Month.DECEMBER, 31); - // SHA-256 certificate fingerprints of subCAs with later distrust dates - private static final Map EXEMPT_SUBCAS = Map.of( - // Subject DN: C=US, O=Apple Inc., OU=Certification Authority, - // CN=Apple IST CA 2 - G1 - // Issuer DN: CN=GeoTrust Global CA, O=GeoTrust Inc., C=US - "AC2B922ECFD5E01711772FEA8ED372DE9D1E2245FCE3F57A9CDBEC77296A424B", - DECEMBER_31_2019, - // Subject DN: C=US, O=Apple Inc., OU=Certification Authority, - // CN=Apple IST CA 8 - G1 - // Issuer DN: CN=GeoTrust Primary Certification Authority - G2, - // OU=(c) 2007 GeoTrust Inc. - For authorized use only, - // O=GeoTrust Inc., C=US - "A4FE7C7F15155F3F0AEF7AAA83CF6E06DEB97CA3F909DF920AC1490882D488ED", - DECEMBER_31_2019 - ); - // Any TLS Server certificate that is anchored by one of the Symantec - // roots above and is issued after this date will be distrusted. - private static final LocalDate APRIL_16_2019 = - LocalDate.of(2019, Month.APRIL, 16); + // roots above will be distrusted. /** * This method assumes the eeCert is a TLS Server Cert and chains back to @@ -163,26 +138,10 @@ static void checkDistrust(X509Certificate[] chain) + "trust anchor of TLS server certificate"); } if (FINGERPRINTS.contains(fp)) { - Date notBefore = chain[0].getNotBefore(); - LocalDate ldNotBefore = LocalDate.ofInstant(notBefore.toInstant(), - ZoneOffset.UTC); - // check if chain goes through one of the subCAs - if (chain.length > 2) { - X509Certificate subCA = chain[chain.length-2]; - fp = fingerprint(subCA); - if (fp == null) { - throw new ValidatorException("Cannot generate fingerprint " - + "for intermediate CA of TLS server certificate"); - } - LocalDate distrustDate = EXEMPT_SUBCAS.get(fp); - if (distrustDate != null) { - // reject if certificate is issued after specified date - checkNotBefore(ldNotBefore, distrustDate, anchor); - return; // success - } - } - // reject if certificate is issued after April 16, 2019 - checkNotBefore(ldNotBefore, APRIL_16_2019, anchor); + throw new ValidatorException( + "TLS Server certificate anchored by a distrusted legacy " + + "Symantec root CA: " +anchor.getSubjectX500Principal(), + ValidatorException.T_UNTRUSTED_CERT, anchor); } } @@ -190,17 +149,5 @@ private static String fingerprint(X509Certificate cert) { return X509CertImpl.getFingerprint("SHA-256", cert, debug); } - private static void checkNotBefore(LocalDate notBeforeDate, - LocalDate distrustDate, X509Certificate anchor) - throws ValidatorException { - if (notBeforeDate.isAfter(distrustDate)) { - throw new ValidatorException - ("TLS Server certificate issued after " + distrustDate + - " and anchored by a distrusted legacy Symantec root CA: " - + anchor.getSubjectX500Principal(), - ValidatorException.T_UNTRUSTED_CERT, anchor); - } - } - private SymantecTLSPolicy() {} } diff --git a/src/java.base/share/conf/security/java.security b/src/java.base/share/conf/security/java.security index 4289254affcf..54a7c3b915a8 100644 --- a/src/java.base/share/conf/security/java.security +++ b/src/java.base/share/conf/security/java.security @@ -1,4 +1,4 @@ -# + # This is the "master security properties file". # # An alternate java.security properties file may be specified @@ -1451,23 +1451,18 @@ jdk.sasl.disabledMechanisms= # The supported values are: # # SYMANTEC_TLS : Distrust TLS Server certificates anchored by a Symantec -# root CA and issued after April 16, 2019 unless issued by one of the -# following subordinate CAs which have a later distrust date: -# 1. Apple IST CA 2 - G1, SHA-256 fingerprint: -# AC2B922ECFD5E01711772FEA8ED372DE9D1E2245FCE3F57A9CDBEC77296A424B -# Distrust after December 31, 2019. -# 2. Apple IST CA 8 - G1, SHA-256 fingerprint: -# A4FE7C7F15155F3F0AEF7AAA83CF6E06DEB97CA3F909DF920AC1490882D488ED -# Distrust after December 31, 2019. +# root CA. # # ENTRUST_TLS : Distrust TLS Server certificates anchored by -# an Entrust root CA and issued after November 11, 2024. +# an Entrust root CA. # # CAMERFIRMA_TLS : Distrust TLS Server certificates anchored by -# a Camerfirma root CA and issued after April 15, 2025. +# a Camerfirma root CA. # # CHUNGHWA_TLS : Distrust TLS Server certificates anchored by # a Chunghwa root CA and issued after March 17, 2026. +# After April 19, 2027, certificates will be distrusted regardless +# of the date the certificate was issued. # # Leading and trailing whitespace surrounding each value are ignored. # Unknown values are ignored. If the property is commented out or set to the diff --git a/test/jdk/sun/security/ssl/X509TrustManagerImpl/distrust/Camerfirma.java b/test/jdk/sun/security/ssl/X509TrustManagerImpl/distrust/Camerfirma.java index 8d9dfe1ef18f..98633de2a8a0 100644 --- a/test/jdk/sun/security/ssl/X509TrustManagerImpl/distrust/Camerfirma.java +++ b/test/jdk/sun/security/ssl/X509TrustManagerImpl/distrust/Camerfirma.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -38,7 +38,7 @@ * @modules java.base/sun.security.validator * @run main/othervm Camerfirma after policyOn invalid * @run main/othervm Camerfirma after policyOff valid - * @run main/othervm Camerfirma before policyOn valid + * @run main/othervm Camerfirma before policyOn invalid * @run main/othervm Camerfirma before policyOff valid */ @@ -50,7 +50,7 @@ public class Camerfirma { // named "-chain.pem". private static final String ROOT_TO_TEST = "camerfirmachambersca"; - // Date after the restrictions take effect + // Date after the restrictions took effect when distrust was first announced private static final ZonedDateTime DISTRUST_DATE = LocalDate.of(2025, 04, 16).atStartOfDay(ZoneOffset.UTC); diff --git a/test/jdk/sun/security/ssl/X509TrustManagerImpl/distrust/Chunghwa.java b/test/jdk/sun/security/ssl/X509TrustManagerImpl/distrust/Chunghwa.java index 8512640bb015..ce94daccdcd9 100644 --- a/test/jdk/sun/security/ssl/X509TrustManagerImpl/distrust/Chunghwa.java +++ b/test/jdk/sun/security/ssl/X509TrustManagerImpl/distrust/Chunghwa.java @@ -42,6 +42,7 @@ * @run main/othervm Chunghwa before policyOff valid */ + public class Chunghwa { private static final String CERT_PATH = "chains" + File.separator + "chunghwa"; @@ -50,12 +51,18 @@ public class Chunghwa { // named "-chain.pem". private static final String ROOT_TO_TEST = "chunghwaepkirootca"; - // Date after the restrictions take effect + // Date after the restrictions took effect when distrust was first announced private static final ZonedDateTime DISTRUST_DATE = LocalDate.of(2026, 03, 18).atStartOfDay(ZoneOffset.UTC); + private static final LocalDate APRIL_19_2027 = LocalDate.of(2027, 04, 19); + private static final LocalDate TODAY = LocalDate.now(); public static void main(String[] args) throws Exception { + if (args[1].equals("policyOn") && TODAY.isAfter(APRIL_19_2027)) { + args[2] = "invalid"; + } + Distrust distrust = new Distrust(args); X509TrustManager[] tms = new X509TrustManager[]{ diff --git a/test/jdk/sun/security/ssl/X509TrustManagerImpl/distrust/Entrust.java b/test/jdk/sun/security/ssl/X509TrustManagerImpl/distrust/Entrust.java index 9fd4b0038d1e..3f25ab4a2132 100644 --- a/test/jdk/sun/security/ssl/X509TrustManagerImpl/distrust/Entrust.java +++ b/test/jdk/sun/security/ssl/X509TrustManagerImpl/distrust/Entrust.java @@ -35,7 +35,7 @@ * @modules java.base/sun.security.validator * @run main/othervm Entrust after policyOn invalid * @run main/othervm Entrust after policyOff valid - * @run main/othervm Entrust before policyOn valid + * @run main/othervm Entrust before policyOn invalid * @run main/othervm Entrust before policyOff valid */ @@ -48,7 +48,7 @@ public class Entrust { private static final String[] ROOTS_TO_TEST = new String[]{ "entrustevca", "entrustrootcaec1", "entrustrootcag2", "entrust2048ca"}; - // Date when the restrictions take effect + // Date after the restrictions took effect when distrust was first announced private static final ZonedDateTime DISTRUST_DATE = LocalDate.of(2024, 11, 12).atStartOfDay(ZoneOffset.UTC); diff --git a/test/jdk/sun/security/ssl/X509TrustManagerImpl/distrust/Symantec.java b/test/jdk/sun/security/ssl/X509TrustManagerImpl/distrust/Symantec.java index 4e099a8de8de..2bfb1c7f6b7a 100644 --- a/test/jdk/sun/security/ssl/X509TrustManagerImpl/distrust/Symantec.java +++ b/test/jdk/sun/security/ssl/X509TrustManagerImpl/distrust/Symantec.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -36,7 +36,7 @@ * @modules java.base/sun.security.validator * @run main/othervm Symantec after policyOn invalid * @run main/othervm Symantec after policyOff valid - * @run main/othervm Symantec before policyOn valid + * @run main/othervm Symantec before policyOn invalid * @run main/othervm Symantec before policyOff valid */ @@ -57,7 +57,7 @@ public class Symantec { // chain stored in a file named "-chain.pem". private static String[] subCAsToTest = new String[]{"appleistca8g1"}; - // Date when the restrictions take effect + // Date after the restrictions took effect when distrust was first announced private static final ZonedDateTime ROOTS_DISTRUST_DATE = LocalDate.of(2019, 4, 17).atStartOfDay(ZoneOffset.UTC); @@ -87,9 +87,5 @@ public static void main(String[] args) throws Exception { // test code-signing chain (should be valid as restrictions don't apply) Date validationDate = new Date(1544197375493L); distrust.testCodeSigningChain(certPath, "verisignclass3g5ca-codesigning", validationDate); - - // test chains issued through subCAs - notBefore = distrust.getNotBefore(SUBCA_DISTRUST_DATE); - distrust.testCertificateChain(certPath, notBefore, tms, subCAsToTest); } } From d996499d72c2db45239dd766e5587c3c758ecd1e Mon Sep 17 00:00:00 2001 From: Weijun Wang Date: Thu, 30 Apr 2026 18:09:42 +0000 Subject: [PATCH 003/169] 8383076: Enhance SASL Authentication Reviewed-by: abarashev, ascarpino, rhalade --- .../com/sun/security/sasl/ntlm/NTLMClient.java | 11 +++++++---- .../com/sun/security/sasl/ntlm/NTLMServer.java | 11 +++++++---- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/java.security.sasl/share/classes/com/sun/security/sasl/ntlm/NTLMClient.java b/src/java.security.sasl/share/classes/com/sun/security/sasl/ntlm/NTLMClient.java index 6f3832d3b435..d79662535eef 100644 --- a/src/java.security.sasl/share/classes/com/sun/security/sasl/ntlm/NTLMClient.java +++ b/src/java.security.sasl/share/classes/com/sun/security/sasl/ntlm/NTLMClient.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,6 +27,8 @@ import com.sun.security.ntlm.Client; import com.sun.security.ntlm.NTLMException; +import sun.security.jca.JCAUtil; + import java.io.IOException; import java.net.InetAddress; import java.net.UnknownHostException; @@ -69,8 +71,9 @@ * * com.sun.security.sasl.ntlm.random * java.util.Random, the nonce source to be used in NTLM v2 or NTLM v1 with - * Client Challenge. Default null, an internal java.util.Random object - * will be used + * Client Challenge. Users are encouraged to provide a + * java.security.SecureRandom object. If not provided, then an internal + * SecureRandom object is used. * * Negotiated Properties: * @@ -127,7 +130,7 @@ final class NTLMClient implements SaslClient { rtmp = (Random)props.get(NTLM_RANDOM); hostname = (String)props.get(NTLM_HOSTNAME); } - this.random = rtmp != null ? rtmp : new Random(); + this.random = rtmp != null ? rtmp : JCAUtil.getDefSecureRandom(); if (version == null) { version = System.getProperty("ntlm.version"); diff --git a/src/java.security.sasl/share/classes/com/sun/security/sasl/ntlm/NTLMServer.java b/src/java.security.sasl/share/classes/com/sun/security/sasl/ntlm/NTLMServer.java index 0bd34e40fd15..94a15989028a 100644 --- a/src/java.security.sasl/share/classes/com/sun/security/sasl/ntlm/NTLMServer.java +++ b/src/java.security.sasl/share/classes/com/sun/security/sasl/ntlm/NTLMServer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,6 +27,8 @@ import com.sun.security.ntlm.NTLMException; import com.sun.security.ntlm.Server; +import sun.security.jca.JCAUtil; + import java.io.IOException; import java.util.Map; import java.util.Random; @@ -67,8 +69,9 @@ * String, the domain of the server, default is server name (fqdn parameter) * * com.sun.security.sasl.ntlm.random - * java.util.Random, the nonce source. Default null, an internal - * java.util.Random object will be used + * java.util.Random, the nonce source. Users are encouraged to provide + * a java.security.SecureRandom object. If not provided, then an internal + * SecureRandom object is used. * * Negotiated Properties: * @@ -123,7 +126,7 @@ final class NTLMServer implements SaslServer { version = (String)props.get(NTLM_VERSION); rtmp = (Random)props.get(NTLM_RANDOM); } - random = rtmp != null ? rtmp : new Random(); + random = rtmp != null ? rtmp : JCAUtil.getDefSecureRandom(); if (version == null) { version = System.getProperty("ntlm.version"); From 5b905b80a99e9d677a1d5bc200c24f026d57e13c Mon Sep 17 00:00:00 2001 From: Weijun Wang Date: Fri, 8 May 2026 14:12:10 +0000 Subject: [PATCH 004/169] 8383281: Enhance MD5 Digesting Reviewed-by: mschoene, rhalade, abarashev, hchao, mpowers --- .../classes/com/sun/security/sasl/digest/DigestMD5Base.java | 6 +++--- .../com/sun/security/sasl/digest/DigestMD5Client.java | 6 +++--- .../com/sun/security/sasl/digest/DigestMD5Server.java | 5 +++-- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/java.security.sasl/share/classes/com/sun/security/sasl/digest/DigestMD5Base.java b/src/java.security.sasl/share/classes/com/sun/security/sasl/digest/DigestMD5Base.java index 567c71711165..547305f55fa2 100644 --- a/src/java.security.sasl/share/classes/com/sun/security/sasl/digest/DigestMD5Base.java +++ b/src/java.security.sasl/share/classes/com/sun/security/sasl/digest/DigestMD5Base.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -1000,7 +1000,7 @@ public byte[] unwrap(byte[] incoming, int start, int len) } /* First, compare MAC's before updating any of our state */ - if (!Arrays.equals(mac, expectedMac)) { + if (!MessageDigest.isEqual(mac, expectedMac)) { // Discard message and do not increment sequence number logger.log(Level.INFO, "DIGEST23:Unmatched MACs"); return EMPTY_BYTE_ARRAY; @@ -1450,7 +1450,7 @@ public byte[] unwrap(byte[] incoming, int start, int len) } // First, compare MACs before updating state - if (!Arrays.equals(mac, expectedMac)) { + if (!MessageDigest.isEqual(mac, expectedMac)) { // Discard message and do not increment sequence number logger.log(Level.INFO, "DIGEST41:Unmatched MACs"); return EMPTY_BYTE_ARRAY; diff --git a/src/java.security.sasl/share/classes/com/sun/security/sasl/digest/DigestMD5Client.java b/src/java.security.sasl/share/classes/com/sun/security/sasl/digest/DigestMD5Client.java index c9938ec675d7..e48192cce9e6 100644 --- a/src/java.security.sasl/share/classes/com/sun/security/sasl/digest/DigestMD5Client.java +++ b/src/java.security.sasl/share/classes/com/sun/security/sasl/digest/DigestMD5Client.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,6 +25,7 @@ package com.sun.security.sasl.digest; +import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -32,7 +33,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.Arrays; import java.util.logging.Level; import static java.nio.charset.StandardCharsets.UTF_8; @@ -657,7 +657,7 @@ private void validateResponseValue(byte[] fromServer) throws SaslException { byte[] expected = generateResponseValue("", digestUri, negotiatedQop, username, negotiatedRealm, passwd, nonce, cnonce, nonceCount, authzidBytes); - if (!Arrays.equals(expected, fromServer)) { + if (!MessageDigest.isEqual(expected, fromServer)) { /* Server's rspauth value does not match */ throw new SaslException( "Server's rspauth value does not match what client expects"); diff --git a/src/java.security.sasl/share/classes/com/sun/security/sasl/digest/DigestMD5Server.java b/src/java.security.sasl/share/classes/com/sun/security/sasl/digest/DigestMD5Server.java index bc508e7397be..f0d43b352237 100644 --- a/src/java.security.sasl/share/classes/com/sun/security/sasl/digest/DigestMD5Server.java +++ b/src/java.security.sasl/share/classes/com/sun/security/sasl/digest/DigestMD5Server.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,6 +27,7 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.StringTokenizer; import java.util.ArrayList; @@ -619,7 +620,7 @@ private byte[] validateClientResponse(byte[][] responseVal) "DIGEST-MD5: problem duplicating client response", e); } - if (!Arrays.equals(responseFromClient, expectedResponse)) { + if (!MessageDigest.isEqual(responseFromClient, expectedResponse)) { throw new SaslException("DIGEST-MD5: digest response format " + "violation. Mismatched response."); } From c8c9bcce19278f4aa5776397bc450abf2bfc9a2a Mon Sep 17 00:00:00 2001 From: Valerie Peng Date: Wed, 13 May 2026 17:10:08 +0000 Subject: [PATCH 005/169] 8384165: Improve ciphtertext handling Reviewed-by: mschoene, rhalade, ascarpino, semery --- .../classes/com/sun/crypto/provider/GaloisCounterMode.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/java.base/share/classes/com/sun/crypto/provider/GaloisCounterMode.java b/src/java.base/share/classes/com/sun/crypto/provider/GaloisCounterMode.java index 2f4df1eba03b..5c09b061a9c9 100644 --- a/src/java.base/share/classes/com/sun/crypto/provider/GaloisCounterMode.java +++ b/src/java.base/share/classes/com/sun/crypto/provider/GaloisCounterMode.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -1603,8 +1603,9 @@ public int doFinal(ByteBuffer src, ByteBuffer dst) NIO_ACCESS.acquireSession(dst); try { Unsafe.getUnsafe().setMemory( - NIO_ACCESS.getBufferAddress(dst), - len + dst.position(), (byte) 0); + NIO_ACCESS.getBufferAddress(dst) + + dst.position(), + len, (byte) 0); } finally { NIO_ACCESS.releaseSession(dst); } From 8a16825012e8a695c1fdc156b368e35e4fbf79c4 Mon Sep 17 00:00:00 2001 From: Daniel Fuchs Date: Tue, 19 May 2026 17:37:15 +0000 Subject: [PATCH 006/169] 8383756: Enhance Network Clients Reviewed-by: michaelm, rhalade, skoivu, djelinski --- src/java.base/share/classes/sun/net/ftp/impl/FtpClient.java | 4 ++-- src/java.base/share/classes/sun/net/smtp/SmtpClient.java | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/java.base/share/classes/sun/net/ftp/impl/FtpClient.java b/src/java.base/share/classes/sun/net/ftp/impl/FtpClient.java index 1d6876f760e7..1951dbc925a1 100644 --- a/src/java.base/share/classes/sun/net/ftp/impl/FtpClient.java +++ b/src/java.base/share/classes/sun/net/ftp/impl/FtpClient.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2009, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -526,7 +526,7 @@ private boolean issueCommand(String cmd) throws IOException, // ignore... } } - if (cmd.indexOf('\n') != -1) { + if (cmd.indexOf('\n') != -1 || cmd.indexOf('\r') != -1) { sun.net.ftp.FtpProtocolException ex = new sun.net.ftp.FtpProtocolException("Illegal FTP command"); ex.initCause(new IllegalArgumentException("Illegal carriage return")); diff --git a/src/java.base/share/classes/sun/net/smtp/SmtpClient.java b/src/java.base/share/classes/sun/net/smtp/SmtpClient.java index 494bcf199879..73968ddf1926 100644 --- a/src/java.base/share/classes/sun/net/smtp/SmtpClient.java +++ b/src/java.base/share/classes/sun/net/smtp/SmtpClient.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1995, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1995, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -74,7 +74,7 @@ private void toCanonical(String s) throws IOException { } public void to(String s) throws IOException { - if (s.indexOf('\n') != -1) { + if (s.indexOf('\n') != -1 || s.indexOf('\r') != -1) { throw new IOException("Illegal SMTP command", new IllegalArgumentException("Illegal carriage return")); } @@ -120,7 +120,7 @@ else if (st == pos) } public void from(String s) throws IOException { - if (s.indexOf('\n') != -1) { + if (s.indexOf('\n') != -1 || s.indexOf('\r') != -1) { throw new IOException("Illegal SMTP command", new IllegalArgumentException("Illegal carriage return")); } From f4a6e72b09101705b3279d07f68b94d322f78b2d Mon Sep 17 00:00:00 2001 From: Phil Race Date: Tue, 19 May 2026 18:13:01 +0000 Subject: [PATCH 007/169] 8384445: Enhance JPEG loading Reviewed-by: mschoene, jdv, rhalade, kizune --- .../com/sun/imageio/plugins/jpeg/JPEGImageReader.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/JPEGImageReader.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/JPEGImageReader.java index 1d6bf2dbf8a4..da815eda4e52 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/JPEGImageReader.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/JPEGImageReader.java @@ -1258,9 +1258,15 @@ private Raster readInternal(int imageIndex, int[] sBands = param.getSourceBands(); if (sBands != null) { - srcBands = sBands; + srcBands = sBands.clone(); numRasterBands = srcBands.length; } + for (int b = 0; b < srcBands.length; b++) { + if (srcBands[b] < 0 || srcBands[b] >= numComponents) { + throw new IllegalArgumentException( + "ImageReadParam out of range source band : " + srcBands[b]); + } + } if (!wantRaster) { // ignore dest bands for Raster destinationBands = param.getDestinationBands(); } From 763b8a5da4497b6fb81f65b25d8a3e931e0a49b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Casta=C3=B1eda=20Lozano?= Date: Wed, 20 May 2026 07:58:27 +0000 Subject: [PATCH 008/169] 8382378: Improve C2 loop transformations Co-authored-by: Emanuel Peter Co-authored-by: Quan Anh Mai Co-authored-by: Christian Hagedorn Reviewed-by: mschoene, rhalade, chagedorn, qamai, epeter --- src/hotspot/share/opto/loopPredicate.cpp | 19 +- src/hotspot/share/opto/loopTransform.cpp | 473 ++++-- src/hotspot/share/opto/loopnode.cpp | 1453 ++++++++++++++--- src/hotspot/share/opto/loopnode.hpp | 12 +- .../c2/irTests/TestLongRangeChecks.java | 36 +- .../irTests/TestVectorizationMultiInvar.java | 22 +- .../compiler/lib/ir_framework/IRNode.java | 6 + .../rangechecks/TestIndexMatching.java | 779 +++++++++ .../TestLongRangeCheckWithZeroScaling.java | 53 + 9 files changed, 2455 insertions(+), 398 deletions(-) create mode 100644 test/hotspot/jtreg/compiler/rangechecks/TestIndexMatching.java create mode 100644 test/hotspot/jtreg/compiler/rangechecks/TestLongRangeCheckWithZeroScaling.java diff --git a/src/hotspot/share/opto/loopPredicate.cpp b/src/hotspot/share/opto/loopPredicate.cpp index be099747fc01..f3c45cd497fd 100644 --- a/src/hotspot/share/opto/loopPredicate.cpp +++ b/src/hotspot/share/opto/loopPredicate.cpp @@ -457,10 +457,10 @@ class Invariance : public StackObj { } }; -//------------------------------is_range_check_if ----------------------------------- -// Returns true if the predicate of iff is in "scale*iv + offset u< load_range(ptr)" format -// Note: this function is particularly designed for loop predication. We require load_range -// and offset to be loop invariant computed on the fly by "invar" +// Returns true if the predicate has the form +// scale*iv + offset = 0. +// Note: This is used by Loop Predication and PhaseIdealLoop::extract_long_range_check(). bool IdealLoopTree::is_range_check_if(IfProjNode* if_success_proj, PhaseIdealLoop *phase, BasicType bt, Node *iv, Node *&range, Node *&offset, jlong &scale) const { IfNode* iff = if_success_proj->in(0)->as_If(); @@ -518,20 +518,23 @@ bool IdealLoopTree::is_range_check_if(IfProjNode* if_success_proj, PhaseIdealLoo return false; } range = cmp->in(2); + + // Now we check the condition R >= 0 from the method comment. if (range->Opcode() != Op_LoadRange) { const TypeInteger* tinteger = phase->_igvn.type(range)->isa_integer(bt); if (tinteger == nullptr || tinteger->empty() || tinteger->lo_as_long() < 0) { - // Allow predication on positive values that aren't LoadRanges. - // This allows optimization of loops where the length of the - // array is a known value and doesn't need to be loaded back - // from the array. + // R is not proven to be non-negative. Bail out. return false; } } else { + // R = LoadRange are always non-negative. assert(bt == T_INT, "no LoadRange for longs"); } scale = 0; offset = nullptr; + // Now check the form + // scale*iv + offset is_scaled_iv_plus_offset(cmp->in(1), iv, bt, &scale, &offset)) { return false; } diff --git a/src/hotspot/share/opto/loopTransform.cpp b/src/hotspot/share/opto/loopTransform.cpp index 209e4bd6bea4..20bfe53396e8 100644 --- a/src/hotspot/share/opto/loopTransform.cpp +++ b/src/hotspot/share/opto/loopTransform.cpp @@ -2612,8 +2612,15 @@ void PhaseIdealLoop::add_constraint(jlong stride_con, jlong scale_con, Node* off //----------------------------------is_iv------------------------------------ // Return true if exp is the value (of type bt) of the given induction var. -// This grammar of cases is recognized, where X is I|L according to bt: -// VIV[iv] = iv | (CastXX VIV[iv]) | (ConvI2X VIV[iv]) +// On success, we guarantee this form: +// if bt = T_INT (implies iv is int): +// exp = iv +// if bt = T_LONG and iv is long: +// exp = iv +// if bt = T_LONG and iv is int: +// exp = ConvI2L(iv) +// +// See grammar specification in PhaseIdealLoop::is_scaled_iv_plus_offset(). bool PhaseIdealLoop::is_iv(Node* exp, Node* iv, BasicType bt) { exp = exp->uncast(); if (exp == iv && iv->bottom_type()->isa_integer(bt)) { @@ -2628,88 +2635,129 @@ bool PhaseIdealLoop::is_iv(Node* exp, Node* iv, BasicType bt) { //------------------------------is_scaled_iv--------------------------------- // Return true if exp is a constant times the given induction var (of type bt). -// The multiplication is either done in full precision (exactly of type bt), -// or else bt is T_LONG but iv is scaled using 32-bit arithmetic followed by a ConvI2L. -// This grammar of cases is recognized, where X is I|L according to bt: -// SIV[iv] = VIV[iv] | (CastXX SIV[iv]) -// | (MulX VIV[iv] ConX) | (MulX ConX VIV[iv]) -// | (LShiftX VIV[iv] ConI) -// | (ConvI2L SIV[iv]) -- a "short-scale" can occur here; note recursion -// | (SubX 0 SIV[iv]) -- same as MulX(iv, -scale); note recursion -// | (AddX SIV[iv] SIV[iv]) -- sum of two scaled iv; note recursion -// | (SubX SIV[iv] SIV[iv]) -- difference of two scaled iv; note recursion -// VIV[iv] = [either iv or its value converted; see is_iv() above] -// On success, the constant scale value is stored back to *p_scale. -// The value (*p_short_scale) reports if such a ConvI2L conversion was present. -bool PhaseIdealLoop::is_scaled_iv(Node* exp, Node* iv, BasicType bt, jlong* p_scale, bool* p_short_scale, int depth) { - BasicType exp_bt = bt; - exp = exp->uncast(); //strip casts - assert(exp_bt == T_INT || exp_bt == T_LONG, "unexpected int type"); - if (is_iv(exp, iv, exp_bt)) { +// On success, we guarantee this form: +// if bt = T_INT (implies iv is int): +// exp = MulI( iv, p_scale) where p_scale is int +// if bt = T_LONG and iv is long: +// exp = MulL( iv, p_scale) +// if bt = T_LONG and iv is int: +// exp = MulL(ConvI2L(iv), p_scale) +// where the constant scale value is stored back to *p_scale unless null. +// The scaling is always done in full precision (exactly of type bt). +// See grammar specification in PhaseIdealLoop::is_scaled_iv_plus_offset(). +bool PhaseIdealLoop::is_scaled_iv(Node* exp, Node* iv, BasicType bt, jlong* p_scale, int depth) { + exp = exp->uncast(); // Strip casts. + assert(bt == T_INT || bt == T_LONG, "unexpected int type"); + if (is_iv(exp, iv, bt)) { + // if bt = T_INT (implies iv is int): + // exp = iv = MulI( iv, p_scale=1) where p_scale=1 is trivially an int + // if bt = T_LONG and iv is long: + // exp = iv = MulL( iv, p_scale=1) + // if bt = T_LONG and iv is int: + // exp = ConvI2L(iv) = MulL(ConvI2L(iv), p_scale=1) if (p_scale != nullptr) { *p_scale = 1; } - if (p_short_scale != nullptr) { - *p_short_scale = false; - } return true; } - if (exp_bt == T_LONG && iv->bottom_type()->isa_int() && exp->Opcode() == Op_ConvI2L) { - exp = exp->in(1); - exp_bt = T_INT; + if (bt == T_LONG && iv->bottom_type()->isa_int() && exp->Opcode() == Op_ConvI2L) { + // Risk for short scaling: avoid long range-check transformations, because + // the current implementation (transform_long_range_checks) does not protect + // against overflow. + return false; } int opc = exp->Opcode(); int which = 0; // this is which subexpression we find the iv in // Can't use is_Mul() here as it's true for AndI and AndL - if (opc == Op_Mul(exp_bt)) { - if ((is_iv(exp->in(which = 1), iv, exp_bt) && exp->in(2)->is_Con()) || - (is_iv(exp->in(which = 2), iv, exp_bt) && exp->in(1)->is_Con())) { + if (opc == Op_Mul(bt)) { + // We need to test both ways because the multiplication node may not have + // been canonicalized yet, for example after successful loop predication. + if ((is_iv(exp->in(which = 1), iv, bt) && exp->in(2)->is_Con()) || + (is_iv(exp->in(which = 2), iv, bt) && exp->in(1)->is_Con())) { Node* factor = exp->in(which == 1 ? 2 : 1); // the other argument - jlong scale = factor->find_integer_as_long(exp_bt, 0); + jlong scale = factor->find_integer_as_long(bt, 0); if (scale == 0) { - return false; // might be top + return false; // find_integer_as_long may have failed, value might be top. } + // if bt = T_INT (implies iv is int): + // exp->in(which) = iv + // Note: find_integer_as_long ensures that scale is int + // -> which = 1: exp = MulI( iv, scale) where scale is int + // -> which = 2: exp = MulI(scale, iv) = MulI( iv, scale) where scale is int + // if bt = T_LONG and iv is long: + // exp->in(which) = iv + // -> which = 1: exp = MulL( iv, scale) + // -> which = 2: exp = MulL(scale, iv) = MulL( iv, scale) + // if bt = T_LONG and iv is int: + // exp->in(which) = ConvI2L(iv) + // -> which = 1: exp = MulL(ConvI2L(iv), scale) + // -> which = 2: exp = MulL(scale, ConvI2L(iv)) = MulL(ConvI2L(iv), scale) if (p_scale != nullptr) { *p_scale = scale; } - if (p_short_scale != nullptr) { - // (ConvI2L (MulI iv K)) can be 64-bit linear if iv is kept small enough... - *p_short_scale = (exp_bt != bt && scale != 1); - } return true; } - } else if (opc == Op_LShift(exp_bt)) { - if (is_iv(exp->in(1), iv, exp_bt) && exp->in(2)->is_Con()) { + } else if (opc == Op_LShift(bt)) { + if (is_iv(exp->in(1), iv, bt) && exp->in(2)->is_Con()) { jint shift_amount = exp->in(2)->find_int_con(min_jint); if (shift_amount == min_jint) { - return false; // might be top + return false; // find_int_con may have failed, value might be top. } jlong scale; - if (exp_bt == T_INT) { + if (bt == T_INT) { + // LShiftI(x, shift_amount) = MulI(x, LShiftI(1, shift_amount)) = MulI(x, scale) + // Note: scale is guaranteed to be an int scale = java_shift_left((jint)1, (juint)shift_amount); - } else if (exp_bt == T_LONG) { + } else if (bt == T_LONG) { + // LShiftL(x, shift_amount) = MulL(x, LShiftL(1L, shift_amount)) = MulL(x, scale) scale = java_shift_left((jlong)1, (julong)shift_amount); } + // if bt = T_INT (implies iv is int): + // exp->in(1) = iv + // exp = LShiftI( iv, shift_amount) = MulI( iv, scale) where scale is int + // if bt = T_LONG and iv is long: + // exp->in(1) = iv + // exp = LShiftL( iv, shift_amount) = MulL( iv, scale) + // if bt = T_LONG and iv is int: + // exp->in(1) = ConvI2L(iv) + // exp = LShiftL(ConvI2L(iv), shift_amount) = MulL(ConvI2L(iv), scale) if (p_scale != nullptr) { *p_scale = scale; } - if (p_short_scale != nullptr) { - // (ConvI2L (MulI iv K)) can be 64-bit linear if iv is kept small enough... - *p_short_scale = (exp_bt != bt && scale != 1); - } return true; } - } else if (opc == Op_Add(exp_bt)) { + } else if (opc == Op_Add(bt)) { jlong scale_l = 0; jlong scale_r = 0; - bool short_scale_l = false; - bool short_scale_r = false; if (depth == 0 && - is_scaled_iv(exp->in(1), iv, exp_bt, &scale_l, &short_scale_l, depth + 1) && - is_scaled_iv(exp->in(2), iv, exp_bt, &scale_r, &short_scale_r, depth + 1)) { - // AddX(iv*K1, iv*K2) => iv*(K1+K2) + is_scaled_iv(exp->in(1), iv, bt, &scale_l, depth + 1) && + is_scaled_iv(exp->in(2), iv, bt, &scale_r, depth + 1)) { + // We know that: + // AddI(MulI(x, scale_l), MulI(x, scale_r)) = MulI(x, scale=AddI(scale_l, scale_r)) where scale is int + // AddL(MulL(x, scale_l), MulL(x, scale_r)) = MulL(x, scale=AddL(scale_l, scale_r)) + // + // if bt = T_INT (implies iv is int): + // e1 = exp->in(1) = MulI(iv, scale_l) where scale_l is int + // e2 = exp->in(2) = MulI(iv, scale_r) where scale_r is int + // exp = AddI(e1, e2) = MulI(iv, scale=AddI(scale_l, scale_r)) where scale is int + // if bt = T_LONG and iv is long: + // e1 = exp->in(1) = MulL(iv, scale_l) + // e2 = exp->in(2) = MulL(iv, scale_r) + // exp = AddL(e1, e2) = MulL(iv, scale=AddL(scale_l, scale_r)) + // if bt = T_LONG and iv is int: + // e1 = exp->in(1) = MulL(ConvI2L(iv), scale_l) + // e2 = exp->in(2) = MulL(ConvI2L(iv), scale_r) + // exp = AddL(e1, e2) = MulL(ConvI2L(iv), scale=AddL(scale_l, scale_r)) + // + // For the scale computation, we could have used the same type case + // distinction as in LShift above. But instead, we compute in long for + // all cases. We need to show that this is OK if bt=T_INT. + // We would have computed + // scale = AddI(scale_l, scale_r) + // which is equivalent to the long addition if the long addition stays + // within (min_jint, max_jint]. jlong scale_sum = java_add(scale_l, scale_r); - if (scale_sum > max_signed_integer(exp_bt) || scale_sum <= min_signed_integer(exp_bt)) { + if (scale_sum > max_signed_integer(bt) || scale_sum <= min_signed_integer(bt)) { // This logic is shared by int and long. For int, the result may overflow // as we use jlong to compute so do the check here. Long result may also // overflow but that's fine because result wraps. @@ -2718,41 +2766,76 @@ bool PhaseIdealLoop::is_scaled_iv(Node* exp, Node* iv, BasicType bt, jlong* p_sc if (p_scale != nullptr) { *p_scale = scale_sum; } - if (p_short_scale != nullptr) { - *p_short_scale = short_scale_l && short_scale_r; - } return true; } - } else if (opc == Op_Sub(exp_bt)) { - if (exp->in(1)->find_integer_as_long(exp_bt, -1) == 0) { - jlong scale = 0; - if (depth == 0 && is_scaled_iv(exp->in(2), iv, exp_bt, &scale, p_short_scale, depth + 1)) { - // SubX(0, iv*K) => iv*(-K) - if (scale == min_signed_integer(exp_bt)) { + } else if (opc == Op_Sub(bt)) { + if (exp->in(1)->find_integer_as_long(bt, -1) == 0) { + jlong scale_r = 0; + if (depth == 0 && is_scaled_iv(exp->in(2), iv, bt, &scale_r, depth + 1)) { + // We know that: + // SubI(0, MulI(x, scale_r)) = MulI(x, scale=SubI(0, scale_r)) where scale is int + // SubL(0, MulL(x, scale_r)) = MulL(x, scale=SubL(0, scale_r)) + // + // if bt = T_INT (implies iv is int): + // e2 = exp->in(2) = MulI(iv, scale_r) where scale_r is int + // exp = SubI(0, e2) = MulI(iv, scale=SubI(0, scale_r)) where scale is int + // if bt = T_LONG and iv is long: + // e2 = exp->in(2) = MulL(iv, scale_r) + // exp = SubL(0, e2) = MulL(iv, scale=SubL(0, scale_r)) + // if bt = T_LONG and iv is int: + // e2 = exp->in(2) = MulL(ConvI2L(iv), scale_r) + // exp = SubL(0, e2) = MulL(ConvI2L(iv), scale=SubL(0, scale_r)) + // + // Again, we could have used bt-sensitive computations as for LShift. + // Instead, we compute in all cases + // scale = SubL(0, scale_r) + // How is this correct for bt=T_INT? + // We know that the input scale is in the int range. + // And so as long as the input scale != min_jint, the + // computation in long is equivalent, since neither the int nor + // the long computation would overflow. + if (scale_r == min_signed_integer(bt)) { // This should work even if -K overflows, but let's not. return false; } - scale = java_multiply(scale, (jlong)-1); + jlong scale_neg = java_multiply(scale_r, (jlong)-1); if (p_scale != nullptr) { - *p_scale = scale; - } - if (p_short_scale != nullptr) { - // (ConvI2L (MulI iv K)) can be 64-bit linear if iv is kept small enough... - *p_short_scale = *p_short_scale || (exp_bt != bt && scale != 1); + *p_scale = scale_neg; } return true; } } else { jlong scale_l = 0; jlong scale_r = 0; - bool short_scale_l = false; - bool short_scale_r = false; if (depth == 0 && - is_scaled_iv(exp->in(1), iv, exp_bt, &scale_l, &short_scale_l, depth + 1) && - is_scaled_iv(exp->in(2), iv, exp_bt, &scale_r, &short_scale_r, depth + 1)) { - // SubX(iv*K1, iv*K2) => iv*(K1-K2) + is_scaled_iv(exp->in(1), iv, bt, &scale_l, depth + 1) && + is_scaled_iv(exp->in(2), iv, bt, &scale_r, depth + 1)) { + // We know that: + // SubI(MulI(x, scale_l), MulI(x, scale_r)) = MulI(x, scale=SubI(scale_l, scale_r)) where scale is int + // SubL(MulL(x, scale_l), MulL(x, scale_r)) = MulL(x, scale=SubL(scale_l, scale_r)) + // + // if bt = T_INT (implies iv is int): + // e1 = exp->in(1) = MulI(iv, scale_l) where scale_l is int + // e2 = exp->in(2) = MulI(iv, scale_r) where scale_r is int + // exp = SubI(e1, e2) = MulI(iv, scale=SubI(scale_l, scale_r)) where scale is int + // if bt = T_LONG and iv is long: + // e1 = exp->in(1) = MulL(iv, scale_l) + // e2 = exp->in(2) = MulL(iv, scale_r) + // exp = SubL(e1, e2) = MulL(iv, scale=SubL(scale_l, scale_r)) + // if bt = T_LONG and iv is int: + // e1 = exp->in(1) = MulL(ConvI2L(iv), scale_l) + // e2 = exp->in(2) = MulL(ConvI2L(iv), scale_r) + // exp = SubL(e1, e2) = MulL(ConvI2L(iv), scale=SubL(scale_l, scale_r)) + // + // For the scale computation, we could have used the same type case + // distinction as in LShift above. But instead, we compute in long for + // all cases. We need to show that this is OK if bt=T_INT. + // We would have computed + // scale = SubI(scale_l, scale_r) + // which is equivalent to the long subtraction if the long subtraction + // stays within (min_jint, max_jint]. jlong scale_diff = java_subtract(scale_l, scale_r); - if (scale_diff > max_signed_integer(exp_bt) || scale_diff <= min_signed_integer(exp_bt)) { + if (scale_diff > max_signed_integer(bt) || scale_diff <= min_signed_integer(bt)) { // This logic is shared by int and long. For int, the result may // overflow as we use jlong to compute so do the check here. Long // result may also overflow but that's fine because result wraps. @@ -2761,9 +2844,6 @@ bool PhaseIdealLoop::is_scaled_iv(Node* exp, Node* iv, BasicType bt, jlong* p_sc if (p_scale != nullptr) { *p_scale = scale_diff; } - if (p_short_scale != nullptr) { - *p_short_scale = short_scale_l && short_scale_r; - } return true; } } @@ -2773,27 +2853,75 @@ bool PhaseIdealLoop::is_scaled_iv(Node* exp, Node* iv, BasicType bt, jlong* p_sc } //-------------------------is_scaled_iv_plus_offset-------------------------- -// Return true if exp is a simple linear transform of the given induction var. +// Return true if exp is a simple affine transform of the given induction var. // The scale must be constant and the addition tree (if any) must be simple. -// This grammar of cases is recognized, where X is I|L according to bt: +// On success, we guarantee this form: +// if bt = T_INT (implies iv is int): +// exp = AddI(MulI( iv, p_scale), p_offset) where p_scale is int +// if bt = T_LONG and iv is long: +// exp = AddL(MulL( iv, p_scale), p_offset) +// if bt = T_LONG and iv is int: +// exp = AddL(MulL(ConvI2L(iv), p_scale), p_offset) +// where: +// - the constant scale value is stored back to *p_scale unless null, and +// - the offset (perhaps a synthetic AddX or SubX node) is stored to *p_offset unless null. +// To avoid looping, the match is depth-limited. +// +// This grammar of cases is recognized, where X is I (int) or L (long) according +// to bt. CastXX operations are also accepted, but omitted for conciseness: +// +// is_iv(): +// +// IV[iv,I] = iv, if iv is int +// IV[iv,L] = iv, if iv is long +// | ConvI2L(iv), if iv is int +// +// is_scaled_iv(): +// +// SIV0[iv,X] = IV[iv,X] +// | MulX(IV[iv,X], ConX), if get_con(ConX) != 0 (ConX is top or MulX is non canonical) +// | MulX(ConX, IV[iv,X]), if get_con(ConX) != 0 (ConX is top or MulX is non canonical) +// | LShiftX(IV[iv,X], ConI), if get_con(ConI) != min_jint (ConI is top or LShiftX is non canonical) // -// OIV[iv] = SIV[iv] | (CastXX OIV[iv]) -// | (AddX SIV[iv] E) | (AddX E SIV[iv]) -// | (SubX SIV[iv] E) | (SubX E SIV[iv]) -// SSIV[iv] = (ConvI2X SIV[iv]) -- a "short scale" might occur here -// SIV[iv] = [a possibly scaled value of iv; see is_scaled_iv() above] +// SIV[iv,X] = SIV0[iv,X] +// | e:AddX(SIV0[iv,X], SIV0[iv,X]), if min_jX < scale(e) <= max_jX +// | SubX(0, e:SIV0[iv,X]), if scale(e) != min_jX +// | e:SubX(SIV0[iv,X], SIV0[iv,X]), if min_jX < scale(e) <= max_jX // -// On success, the constant scale value is stored back to *p_scale unless null. -// Likewise, the addend (perhaps a synthetic AddX node) is stored to *p_offset. -// Also, (*p_short_scale) reports if a ConvI2L conversion was seen after a MulI, -// meaning bt is T_LONG but iv was scaled using 32-bit arithmetic. -// To avoid looping, the match is depth-limited, and so may fail to match the grammar to complex expressions. -bool PhaseIdealLoop::is_scaled_iv_plus_offset(Node* exp, Node* iv, BasicType bt, jlong* p_scale, Node** p_offset, bool* p_short_scale, int depth) { +// is_scaled_iv_plus_offset(): +// +// OSIV0[iv,X] = SIV[iv,X] +// | AddX(SIV[iv,X], e) +// | AddX(e, SIV[iv,X]) +// | SubX(SIV[iv,X], e) +// | SubX(e1, e2:SIV[iv,X]), if scale(e2) != min_jX +// +// OSIV1[iv,X] = OSIV0[iv,X] +// | AddX(OSIV0[iv,X], ConX) +// | AddX(ConX, OSIV0[iv,X]) +// +// OSIV[iv,X] = OSIV1[iv,X] +// | AddX(OSIV1[iv,X], ConX) +// | AddX(ConX, OSIV1[iv,X]) +// +// where scale(IV[iv,X]) = 1L +// scale(MulX(_, ConX)) = get_con(ConX) +// scale(MulX(ConX, _)) = get_con(ConX) +// scale(LShiftX(_, ConI)) = java_shift_left((jX)1, get_con(ConI)) +// scale(AddX(e1:SIV0, e2:SIV0)) = java_add(scale(e1), scale(e2)) +// scale(SubX(0, e:SIV0)) = java_multiply(scale(e), -1L) +// scale(SubX(e1:SIV0, e2:SIV0)) = java_subtract(scale(e1), scale(e2)) +bool PhaseIdealLoop::is_scaled_iv_plus_offset(Node* exp, Node* iv, BasicType bt, jlong* p_scale, Node** p_offset, int depth) { assert(bt == T_INT || bt == T_LONG, "unexpected int type"); jlong scale = 0; // to catch result from is_scaled_iv() - BasicType exp_bt = bt; exp = exp->uncast(); - if (is_scaled_iv(exp, iv, exp_bt, &scale, p_short_scale)) { + if (is_scaled_iv(exp, iv, bt, &scale)) { + // if bt = T_INT (implies iv is int): + // exp = MulI( iv, scale) = AddI(MulI( iv, scale), offset=0) where scale is int + // if bt = T_LONG and iv is long: + // exp = MulL( iv, scale) = AddL(MulL( iv, scale), offset=0) + // if bt = T_LONG and iv is int: + // exp = MulL(ConvI2L(iv), scale) = AddL(MulL(ConvI2L(iv), scale), offset=0) if (p_scale != nullptr) { *p_scale = scale; } @@ -2803,58 +2931,109 @@ bool PhaseIdealLoop::is_scaled_iv_plus_offset(Node* exp, Node* iv, BasicType bt, } return true; } - if (exp_bt != bt) { - // We would now be matching inputs like (ConvI2L exp:(AddI (MulI iv S) E)). - // It's hard to make 32-bit arithmetic linear if it overflows. Although we do - // cope with overflowing multiplication by S, it would be even more work to - // handle overflowing addition of E. So we bail out here on ConvI2L input. - return false; - } int opc = exp->Opcode(); - int which = 0; // this is which subexpression we find the iv in - Node* offset = nullptr; - if (opc == Op_Add(exp_bt)) { - // Check for a scaled IV in (AddX (MulX iv S) E) or (AddX E (MulX iv S)). - if (is_scaled_iv(exp->in(which = 1), iv, bt, &scale, p_short_scale) || - is_scaled_iv(exp->in(which = 2), iv, bt, &scale, p_short_scale)) { - offset = exp->in(which == 1 ? 2 : 1); // the other argument + int which = 0; // Input index of the subexpression we find the IV in. + Node* offset_other = nullptr; // Offset subexpression (the other input). + if (opc == Op_Add(bt)) { + jlong scale_which = 0; // Scale of the IV subexpression. + // Check for a scaled IV in AddX(MulX(iv, S), E) or AddX(E, MulX(iv, S)). + if (is_scaled_iv(exp->in(which = 1), iv, bt, &scale_which) || + is_scaled_iv(exp->in(which = 2), iv, bt, &scale_which)) { + offset_other = exp->in(which == 1 ? 2 : 1); // The other argument. + // if bt = T_INT (implies iv is int): + // exp->in(which) = MulI( iv, scale_which) where scale_which is int + // -> which = 1: exp = AddI(MulI( iv, scale_which), offset_other) where scale_which is int + // -> which = 2: exp = AddI(offset_other, MulI( iv, scale_which)) = AddI(MulI( iv, scale_which), offset_other) where scale_which is int + // if bt = T_LONG and iv is long: + // exp->in(which) = MulL( iv, scale_which) + // -> which = 1: exp = AddL(MulL( iv, scale_which), offset_other) + // -> which = 2: exp = AddL(offset_other, MulL( iv, scale_which)) = AddL(MulL( iv, scale_which), offset_other) + // if bt = T_LONG and iv is int: + // exp->in(which) = MulL(ConvI2L(iv), scale_which) + // -> which = 1: exp = AddL(MulL(ConvI2L(iv), scale_which), offset_other) + // -> which = 2: exp = AddL(offset_other, MulL(ConvI2L(iv), scale_which)) = AddL(MulL(ConvI2L(iv), scale_which), offset_other) if (p_scale != nullptr) { - *p_scale = scale; + *p_scale = scale_which; } if (p_offset != nullptr) { - *p_offset = offset; + *p_offset = offset_other; } return true; } - // Check for more addends, like (AddX (AddX (MulX iv S) E1) E2), etc. - if (is_scaled_iv_plus_extra_offset(exp->in(1), exp->in(2), iv, bt, p_scale, p_offset, p_short_scale, depth) || - is_scaled_iv_plus_extra_offset(exp->in(2), exp->in(1), iv, bt, p_scale, p_offset, p_short_scale, depth)) { + // Check for more addends, like AddX(AddX(MulX(iv, S), E1), E2), etc. + if (is_scaled_iv_plus_extra_offset(exp->in(1), exp->in(2), iv, bt, p_scale, p_offset, depth) || + is_scaled_iv_plus_extra_offset(exp->in(2), exp->in(1), iv, bt, p_scale, p_offset, depth)) { + // See proof in is_scaled_iv_plus_extra_offset. return true; } - } else if (opc == Op_Sub(exp_bt)) { - if (is_scaled_iv(exp->in(which = 1), iv, bt, &scale, p_short_scale) || - is_scaled_iv(exp->in(which = 2), iv, bt, &scale, p_short_scale)) { - // Match (SubX SIV[iv] E) as if (AddX SIV[iv] (SubX 0 E)), and - // match (SubX E SIV[iv]) as if (AddX E (SubX 0 SIV[iv])). - offset = exp->in(which == 1 ? 2 : 1); // the other argument + } else if (opc == Op_Sub(bt)) { + jlong scale_which = 0; // Scale of the IV subexpression. + if (is_scaled_iv(exp->in(which = 1), iv, bt, &scale_which) || + is_scaled_iv(exp->in(which = 2), iv, bt, &scale_which)) { + offset_other = exp->in(which == 1 ? 2 : 1); // The other argument. + // if bt = T_INT (implies iv is int): + // exp->in(which) = MulI(iv, scale_which) where scale_which is int + // if which=1: + // exp = SubI(MulI( iv, scale_which), offset_other ) + // = AddI(MulI( iv, scale_which), offset=SubI(0, offset_other)) + // if which=2: + // exp = SubI(offset_other, MulI( iv, scale_which )) + // = AddI(offset_other, MulI( iv, scale=SubI(0, scale_which))) + // = AddI(MulI( iv, scale=SubI(0, scale_which)), offset_other) + // + // if bt = T_LONG and iv is long: + // exp->in(which) = MulL(iv, scale_which) + // if which=1: + // exp = SubL(MulL( iv, scale_which), offset_other ) + // = AddL(MulL( iv, scale_which), offset=SubL(0, offset_other)) + // if which=2: + // exp = SubL(offset_other, MulL( iv, scale_which )) + // = AddL(offset_other, MulL( iv, scale=SubL(0, scale_which))) + // = AddL(MulL( iv, scale=SubL(0, scale_which)), offset_other) + // + // if bt = T_LONG and iv is int: + // exp->in(which) = MulL(ConvI2L(iv), scale_which) + // if which=1: + // exp = SubL(MulL(ConvI2L(iv), scale_which), offset_other ) + // = AddL(MulL(ConvI2L(iv), scale_which), offset=SubL(0, offset_other)) + // if which=2: + // exp = SubL(offset_other, MulL(ConvI2L(iv), scale_which )) + // = AddL(offset_other, MulL(ConvI2L(iv), scale=SubL(0, scale_which))) + // = AddL(MulL(ConvI2L(iv), scale=SubL(0, scale_which)), offset_other) + // + // Match SubX(SIV[iv], E) as if AddX(SIV[iv], SubX(0, E)), and + // match SubX(E, SIV[iv]) as if AddX(E, SubX(0, SIV[iv])). + // + // Let's compute the scale: + // which=1: scale= scale_which + // which=2: scale=SubX(0, scale_which) + // We could have used the bt case distinction for SubX(0, scale_which), + // but instead we compute it in long for both cases. For int, this + // can only lead to a wrong result (i.e. go out of int range) for + // min_jint, so we exclude that case. + jlong scale_maybe_neg = scale_which; if (which == 2) { // We can't handle a scale of min_jint (or min_jlong) here as -1 * min_jint = min_jint - if (scale == min_signed_integer(bt)) { + if (scale_which == min_signed_integer(bt)) { return false; // cannot negate the scale of the iv } - scale = java_multiply(scale, (jlong)-1); + // scale = SubL(0, scale_which) = MulL(scale_which, -1) + scale_maybe_neg = java_multiply(scale_which, (jlong)-1); } if (p_scale != nullptr) { - *p_scale = scale; + *p_scale = scale_maybe_neg; } + // Now let's compute the offset: + // which=1: offset=SubX(0, offset_other) + // which=2: offset= offset_other if (p_offset != nullptr) { + Node* offset_maybe_neg = offset_other; if (which == 1) { // must negate the extracted offset - Node* zero = integercon(0, exp_bt); - Node *ctrl_off = get_ctrl(offset); - offset = SubNode::make(zero, offset, exp_bt); - register_new_node(offset, ctrl_off); + Node* zero = integercon(0, bt); + offset_maybe_neg = SubNode::make(zero, offset_other, bt); + register_new_node(offset_maybe_neg, get_ctrl(offset_other)); } - *p_offset = offset; + *p_offset = offset_maybe_neg; } return true; } @@ -2863,26 +3042,54 @@ bool PhaseIdealLoop::is_scaled_iv_plus_offset(Node* exp, Node* iv, BasicType bt, } // Helper for is_scaled_iv_plus_offset(), not called separately. -// The caller encountered (AddX exp1 offset3) or (AddX offset3 exp1). -// Here, exp1 is inspected to see if it is a simple linear transform of iv. -// If so, the offset3 is combined with any other offset2 from inside exp1. +// The caller encountered AddX(exp1, offset3) or AddX(offset3, exp1). +// Here, exp1 is inspected to see if it is a simple affine transform of iv. +// If so, the (constant) offset3 is combined with any other offset2 from inside exp1. +// +// We assume the input form is equivalent to: +// if bt = T_INT: +// exp = AddI(exp1, offset3) +// if bt = T_LONG: +// exp = AddL(exp1, offset3) +// +// On success, we guarantee this form: +// if bt = T_INT (implies iv is int): +// exp = AddI(MulI( iv, p_scale), p_offset) where p_scale is int +// if bt = T_LONG and iv is long: +// exp = AddL(MulL( iv, p_scale), p_offset) +// if bt = T_LONG and iv is int: +// exp = AddL(MulL(ConvI2L(iv), p_scale), p_offset) +// bool PhaseIdealLoop::is_scaled_iv_plus_extra_offset(Node* exp1, Node* offset3, Node* iv, BasicType bt, jlong* p_scale, Node** p_offset, - bool* p_short_scale, int depth) { + int depth) { // By the time we reach here, it is unlikely that exp1 is a simple iv*K. - // If is a linear iv transform, it is probably an add or subtract. + // If it is an affine iv transform, it is probably an add or subtract. // Let's collect the internal offset2 from it. Node* offset2 = nullptr; if (offset3->is_Con() && depth < 2 && is_scaled_iv_plus_offset(exp1, iv, bt, p_scale, - &offset2, p_short_scale, depth+1)) { + &offset2, depth+1)) { + // We know that: + // AddI(AddI(x, offset2), offset3) = AddI(x, offset=AddI(offset2, offset3)) + // AddL(AddL(x, offset2), offset3) = AddL(x, offset=AddL(offset2, offset3)) + // + // if bt = T_INT (implies iv is int): + // exp1 = AddI(MulI( iv, p_scale), offset2) where p_scale is int + // -> exp = AddI(AddI(MulI( iv, p_scale), offset2), offset3) = AddI(MulI( iv, p_scale), offset=AddI(offset2, offset3)) + // if bt = T_LONG and iv is long: + // exp1 = AddL(MulL( iv, p_scale), offset2) + // -> exp = AddL(AddL(MulL( iv, p_scale), offset2), offset3) = AddL(MulL( iv, p_scale), offset=AddL(offset2, offset3)) + // if bt = T_LONG and iv is int: + // exp1 = AddL(MulL(ConvI2L(iv), p_scale), offset2) + // -> exp = AddL(AddL(MulL(ConvI2L(iv), p_scale), offset2), offset3) = AddL(MulL(ConvI2L(iv), p_scale), offset=AddL(offset2, offset3)) + // if (p_offset != nullptr) { - Node* ctrl_off2 = get_ctrl(offset2); - Node* offset = AddNode::make(offset2, offset3, bt); - register_new_node(offset, ctrl_off2); - *p_offset = offset; + Node* offset_sum = AddNode::make(offset2, offset3, bt); + register_new_node(offset_sum, get_ctrl(offset2)); + *p_offset = offset_sum; } return true; } diff --git a/src/hotspot/share/opto/loopnode.cpp b/src/hotspot/share/opto/loopnode.cpp index 6fc07d83a923..a6dca7f5b801 100644 --- a/src/hotspot/share/opto/loopnode.cpp +++ b/src/hotspot/share/opto/loopnode.cpp @@ -1356,11 +1356,16 @@ bool PhaseIdealLoop::try_make_short_running_loop(IdealLoopTree* loop, jint strid return true; } +// This method extracts long range checks and computes the reduced iteration count (reduced_iters_limit) such that all +// extracted long range checks can be transformed. In particular, we check that +// - reduced_iters_limit * ABS(scale) <= original_iters_limit < max_jint +// - we have the proper form "scale*iv + offset _body.size(); i++) { Node* c = loop->_body.at(i); if (c->is_IfProj() && c->in(0)->is_RangeCheck()) { @@ -1375,6 +1380,7 @@ int PhaseIdealLoop::extract_long_range_checks(const IdealLoopTree* loop, jint st scale != min_jlong && original_iters_limit / ABS(scale) >= min_iters * ABS(stride_con)) { assert(scale == (jint)scale, "scale should be an int"); + // Ensure: reduced_iters_limit * ABS(scale) <= original_iters_limit < max_jint reduced_iters_limit = MIN2(reduced_iters_limit, original_iters_limit/ABS(scale)); range_checks.push(c); } @@ -1385,138 +1391,1056 @@ int PhaseIdealLoop::extract_long_range_checks(const IdealLoopTree* loop, jint st return checked_cast(reduced_iters_limit); } -// One execution of the inner loop covers a sub-range of the entire iteration range of the loop: [A,Z), aka [A=init, -// Z=limit). If the loop has at least one trip (which is the case here), the iteration variable i always takes A as its -// first value, followed by A+S (S is the stride), next A+2S, etc. The limit is exclusive, so that the final value B of -// i is never Z. It will be B=Z-1 if S=1, or B=Z+1 if S=-1. - -// If |S|>1 the formula for the last value B would require a floor operation, specifically B=floor((Z-sgn(S)-A)/S)*S+A, -// which is B=Z-sgn(S)U for some U in [1,|S|]. So when S>0, i ranges as i:[A,Z) or i:[A,B=Z-U], or else (in reverse) -// as i:(Z,A] or i:[B=Z+U,A]. It will become important to reason about this inclusive range [A,B] or [B,A]. - -// Within the loop there may be many range checks. Each such range check (R.C.) is of the form 0 <= i*K+L < R, where K -// is a scale factor applied to the loop iteration variable i, and L is some offset; K, L, and R are loop-invariant. -// Because R is never negative (see below), this check can always be simplified to an unsigned check i*K+L 0), where the limit is -// chosen to prevent various cases of 32-bit overflow (including multiplications j*K below). In the sub-loop the -// logical value i is offset from j by a 64-bit constant C, so i ranges in i:C+[0,Z_2). - -// For S<0, j ranges (in reverse!) through j:[-|B_2|,0] or (-|Z_2|,0]. For either sign of S, we can say i=j+C and j -// ranges through 32-bit ranges [A_2,B_2] or [B_2,A_2] (A_2=0 of course). - -// The disjoint union of all the C+[A_2,B_2] ranges from the sub-loops must be identical to the whole range [A,B]. -// Assuming S>0, the first C must be A itself, and the next C value is the previous C+B_2, plus S. If |S|=1, the next -// C value is also the previous C+Z_2. In each sub-loop, j counts from j=A_2=0 and i counts from C+0 and exits at -// j=B_2 (i=C+B_2), just before it gets to i=C+Z_2. Both i and j count up (from C and 0) if S>0; otherwise they count -// down (from C and 0 again). - -// Returning to range checks, we see that each i*K+L 0 and K>0), Q_min=A_2*K+Q and Q_max=Q (if S<0 and K>0), -// Q_min=B_2*K+Q and Q_max=Q if (S>0 and K<0), Q_min=Q and Q_max=A_2*K+Q (if S<0 and K<0) - -// Note that the first R.C. value is always Q=(S*K>0 ? Q_min : Q_max). Also Q_{min,max} = Q + {min,max}(A_2*K,B_2*K). -// If S*K>0 then, as the loop iterations progress, each R.C. value i*K+L = j*K+Q goes up from Q=Q_min towards Q_max. -// If S*K<0 then j*K+Q starts at Q=Q_max and goes down towards Q_min. - -// Case A: Some Negatives (but no overflow). -// Number line: -// |s64_min . . . 0 . . . s64_max| -// | . Q_min..Q_max . 0 . . . . | s64 negative -// | . . . . R=0 R< R< R< R< | (against R values) -// | . . . Q_min..0..Q_max . . . | small mixed -// | . . . . R R R< R< R< | (against R values) -// -// R values which are out of range (>Q_max+1) are reduced to max(0,Q_max+1). They are marked on the number line as R<. -// -// So, if Q_min 0 (R.C.E. steps upward) -// j*K + s32_trunc(Q_max) R R R< R< | (against R values) -// | . . . . 0 . Q_min..Q_max . | s64 positive -// | . . . . R> R> R R R< | (against R values) -// -// R values which are out of range (Q_max+1) are reduced as marked: R> up to Q_min, R< down to Q_max+1. -// Then the whole comparison is shifted left by Q_min, so it can take place at zero, which is a nice 32-bit value. -// -// So, if both Q_min, Q_max+1 >=s64 0, then use this test: -// j*K + 0 0 -// More generally: -// j*K + Q - Q_min R> R> R> R | (against R values) -// -// In this case, Q_min >s64 Q_max+1, even though the mathematical values of Q_min and Q_max+1 are correctly ordered. -// The formulas from the previous case can be used, except that the bad upper bound Q_max is replaced by max_jlong. -// (In fact, we could use any replacement bound from R to max_jlong inclusive, as the input to the clamp function.) -// -// So if Q_min >=s64 0 but Q_max+1 0 -// More generally: -// j*K + Q - Q_min H, it returns L not H. -// -// All of the formulas above can be merged into a single one: -// L_clamp = Q_min < 0 ? 0 : Q_min --whether and how far to left-shift -// H_clamp = Q_max+1 < Q_min ? max_jlong : Q_max+1 -// = Q_max+1 < 0 && Q_min >= 0 ? max_jlong : Q_max+1 -// Q_first = Q = (S*K>0 ? Q_min : Q_max) = (C*K+L) -// R_clamp = clamp(R, L_clamp, H_clamp) --reduced dynamic range -// replacement R.C.: -// j*K + Q_first - L_clamp = min_iters * ABS(stride_con) (KI-1) +// where +// K = scale +// min_iters = 2 +// 0 < original_iters_limit < max_jint +// ABS(stride_con) > 0 +// From (KI-1) +// min_iters * ABS(stride_con) = 2 * ABS(stride_con) <= original_iters_limit / ABS(K) +// then +// 2 * ABS(stride_con) * ABS(K) <= original_iters_limit +// and +// 0 <= ABS(K) <= original_iters_limit < max_jint +// +// It follows that K is indeed an int32_t (KI). +// +// Condition (R0) +// -------------- +// In PhaseIdealLoop::extract_long_range_check(), we check IdealLoopTree::is_range_check_if() which guarantees that +// R >= 0 +// because R is either a LoadRange or an integer that is non-negative (R0). +// +// Conditions (P-lo), (P-hi), (Q-lo), (Q-hi), and (Q-lo-hi) +// -------------------------------------------------------- +// j is to be the induction variable of an int counted loop (i.e. the inner iv). +// From PhaseIdealLoop::extract_long_range_check() and K = scale, we reduce the iterations so that: +// reduced_iters_limit * ABS(K) <= original_iters_limit < max_jint (Q1) +// +// By the fundamentals of an int counted loop, we have: +// j_min, j_max being int32_t values +// such that +// SI(j_min) <= SI(j) <= SI(j_max) (Q2) +// +// Furthermore, due to how we construct the int counted loop, we also have +// 0 <= SI(j_max) - SI(j_min) <= reduced_iters_limit (Q3) +// +// This means that: +// +// For K > 0, we multiply K to (Q2) +// SI(j_min) * K <= SI(j) * K <= SI(j_max) * K +// and to (Q3) +// 0 <= SI(j_max) * K - SI(j_min) * K <= reduced_iters_limit * K +// < max_jint (apply (Q1)) +// and we get +// 0 < SI(j_max) * K - SI(j_min) * K + 1 <= max_jint (Q4) +// +// For K < 0, we multiply K to (Q2) +// SI(j_min) * K >= SI(j) * K >= SI(j_max) * K +// and to (Q3) +// 0 <= SI(j_min) * K - SI(j_max) * K <= reduced_iters_limit * -K +// < max_jint (apply (Q1)) +// and we get +// 0 < SI(j_min) * K - SI(j_max) * K + 1 <= max_jint (Q5) +// +// Since the multiplication of int32_t values must be an int64_t value, we can choose: +// (P-lo): P_lo such that SL(P_lo) is the smallest value of "SI(j) * K" +// (P-hi): P_hi such that SL(P_hi) is the largest value of "SI(j) * K" plus 1. +// +// This means that: +// +// For K > 0, we have: +// SL(P_lo) = SI(j_min) * K +// SL(P_hi) = SI(j_max) * K + 1 +// which implies +// SL(P_lo) = SI(j_min) * K <= SI(j) * K <= SI(j_max) * K + 1 = SL(P_hi) +// Starting from (Q4) +// 0 < SI(j_max) * K - SI(j_min) * K + 1 <= max_jint +// we get +// 0 < SL(P_hi) - SL(P_lo) <= max_jint (Q6) +// +// For K < 0, we have: +// SL(P_lo) = SI(j_max) * K +// SL(P_hi) = SI(j_min) * K + 1 +// which implies +// SL(P_lo) = SI(j_max) * K <= SI(j) * K <= SI(j_min) * K + 1 = SL(P_hi) +// Starting from (Q5) +// 0 < SI(j_min) * K - SI(j_max) * K + 1 <= max_jint +// we get +// 0 < SL(P_hi) - SL(P_lo) <= max_jint (Q7) +// +// As a result, from (Q6) and (Q7), we see that +// 0 < SL(P_hi) - SL(P_lo) <= max_jint (Q8) +// +// From (P-lo) +// SL(P_lo) <= SI(j) * K +// and (P-hi) +// SL(P_hi) >= SI(j) * K + 1 +// and (Q8) follows +// 0 <= SI(j) * K - SL(P_lo) +// < SL(P_hi) - SL(P_lo) +// <= max_jint (Q9) +// +// Since SI(j) and K are int32_t values, their multiplication +// SI(j) * K +// cannot overflow the int64_t range and therefore +// SI(j) * K = SL(MulL(ConvI2L(j), K)) (Q10) +// +// Starting from (Q9) +// 0 <= SI(j) * K - SL(P_lo) < SL(P_hi) - SL(P_lo) <= max_jint +// it follows with (Q10) +// 0 <= SL(MulL(ConvI2L(j), K)) - SL(P_lo) < SL(P_hi) - SL(P_lo) <= max_jint (Q11) +// +// From (Q9) +// 0 <= SL(P_hi) - SL(P_lo) <= max_jint +// it follows that SubL(P_hi, P_lo) does not overflow the signed long domain: +// SL(SubL(P_hi, P_lo)) = SL(P_hi) - SL(P_lo) (Q12) +// and from (Q11) +// SubL(MulL(ConvI2L(j), K), P_lo) +// does not overflow the signed long domain: +// SL(SubL(MulL(ConvI2L(j), K), P_lo) = SL(MulL(ConvI2L(j), K)) - SL(P_lo) (Q13) +// +// From (Q11) +// 0 <= SL(MulL(ConvI2L(j), K)) - SL(P_lo) < SL(P_hi) - SL(P_lo) <= max_jint +// we apply (Q13) +// 0 <= SL(SubL(MulL(ConvI2L(j), K), P_lo)) < SL(P_hi) - SL(P_lo) <= max_jint +// and apply (Q12) +// 0 <= SubL(MulL(ConvI2L(j), K), P_lo) < SubL(P_hi, P_lo) <= max_jint (Q14) +// +// Note that we can do a simple reassociation of SubL if x, y, and z are int64_t: +// SubL(x, y) = SubL(AddL(x, z), AddL(y, z)) (Q15) +// We apply (Q15) to both sides of (Q14) +// 0 <= SubL( MulL(ConvI2L(j), K), P_lo)) < SubL( P_hi, P_lo) <= max_jint +// and get +// 0 <= SubL(AddL(MulL(ConvI2L(j), K), Q), AddL(P_lo, Q)) < SubL(AddL(P_hi, Q), AddL(P_lo, Q)) <= max_jint (Q16) +// +// We can insert +// Q_lo = AddL(P_lo, Q) (Q-lo) +// and +// Q_hi = AddL(P_hi, Q) (Q-hi) +// in (Q16) and get (Q-lo-hi) +// 0 <= SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo ) < SubL(Q_hi, Q_lo) <= max_jint +// +// Condition (LHS) +// --------------- +// We ensure with +// PhaseIdealLoop::extract_long_range_check() -> PhaseIdealLoop::is_range_check_if() +// that we indeed have the form +// AddL(MulL(ConvI2L(j), K), Q) (done with PhaseIdealLoop::is_scaled_iv_plus_offset()) +// for the long range check with R (R0): +// LHS = CmpUL(AddL(MulL(ConvI2L(j), K), Q), R) +// +// +// We have our conditions +// (KI): K is an int32_t constant +// (P-lo): P_lo: The smallest value of "j * K" (i.e. the smallest value of the inner scaled iv), an int64_t value +// (P-hi): P_hi: The largest value of "j * K" (i.e. the largest value of the inner scaled iv) plus 1, an int64_t value +// (Q-lo): Q_lo = AddL(P_lo, Q), an int64_t value +// (Q-hi): Q_hi = AddL(P_hi, Q), an int64_t value +// (R0): R is an int64_t value such that R >= 0 +// (Q-lo-hi): 0 <= SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo) < SubL(Q_hi, Q_lo) <= max_jint +// (LHS): LHS = CmpUL(AddL(MulL(ConvI2L(j), K), Q), R) +// and define the additional variables +// (JI): j is the inner iv, an int32_t value (actually further clamped by the possible values of the inner iv) +// (QL): Q is an int64_t value +// (L-clamp): L_clamp = MaxL(Q_lo, 0) +// (H-clamp): H_clamp = Q_hi < Q_lo ? max_jlong : Q_hi +// (R-clamp): R_clamp = MaxL(MinL(R, H_clamp), L_clamp) +// and our target int range check as +// RHS = CmpU(AddI(MulI(j, K), ConvL2I(SubL(Q, L_clamp))), ConvL2I(SubL(R_clamp, L_clamp))) +// +// +// Now that we established our conditions and definitions, we prove that +// (LHS < 0) == (RHS < 0) +// which means that we can safely replace the long range check (LHS) with the int range check (RHS). +// +// +// Proof +// ===== +// Before starting with the proof, we start with some definitions and lemmas relevant for the proof. +// +// Definitions +// =========== +// Given x, y being int64_t values, we have: +// (CmpL-SL): CmpL(x, y) = Cmp(SL(x), SL(y)) +// (CmpUL-UL): CmpUL(x, y) = Cmp(UL(x), UL(y)) +// +// Given x, y being int64_t values with x < 0 and y >= 0: +// (CmpUL-gt): CmpUL(x, y) > 0 +// +// Given x being an int32_t value: +// (CmpU-gte-zero): CmpU(x, 0) >= 0 +// +// When the following AddL/SubL do not overflow, then: +// (AddL-SL-no-overflow): SL(AddL(x, y)) = SL(x) + SL(y) +// (SubL-UL-no-overflow): UL(SubL(x, y)) = UL(x) - UL(y) +// +// When x, y < 0, then +// (SubL-no-overflow): SubL(x, y) does not overflow the long signed domain +// +// No-Overflow-Lemmas +// ================== +// SubL-pos-no-overflow +// -------------------- +// When x, y >= 0, both being int64_t values, then +// SubL(x, y) +// does not overflow the long signed domain. +// +// Proof: +// max_jlong >= SL(x) - 0 +// > SL(x) - 1 +// > ... +// > SL(x) - max_jlong +// > min_jlong +// +// We refer to this lemma with (SubL-pos-no-overflow). +// +// AddL-no-overflow +// ---------------- +// When x <= 0 and y >= 0, both being int64_t values, then +// AddL(x, y) +// does not overflow the long signed domain. +// +// Proof: +// min_jlong <= SL(x) + 0 +// < SL(x) + 1 +// < ... +// < SL(x) + max_jlong +// <= max_jlong +// +// We refer to this lemma with (AddL-no-overflow). +// +// Lemma 1 +// ======= +// For x being an int64_t value such that min_jint <= x <= max_jint +// x = ConvI2L(ConvL2I(x)) +// +// Proof: +// If 0 <= x <= max_jint, the upper 33 bits of x are 0s. +// ConvL2I(x) truncates 32 bits, leaving a value with the highest bit being 0. +// ConvI2L(ConvL2I(x)) signed extends this value, filling the 32 upper bits of the result with 0. +// As a result, x and ConvI2L(ConvL2I(x)) have the same 32 lower bits, and their 32 upper bits are all 0, +// which means x == ConvI2L(ConvL2I(x)). +// +// Otherwise, min_jint <= x < 0, the upper 33 bits of x are 1s. +// ConvL2I(x) truncates 32 bits, leaving a value with the highest bit being 1. +// ConvI2L(ConvL2I(x)) signed extends this value, filling the 32 upper bits of the result with 1. +// As a result, x and ConvI2L(ConvL2I(x)) have the same 32 lower bits, and their 32 upper bits are all 1, which means x == ConvI2L(ConvL2I(x)) +// +// Lemma 2 +// ======= +// Fox x being an int32_t value +// x = ConvL2I(ConvI2L(x)) +// +// Proof: +// ConvL2I(ConvI2L(x)) extends x with 32 bits, then truncates those without touching the 32 lower bits. As a result, x == ConvL2I(ConvI2L(x)) +// +// Lemma 3 +// ======= +// For x, y being int32_t values +// CmpU(x, y) = CmpUL(ConvI2L(x), ConvI2L(y)) +// +// Proof: +// This is because +// UL(ConvI2L(0)) < UL(ConvI2L(1)) < UL(ConvI2L(2)) < ... < UL(ConvI2L(max_jint)) +// < UL(ConvI2L(min_jint)) < ... < UL(ConvI2L(-1)) +// is the same ordering as +// UI(0) < UI(1) < UI(2) < ... < UI(max_jint) < UI(min_jint) < ... < UI(-1) +// +// Lemma 4 +// ======= +// For x, y being int64_t values +// ConvL2I(AddL/SubL/MulL(x, y)) == AddI/SubI/MulI(ConvL2I(x), ConvL2I(y)) +// +// Proof: +// UI(ConvL2I(AddL(x, y))) == UL(AddL(x, y)) mod 2^32 (because UI(ConvL2I(v)) == UL(v) mod 2^32) +// == ((UL(x) + UL(y)) mod 2^64) mod 2^32 (because UL(AddL(u, v)) == (UL(u) + UL(v)) mod 2^64) +// == (UL(x) + UL(y)) mod 2^32 (because (v mod 2^64) mod 2^32 == v mod 2^32) +// == ((UL(x) mod 2^32) + (UL(y) mod 2^32)) mod 2^32 (because (u + v) mod 2^32 == ((u mod 2^32) + (v mod 2^32)) mod 2^32) +// == (UI(ConvL2I(x)) + UI(ConvL2I(y))) mod 2^32 (because UI(ConvL2I(v)) == UL(v) mod 2^32) +// == UI(AddI(ConvL2I(x), ConvL2I(y))) (because UI(AddI(u, v)) == (UI(u) + UI(v)) mod 2^32) +// +// Which means ConvL2I(AddL(x, y)) == AddI(ConvL2I(x), ConvL2I(y)) (because UI(u) == UI(v) necessarily implies u == v) +// +// Lemma 5 +// ======= +// For x, y, z being int64_t values such that AddL(x, z) and AddL(y, z) do not under-/overflow the signed long domain. +// CmpL(x, y) == CmpL(AddL(x, z), AddL(y, z)) +// +// Proof: +// This means that: +// CmpL(AddL(x, z), AddL(y, z)) = Cmp(SL(AddL(x, z)), SL(AddL(y, z)) (apply (CmpL-SL)) +// = Cmp(SL(x) + SL(z), SL(y) + SL(z)) (apply (AddL-SL-no-overflow))) +// = Cmp(S(x), S(y)) (because Cmp(u + t, v + t) == Cmp(u, v)) +// = CmpL(x, z) (apply (CmpL-SL)) +// +// Lemma 6 +// ======= +// For x, y, z being int64_t values such that SubL(x, z) and SubL(y, z) do not under-/overflow the unsigned long domain +// CmpUL(x, y) == CmpUL(SubL(x, z), SubL(y, z)) +// +// Proof: +// This means that: +// CmpUL(SubL(x, z), SubL(y, z)) = Cmp(UL(SubL(x, z)), UL(SubL(y, z))) (apply (CmpL-SL)) +// = Cmp(U(x) - U(z), U(y) - U(z)) (apply (SubL-UL-no-overflow)) +// = Cmp(U(x), U(y)) (because Cmp(u - t, v - t) == Cmp(u, v)) +// = CmpUL(x, y) (apply (CmpL-SL)) +// +// Lemma 7 +// ======= +// For x, y, z being int64_t values such that x < y, y >= 0, z >= 0 +// CmpUL(x, z) = CmpUL(x, MinL(y, z)) +// +// Proof: +// If y > z (trivial): +// CmpUL(x, z) = CmpUL(x, MinL(y, z)) +// = CmpUL(x, z) +// +// If y <= z: +// CmpUL(x, z) = CmpUL(x, MinL(y, z)) +// = CmpUL(x, y) +// +// We now show that for all x, the above equation is true. +// +// Case x >= 0: +// From all case assumptions, we have: +// 0 <= x < y <= z +// We can directly convert to unsigned +// 0 <=u x = 0 +// - 4. Q_hi < 0 < Q_lo +// for which we all need to show that +// (LHS < 0) == (RHS < 0) +// +// Case 1 and 3 can actually be improved: +// Case 1: Q_lo, Q_hi <= 0 -> Q_lo < Q_hi <= 0 +// Case 3: Q_lo, Q_hi >= 0 -> 0 <= Q_lo < Q_hi +// +// Proof by Contradiction: +// If Q_lo = Q_hi: +// SubL(Q_hi, Q_lo) = SubL(Q_hi, Q_hi) = 0 +// which violates (Q-lo-hi) +// 0 < SubL(Q_hi, Q_lo) <= max_jint +// +// If Q_lo > Q_hi: +// Case 1: Q_hi < Q_lo <= 0 +// Case 3: 0 <= Q_hi < Q_lo +// then +// min_jlong <= SubL(Q_hi, Q_lo) < 0 +// in both cases which violates (Q-lo-hi) +// +// +// We use the improved cases 1 and 3 in the following when proving +// (LHS < 0) == (RHS < 0) +// for all 4 cases for Q_hi and Q_lo: +// +// +// Case 1: Q_lo < Q_hi <= 0 +// ======================== +// We can simplify the given clamp values: +// L_clamp = MaxL(Q_lo, 0) +// = 0 +// H_clamp = Q_hi < Q_lo ? max_jlong : Q_hi +// = Q_hi +// R_clamp = MaxL(MinL(R, H_clamp), L_clamp) +// = MaxL(MinL(R, Q_hi ), 0) +// = MaxL(Q_hi, 0) (because R >= 0, Q_hi < 0) +// = 0 (because Q_hi < 0) +// +// +// From (Q-lo-hi) +// 0 <= SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo) < SubL(Q_hi, Q_lo) +// together with Q_lo < 0 (Case 1), we see that +// AddL(SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo), Q_lo) +// ---------------- >= 0 ------------------ --- < 0 --- +// and +// AddL(SubL(Q_hi, Q_lo), Q_lo) +// ----- > 0 ----- --- < 0 --- +// both cannot not overflow the signed long domain (1-no-overflow) due to (AddL-no-overflow). +// +// The form (Q-lo-hi) +// 0 <= SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo) < SubL(Q_hi, Q_lo) <= max_jint +// can also be written as +// CmpL( SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo), SubL(Q_hi, Q_lo)) < 0 +// with (1-no-overflow), we can apply Lemma 5 and add Q_lo to both sides +// CmpL(AddL(SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo), Q_lo), AddL(SubL(Q_hi, Q_lo), Q_lo)) < 0 +// cancel out the Q_lo terms +// CmpL( AddL(MulL(ConvI2L(j), K), Q), Q_hi) < 0 +// and with (Case 1) see that +// AddL(MulL(ConvI2L(j), K), Q) < Q_hi <= 0 (1a) +// +// From (1a), we know that +// a = AddL(MulL(ConvI2L(j), K), Q) < 0 +// and from (R0) that +// b = R >= 0 +// which satisfies (CmpUL-gt) and we can conclude that +// LHS = CmpUL(AddL(MulL(ConvI2L(j), K), Q), R) > 0 +// ----------- = a ----------- --- = b --- +// +// Inserting the simplified L_clamp and R_clamp from above, we get for RHS +// RHS == CmpU(AddI(MulI(j, K), ConvL2I(SubL(Q, L_clamp))), ConvL2I(SubL(R_clamp, L_clamp))) +// == CmpU(AddI(MulI(j, K), ConvL2I(SubL(Q, 0))), ConvL2I(SubL(0, 0))) +// == CmpU(AddI(MulI(j, K), ConvL2I( Q )), 0) +// == CmpU(c , 0) +// >= 0 (apply (CmpU-gte-zero)) +// +// +// We showed that +// LHS > 0 +// and +// RHS >= 0 +// and therefore proved that +// (LHS < 0) == (RHS < 0) == false +// +// +// Case 2: Q_lo < 0 < Q_hi +// ======================= +// We can simplify the given clamp values: +// L_clamp = MaxL(Q_lo, 0) +// = 0 +// H_clamp = Q_hi < Q_lo ? max_jlong : Q_hi +// = Q_hi +// R_clamp = MaxL(MinL(R, H_clamp), L_clamp) +// = MaxL(MinL(R, Q_hi ), 0) +// = MinL(R, Q_hi) (because R, Q_hi >= 0) +// +// +// Looking at (Case 2): +// Q_hi > 0 > Q_lo +// we have a constrained positive difference +// 0 < SL(Q_hi) - SL(Q_lo) +// <= max_jlong + (-min_jlong) +// = (2 * max_jlong) + 1 +// = 2^64 - 1 +// +// If +// 0 < SL(Q_hi) - SL(Q_lo) <= max_jlong +// then we don't have an overflow of SubL(Q_hi, Q_lo). +// +// If +// max_jlong < SL(Q_hi) - SL(Q_lo) <= 2^64 - 1 +// then SubL(Q_hi, Q_lo) leads to exactly one overflow. The result is always negative: +// SubL(Q_hi, Q_lo) = TruncL(SL(Q_hi) - SL(Q_lo)) = SL(Q_hi) - SL(Q_lo) - 2^64 < 0 +// This case is impossible as it violates (Q-lo-hi) +// 0 < SubL(Q_hi, Q_lo) +// We conclude that SubL(Q_hi, Q_lo) does not overflow the signed long domain (2a-no-overflow). +// +// +// From (Q-lo-hi) and (2a-no-overflow), we get +// max_jint >= SubL(Q_hi, Q_lo) +// = SL(SubL(Q_hi, Q_lo)) +// = SL(Q_hi) - SL(Q_lo) (2a) +// +// Moreover, from (Case 2), we get +// Q_hi > 0 +// SL(Q_hi) > 0 (2b) +// and +// Q_lo < 0 +// SL(Q_lo) < 0 (2c) +// +// It follows for Q_lo +// SL(Q_lo) = SL(Q_hi) - (SL(Q_hi) - SL(Q_lo)) +// > 0 - (SL(Q_hi) - SL(Q_lo)) (apply (2b)) +// >= 0 - max_jint (apply (2a)) +// = -max_jint +// and as a result +// Q_lo > -max_jint (2e) +// +// It follows for Q_hi +// SL(Q_hi) = (SL(Q_hi) - SL(Q_lo)) + SL(Q_lo) +// <= max_jint + SL(Q_lo) (apply (2a)) +// < max_jint (apply (2c)) +// and as a result +// Q_hi < max_jint (2f) +// +// With (2e) (2f), and (Case 2), we see that Q_lo and Q_hi fit into the 32-bit integer range: +// -max_jint < Q_lo < 0 < Q_hi < max_jint (2g) +// +// +// From (Q-lo-hi) +// 0 <= SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo) < SubL(Q_hi, Q_lo) <= max_jint +// together with Q_lo < 0 (Case 2), we see that neither adding Q_lo to the left side +// AddL(SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo), Q_lo) +// ---------------- >= 0 ------------------ --- < 0 --- +// nor to the right side +// AddL(SubL(Q_hi, Q_lo), Q_lo) +// ----- > 0 ----- --- < 0 --- +// can overflow the signed long domain (2b-no-overflow). +// +// +// The form (Q-lo-hi) +// 0 <= SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo) < SubL(Q_hi, Q_lo) <= max_jint +// can also be written as +// CmpL( SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo), SubL(Q_hi, Q_lo)) < 0 +// with (2b-no-overflow), we can apply Lemma 5 to add Q_lo +// CmpL(AddL(SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo), Q_lo), AddL(SubL(Q_hi, Q_lo), Q_lo)) < 0 +// cancel out the Q_lo terms +// CmpL( AddL(MulL(ConvI2L(j), K), Q), Q_hi) < 0 +// and with (Case 2) and (2f) see that +// AddL(MulL(ConvI2L(j), K), Q) < Q_hi < max_jint (2h) +// +// +// From (Q-lo-hi) +// 0 <= SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo) +// we see that neither adding Q_lo to the left side +// AddL(0, Q_lo) = Q_lo +// nor to the right side (already shown with (2b-no-overflow)) +// AddL(SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo), Q_lo) +// can overflow the signed long domain (2c-no-overflow). +// +// +// The form (Q-lo-hi) +// 0 <= SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo) < SubL(Q_hi, Q_lo) <= max_jint +// can also be written as +// CmpL( 0, SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo)) <= 0 +// with (2c-no-overflow), we can apply Lemma 5 to add Q_lo +// CmpL(AddL(0, Q_lo), AddL(SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo), Q_lo)) <= 0 +// cancel out the Q_lo terms and simplify +// CmpL( Q_lo , AddL( MulL(ConvI2L(j), K), Q)) <= 0 +// and with (Case 2) and (2e) see that +// AddL(MulL(ConvI2L(j), K), Q) >= Q_lo > -max_jint (2i) +// +// +// Putting (2h) and (2i) together, we see that the AddL actually fits into the 32-bit integer range +// -max_jint < Q_lo <= AddL(MulL(ConvI2L(j), K), Q) < Q_hi < max_jint (2j) +// +// With (2j) and (R0), we also conclude +// -max_jint < MinL(Q_hi, R) < max_jint (2k) +// +// We notice with +// (2h): x = AddL(MulL(ConvI2L(j), K), Q) < Q_hi +// (Case 2): y = Q_hi > 0 +// (R0): z = R >= 0 +// that Lemma 7 can be applied +// LHS = CmpUL( AddL(MulL(ConvI2L(j), K), Q), R) +// ------------------ x ----------------- -- z -- +// = CmpUL( AddL(MulL(ConvI2L(j), K), Q), MinL(Q_hi, R)) +// ------------------ x ----------------- -- y -- -- z -- +// apply Lemma 1 to add ConvI2L-ConvL2I to both sides of CmpUL because they both fit into an int32_t ((2j) and (2k)) +// LHS = CmpUL(ConvI2L(ConvL2I(AddL(MulL(ConvI2L(j), K), Q))), ConvI2L(ConvL2I(MinL(Q_hi, R)))) +// apply Lemma 3 to transform to CmpU because both sides of CmpUL are int32_t values +// = CmpU ( ConvL2I(AddL(MulL(ConvI2L(j), K), Q)) , ConvL2I(MinL(Q_hi, R)) +// apply Lemma 4 twice to move the ConvL2I inwards +// = CmpU ( AddI(ConvL2I( MulL(ConvI2L(j), K)), ConvL2I(Q)), ConvL2I(MinL(Q_hi, R))) +// = CmpU ( AddI(MulI( ConvL2I(ConvI2L(j)), ConvL2I(K)), ConvL2I(Q)), ConvL2I(MinL(Q_hi, R))) +// apply Lemma 2 to remove the ConvL2I-ConvI2L due to (JI) and simplify K due to (KI) +// = CmpU ( AddI(MulI( j, K), ConvL2I(Q)), ConvL2I(MinL(Q_hi, R))) +// we now add a minus 0 on Q and MinL() +// = CmpU( AddI(MulI( j, K), ConvL2I(SubL(Q, 0))), ConvL2I(SubL(MinL(Q_hi, R), 0))) +// and eventually insert the simplified L_clamp and R_clamp values +// = CmpU( AddI(MulI( j, K), ConvL2I(SubL(Q, L_clamp))), ConvL2I(SubL(R_clamp, L_clamp))) +// which is equivalent to the definition of RHS +// +// +// We proved that +// LHS = RHS +// and thus +// (LHS < 0) == (RHS < 0) +// +// +// Case 3: 0 <= Q_lo < Q_hi +// ======================== +// We can simplify the given clamp values: +// L_clamp = MaxL(Q_lo, 0) +// = Q_lo +// H_clamp = Q_hi < Q_lo ? max_jlong : Q_hi +// = Q_hi +// R_clamp = MaxL(MinL(R, H_clamp), L_clamp) +// = MaxL(MinL(R, Q_hi), Q_lo) +// +// RHS = CmpU(AddI(MulI(j, K), ConvL2I(SubL(Q, L_clamp))), ConvL2I(SubL(R_clamp, L_clamp))) +// = CmpU(AddI(MulI(j, K), ConvL2I(SubL(Q, Q_lo))), ConvL2I(SubL(MaxL(MinL(R, Q_hi), Q_lo), Q_lo))) +// +// Let's first look at both SubL of (Q-lo-hi) +// 0 <= SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo) < SubL(Q_hi, Q_lo) <= max_jint +// +// We see with (SubL-pos-no-overflow) and (Case 3) that +// SubL(Q_hi, Q_lo) +// does not overflow the signed long domain. Therefore, adding +// Q_lo >= 0 (from Case 3) +// to the right side +// AddL(SubL(Q_hi, Q_lo), Q_lo) = Q_hi +// does not overflow the signed long domain and therefore adding +// Q_lo >= 0 (from Case 3) +// to the left side SubL +// AddL(SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo) Q_lo) < AddL(SubL(Q_hi, Q_lo), Q_lo) +// also does not overflow the signed long domain (3a-no-overflow). +// +// +// The form (Q-lo-hi) +// 0 <= SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo) < SubL(Q_hi, Q_lo) <= max_jint +// can also be written as +// CmpL( SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo), SubL(Q_hi, Q_lo)) < 0 +// with (3a-no-overflow), we can apply Lemma 5 and add Q_lo to both sides without overflow +// CmpL(AddL(SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo), Q_lo), AddL(SubL(Q_hi, Q_lo), Q_lo)) < 0 +// cancel out the Q_lo terms +// CmpL( AddL(MulL(ConvI2L(j), K), Q), Q_hi) < 0 +// and see that +// AddL(MulL(ConvI2L(j), K), Q) < Q_hi (3a) +// +// +// From (Q-lo-hi) +// 0 <= SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo) +// we see that neither adding Q_lo to the left side +// AddL(0, Q_lo) = Q_lo +// nor to the right side (3a-no-overflow) +// AddL(SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo), Q_lo) +// can overflow the signed long domain (3b-no-overflow). +// +// +// The form (Q-lo-hi) +// 0 <= SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo) < SubL(Q_hi, Q_lo) <= max_jint +// can also be written as +// CmpL( 0, SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo)) <= 0 +// with (3b-no-overflow), we can apply Lemma 5 and add Q_lo to both sides without overflow +// CmpL(AddL(0, Q_lo), AddL(SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo), Q_lo)) <= 0 +// cancel out the Q_lo terms and simplify +// CmpL( Q_lo , AddL( MulL(ConvI2L(j), K), Q)) <= 0 +// and seee that +// AddL(MulL(ConvI2L(j), K), Q) >= Q_lo (3b) +// +// +// Putting (3a) and (3b) together, we see that +// 0 <= Q_lo <= AddL(MulL(ConvI2L(j), K), Q) < Q_hi (3c) +// +// Looking at the definition of LHS +// LHS = CmpUL(AddL(MulL(ConvI2L(j), K), Q), R) +// we have 3 cases for R with respect to Q_lo and Q_hi: +// 3-R1: 0 <= R <= Q_lo +// 3-R2: 0 <= Q_lo < R < Q_hi +// 3-R3: 0 <= Q_lo < Q_hi <= R +// +// We need to prove for all three cases that (LHS < 0) == (RHS < 0) +// +// 3-R1: 0 <= R <= Q_lo +// -------------------- +// Combining (3-R1) and (3c), we have +// 0 <= R <= Q_lo <= AddL(MulL(ConvI2L(j), K), Q) < Q_hi +// and conclude +// LHS = CmpUL(AddL(MulL(ConvI2L(j), K), Q), R) +// >= 0 +// +// From the definition of RHS +// RHS = CmpU(AddI(MulI(j, K), ConvL2I(SubL(Q, Q_lo))), ConvL2I(SubL(MaxL(MinL(R, Q_hi), Q_lo), Q_lo))) +// we know from (3-R1) that R < Q_hi: +// RHS = CmpU(AddI(MulI(j, K), ConvL2I(SubL(Q, Q_lo))), ConvL2I(SubL(MaxL( R , Q_lo), Q_lo))) +// and R <= Q_lo: +// RHS = CmpU(AddI(MulI(j, K), ConvL2I(SubL(Q, Q_lo))), ConvL2I(SubL( Q_lo , Q_lo))) +// = CmpU(AddI(MulI(j, K), ConvL2I(SubL(Q, Q_lo))), 0) +// = CmpU(c , 0) +// >= 0 (apply (CmpU-gte-zero)) +// +// +// We showed that +// LHS >= 0 +// and +// RHS >= 0 +// and therefore proved that +// (LHS < 0) == (RHS < 0) == false +// +// +// 3-R2: 0 <= Q_lo < R < Q_hi +// -------------------------- +// From (Q-lo-hi) +// 0 <= SubL(Q_hi, Q_lo) <= max_jint +// and (3-R2), we see that subtracting Q_lo from R +// 0 < SubL(R, Q_lo) < SubL(Q_hi, Q_lo) <= max_jint (3d) +// does not overflow the unsigned long domain (3-R2a-no-overflow). +// +// Further, we know from (3c) +// 0 <= Q_lo <= AddL(MulL(ConvI2L(j), K), Q) +// that subtracting Q_lo +// 0 <= SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo) <= AddL(MulL(ConvI2L(j), K), Q) +// does also not overflow the unsigned long domain (3-R2b-no-overflow). +// +// We can now apply several lemmas and transformations to show the equivalence of LHS and RHS +// LHS = CmpUL( AddL(MulL(ConvI2L(j), K), Q), R) +// apply Lemma 6 to add SubL to both sides because both SubL do not overflow ((3-R2a-no-overflow) and (3-R2b-no-overflow)) +// LHS = CmpUL( SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo), SubL(R, Q_lo)) +// apply Lemma 1 to add ConvI2L-ConvL2I because both sides of CmpUL are int32_t values ((Q-lo-hi) + (3d)) +// LHS = CmpUL(ConvI2L(ConvL2I(SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo)), ConvI2L( ConvL2I(SubL(R, Q_lo))))) +// apply Lemma 3 to transform to CmpU because both sides of CmpUL are int32_t values +// LHS = CmpU (ConvL2I( SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo)), ConvL2I(SubL(R, Q_lo))) +// apply Lemma 4 twice to move the ConvL2I inwards +// LHS = CmpU (AddI(ConvL2I( MulL(ConvI2L(j), K )), ConvL2I(SubL(Q, Q_lo))), ConvL2I(SubL(R, Q_lo))) +// = CmpU (AddI(MulI(ConvL2I( ConvI2L(j)), ConvL2I(K)), ConvL2I(SubL(Q, Q_lo))), ConvL2I(SubL(R, Q_lo))) +// apply Lemma 2 to remove the ConvL2I-ConvI2L due to (JI) and simplify K due to (KI) +// LHS = CmpU (AddI(MulI( j, K), ConvL2I(SubL(Q, Q_lo))), ConvL2I(SubL(R, Q_lo))) +// and space it differently +// LHS = CmpU (AddI(MulI(j, K), ConvL2I(SubL(Q, Q_lo))), ConvL2I(SubL( R , Q_lo))) +// apply R > Q_lo (3-R2) to add an equivalent MaxL for R +// LHS = CmpU (AddI(MulI(j, K), ConvL2I(SubL(Q, Q_lo))), ConvL2I(SubL(MaxL( R , Q_lo), Q_lo))) +// apply R < Q_hi (3-R2) to add an equivlanet MinL for R +// LHS = CmpU (AddI(MulI(j, K), ConvL2I(SubL(Q, Q_lo))), ConvL2I(SubL(MaxL(MinL(R, Q_hi), Q_lo), Q_lo))) +// which is equivalent to the definition of RHS +// +// +// We proved that +// LHS = RHS +// and thus +// (LHS < 0) == (RHS < 0) +// +// +// 3-R3: 0 <= Q_lo < Q_hi <= R +// -------------------------- +// From (3-R3) and (3c), we know that +// 0 <= AddL(MulL(ConvI2L(j), K), Q) < Q_hi <= R +// 0 <= AddL(MulL(ConvI2L(j), K), Q) < R +// and therefore +// LHS = CmpUL(AddL(MulL(ConvI2L(j), K), Q), R) +// < 0 +// +// +// From the definition of +// RHS = CmpU(AddI(MulI(j, K), ConvL2I(SubL(Q, Q_lo))), ConvL2I(SubL(MaxL(MinL(R, Q_hi), Q_lo), Q_lo))) +// we know from (3-R3) that R >= Q_hi: +// RHS = CmpU(AddI(MulI(j, K), ConvL2I(SubL(Q, Q_lo))), ConvL2I(SubL(MaxL( Q_hi, Q_lo), Q_lo))) +// and Q_lo < Q_hi: +// RHS = CmpU(AddI(MulI(j, K), ConvL2I(SubL(Q, Q_lo))), ConvL2I(SubL( Q_hi, Q_lo))) +// apply Lemma 2 to j due to (JI) +// RHS = CmpU(AddI(MulI(ConvL2I(ConvI2L(j)), K), ConvL2I(SubL(Q, Q_lo))), ConvL2I(SubL(Q_hi, Q_lo))) +// apply Lemma 4 twice to move the ConvL2I outwards +// RHS = CmpU(AddI(ConvL2I(MulL(ConvI2L(j), K)), ConvL2I(SubL(Q, Q_lo))), ConvL2I(SubL(Q_hi, Q_lo))) +// CmpU(ConvL2I(AddL(MulL(ConvI2L(j), K), SubL(Q, Q_lo))), ConvL2I(SubL(Q_hi, Q_lo))) +// ------- x -------- -- y -- -- z -- +// reassociated AddL/SubL +// AddL(x, SubL(y, z)) = SubL(AddL(x, y), z) +// and space it differently as +// RHS = CmpU (ConvL2I( SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo)), ConvL2I( SubL(Q_hi, Q_lo))) +// ------- x -------- -- y -- -- z -- +// apply Lemma 3 to transform to CmpUL form because both sides are int32_t values due to (Q-lo-hi) +// RHS = CmpUL(ConvI2L(ConvL2I(SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo))), ConvI2L(ConvL2I(SubL(Q_hi, Q_lo)))) +// apply Lemma 1 to remove both ConvI2L-ConvL2I because the inner values are int32_t due to (Q-lo-hi) +// RHS = CmpUL( SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo), SubL(Q_hi, Q_lo)) +// and see with (Q-lo-hi) that +// RHS < 0 +// +// We showed that +// LHS < 0 +// and +// RHS < 0 +// and therefore proved that +// (LHS < 0) == (RHS < 0) == true +// +// +// Case 4: Q_hi < 0 < Q_lo +// ======================= +// We can simplify the given clamp values: +// L_clamp = MaxL(Q_lo, 0) +// = Q_lo +// H_clamp = Q_hi < Q_lo ? max_jlong : Q_hi +// = max_jlong +// R_clamp = MaxL(MinL(R, H_clamp), L_clamp) +// = MaxL(MinL(R, max_jlong), Q_lo) +// = MaxL(R, Q_lo) (because R <= max_jlong (R0)) +// +// And insert them into the definition of +// RHS = CmpU(AddI(MulI( j, K), ConvL2I(SubL(Q, L_clamp))), ConvL2I(SubL(R_clamp, L_clamp))) +// = CmpU(AddI(MulI( j, K), ConvL2I(SubL(Q, Q_lo)), ConvL2I(SubL(MaxL(R, Q_lo), Q_lo)))) +// apply Lemma 2 to add ConvL2I-ConvI2L due to (JI) +// RHS = CmpU(AddI(MulI(ConvL2I(ConvI2L(j)), K), ConvL2I(SubL(Q, Q_lo)), ConvL2I(SubL(MaxL(R, Q_lo), Q_lo))) +// apply Lemma 4 twice to move the ConvL2I outwards: +// RHS = CmpU(AddI(ConvL2I(MulL(ConvI2L(j), K)), ConvL2I(SubL(Q, Q_lo)), ConvL2I(SubL(MaxL(R, Q_lo), Q_lo))) +// = CmpU(ConvL2I(AddL(MulL(ConvI2L(j), K), SubL(Q, Q_lo))), ConvL2I(SubL(MaxL(R, Q_lo), Q_lo))) +// ------- x -------- -- y -- -- z -- +// reassociate AddL/SubL +// AddL(x, SubL(y, z)) = SubL(AddL(x, y), z) +// and space it differently as +// RHS = CmpU(ConvL2I( SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo)), C onvL2I(SubL(MaxL(R, Q_lo), Q_lo))) +// ------- x -------- -- y -- -- z -- +// apply Lemma 3 to transform to CmpUL form because both sides are int32_t values with ConvL2I +// RHS = CmpUL(ConvI2L(ConvL2I(SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo))), ConvI2L(ConvL2I(SubL(MaxL(R, Q_lo), Q_lo)))) +// apply Lemma 1 to remove the left ConvI2L-ConvL2I because the inner value is int32_t due to (Q-lo-hi) +// RHS = CmpUL( SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo), ConvI2L(ConvL2I(SubL(MaxL(R, Q_lo), Q_lo)))) +// +// +// To remove the right-side ConvI2L-ConvL2I, we first need to show that +// SubL(MaxL(R, Q_lo), Q_lo) +// is an int32_t value +// +// SubL is an int32_t +// ------------------ +// (Case 4) +// Q_hi < 0 < Q_lo +// is equivalent to +// 0 u 0 +// does not overflow the unsigned long domain and thus +// UL(Q_hi) - UL(Q_lo) = UL(SubL(Q_hi, Q_lo)) (4b) +// +// It also follows that +// Q_hi u> max_jlong +// UL(Q_hi) > max_jlong (4c) +// +// From (R0) +// 0 <= R <= max_jlong +// and (Case 4) +// 0 <= Q_lo <= max_jlong +// we see that +// 0 <= MaxL(R, Q_lo) <= max_jlong (4d) +// +// We notice that +// 0 < Q_lo <= MaxL(R, Q_lo) +// which means that +// 0 <= SubL(MaxL(R, Q_lo), Q_lo) +// does not overflow the unsigned long domain (4b-no-overflow) +// +// +// With (4b-no-overflow), we get +// 0 <= SubL(MaxL(R, Q_lo), Q_lo) +// = UL(SubL(MaxL(R, Q_lo), Q_lo)) +// = UL(MaxL(R, Q_lo)) - UL(Q_lo) +// <= max_jlong - UL(Q_lo) (apply (4d)) +// < UL(Q_hi) - UL(Q_lo) (apply (4c)) +// = UL(SubL(Q_hi, Q_lo)) (apply (4b)) +// <= UL(max_jint) (apply (Q-lo-hi)) +// = max_jint +// and thus showed that +// 0 <= SubL(MaxL(R, Q_lo), Q_lo) < max_jint (4e) +// indeed fits into an int32_t. +// +// +// Thus, we can apply Lemma 1 to remove the second ConvI2L-ConvL2I as well +// RHS = CmpUL(SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo), ConvI2L(ConvL2I(SubL(MaxL(R, Q_lo), Q_lo)))) +// RHS = CmpUL(SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo), SubL(MaxL(R, Q_lo), Q_lo)) +// +// We now want to remove the SubL on both sides with Lemma 6. We already showed with (4e) that the right SubL does +// not overflow. Which leaves us by showing that the left SubL does not overflow. +// +// +// No Overflow of Left SubL +// ------------------------ +// Consider the subtraction +// UL(x - Q_lo) +// which only overflows if +// x = Q_lo +// 4-R2: R < Q_lo +// +// We need to prove for both cases that (LHS < 0) == (RHS < 0) +// +// 4-R1: R >= Q_lo +// --------------- +// Starting from (4g) +// RHS = CmpUL(AddL(MulL(ConvI2L(j), K), Q), MaxL(R, Q_lo)) +// = CmpUL(AddL(MulL(ConvI2L(j), K), Q), R) +// = LHS +// +// which implies (LHS < 0) == (RHS < 0) +// +// 4-R2: R < Q_lo +// --------------- +// Starting from (4g) +// RHS = CmpUL(AddL(MulL(ConvI2L(j), K), Q), MaxL(R, Q_lo)) +// = CmpUL(AddL(MulL(ConvI2L(j), K), Q), Q_lo) +// +// From (4c-no-overflow), we know, that +// SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo) +// does not overflow and with (Q-lo-hi) +// 0 < SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo) < max_jint +// it follows that +// 0 =u Q_lo (4h) +// must hold and therefore +// RHS = CmpUL(AddL(MulL(ConvI2L(j), K), Q), Q_lo) >= 0 +// +// From (4-R2) and (R0) we know +// 0 <= R < Q_lo +// which is the same as +// R 0 +// +// We showed that +// LHS > 0 +// and +// RHS >= 0 +// and therefore proved that +// (LHS < 0) == (RHS < 0) == false +// +// We proved that the transformation is valid under the stated pre-conditions. void PhaseIdealLoop::transform_long_range_checks(int stride_con, const Node_List &range_checks, Node* outer_phi, Node* inner_iters_actual_int, Node* inner_phi, Node* iv_add, LoopNode* inner_head) { @@ -1536,150 +2460,195 @@ void PhaseIdealLoop::transform_long_range_checks(int stride_con, const Node_List // could be shared and have already been taken care of continue; } - bool short_scale = false; - bool ok = is_scaled_iv_plus_offset(rc_cmp->in(1), iv_add, T_LONG, &scale, &offset, &short_scale); + bool ok = is_scaled_iv_plus_offset(rc_cmp->in(1), iv_add, T_LONG, &scale, &offset); assert(ok, "inconsistent: was tested before"); + // Because is_scaled_iv_plus_offset is successful, we know that + // rc_cmp->in(1) is semantically equivalent to either: + // AddL(MulL(iv_add, scale), offset), if iv_add is long; or + // AddL(MulL(ConvI2L(iv_add), scale), offset), if iv_add is int. + // See the equivalence proof in PhaseIdealLoop::is_scaled_iv_plus_offset and + // functions called therein. Node* range = rc_cmp->in(2); Node* c = rc->in(0); Node* entry_control = inner_head->in(LoopNode::EntryControl); Node* R = range; Node* K = longcon(scale); - Node* L = offset; - - if (short_scale) { - // This converts: - // (int)i*K + L u64 (long)max_jint and still is C, int_zero, inner_iters_actual_int, int_stride); - register_new_node(B_2, entry_control); - B_2 = new SubINode(B_2, int_stride); - register_new_node(B_2, entry_control); - B_2 = new ConvI2LNode(B_2); - register_new_node(B_2, entry_control); - - Node* Q_max = new MulLNode(B_2, K); - register_new_node(Q_max, entry_control); - Q_max = new AddLNode(Q_max, Q_first); - register_new_node(Q_max, entry_control); - + // Last iteration, j = last, where + // last = ((iter_count - 1) * stride + init) + // = ( iter_count * stride + init) - stride + // = LoopLimitNode(init, limit, stride ) - stride + // = LoopLimitNode(0, inner_iters_actual_int, int_stride) - stride + // which is used to get + // Q_last = (j * K) + Q + // = (last * K) + Q + // = (LoopLimitNode(0, inner_iters_actual_int, int_stride) - stride * K) + Q (def. of last) + // = (LoopLimitNode(0, inner_iters_actual_int, int_stride) - stride * K) + (C * K) + L (def. of Q) + // = (LoopLimitNode(0, inner_iters_actual_int, int_stride) - stride * K) + Q_first (def. of Q_first) + Node* last = new LoopLimitNode(this->C, int_zero, inner_iters_actual_int, int_stride); + register_new_node(last, entry_control); + last = new SubINode(last, int_stride); + register_new_node(last, entry_control); + last = new ConvI2LNode(last); + register_new_node(last, entry_control); + + Node* Q_last = new MulLNode(last, K); + register_new_node(Q_last, entry_control); + Q_last = new AddLNode(Q_last, Q_first); + register_new_node(Q_last, entry_control); + + // We define Q_lo and Q_hi now from Q_first, Q_last, P_lo, and P_hi: + // + // If Q_first < Q_last (which is the case if scale * stride_con > 0): + // Q_lo = P_lo + Q (def. Q_lo) + // = 0 * K + Q (def. P_lo) + // = Q + // = Q_first + // and + // Q_hi = P_hi + Q (def. Q_hi) + // = last * K + 1 + Q (def. P_hi) + // = last * K + Q + 1 + // = Q_last + 1 + // If Q_first > Q_last (which is the case if scale * stride_con < 0): + // Q_lo = P_lo + Q (def. Q_lo) + // = last * K + Q (def. P_lo) + // = Q_last + // and + // Q_hi = P_hi + Q (def. Q_hi) + // = 0 * K + 1 + Q (def. P_hi) + // = 0 * K + Q + 1 + // = Q_first + 1 + Node* Q_lo = Q_first; + Node* Q_hi = Q_last; if (scale * stride_con < 0) { - swap(Q_min, Q_max); + swap(Q_lo, Q_hi); } - // Now, mathematically, Q_max > Q_min, and they are close enough so that (Q_max-Q_min) fits in 32 bits. - // L_clamp = Q_min < 0 ? 0 : Q_min - Node* Q_min_cmp = new CmpLNode(Q_min, long_zero); - register_new_node(Q_min_cmp, entry_control); - Node* Q_min_bool = new BoolNode(Q_min_cmp, BoolTest::lt); - register_new_node(Q_min_bool, entry_control); - Node* L_clamp = new CMoveLNode(Q_min_bool, Q_min, long_zero, TypeLong::LONG); - register_new_node(L_clamp, entry_control); - // (This could also be coded bitwise as L_clamp = Q_min & ~(Q_min>>63).) + // Q_lo is now correct. Add plus one to get the correct Q_hi value as defined above as well: + Q_hi = new AddLNode(Q_hi, long_one); + register_new_node(Q_hi, entry_control); - Node* Q_max_plus_one = new AddLNode(Q_max, long_one); - register_new_node(Q_max_plus_one, entry_control); + // Let's next define all clamp values: + // + // First, we compute + // L_clamp = Q_lo < 0 ? 0 : Q_lo + Node* Q_lo_cmp = new CmpLNode(Q_lo, long_zero); + register_new_node(Q_lo_cmp, entry_control); + Node* Q_lo_bool = new BoolNode(Q_lo_cmp, BoolTest::lt); + register_new_node(Q_lo_bool, entry_control); + Node* L_clamp = new CMoveLNode(Q_lo_bool, Q_lo, long_zero, TypeLong::LONG); + register_new_node(L_clamp, entry_control); + // (This could also be coded bitwise as L_clamp = Q_lo & ~(Q_lo>>63).) - // H_clamp = Q_max+1 < Q_min ? max_jlong : Q_max+1 - // (Because Q_min and Q_max are close, the overflow check could also be encoded as Q_max+1 < 0 & Q_min >= 0.) + // Next, we compute + // H_clamp = Q_hi< Q_lo ? max_jlong : Q_hi Node* max_jlong_long = longcon(max_jlong); - Node* Q_max_cmp = new CmpLNode(Q_max_plus_one, Q_min); - register_new_node(Q_max_cmp, entry_control); - Node* Q_max_bool = new BoolNode(Q_max_cmp, BoolTest::lt); - register_new_node(Q_max_bool, entry_control); - Node* H_clamp = new CMoveLNode(Q_max_bool, Q_max_plus_one, max_jlong_long, TypeLong::LONG); + Node* Q_hi_cmp = new CmpLNode(Q_hi, Q_lo); + register_new_node(Q_hi_cmp, entry_control); + Node* Q_hi_bool = new BoolNode(Q_hi_cmp, BoolTest::lt); + register_new_node(Q_hi_bool, entry_control); + Node* H_clamp = new CMoveLNode(Q_hi_bool, Q_hi, max_jlong_long, TypeLong::LONG); register_new_node(H_clamp, entry_control); - // (This could also be coded bitwise as H_clamp = ((Q_max+1)<<1 | M)>>>1 where M = (Q_max+1)>>63 & ~Q_min>>63.) - - // R_2 = clamp(R, L_clamp, H_clamp) - L_clamp - // that is: R_2 = clamp(R, L_clamp=0, H_clamp=Q_max) if Q_min < 0 - // or else: R_2 = clamp(R, L_clamp, H_clamp) - Q_min if Q_min >= 0 - // and also: R_2 = clamp(R, L_clamp, Q_max+1) - L_clamp if Q_min < Q_max+1 (no overflow) - // or else: R_2 = clamp(R, L_clamp, *no limit*)- L_clamp if Q_max+1 < Q_min (overflow) - Node* R_2 = clamp(R, L_clamp, H_clamp); - R_2 = new SubLNode(R_2, L_clamp); - register_new_node(R_2, entry_control); - R_2 = new ConvL2INode(R_2, TypeInt::POS); - register_new_node(R_2, entry_control); - - // L_2 = Q_first - L_clamp - // We are subtracting L_clamp from both sides of the 0, then Q_first == 0 and the R.C. expression at -L_clamp and steps upward to Q_max-L_clamp. - // If S*K<0, then Q_first != 0 and the R.C. expression starts high and steps downward to Q_min-L_clamp. - Node* L_2 = new SubLNode(Q_first, L_clamp); - register_new_node(L_2, entry_control); - L_2 = new ConvL2INode(L_2, TypeInt::INT); - register_new_node(L_2, entry_control); - - // Transform the range check using the computed values L_2/R_2 - // from: i*K + L >>1 where M = (Q_hi)>>63 & ~Q_lo>>63.) + + // Finally, we compute + // R_clamp = MaxL(MinL(R, H_clamp), L_clamp) + Node* R_clamp = clamp(R, L_clamp, H_clamp); + + // Now we have everything together to transform the long range check: + // (i * K) + L (scale)); Node* scaled_iv = new MulINode(inner_phi, K); register_new_node(scaled_iv, c); - Node* scaled_iv_plus_offset = new AddINode(scaled_iv, L_2); + Node* scaled_iv_plus_offset = new AddINode(scaled_iv, L2); register_new_node(scaled_iv_plus_offset, c); - Node* new_rc_cmp = new CmpUNode(scaled_iv_plus_offset, R_2); + Node* new_rc_cmp = new CmpUNode(scaled_iv_plus_offset, R2); register_new_node(new_rc_cmp, c); _igvn.replace_input_of(rc_bol, 1, new_rc_cmp); } } -Node* PhaseIdealLoop::clamp(Node* R, Node* L, Node* H) { - Node* min = MinMaxNode::signed_min(R, H, TypeLong::LONG, _igvn); +Node* PhaseIdealLoop::clamp(Node* R, Node* L_clamp, Node* H_clamp) { + Node* min = MinMaxNode::signed_min(R, H_clamp, TypeLong::LONG, _igvn); set_subtree_ctrl(min, true); - Node* max = MinMaxNode::signed_max(L, min, TypeLong::LONG, _igvn); + Node* max = MinMaxNode::signed_max(L_clamp, min, TypeLong::LONG, _igvn); set_subtree_ctrl(max, true); return max; } diff --git a/src/hotspot/share/opto/loopnode.hpp b/src/hotspot/share/opto/loopnode.hpp index a6f0ddd8b6b8..70ee7fc45f35 100644 --- a/src/hotspot/share/opto/loopnode.hpp +++ b/src/hotspot/share/opto/loopnode.hpp @@ -1569,12 +1569,12 @@ class PhaseIdealLoop : public PhaseTransform { void do_unroll( IdealLoopTree *loop, Node_List &old_new, bool adjust_min_trip ); // Return true if exp is a constant times an induction var - bool is_scaled_iv(Node* exp, Node* iv, BasicType bt, jlong* p_scale, bool* p_short_scale, int depth = 0); + bool is_scaled_iv(Node* exp, Node* iv, BasicType bt, jlong* p_scale, int depth = 0); bool is_iv(Node* exp, Node* iv, BasicType bt); - // Return true if exp is a scaled induction var plus (or minus) constant - bool is_scaled_iv_plus_offset(Node* exp, Node* iv, BasicType bt, jlong* p_scale, Node** p_offset, bool* p_short_scale = nullptr, int depth = 0); + // Return true if exp is a scaled induction var plus (or minus) an offset + bool is_scaled_iv_plus_offset(Node* exp, Node* iv, BasicType bt, jlong* p_scale, Node** p_offset, int depth = 0); bool is_scaled_iv_plus_offset(Node* exp, Node* iv, int* p_scale, Node** p_offset) { jlong long_scale; if (is_scaled_iv_plus_offset(exp, iv, T_INT, &long_scale, p_offset)) { @@ -1587,10 +1587,10 @@ class PhaseIdealLoop : public PhaseTransform { return false; } // Helper for finding more complex matches to is_scaled_iv_plus_offset. - bool is_scaled_iv_plus_extra_offset(Node* exp1, Node* offset2, Node* iv, + bool is_scaled_iv_plus_extra_offset(Node* exp1, Node* offset3, Node* iv, BasicType bt, jlong* p_scale, Node** p_offset, - bool* p_short_scale, int depth); + int depth); // Create a new if above the uncommon_trap_if_pattern for the predicate to be promoted IfTrueNode* create_new_if_for_predicate(const ParsePredicateSuccessProj* parse_predicate_proj, Node* new_entry, @@ -2040,7 +2040,7 @@ class PhaseIdealLoop : public PhaseTransform { void try_sink_out_of_loop(Node* n); - Node* clamp(Node* R, Node* L, Node* H); + Node* clamp(Node* R, Node* L_clamp, Node* H_clamp); bool safe_for_if_replacement(const Node* dom) const; diff --git a/test/hotspot/jtreg/compiler/c2/irTests/TestLongRangeChecks.java b/test/hotspot/jtreg/compiler/c2/irTests/TestLongRangeChecks.java index 9a06d12b15f7..3e350e83091a 100644 --- a/test/hotspot/jtreg/compiler/c2/irTests/TestLongRangeChecks.java +++ b/test/hotspot/jtreg/compiler/c2/irTests/TestLongRangeChecks.java @@ -89,9 +89,14 @@ private void testStridePosScalePosInIntLoop1_runner() { } @Test - @IR(applyIf = { "ShortRunningLongLoop", "false" }, counts = { IRNode.LOOP, "1" }) @IR(applyIf = { "ShortRunningLongLoop", "true" }, failOn = IRNode.LOOP) - @IR(failOn = { IRNode.COUNTED_LOOP}) + // This is a negative test: the range check cannot be currently optimized + // because the index expression scale * i + offset contains short scaling + // (see PhaseIdealLoop::is_scaled_iv) and is thus not a candidate for long + // range check transformations. The two rules below verify that range check + // optimization does NOT take place. + @IR(applyIf = { "ShortRunningLongLoop", "false" }, failOn = IRNode.LOOP) + @IR(counts = { IRNode.COUNTED_LOOP, "1"}) public static void testStridePosScalePosInIntLoop2(int start, int stop, long length, long offset) { final int scale = 2; final int stride = 1; @@ -143,9 +148,14 @@ private void testStrideNegScaleNegInIntLoop1_runner() { } @Test - @IR(applyIf = { "ShortRunningLongLoop", "false" }, counts = { IRNode.LOOP, "1" }) @IR(applyIf = { "ShortRunningLongLoop", "true" }, failOn = IRNode.LOOP) - @IR(failOn = { IRNode.COUNTED_LOOP}) + // This is a negative test: the range check cannot be currently optimized + // because the index expression scale * i + offset contains short scaling + // (see PhaseIdealLoop::is_scaled_iv) and is thus not a candidate for long + // range check transformations. The two rules below verify that range check + // optimization does NOT take place. + @IR(applyIf = { "ShortRunningLongLoop", "false" }, failOn = IRNode.LOOP) + @IR(counts = { IRNode.COUNTED_LOOP, "1"}) public static void testStrideNegScaleNegInIntLoop2(int start, int stop, long length, long offset) { final int scale = -2; final int stride = 1; @@ -195,9 +205,14 @@ private void testStrideNegScalePosInIntLoop1_runner() { } @Test - @IR(applyIf = { "ShortRunningLongLoop", "false" }, counts = { IRNode.LOOP, "1" }) @IR(applyIf = { "ShortRunningLongLoop", "true" }, failOn = IRNode.LOOP) - @IR(failOn = { IRNode.COUNTED_LOOP}) + // This is a negative test: the range check cannot be currently optimized + // because the index expression scale * i + offset contains short scaling + // (see PhaseIdealLoop::is_scaled_iv) and is thus not a candidate for long + // range check transformations. The two rules below verify that range check + // optimization does NOT take place. + @IR(applyIf = { "ShortRunningLongLoop", "false" }, failOn = IRNode.LOOP) + @IR(counts = { IRNode.COUNTED_LOOP, "1"}) public static void testStrideNegScalePosInIntLoop2(int start, int stop, long length, long offset) { final int scale = 2; final int stride = 1; @@ -246,9 +261,14 @@ private void testStridePosScaleNegInIntLoop1_runner() { } @Test - @IR(applyIf = { "ShortRunningLongLoop", "false" }, counts = { IRNode.LOOP, "1" }) @IR(applyIf = { "ShortRunningLongLoop", "true" }, failOn = IRNode.LOOP) - @IR(failOn = { IRNode.COUNTED_LOOP}) + // This is a negative test: the range check cannot be currently optimized + // because the index expression scale * i + offset contains short scaling + // (see PhaseIdealLoop::is_scaled_iv) and is thus not a candidate for long + // range check transformations. The two rules below verify that range check + // optimization does NOT take place. + @IR(applyIf = { "ShortRunningLongLoop", "false" }, failOn = IRNode.LOOP) + @IR(counts = { IRNode.COUNTED_LOOP, "1"}) public static void testStridePosScaleNegInIntLoop2(int start, int stop, long length, long offset) { final int scale = -2; final int stride = 1; diff --git a/test/hotspot/jtreg/compiler/c2/irTests/TestVectorizationMultiInvar.java b/test/hotspot/jtreg/compiler/c2/irTests/TestVectorizationMultiInvar.java index eb4b611e8ebe..4239dd469566 100644 --- a/test/hotspot/jtreg/compiler/c2/irTests/TestVectorizationMultiInvar.java +++ b/test/hotspot/jtreg/compiler/c2/irTests/TestVectorizationMultiInvar.java @@ -64,7 +64,7 @@ public static void main(String[] args) { @IR(counts = { IRNode.LOAD_VECTOR_L, ">=1", IRNode.STORE_VECTOR, ">=1" }) public static void testByteLong1(byte[] dest, long[] src) { for (int i = 0; i < src.length; i++) { - long j = Objects.checkIndex(i * 8, (long)(src.length * 8)); + long j = Objects.checkIndex(i * 8L, (long)(src.length * 8)); UNSAFE.putLongUnaligned(dest, baseOffset + j, src[i]); } } @@ -75,6 +75,26 @@ public static void testByteLong1_runner() { testByteLong1(byteArray, longArray); } + // This is a negative version of the test above where C2 currently fails to + // perform range check elimination, which inhibits vectorization. + // The reason is that the i * 8 index expression is not accepted as a valid + // index for long range check transformations, see + // PhaseIdealLoop::is_scaled_iv_plus_offset. + @Test + @IR(failOn = { IRNode.LOAD_VECTOR_L, IRNode.STORE_VECTOR }) + public static void testNegativeByteLong1(byte[] dest, long[] src) { + for (int i = 0; i < src.length; i++) { + long j = Objects.checkIndex(i * 8, (long)(src.length * 8)); + UNSAFE.putLongUnaligned(dest, baseOffset + j, src[i]); + } + } + + @Run(test = "testNegativeByteLong1") + public static void testNegativeByteLong1_runner() { + baseOffset = UNSAFE.ARRAY_BYTE_BASE_OFFSET; + testNegativeByteLong1(byteArray, longArray); + } + @Test @IR(counts = { IRNode.LOAD_VECTOR_B, ">=1", IRNode.STORE_VECTOR, ">=1" }) public static void testLoopNest1(byte[] dest, byte[] src, diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java b/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java index 87ad9f029f7c..9822c6e07545 100644 --- a/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java @@ -490,6 +490,12 @@ public static void allocateArrayOfNodes(String irNodePlaceholder, String allocat superWordNodes(AND_REDUCTION_V, "AndReductionV"); } + public static final String ANY_LOOP = PREFIX + "ANY_LOOP" + POSTFIX; + static { + String regex = START + "(Counted|LongCounted|OuterStripMined)?Loop\\b" + MID + END; + fromBeforeCountedLoops(ANY_LOOP, regex); + } + public static final String CALL = PREFIX + "CALL" + POSTFIX; static { beforeMatchingNameRegex(CALL, "Call.*Java"); diff --git a/test/hotspot/jtreg/compiler/rangechecks/TestIndexMatching.java b/test/hotspot/jtreg/compiler/rangechecks/TestIndexMatching.java new file mode 100644 index 000000000000..72b4cc7e5e97 --- /dev/null +++ b/test/hotspot/jtreg/compiler/rangechecks/TestIndexMatching.java @@ -0,0 +1,779 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.rangechecks; + +import compiler.lib.ir_framework.*; +import java.util.Objects; + +/** + * @test + * @bug 8382378 + * @summary Tests that valid index expressions in range checks are matched for + * range check optimizations, and that invalid expressions are + * rejected. These tests should be kept updated with the grammar-based + * specification provided as a comment on + * PhaseIdealLoop::is_scaled_iv_plus_offset(). + * @library /test/lib / + * @run driver ${test.main.class} + */ + +public class TestIndexMatching { + + static int intVar0 = 0; + static int intVar3 = 3; + static int intVar100 = 100; + static int intVar1000 = 1000; + static int intVar10000 = 10000; + + static long longVar0 = 0L; + static long longVar3 = 3L; + static long longVar100 = 100L; + static long longVar1000 = 1000L; + static long longVar10000 = 10000L; + + public static void main(String[] args) { + TestFramework.run(); + } + + // Positive tests: expect the index expressions to be matched and range + // check optimizations to trigger. Use C2-opaque range check limits to + // increase the likelihood that loop elimination is indeed caused by range + // check optimizations and not accidentally by other transformations. + + // Int indices using int induction variables + + // iv + @Test + @IR(failOn = {IRNode.ANY_LOOP}) + static void testIntIndexIntIV() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex(iv, intVar10000); + } + } + + // AddI(iv, e) + @Test + @IR(failOn = {IRNode.ANY_LOOP}) + static void testIntIndexIntIVWithAddedVarOffset() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex(iv + intVar0, intVar10000); + } + } + + // SubI(iv, e) + @Test + @IR(failOn = {IRNode.ANY_LOOP}) + static void testIntIndexIntIVWithSubtractedVarOffset() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex(iv - intVar0, intVar10000); + } + } + + // SubI(e, iv) + @Test + @IR(failOn = {IRNode.ANY_LOOP}) + static void testIntIndexIntIVSubtractedFromVarOffset() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex(intVar1000 - iv, intVar10000); + } + } + + // AddI(iv, ConI) + @Test + @IR(failOn = {IRNode.ANY_LOOP}) + static void testIntIndexIntIVWithAddedConstOffset() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex(iv + 7, intVar10000); + } + } + + // AddI((AddI(iv, e)), ConI) + @Test + @IR(failOn = {IRNode.ANY_LOOP}) + static void testIntIndexIntIVWithAddedNestedOffset() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex((iv + intVar0) + 7, intVar10000); + } + } + + // MulI(iv, ConI) + @Test + @IR(failOn = {IRNode.ANY_LOOP}) + static void testIntIndexIntIVWithMulScaling() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex(iv * 11, intVar10000); + } + } + + // AddI(MulI(iv, ConI), e) + @Test + @IR(failOn = {IRNode.ANY_LOOP}) + static void testIntIndexIntIVWithMulScalingAndAddedVarOffset() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex((iv * 11) + intVar0, intVar10000); + } + } + + // AddI(AddI(MulI(iv, ConI), e), ConI) + @Test + @IR(failOn = {IRNode.ANY_LOOP}) + static void testIntIndexIntIVWithMulScalingAndAddedNestedOffset() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex(((iv * 11) + intVar0) + 7, intVar10000); + } + } + + // LShiftI(iv, ConI) + @Test + @IR(failOn = {IRNode.ANY_LOOP}) + static void testIntIndexIntIVWithShiftScaling() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex(iv << 2, intVar10000); + } + } + + // SubI(0, MulI(iv, ConI)) + @Test + @IR(failOn = {IRNode.ANY_LOOP}) + static void testIntIndexIntIVWithNegatedMulScaling() { + for (int iv = -intVar100; iv <= 0; iv++) { + Objects.checkIndex(0 - (iv * 11), intVar10000); + } + } + + // AddI(MulI(iv, ConI), MulI(iv, ConI)) + @Test + @IR(failOn = {IRNode.ANY_LOOP}) + static void testIntIndexIntIVWithAddOfMulsScaling() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex((iv * 3) + (iv * 7), intVar10000); + } + } + + // SubI(LShiftI(iv, ConI), MulI(iv, ConI)) + @Test + @IR(failOn = {IRNode.ANY_LOOP}) + static void testIntIndexIntIVWithSubOfShiftAndMulScaling() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex((iv << 4) - (iv * 13), intVar10000); + } + } + + // Long indices using long induction variables + + // iv + @Test + @IR(failOn = {IRNode.CONV_I2L}, phase = CompilePhase.AFTER_PARSING) + @IR(failOn = {IRNode.ANY_LOOP}) + static void testLongIndexLongIV() { + for (long iv = 0L; iv <= longVar100; iv++) { + Objects.checkIndex(iv, longVar10000); + } + } + + // AddL(iv, e) + @Test + @IR(failOn = {IRNode.CONV_I2L}, phase = CompilePhase.AFTER_PARSING) + @IR(failOn = {IRNode.ANY_LOOP}) + static void testLongIndexLongIVWithAddedVarOffset() { + for (long iv = 0L; iv <= longVar100; iv++) { + Objects.checkIndex(iv + longVar0, longVar10000); + } + } + + // SubL(iv, e) + @Test + @IR(failOn = {IRNode.CONV_I2L}, phase = CompilePhase.AFTER_PARSING) + @IR(failOn = {IRNode.ANY_LOOP}) + static void testLongIndexLongIVWithSubtractedVarOffset() { + for (long iv = 0L; iv <= longVar100; iv++) { + Objects.checkIndex(iv - longVar0, longVar10000); + } + } + + // SubL(e, iv) + @Test + @IR(failOn = {IRNode.CONV_I2L}, phase = CompilePhase.AFTER_PARSING) + @IR(failOn = {IRNode.ANY_LOOP}) + static void testLongIndexLongIVSubtractedFromVarOffset() { + for (long iv = 0L; iv <= longVar100; iv++) { + Objects.checkIndex(longVar1000 - iv, longVar10000); + } + } + + // AddL(iv, ConL) + @Test + @IR(failOn = {IRNode.CONV_I2L}, phase = CompilePhase.AFTER_PARSING) + @IR(failOn = {IRNode.ANY_LOOP}) + static void testLongIndexLongIVWithAddedConstOffset() { + for (long iv = 0L; iv <= longVar100; iv++) { + Objects.checkIndex(iv + 1L, longVar10000); + } + } + + // AddL((AddL(iv, e)), ConL) + @Test + @IR(failOn = {IRNode.CONV_I2L}, phase = CompilePhase.AFTER_PARSING) + @IR(failOn = {IRNode.ANY_LOOP}) + static void testLongIndexLongIVWithAddedNestedOffset() { + for (long iv = 0L; iv <= longVar100; iv++) { + Objects.checkIndex((iv + longVar0) + 7L, longVar10000); + } + } + + // MulL(iv, ConL) + @Test + @IR(failOn = {IRNode.CONV_I2L}, phase = CompilePhase.AFTER_PARSING) + @IR(failOn = {IRNode.ANY_LOOP}) + static void testLongIndexLongIVWithMulScaling() { + for (long iv = 0L; iv <= longVar100; iv++) { + Objects.checkIndex(iv * 11L, longVar10000); + } + } + + // AddL(MulL(iv, ConL), e) + @Test + @IR(failOn = {IRNode.CONV_I2L}, phase = CompilePhase.AFTER_PARSING) + @IR(failOn = {IRNode.ANY_LOOP}) + static void testLongIndexLongIVWithMulScalingAndAddedVarOffset() { + for (long iv = 0L; iv <= longVar100; iv++) { + Objects.checkIndex((iv * 11L) + longVar0, longVar10000); + } + } + + // AddL(AddL(MulL(iv, ConL), e), ConL) + @Test + @IR(failOn = {IRNode.CONV_I2L}, phase = CompilePhase.AFTER_PARSING) + @IR(failOn = {IRNode.ANY_LOOP}) + static void testLongIndexLongIVWithMulScalingAndAddedNestedOffset() { + for (long iv = 0L; iv <= longVar100; iv++) { + Objects.checkIndex(((iv * 11L) + longVar0) + 7L, longVar10000); + } + } + + // LShiftL(iv, ConI) + @Test + @IR(failOn = {IRNode.CONV_I2L}, phase = CompilePhase.AFTER_PARSING) + @IR(failOn = {IRNode.ANY_LOOP}) + static void testLongIndexLongIVWithShiftScaling() { + for (long iv = 0L; iv <= longVar100; iv++) { + Objects.checkIndex(iv << 2, longVar10000); + } + } + + // SubL(0L, MulL(iv, ConL)) + @Test + @IR(failOn = {IRNode.CONV_I2L}, phase = CompilePhase.AFTER_PARSING) + @IR(failOn = {IRNode.ANY_LOOP}) + static void testLongIndexLongIVWithNegatedMulScaling() { + for (long iv = -longVar100; iv <= 0; iv++) { + Objects.checkIndex(0L - (iv * 11L), longVar10000); + } + } + + // Long indices using int induction variables + + // ConvI2L(iv) + @Test + @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING) + @IR(failOn = {IRNode.ANY_LOOP}) + static void testLongIndexIntIV() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex((long)iv, longVar10000); + } + } + + // AddL(ConvI2L(iv), e) + @Test + @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING) + @IR(failOn = {IRNode.ANY_LOOP}) + static void testLongIndexIntIVWithAddedVarOffset() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex((long)iv + longVar0, longVar10000); + } + } + + // AddL(ConvI2L(iv), ConL) + @Test + @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING) + @IR(failOn = {IRNode.ANY_LOOP}) + static void testLongIndexIntIVWithAddedConstOffset() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex((long)iv + 1L, longVar10000); + } + } + + // MulL(ConvI2L(iv), ConL) + @Test + @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING) + @IR(failOn = {IRNode.ANY_LOOP}) + static void testLongIndexIntIVWithMulScaling() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex((long)iv * 11L, longVar10000); + } + } + + // AddL(MulL(ConvI2L(iv), ConL), e) + @Test + @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING) + @IR(failOn = {IRNode.ANY_LOOP}) + static void testLongIndexIntIVWithMulScalingAndAddedVarOffset() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex(((long)iv * 11L) + longVar0, longVar10000); + } + } + + // LShiftL(ConvI2L(iv), ConI) + @Test + @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING) + @IR(failOn = {IRNode.ANY_LOOP}) + static void testLongIndexIntIVWithShiftScaling() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex((long)iv << 2, longVar10000); + } + } + + // SubL(0L, MulL(ConvI2L(iv), ConL)) + @Test + @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING) + @IR(failOn = {IRNode.ANY_LOOP}) + static void testLongIndexIntIVWithNegatedMulScaling() { + for (int iv = -intVar100; iv <= 0; iv++) { + Objects.checkIndex(0L - ((long)iv * 11L), longVar10000); + } + } + + // ConvI2L(MulI(iv, ConI)) + // Range check optimizations still trigger because the short scaling is + // optimized away during IGVN. + @Test + @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING) + @IR(failOn = {IRNode.ANY_LOOP}) + static void testLongIndexIntIVWithShortMulScaling() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex((long)(iv * 1), longVar10000); + } + } + + // ConvI2L(LShiftI(iv, ConI)) + // Range check optimizations still trigger because the short scaling is + // optimized away during IGVN. + @Test + @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING) + @IR(failOn = {IRNode.ANY_LOOP}) + static void testLongIndexIntIVWithShortShiftScaling() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex((long)(iv << 0), longVar10000); + } + } + + // ConvI2L(AddI(MulI(iv, ConI), MulI(iv, ConI))) + // Range check optimizations still trigger because the short scaling is + // optimized away during IGVN. + @Test + @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING) + @IR(failOn = {IRNode.ANY_LOOP}) + static void testLongIndexIntIVWithShortAddOfMulsScaling() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex((long)((iv * 3) + (iv * -2)), longVar10000); + } + } + + // ConvI2L(SubI(0, MulI(iv, ConI))) + // Range check optimizations still trigger because the short scaling is + // optimized away during IGVN. + @Test + @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING) + @IR(failOn = {IRNode.ANY_LOOP}) + static void testLongIndexIntIVWithShortNegatedSubScaling() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex((long)(0 - (iv * -1)), longVar10000); + } + } + + // ConvI2L(SubI(MulI(iv, ConI), MulI(iv, ConI))) + // Range check optimizations still trigger because the short scaling is + // optimized away during IGVN. + @Test + @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING) + @IR(failOn = {IRNode.ANY_LOOP}) + static void testLongIndexIntIVWithShortSubOfMulsScaling() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex((long)((iv * 3) - (iv * 2)), longVar10000); + } + } + + // AddL(MulL(ConvI2L(iv), ConL), MulL(ConvI2L(iv), ConL)) + @Test + @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING) + @IR(failOn = {IRNode.ANY_LOOP}) + static void testLongIndexIntIVWithAddOfMulsScaling() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex(((long)iv * 3L) + ((long)iv * 7L), longVar10000); + } + } + + // SubL(LShiftL(ConvI2L(iv), ConI), ConvI2L(iv)) + @Test + @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING) + @IR(failOn = {IRNode.ANY_LOOP}) + static void testLongIndexIntIVWithSubOfShiftScaling() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex(((long)iv << 3) - iv, longVar10000); + } + } + + // ConvI2L(AddI(MulI(iv, ConI), LShiftI(iv, ConI))) + // Range check optimizations still trigger because the short scaling is + // optimized away during IGVN. + @Test + @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING) + @IR(failOn = {IRNode.ANY_LOOP}) + static void testLongIndexIntIVWithShortAddOfMulAndShiftScaling() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex((long)((iv * -15) + (iv << 4)), longVar10000); + } + } + + // AddL(AddL(ConvI2L(AddI(MulI(iv, ConI), MulI(iv, ConI))), e), ConL) + // Range check optimizations still trigger because the short scaling is + // optimized away during IGVN. + @Test + @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING) + @IR(failOn = {IRNode.ANY_LOOP}) + static void testLongIndexIntIVWithShortAddOfMulsScalingAndAddedNestedOffset() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex(((long)((iv * 3) + (iv * -2)) + longVar0) + 7L, longVar10000); + } + } + + // Negative tests: expect the index expressions to be rejected and the loop + // to not be eliminated. + + // Int indices using int induction variables + + // MulI(iv, LoadI(..)) + @Test + @IR(counts = {IRNode.COUNTED_LOOP, ">0"}) + static void testIntIndexIntIVWithVarMulScaling() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex(iv * intVar3, 10000); + } + } + + // LShiftI(iv, LoadI(..)) + @Test + @IR(counts = {IRNode.COUNTED_LOOP, ">0"}) + static void testIntIndexIntIVWithVarShiftScaling() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex(iv << intVar0, 10000); + } + } + + // MulI(AddI(iv, LoadI(..)), ConI) + @Test + @IR(counts = {IRNode.COUNTED_LOOP, ">0"}) + static void testIntIndexIntIVWithNonTrivialMulScaling() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex((iv + intVar0) * 11, 10000); + } + } + + // LShiftI(AddI(iv, LoadI(..)), LoadI(..)) + @Test + @IR(counts = {IRNode.COUNTED_LOOP, ">0"}) + static void testIntIndexIntIVWithNonTrivialShiftScaling() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex((iv + intVar0) << intVar0, 10000); + } + } + + // RShiftI(iv, ConI) + @Test + @IR(counts = {IRNode.COUNTED_LOOP, ">0"}) + static void testIntIndexIntIVWithUnsupportedRightShift() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex(iv >> 1, 10000); + } + } + + // OrI(iv, ConI) + @Test + @IR(counts = {IRNode.COUNTED_LOOP, ">0"}) + static void testIntIndexIntIVWithUnsupportedOr() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex(iv | 1024, 10000); + } + } + + // XorI(iv, ConI) + @Test + @IR(counts = {IRNode.COUNTED_LOOP, ">0"}) + static void testIntIndexIntIVWithUnsupportedXor() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex(iv ^ 1024, 10000); + } + } + + // SubI(0, LShiftI(iv, ConI)) where scale(LShiftI(..)) == min_jint + @Test + @IR(counts = {IRNode.COUNTED_LOOP, ">0"}) + static void testIntIndexIntIVWithNegatedMinJIntShiftScaling() { + for (int iv = 0; iv <= intVar100; iv += 2) { + Objects.checkIndex(0 - (iv << 31), 10000); + } + } + + // Long indices using long induction variables + + // MulL(iv, LoadL(..)) + @Test + @IR(failOn = {IRNode.CONV_I2L}, phase = CompilePhase.AFTER_PARSING) + @IR(counts = {IRNode.COUNTED_LOOP, ">0"}) + static void testLongIndexLongIVWithVarMulScaling() { + for (long iv = 0L; iv <= longVar100; iv++) { + Objects.checkIndex(iv * longVar3, 10000L); + } + } + + // LShiftL(iv, LoadI(..)) + @Test + @IR(failOn = {IRNode.CONV_I2L}, phase = CompilePhase.AFTER_PARSING) + @IR(counts = {IRNode.COUNTED_LOOP, ">0"}) + static void testLongIndexLongIVWithVarShiftScaling() { + for (long iv = 0L; iv <= longVar100; iv++) { + Objects.checkIndex(iv << intVar0, 10000L); + } + } + + // MulL(AddL(iv, LoadL(..)), ConL) + @Test + @IR(failOn = {IRNode.CONV_I2L}, phase = CompilePhase.AFTER_PARSING) + @IR(counts = {IRNode.COUNTED_LOOP, ">0"}) + static void testLongIndexLongIVWithNonTrivialMulScaling() { + for (long iv = 0L; iv <= longVar100; iv++) { + Objects.checkIndex((iv + longVar0) * 11L, 10000L); + } + } + + // LShiftL(AddL(iv, LoadL(..)), LoadI(..)) + @Test + @IR(failOn = {IRNode.CONV_I2L}, phase = CompilePhase.AFTER_PARSING) + @IR(counts = {IRNode.COUNTED_LOOP, ">0"}) + static void testLongIndexLongIVWithNonTrivialShiftScaling() { + for (long iv = 0L; iv <= longVar100; iv++) { + Objects.checkIndex((iv + longVar0) << intVar0, 10000L); + } + } + + // RShiftL(iv, ConI) + @Test + @IR(failOn = {IRNode.CONV_I2L}, phase = CompilePhase.AFTER_PARSING) + @IR(counts = {IRNode.COUNTED_LOOP, ">0"}) + static void testLongIndexLongIVWithUnsupportedRightShift() { + for (long iv = 0L; iv <= longVar100; iv++) { + Objects.checkIndex(iv >> 1, 10000L); + } + } + + // OrL(iv, ConL) + @Test + @IR(failOn = {IRNode.CONV_I2L}, phase = CompilePhase.AFTER_PARSING) + @IR(counts = {IRNode.COUNTED_LOOP, ">0"}) + static void testLongIndexLongIVWithUnsupportedOr() { + for (long iv = 0L; iv <= longVar100; iv++) { + Objects.checkIndex(iv | 1024L, 10000L); + } + } + + // XorL(iv, ConL) + @Test + @IR(failOn = {IRNode.CONV_I2L}, phase = CompilePhase.AFTER_PARSING) + @IR(counts = {IRNode.COUNTED_LOOP, ">0"}) + static void testLongIndexLongIVWithUnsupportedXor() { + for (long iv = 0L; iv <= longVar100; iv++) { + Objects.checkIndex(iv ^ 1024L, 10000L); + } + } + + // SubL(0L, LShiftL(iv, ConI)) where scale(LShiftL(..)) == min_jlong + @Test + @IR(failOn = {IRNode.CONV_I2L}, phase = CompilePhase.AFTER_PARSING) + @IR(counts = {IRNode.COUNTED_LOOP, ">0"}) + static void testLongIndexLongIVWithNegatedMinJLongShiftScaling() { + for (long iv = 0L; iv <= longVar100; iv += 2L) { + Objects.checkIndex(0L - (iv << 63), 10000L); + } + } + + // Long indices using int induction variables + + // ConvI2L(MulI(iv, ConI)) + @Test + @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING) + @IR(counts = {IRNode.COUNTED_LOOP, ">0"}) + static void testLongIndexIntIVWithNonUnitShortMulScaling() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex((long)(iv * 11), 10000L); + } + } + + // ConvI2L(LShiftI(iv, ConI)) + @Test + @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING) + @IR(counts = {IRNode.COUNTED_LOOP, ">0"}) + static void testLongIndexIntIVWithNonUnitShortShiftScaling() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex((long)(iv << 2), 10000L); + } + } + + // ConvI2L(SubI(0, MulI(iv, ConI))) + @Test + @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING) + @IR(counts = {IRNode.COUNTED_LOOP, ">0"}) + static void testLongIndexIntIVWithNonUnitShortNegatedSubScaling() { + for (int iv = -100; iv <= 0; iv++) { + Objects.checkIndex((long)(0 - (iv * 3)), + // Using a variable range check length is + // required here for the index expression not to + // be optimized into something else that matches. + longVar10000); + } + } + + // ConvI2L(SubI(0, LShiftI(iv, ConI))) where scale(LShiftI(..)) == min_jint + @Test + @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING) + @IR(counts = {IRNode.COUNTED_LOOP, ">0"}) + static void testLongIndexIntIVWithShortNegatedSubMinJIntShiftScaling() { + for (int iv = 0; iv <= intVar100; iv += 2) { + Objects.checkIndex((long)(0 - (iv << 31)), 10000L); + } + } + + // ConvI2L(MulI(iv, LoadI(..))) + @Test + @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING) + @IR(counts = {IRNode.COUNTED_LOOP, ">0"}) + static void testLongIndexIntIVWithShortVarMulScaling() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex((long)(iv * intVar3), 10000L); + } + } + + // ConvI2L(LShiftI(iv, LoadI(..))) + @Test + @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING) + @IR(counts = {IRNode.COUNTED_LOOP, ">0"}) + static void testLongIndexIntIVWithShortVarShiftScaling() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex((long)(iv << intVar3), 10000L); + } + } + + // MulL(AddL(ConvI2L(iv), LoadL(..)), ConL) + @Test + @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING) + @IR(counts = {IRNode.COUNTED_LOOP, ">0"}) + static void testLongIndexIntIVWithMulOfAddScaling() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex(((long)iv + longVar0) * 11L, 10000L); + } + } + + // RShiftL(ConvI2L(iv), ConI) + @Test + @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING) + @IR(counts = {IRNode.COUNTED_LOOP, ">0"}) + static void testLongIndexIntIVWithUnsupportedRightShift() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex((long)iv >> 1, 10000L); + } + } + + // ConvI2L(AddI(MulI(iv, ConI), MulI(iv, ConI))) + @Test + @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING) + @IR(counts = {IRNode.COUNTED_LOOP, ">0"}) + static void testLongIndexIntIVWithNonUnitShortAddOfMulsScaling() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex((long)((iv * 3) + (iv * 5)), 10000L); + } + } + + // ConvI2L(AddI(MulI(iv, ConI), LShiftI(iv, ConI))) + @Test + @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING) + @IR(counts = {IRNode.COUNTED_LOOP, ">0"}) + static void testLongIndexIntIVWithNonUnitShortAddOfMulAndShiftScaling() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex((long)((iv * -15) + (iv << 5)), 10000L); + } + } + + // ConvI2L(SubI(MulI(iv, ConI), MulI(iv, ConI))) + @Test + @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING) + @IR(counts = {IRNode.COUNTED_LOOP, ">0"}) + static void testLongIndexIntIVWithNonUnitShortSubOfMulsScaling() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex((long)((iv * 7) - (iv * 2)), 10000L); + } + } + + // ConvI2L(SubI(LShiftI(iv, ConI), LShiftI(iv, ConI))) + @Test + @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING) + @IR(counts = {IRNode.COUNTED_LOOP, ">0"}) + static void testLongIndexIntIVWithShortSubOfShiftsScaling() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex((long)((iv << 33) - (iv << 0)), longVar10000); + } + } + + // ConvI2L(SubI(LShiftI(iv, ConI), LShiftI(iv, ConI))) + @Test + @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING) + @IR(counts = {IRNode.COUNTED_LOOP, ">0"}) + static void testLongIndexIntIVWithNonUnitShortSubOfShiftsScaling() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex((long)((iv << 5) - (iv << 1)), 10000L); + } + } + + // ConvI2L(SubI(0, LShiftI(iv, ConI))) + @Test + @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING) + @IR(counts = {IRNode.COUNTED_LOOP, ">0"}) + static void testLongIndexIntIVWithNonUnitShortNegatedSubShiftScaling() { + for (int iv = -100; iv <= 0; iv++) { + Objects.checkIndex((long)(0 - (iv << 2)), + // Using a variable range check length is + // required here for the index expression not to + // be optimized into something else that matches. + longVar10000); + } + } +} diff --git a/test/hotspot/jtreg/compiler/rangechecks/TestLongRangeCheckWithZeroScaling.java b/test/hotspot/jtreg/compiler/rangechecks/TestLongRangeCheckWithZeroScaling.java new file mode 100644 index 000000000000..f170929a2e01 --- /dev/null +++ b/test/hotspot/jtreg/compiler/rangechecks/TestLongRangeCheckWithZeroScaling.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.rangechecks; + +import java.util.Objects; + +/** + * @test + * @bug 8382378 + * @summary Test that C2 can handle long range check indices scaled by zero. + * @run main ${test.main.class} + * @run main/othervm -Xcomp -XX:-TieredCompilation + * -XX:CompileCommand=compileonly,${test.main.class}::test* + * ${test.main.class} + */ + +public class TestLongRangeCheckWithZeroScaling { + + static void testCheckCancellingSubtractionOfScaledIndices() { + for (int i = 0; i < 100; i++) { + long index = ((long)(i << 10)) - (((long)i) << 10); + Objects.checkIndex(index, 100); + } + } + + public static void main(String[] args) { + Objects.checkIndex(0, 1); // Ensure java.util.Objects is loaded. + for (int i = 0; i < 10_000; i++) { + testCheckCancellingSubtractionOfScaledIndices(); + } + } +} From ce0dad268e99b110e46354769ed7c9f7d0fffd18 Mon Sep 17 00:00:00 2001 From: Daniel Fuchs Date: Wed, 20 May 2026 17:54:50 +0000 Subject: [PATCH 009/169] 8382407: Enhance LDAP Lookups Reviewed-by: rhalade, aefimov, jpai --- .../sun/jndi/ldap/LdapBindingEnumeration.java | 4 +- .../classes/com/sun/jndi/ldap/LdapCtx.java | 14 +- .../sun/jndi/ldap/LdapReferralContext.java | 4 +- .../sun/jndi/ldap/LdapSearchEnumeration.java | 4 +- .../share/classes/com/sun/jndi/ldap/Obj.java | 10 +- .../naming/internal/NamingManagerHelper.java | 29 ++-- .../internal/ObjectFactoriesFilter.java | 136 +++++++++++++++++- .../share/classes/module-info.java | 24 +++- 8 files changed, 202 insertions(+), 23 deletions(-) diff --git a/src/java.naming/share/classes/com/sun/jndi/ldap/LdapBindingEnumeration.java b/src/java.naming/share/classes/com/sun/jndi/ldap/LdapBindingEnumeration.java index 5259cb638014..936ec08a6d13 100644 --- a/src/java.naming/share/classes/com/sun/jndi/ldap/LdapBindingEnumeration.java +++ b/src/java.naming/share/classes/com/sun/jndi/ldap/LdapBindingEnumeration.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -65,7 +65,7 @@ final class LdapBindingEnumeration try { obj = NamingManagerHelper.getDirObjectInstance(obj, cn, homeCtx, - homeCtx.envprops, attrs, ObjectFactoriesFilter::checkLdapFilter); + homeCtx.envprops, attrs, ObjectFactoriesFilter.ldapFactoryFilter()); } catch (NamingException e) { throw e; diff --git a/src/java.naming/share/classes/com/sun/jndi/ldap/LdapCtx.java b/src/java.naming/share/classes/com/sun/jndi/ldap/LdapCtx.java index 636d8f5fe2b6..90bf789de1ff 100644 --- a/src/java.naming/share/classes/com/sun/jndi/ldap/LdapCtx.java +++ b/src/java.naming/share/classes/com/sun/jndi/ldap/LdapCtx.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -388,6 +388,16 @@ public LdapCtx(String dn, String host, int port_number, initEnv(); } + /** + * Whether this LDAP provider implementation trusts serial data. + * See {@linkplain java.naming/ java.naming module info} for more info. + * + * @return true if this LDAP provider implementation trusts serial data + */ + public static boolean trustSerialData() { + return Obj.trustSerialData(); + } + public LdapContext newInstance(Control[] reqCtls) throws NamingException { LdapContext clone = new LdapCtx(this, currentDN); @@ -1113,7 +1123,7 @@ protected Object c_lookup(Name name, Continuation cont) try { return NamingManagerHelper.getDirObjectInstance(obj, name, this, - envprops, attrs, ObjectFactoriesFilter::checkLdapFilter); + envprops, attrs, ObjectFactoriesFilter.ldapFactoryFilter()); } catch (NamingException e) { throw cont.fillInException(e); diff --git a/src/java.naming/share/classes/com/sun/jndi/ldap/LdapReferralContext.java b/src/java.naming/share/classes/com/sun/jndi/ldap/LdapReferralContext.java index f3f937029803..095cc4185e79 100644 --- a/src/java.naming/share/classes/com/sun/jndi/ldap/LdapReferralContext.java +++ b/src/java.naming/share/classes/com/sun/jndi/ldap/LdapReferralContext.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -118,7 +118,7 @@ final class LdapReferralContext implements DirContext, LdapContext { Object obj; try { obj = NamingManagerHelper.getObjectInstance(ref, null, null, - env, ObjectFactoriesFilter::checkLdapFilter); + env, ObjectFactoriesFilter.ldapFactoryFilter()); } catch (NamingException e) { if (handleReferrals == LdapClient.LDAP_REF_THROW) { diff --git a/src/java.naming/share/classes/com/sun/jndi/ldap/LdapSearchEnumeration.java b/src/java.naming/share/classes/com/sun/jndi/ldap/LdapSearchEnumeration.java index 6f1981df1028..714481d151c2 100644 --- a/src/java.naming/share/classes/com/sun/jndi/ldap/LdapSearchEnumeration.java +++ b/src/java.naming/share/classes/com/sun/jndi/ldap/LdapSearchEnumeration.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -124,7 +124,7 @@ protected SearchResult createItem(String dn, Attributes attrs, // rcn is either relative to homeCtx or a fully qualified DN obj = NamingManagerHelper.getDirObjectInstance( obj, rcn, (relative ? homeCtx : null), - homeCtx.envprops, attrs, ObjectFactoriesFilter::checkLdapFilter); + homeCtx.envprops, attrs, ObjectFactoriesFilter.ldapFactoryFilter()); } catch (NamingException e) { throw e; } catch (Exception e) { diff --git a/src/java.naming/share/classes/com/sun/jndi/ldap/Obj.java b/src/java.naming/share/classes/com/sun/jndi/ldap/Obj.java index 5e34f9543021..6065273a829e 100644 --- a/src/java.naming/share/classes/com/sun/jndi/ldap/Obj.java +++ b/src/java.naming/share/classes/com/sun/jndi/ldap/Obj.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -119,6 +119,14 @@ final class Obj { static final int SER_OBJECT = 3; // auxiliary serialized object class static final int MAR_OBJECT = 4; // auxiliary marshalled object class + /** + * Whether deserialization of serial data is allowed + * @return true if deserialization of serial data is allowed + */ + static boolean trustSerialData() { + return trustSerialData; + } + /** * Encode an object in LDAP attributes. * Supports binding Referenceable or Reference, Serializable, diff --git a/src/java.naming/share/classes/com/sun/naming/internal/NamingManagerHelper.java b/src/java.naming/share/classes/com/sun/naming/internal/NamingManagerHelper.java index 856a1b0a4446..43eeb788e400 100644 --- a/src/java.naming/share/classes/com/sun/naming/internal/NamingManagerHelper.java +++ b/src/java.naming/share/classes/com/sun/naming/internal/NamingManagerHelper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -88,7 +88,7 @@ public static Object getObjectInstance(Object refInfo, Name name, Context nameCt // if reference has no factory, check for addresses // containing URLs - answer = processURLAddrs(ref, name, nameCtx, environment); + answer = processURLAddrs(ref, name, nameCtx, environment, factoryFilter); if (answer != null) { return answer; } @@ -156,7 +156,7 @@ public static Object getDirObjectInstance(Object refInfo, Name name, Context nam // containing URLs // ignore name & attrs params; not used in URL factory // RMI references from ' - answer = processURLAddrs(ref, name, nameCtx, environment); + answer = processURLAddrs(ref, name, nameCtx, environment, factoryFilter); if (answer != null) { return answer; } @@ -239,7 +239,8 @@ private static Object createObjectFromFactories(Object obj, Name name, * invoking a factory. */ static Object processURLAddrs(Reference ref, Name name, Context nameCtx, - Hashtable environment) + Hashtable environment, + Predicate> factoryFilter) throws NamingException { for (int i = 0; i < ref.size(); i++) { @@ -248,7 +249,7 @@ static Object processURLAddrs(Reference ref, Name name, Context nameCtx, addr.getType().equalsIgnoreCase("URL")) { String url = (String)addr.getContent(); - Object answer = processURL(url, name, nameCtx, environment); + Object answer = processURL(url, name, nameCtx, environment, factoryFilter); if (answer != null) { return answer; } @@ -258,7 +259,8 @@ static Object processURLAddrs(Reference ref, Name name, Context nameCtx, } private static Object processURL(Object refInfo, Name name, - Context nameCtx, Hashtable environment) + Context nameCtx, Hashtable environment, + Predicate> factoryFilter) throws NamingException { Object answer; @@ -269,7 +271,7 @@ private static Object processURL(Object refInfo, Name name, String scheme = getURLScheme(url); if (scheme != null) { answer = getURLObject(scheme, refInfo, name, nameCtx, - environment); + environment, factoryFilter); if (answer != null) { return answer; } @@ -285,7 +287,7 @@ private static Object processURL(Object refInfo, Name name, String scheme = getURLScheme(urls[i]); if (scheme != null) { answer = getURLObject(scheme, refInfo, name, nameCtx, - environment); + environment, factoryFilter); if (answer != null) return answer; } @@ -327,11 +329,16 @@ private static String getURLScheme(String str) { * @param nameCtx Context whose provider resource file will be searched * for package prefix values (or null if none) * @param environment Environment properties for creating the context + * @param factoryFilter an object factory filter. If the factoryFilter is + * an instance of {@link ObjectFactoriesFilter.ContextFactoryFilter} + * it will be used to check whether the selected URL Context Factory + * is allowed to process a URL Reference of the given URL scheme. * @see javax.naming.InitialContext */ private static Object getURLObject(String scheme, Object urlInfo, Name name, Context nameCtx, - Hashtable environment) + Hashtable environment, + Predicate> factoryFilter) throws NamingException { // e.g. "ftpURLContextFactory" @@ -342,6 +349,10 @@ private static Object getURLObject(String scheme, Object urlInfo, if (factory == null) return null; + if (!ObjectFactoriesFilter.checkURLContextFactory(scheme, factory, factoryFilter)) { + return null; + } + // Found object factory try { return factory.getObjectInstance(urlInfo, name, nameCtx, environment); diff --git a/src/java.naming/share/classes/com/sun/naming/internal/ObjectFactoriesFilter.java b/src/java.naming/share/classes/com/sun/naming/internal/ObjectFactoriesFilter.java index c7d50778d7bf..5c5267f2275f 100644 --- a/src/java.naming/share/classes/com/sun/naming/internal/ObjectFactoriesFilter.java +++ b/src/java.naming/share/classes/com/sun/naming/internal/ObjectFactoriesFilter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,12 +25,19 @@ package com.sun.naming.internal; +import com.sun.jndi.ldap.LdapCtx; import sun.security.util.SecurityProperties; import javax.naming.Reference; +import javax.naming.spi.ObjectFactory; import java.io.ObjectInputFilter; import java.io.ObjectInputFilter.FilterInfo; import java.io.ObjectInputFilter.Status; +import java.util.List; +import java.util.Locale; +import java.util.function.BiPredicate; +import java.util.function.Predicate; +import java.util.stream.Stream; /** * This class implements the filter that validates object factories classes instantiated @@ -47,6 +54,133 @@ */ public final class ObjectFactoriesFilter { + /** + * Defines an Object Factory Filter that can be used to {@linkplain + * #test(Class) validate usage of regular ObjectFactory classes} as + * well as {@linkplain #checkURLContextFactory(String, Class) validate + * usage of URL Context Factory classes}. + */ + public static final class ContextFactoryFilter implements Predicate> { + private final Predicate> factoryFilter; + private final BiPredicate> urlFactoryFilter; + + /** + * Construct a new {@code ContextFactoryFilter} from the given + * {@code factoryFilter} and {@code urlFactoryFilter} + * @param factoryFilter A predicate that tells whether a given + * object factory class can be used to reconstruct an object + * from a given Reference. + * This is typically {@link #checkLdapFilter(Class) + * ObjectFactoriesFilter::checkLdapFilter} + * @param urlFactoryFilter A bi-predicate that tells whether + * a URL reference of a given scheme should be processed + * using the selected URL Context Factory. + * This is typically {@link #checkURLContextFactory(String, Class) + * ObjectFactoriesFilter::checkURLContextFactory} + */ + private ContextFactoryFilter(Predicate> factoryFilter, + BiPredicate> urlFactoryFilter) { + this.factoryFilter = factoryFilter; + this.urlFactoryFilter = urlFactoryFilter; + } + + /** + * Checks whether a URL reference of the given scheme should be processed + * using the selected URL Context Factory. + * @param scheme the URL scheme + * @param urlContextFactoryClass the URL Context Factory class + * @return true if the URL Reference can be processed + */ + public boolean checkURLContextFactory(String scheme, Class urlContextFactoryClass) { + return urlFactoryFilter.test(scheme, urlContextFactoryClass); + } + + @Override + public boolean test(Class factoryClass) { + return factoryFilter.test(factoryClass); + } + + @Override + public ContextFactoryFilter negate() { + return new ContextFactoryFilter(factoryFilter.negate(), urlFactoryFilter.negate()); + } + + private static final ContextFactoryFilter LDAP_CONTEXT_FILTER = + new ContextFactoryFilter(ObjectFactoriesFilter::checkLdapFilter, + ObjectFactoriesFilter::ldapFollowsURL); + } + + private static final class URLSchemes { + private static final String LDAP_DISABLED_URL_SCHEMES_PROP = + "jdk.jndi.ldap.disabledURLSchemes"; + private static final String DEFAULT_LDAP_DISABLED_URL_SCHEMES = + LdapCtx.trustSerialData() ? "" : "rmi"; + private static final String LDAP_DISABLED_URL_SCHEMES = + System.getProperty(LDAP_DISABLED_URL_SCHEMES_PROP, + DEFAULT_LDAP_DISABLED_URL_SCHEMES); + static final List LDAP_DISABLED_URL_SCHEMES_LIST; + + static { + String[] protocols = LDAP_DISABLED_URL_SCHEMES.split(","); + LDAP_DISABLED_URL_SCHEMES_LIST = Stream.of(protocols) + .filter(Predicate.not(String::isEmpty)) + .map(s -> s.toLowerCase(Locale.ROOT)) + .toList(); + } + } + + /** + * Returns a {@code ContextFactoryFilter} used to check whether Reference + * objects retrieved from an LDAP context should be processed. + * The returned {@code ContextFactoryFilter} should be used to check + * whether a given object factory class can be used to reconstruct an object + * from a Reference, or whether a URL Reference of a given scheme can be + * processed using a given URL Context Factory class. + * + * @return a {@code ContextFactoryFilter} used to check whether Reference + * objects retrieved from an LDAP context should be processed. + */ + public static ContextFactoryFilter ldapFactoryFilter() { + return ContextFactoryFilter.LDAP_CONTEXT_FILTER; + } + + /** + * Checks whether URL References of the given scheme should be + * processed with the given URL Context Factory class, when the + * URL Reference was retrieved from LDAP. + * @param scheme the URL scheme + * @param urlContextFactory the URL Context Factory class + * @return true if the URL Reference should be processed by the + * given urlContextFactory class. + */ + public static boolean ldapFollowsURL(String scheme, Class urlContextFactory) { + // do not follow URLs if they are disabled; + for (String p : URLSchemes.LDAP_DISABLED_URL_SCHEMES_LIST) { + if (p.equalsIgnoreCase(scheme)) return false; + } + return true; + } + + /** + * This method is called from NamingManagerHelper to verify whether + * a URL Reference of the given URL scheme can be processed by the + * given ObjectFactory. + * @param scheme the URL scheme + * @param urlContextFactory the URL Context Factory selected for that scheme + * @param factoryFilter the factory filter. If the Reference was retrieved from + * LDAP this is typically an instance of ContextFactoryFilter. + * @return true if a URL Reference of the given URL scheme is allowed to be processed + * by the given ObjectFactory. + */ + public static boolean checkURLContextFactory(String scheme, + ObjectFactory urlContextFactory, + Predicate factoryFilter) { + if (factoryFilter instanceof ContextFactoryFilter ucf) { + return ucf.checkURLContextFactory(scheme, urlContextFactory.getClass()); + } + return true; + } + /** * Checks if serial filter configured with {@code "jdk.jndi.object.factoriesFilter"} * system property value allows instantiation of the specified objects factory class. diff --git a/src/java.naming/share/classes/module-info.java b/src/java.naming/share/classes/module-info.java index 5a731000194c..a31e808588d8 100644 --- a/src/java.naming/share/classes/module-info.java +++ b/src/java.naming/share/classes/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -89,18 +89,34 @@ *

The following implementation specific system properties are supported by the * default LDAP Naming Service Provider implementation in the JDK: *

    - *
  • {@systemProperty com.sun.jndi.ldap.object.trustSerialData}: + *
  • {@systemProperty com.sun.jndi.ldap.object.trustSerialData}: *
    The value of this system property is the string representation of a boolean value * that controls the deserialization of java objects from the {@code javaSerializedData} LDAP * attribute, reconstruction of RMI references from the {@code javaRemoteLocation} LDAP attribute, and * reconstruction of {@linkplain javax.naming.BinaryRefAddr binary reference addresses} from - * the {@code javaReferenceAddress} LDAP attribute. + * the {@code javaReferenceAddress} LDAP attribute. It also controls whether RMI URL References + * obtained from LDAP will be followed. * To allow the deserialization or reconstruction of java objects from {@code javaSerializedData}, * {@code javaRemoteLocation} or {@code javaReferenceAddress} attributes, the system property value * can be set to {@code true} (case insensitive). *
    If the property is not specified the deserialization of java objects * from the {@code javaSerializedData}, the {@code javaRemoteLocation}, or {@code javaReferenceAddress} - * attributes is not allowed. + * attributes is not allowed. In addition URL References obtained from LDAP that use the + * {@code rmi} scheme will not be followed by default, unless the default value of the {@link + * ##disabledURLSchemes jdk.jndi.ldap.disabledURLSchemes} system property is overridden on the + * java command line to allow it. + *
  • + *
  • {@systemProperty jdk.jndi.ldap.disabledURLSchemes}: + *
    The value of this system property is a comma separated list of URL schemes for which + * use of a URL Context Factory is disabled when processing {@linkplain javax.naming.StringRefAddr + * string reference addresses} in the built-in JNDI/LDAP provider. If the value of that property + * is empty then all schemes are allowed. + *
    The default value of that property is empty if {@linkplain ##trustSerialData + * deserialization of java objects} is allowed, and {@code "rmi"} if deserialization of java + * objects is not allowed: in that latter case {@linkplain javax.naming.StringRefAddr string reference + * addresses} of {@linkplain javax.naming.StringRefAddr#getType() type} {@code "URL"} whose {@linkplain + * javax.naming.StringRefAddr#getContent() content} is an URL string with scheme {@code "rmi"} + * will not be followed. *
  • *
  • {@systemProperty jdk.jndi.object.factoriesFilter}: *
    The value of this system property defines a filter used by From 599168ac348323ec1bd79069a9323cff4f428998 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hannes=20Walln=C3=B6fer?= Date: Thu, 21 May 2026 14:33:06 +0000 Subject: [PATCH 010/169] 8383530: Enhance Javadoc Searching Reviewed-by: rhalade, nbenalla --- .../doclets/formats/html/resources/script.js.template | 6 +++--- .../doclets/formats/html/resources/search.js.template | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/script.js.template b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/script.js.template index e00b8682811f..9dfc7ea45e19 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/script.js.template +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/script.js.template @@ -242,13 +242,13 @@ function isInput(elem) { function switchCopyLabel(button, span) { var copied = span.getAttribute("data-copied"); button.classList.add("visible"); - var initialLabel = span.innerHTML; - span.innerHTML = copied; + var initialLabel = span.textContent; + span.textContent = copied; setTimeout(function() { button.classList.remove("visible"); setTimeout(function() { if (initialLabel !== copied) { - span.innerHTML = initialLabel; + span.textContent = initialLabel; } }, 100); }, 1900); diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/search.js.template b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/search.js.template index 04b7ac7e3717..c00e4c0df39c 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/search.js.template +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/search.js.template @@ -449,7 +449,7 @@ window.addEventListener("load", () => { if (redirect.checked) { href += "&r=1"; } - searchLink.innerHTML = href; + searchLink.textContent = href; copy.onmouseenter(); } function copyLink(e) { @@ -670,7 +670,7 @@ window.addEventListener("load", () => { const descSpan = document.createElement("span"); descSpan.className = "search-result-desc"; - descSpan.innerHTML = desc; + descSpan.textContent = desc; link.appendChild(labelSpan); link.appendChild(descSpan); From c0987ae2ceb549ae5f206a929a3a329abd51fcf2 Mon Sep 17 00:00:00 2001 From: Volkan Yazici Date: Tue, 26 May 2026 06:06:49 +0000 Subject: [PATCH 011/169] 8383439: Enhance HTTP Client Response Handling Reviewed-by: michaelm, dfuchs, rhalade, djelinski --- .../internal/net/http/ResponseContent.java | 69 +++++++++---------- .../net/httpserver/ChunkedInputStream.java | 30 ++++++-- 2 files changed, 56 insertions(+), 43 deletions(-) diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/ResponseContent.java b/src/java.net.http/share/classes/jdk/internal/net/http/ResponseContent.java index 8c2c095ba0fa..e6933a3c2aae 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/ResponseContent.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/ResponseContent.java @@ -26,6 +26,7 @@ package jdk.internal.net.http; import java.io.IOException; +import java.net.ProtocolException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collections; @@ -141,7 +142,7 @@ class ChunkedBodyParser implements BodyParser { volatile boolean cr = false; // tryReadChunkLength has found CR volatile int chunkext = 0; // number of bytes already read in the chunk extension volatile int digits = 0; // number of chunkLength bytes already read - volatile int bytesToConsume; // number of bytes that still need to be consumed before proceeding + volatile int crlfBytesToConsume; // number of CRLF bytes that still need to be consumed volatile ChunkState state = ChunkState.READING_LENGTH; // current state volatile AbstractSubscription sub; ChunkedBodyParser(Consumer onComplete) { @@ -252,8 +253,8 @@ public void accept(ByteBuffer b) { private int tryReadChunkLen(ByteBuffer chunkbuf) throws IOException { assert state == ChunkState.READING_LENGTH; while (chunkbuf.hasRemaining()) { - if (chunkext + digits >= MAX_CHUNK_HEADER_SIZE) { - throw new IOException("Chunk header size too long: " + (chunkext + digits)); + if (chunkext >= MAX_CHUNK_HEADER_SIZE - digits) { + throw new IOException("Chunk header size too long: " + (chunkext + (long) digits)); } int c = chunkbuf.get(); if (cr) { @@ -265,8 +266,8 @@ private int tryReadChunkLen(ByteBuffer chunkbuf) throws IOException { } if (c == CR) { cr = true; - if (digits == 0 && debug.on()) { - debug.log("tryReadChunkLen: invalid chunk header? No digits in chunkLen?"); + if (digits == 0) { + throw new ProtocolException("Chunk header doesn't contain any digits"); } } else if (cr == false && chunkext > 0) { // we have seen a non digit character after the chunk length. @@ -291,41 +292,33 @@ private int tryReadChunkLen(ByteBuffer chunkbuf) throws IOException { } } else { digits++; - partialChunklen = partialChunklen * 16 + digit; + long nextPartialChunklen = partialChunklen * 16L + digit; + if (nextPartialChunklen > Integer.MAX_VALUE) { + throw new ProtocolException("chunk length too big: " + nextPartialChunklen); + } + partialChunklen = (int) nextPartialChunklen; } } } return -1; } - - // try to consume as many bytes as specified by bytesToConsume. - // returns the number of bytes that still need to be consumed. - // In practice this method is only called to consume one CRLF pair - // with bytesToConsume set to 2, so it will only return 0 (if completed), - // 1, or 2 (if chunkbuf doesn't have the 2 chars). - private int tryConsumeBytes(ByteBuffer chunkbuf) throws IOException { - int n = bytesToConsume; - if (n > 0) { - int e = Math.min(chunkbuf.remaining(), n); - - // verifies some assertions - // this methods is called only to consume CRLF - if (Utils.ASSERTIONSENABLED) { - assert n <= 2 && e <= 2; - ByteBuffer tmp = chunkbuf.slice(); - // if n == 2 assert that we will first consume CR - assert (n == 2 && e > 0) ? tmp.get() == CR : true; - // if n == 1 || n == 2 && e == 2 assert that we then consume LF - assert (n == 1 || e == 2) ? tmp.get() == LF : true; + private boolean tryConsumeCRLF(ByteBuffer buffer) throws ProtocolException { + assert crlfBytesToConsume >= 0 && crlfBytesToConsume <= 2; + for (; crlfBytesToConsume > 0 && buffer.remaining() > 0; crlfBytesToConsume--) { + byte b = buffer.get(); + if (crlfBytesToConsume == 1) { // 1 byte to go (implies already consumed CR), check LF + if (b != LF) { + throw new ProtocolException("LF expected"); + } + } else { // 2 bytes to go, check CR + assert crlfBytesToConsume == 2; + if (b != CR) { + throw new ProtocolException("CR expected"); + } } - - chunkbuf.position(chunkbuf.position() + e); - n -= e; - bytesToConsume = n; } - assert n >= 0; - return n; + return crlfBytesToConsume == 0; } /** @@ -338,7 +331,7 @@ private int tryConsumeBytes(ByteBuffer chunkbuf) throws IOException { */ ByteBuffer tryReadOneHunk(ByteBuffer chunk) throws IOException { int unfulfilled = bytesremaining; - int toconsume = bytesToConsume; + int toconsume = crlfBytesToConsume; ChunkState st = state; if (st == ChunkState.READING_LENGTH && chunklen == -1) { if (debug.on()) debug.log(() -> "Trying to read chunk len" @@ -349,7 +342,7 @@ ByteBuffer tryReadOneHunk(ByteBuffer chunk) throws IOException { if (debug.on()) debug.log("Got chunk len %d", clen); cr = false; partialChunklen = 0; unfulfilled = bytesremaining = clen; - if (clen == 0) toconsume = bytesToConsume = 2; // that was the last chunk + if (clen == 0) toconsume = crlfBytesToConsume = 2; // that was the last chunk else st = state = ChunkState.READING_DATA; // read the data } @@ -357,12 +350,12 @@ ByteBuffer tryReadOneHunk(ByteBuffer chunk) throws IOException { if (debug.on()) debug.log("Trying to consume bytes: %d (remaining in buffer: %s)", toconsume, chunk.remaining()); - if (tryConsumeBytes(chunk) > 0) { + if (!tryConsumeCRLF(chunk)) { return READMORE; } } - toconsume = bytesToConsume; + toconsume = crlfBytesToConsume; assert toconsume == 0; @@ -394,7 +387,7 @@ ByteBuffer tryReadOneHunk(ByteBuffer chunk) throws IOException { debug.log( "Returning chunk bytes: %d", bytes2return); returnBuffer = Utils.sliceWithLimitedCapacity(chunk, bytes2return).asReadOnlyBuffer(); unfulfilled = bytesremaining -= bytes2return; - if (unfulfilled == 0) bytesToConsume = 2; + if (unfulfilled == 0) crlfBytesToConsume = 2; } assert unfulfilled >= 0; @@ -408,7 +401,7 @@ ByteBuffer tryReadOneHunk(ByteBuffer chunk) throws IOException { // then we will come back here later - skipping the block // that reads data because remaining==0, and finding // that the two bytes are now consumed. - if (tryConsumeBytes(chunk) == 0) { + if (tryConsumeCRLF(chunk)) { // we're done for this chunk! reset all states and // prepare to read the next chunk. chunklen = -1; diff --git a/src/jdk.httpserver/share/classes/sun/net/httpserver/ChunkedInputStream.java b/src/jdk.httpserver/share/classes/sun/net/httpserver/ChunkedInputStream.java index 0c003378d77c..057f15b93371 100644 --- a/src/jdk.httpserver/share/classes/sun/net/httpserver/ChunkedInputStream.java +++ b/src/jdk.httpserver/share/classes/sun/net/httpserver/ChunkedInputStream.java @@ -26,11 +26,11 @@ package sun.net.httpserver; import java.io.*; -import java.net.*; -import com.sun.net.httpserver.*; -import com.sun.net.httpserver.spi.*; class ChunkedInputStream extends LeftOverInputStream { + + private IOException readFailure = null; + ChunkedInputStream(ExchangeImpl t, InputStream src) { super (t, src); } @@ -50,6 +50,9 @@ class ChunkedInputStream extends LeftOverInputStream { private int numeric(char[] arr, int nchars) throws IOException { assert arr.length >= nchars; + if (nchars == 0) { + throw new IOException("missing chunk length"); + } int len = 0; for (int i=0; i Integer.MAX_VALUE) { + throw new IOException("chunk length too big: " + nextLen); + } + len = (int) nextLen; } return len; } @@ -111,14 +118,27 @@ private int readChunkHeader() throws IOException { } protected int readImpl(byte[] b, int off, int len) throws IOException { + // Chunk reader is stateful, don't retry after a failure + if (readFailure != null) { + throw readFailure; + } + try { + return readImpl0(b, off, len); + } catch (IOException ioe) { + readFailure = ioe; + throw ioe; + } + } + + private int readImpl0(byte[] b, int off, int len) throws IOException { if (eof) { return -1; } if (needToReadHeader) { remaining = readChunkHeader(); if (remaining == 0) { - eof = true; consumeCRLF(); + eof = true; t.getServerImpl().requestCompleted(t.getConnection()); return -1; } From 943540d4ff36d39a453c4bc677caa22e595fdb1a Mon Sep 17 00:00:00 2001 From: Joe Wang Date: Thu, 28 May 2026 18:27:43 +0000 Subject: [PATCH 012/169] 8383301: Enhance WebRowSets Reviewed-by: aefimov, rhalade, lancea, jlu, naoto --- make/modules/java.sql.rowset/Java.gmk | 1 + .../rowset/RowSetResourceBundle.properties | 8 +- .../classes/com/sun/rowset/WebRowSetImpl.java | 34 +- .../rowset/internal/WebRowSetXmlReader.java | 168 +++++----- .../rowset/internal/WebRowSetXmlWriter.java | 40 ++- .../sun/rowset/internal/XmlErrorHandler.java | 36 +- .../internal/XmlReaderContentHandler.java | 175 +++++++--- .../com/sun/rowset/internal/XmlResolver.java | 38 ++- .../webrowset/CommonWebRowSetTests.java | 309 +++++++++++++++++- test/jdk/javax/sql/xml/COFFEE_ROWS.xml | 32 +- .../jdk/javax/sql/xml/DELETED_COFFEE_ROWS.xml | 52 +-- .../javax/sql/xml/INSERTED_COFFEE_ROWS.xml | 32 +- .../sql/xml/MODFIED_DELETED_COFFEE_ROWS.xml | 56 ++-- .../jdk/javax/sql/xml/UPDATED_COFFEE_ROWS.xml | 40 ++- .../sql/xml/UPDATED_INSERTED_COFFEE_ROWS.xml | 36 +- 15 files changed, 702 insertions(+), 355 deletions(-) diff --git a/make/modules/java.sql.rowset/Java.gmk b/make/modules/java.sql.rowset/Java.gmk index ecfe3e6e6412..9326a59220d1 100644 --- a/make/modules/java.sql.rowset/Java.gmk +++ b/make/modules/java.sql.rowset/Java.gmk @@ -29,6 +29,7 @@ DISABLED_WARNINGS_java += dangling-doc-comments DOCLINT += -Xdoclint:all/protected \ '-Xdoclint/package:java.*,javax.*' +COPY += .xsd CLEAN_FILES += $(wildcard \ $(TOPDIR)/src/java.sql.rowset/share/classes/com/sun/rowset/*.properties \ $(TOPDIR)/src/java.sql.rowset/share/classes/javax/sql/rowset/*.properties) diff --git a/src/java.sql.rowset/share/classes/com/sun/rowset/RowSetResourceBundle.properties b/src/java.sql.rowset/share/classes/com/sun/rowset/RowSetResourceBundle.properties index a3b810fb8f13..ecb246e24463 100644 --- a/src/java.sql.rowset/share/classes/com/sun/rowset/RowSetResourceBundle.properties +++ b/src/java.sql.rowset/share/classes/com/sun/rowset/RowSetResourceBundle.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2005, 2026, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -137,9 +137,11 @@ syncrsimpl.syncnotpos = Synchronization is not possible syncrsimpl.valtores = Value to be resolved can either be in the database or in cachedrowset #WebRowSetXmlReader exception -wrsxmlreader.invalidcp = End of RowSet reached. Invalid cursor position wrsxmlreader.readxml = readXML : {0} -wrsxmlreader.parseerr = ** Parsing Error : {0} , line : {1} , uri : {2} +wrsxmlreader.stdschema = readXML : Cannot locate standard WebRowSet schema: {0}. +wrsxmlreader.schemaloc = readXML : Malformed xsi:schemaLocation: {0}. +wrsxmlreader.schema = readXML : Non-standard schema specified for namespace {0} : {1}. +wrsxmlreader.entity = readXML : external entity {0} other than the standard schema is not allowed. #WebRowSetXmlWriter exceptions wrsxmlwriter.ioex = IOException : {0} diff --git a/src/java.sql.rowset/share/classes/com/sun/rowset/WebRowSetImpl.java b/src/java.sql.rowset/share/classes/com/sun/rowset/WebRowSetImpl.java index a72ed6cdf43d..e638e956c185 100644 --- a/src/java.sql.rowset/share/classes/com/sun/rowset/WebRowSetImpl.java +++ b/src/java.sql.rowset/share/classes/com/sun/rowset/WebRowSetImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -179,27 +179,23 @@ public void readXml(java.io.Reader reader) throws SQLException { // This will change to a XmlReader, which over-rides the default // Xml that is used when a WRS is instantiated. //WebRowSetXmlReader xmlReader = getXmlReader(); - try { - if (reader != null) { - xmlReader.readXML(this, reader); - - // Position is before the first row - // The cursor position is to be stored while serializng - // and deserializing the WebRowSet Object. - if(curPosBfrWrite == 0) { - this.beforeFirst(); - } + if (reader != null) { + xmlReader.readXML(this, reader); - // Return the position back to place prior to callin writeXml - else { - this.absolute(curPosBfrWrite); - } + // Position is before the first row + // The cursor position is to be stored while serializing + // and deserializing the WebRowSet Object. + if(curPosBfrWrite == 0) { + this.beforeFirst(); + } - } else { - throw new SQLException(resBundle.handleGetObject("webrowsetimpl.invalidrd").toString()); + // Return the position back to place prior to callin writeXml + else { + this.absolute(curPosBfrWrite); } - } catch (Exception e) { - throw new SQLException(e.getMessage()); + + } else { + throw new SQLException(resBundle.handleGetObject("webrowsetimpl.invalidrd").toString()); } } diff --git a/src/java.sql.rowset/share/classes/com/sun/rowset/internal/WebRowSetXmlReader.java b/src/java.sql.rowset/share/classes/com/sun/rowset/internal/WebRowSetXmlReader.java index a0d99e4fbcf4..2c105bd4ae88 100644 --- a/src/java.sql.rowset/share/classes/com/sun/rowset/internal/WebRowSetXmlReader.java +++ b/src/java.sql.rowset/share/classes/com/sun/rowset/internal/WebRowSetXmlReader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,18 +25,23 @@ package com.sun.rowset.internal; +import java.net.URL; import java.sql.*; import javax.sql.*; import java.io.*; import org.xml.sax.*; import org.xml.sax.helpers.*; + +import javax.xml.XMLConstants; import javax.xml.parsers.*; import com.sun.rowset.*; import java.text.MessageFormat; import javax.sql.rowset.*; import javax.sql.rowset.spi.*; +import javax.xml.validation.Schema; +import javax.xml.validation.SchemaFactory; /** * An implementation of the XmlReader interface, which @@ -45,7 +50,9 @@ * as its parser. */ public class WebRowSetXmlReader implements XmlReader, Serializable { - + // standard schema + static final String WEBROWSET_XSD = "webrowset.xsd"; + private static final String VALIDATION_PROPERTY = "jdk.sql.rowset.webrowsetValidation"; private JdbcRowSetResourceBundle resBundle; @@ -81,56 +88,7 @@ public WebRowSetXmlReader(){ * @see XmlReaderContentHandler */ public void readXML(WebRowSet caller, java.io.Reader reader) throws SQLException { - try { - // Crimson Parser(as in J2SE 1.4.1 is NOT able to handle - // Reader(s)(FileReader). - // - // But getting the file as a Stream works fine. So we are going to take - // the reader but send it as a InputStream to the parser. Note that this - // functionality needs to work against any parser - // Crimson(J2SE 1.4.x) / Xerces(J2SE 1.5.x). - InputSource is = new InputSource(reader); - DefaultHandler dh = new XmlErrorHandler(); - XmlReaderContentHandler hndr = new XmlReaderContentHandler((RowSet)caller); - SAXParserFactory factory = SAXParserFactory.newInstance(); - factory.setNamespaceAware(true); - factory.setValidating(true); - SAXParser parser = factory.newSAXParser() ; - - parser.setProperty( - "http://java.sun.com/xml/jaxp/properties/schemaLanguage", "http://www.w3.org/2001/XMLSchema"); - - XMLReader reader1 = parser.getXMLReader() ; - reader1.setEntityResolver(new XmlResolver()); - reader1.setContentHandler(hndr); - - reader1.setErrorHandler(dh); - - reader1.parse(is); - - } catch (SAXParseException err) { - System.out.println (MessageFormat.format(resBundle.handleGetObject("wrsxmlreader.parseerr").toString(), new Object[]{ err.getMessage (), err.getLineNumber(), err.getSystemId()})); - err.printStackTrace(); - throw new SQLException(err.getMessage()); - - } catch (SAXException e) { - Exception x = e; - if (e.getException () != null) - x = e.getException(); - x.printStackTrace (); - throw new SQLException(x.getMessage()); - - } - - // Will be here if trying to write beyond the RowSet limits - - catch (ArrayIndexOutOfBoundsException aie) { - throw new SQLException(resBundle.handleGetObject("wrsxmlreader.invalidcp").toString()); - } - catch (Throwable e) { - throw new SQLException(MessageFormat.format(resBundle.handleGetObject("wrsxmlreader.readxml").toString() , e.getMessage())); - } - + readXml(caller, new InputSource(reader)); } @@ -160,52 +118,76 @@ public void readXML(WebRowSet caller, java.io.Reader reader) throws SQLException * @see XmlReaderContentHandler */ public void readXML(WebRowSet caller, java.io.InputStream iStream) throws SQLException { - try { - InputSource is = new InputSource(iStream); - DefaultHandler dh = new XmlErrorHandler(); - - XmlReaderContentHandler hndr = new XmlReaderContentHandler((RowSet)caller); - SAXParserFactory factory = SAXParserFactory.newInstance(); - factory.setNamespaceAware(true); - factory.setValidating(true); - - SAXParser parser = factory.newSAXParser() ; - - parser.setProperty("http://java.sun.com/xml/jaxp/properties/schemaLanguage", - "http://www.w3.org/2001/XMLSchema"); - - XMLReader reader1 = parser.getXMLReader() ; - reader1.setEntityResolver(new XmlResolver()); - reader1.setContentHandler(hndr); - - reader1.setErrorHandler(dh); - - reader1.parse(is); - - } catch (SAXParseException err) { - System.out.println (MessageFormat.format(resBundle.handleGetObject("wrsxmlreader.parseerr").toString(), new Object[]{err.getLineNumber(), err.getSystemId() })); - System.out.println(" " + err.getMessage ()); - err.printStackTrace(); - throw new SQLException(err.getMessage()); - - } catch (SAXException e) { - Exception x = e; - if (e.getException () != null) - x = e.getException(); - x.printStackTrace (); - throw new SQLException(x.getMessage()); + readXml(caller, new InputSource(iStream)); + } + /** + * Parses the supplied XML input into the given {@code WebRowSet}. + * + * The parser validates the XML against the standard WebRowSet schema by default. + * Validation can be disabled by setting {@systemProperty jdk.sql.rowset.webrowsetValidation} + * to {@code false} so that the parser can read legacy WebRowSet XML. + * + * @param caller the rowset to populate + * @param is the SAX input source to parse + * @throws SQLException if parsing or rowset population fails + */ + private void readXml(WebRowSet caller, InputSource is) throws SQLException { + try { + boolean validating = validationEnabled(); + + SAXParserFactory spf = SAXParserFactory.newDefaultNSInstance(); + spf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); + if (validating) { + spf.setSchema(getSchema()); + } + + XMLReader reader = spf.newSAXParser().getXMLReader(); + reader.setEntityResolver(new XmlResolver()); + reader.setContentHandler(new XmlReaderContentHandler(caller, validating)); + reader.setErrorHandler(new XmlErrorHandler()); + reader.parse(is); + } catch (SAXException | ParserConfigurationException | IOException err) { + throw new SQLException(MessageFormat.format(resBundle.handleGetObject("wrsxmlreader.readxml").toString(), + err.getMessage()), err); } + } - // Will be here if trying to write beyond the RowSet limits - - catch (ArrayIndexOutOfBoundsException aie) { - throw new SQLException(resBundle.handleGetObject("wrsxmlreader.invalidcp").toString()); + /** + * Returns the standard WebRowSet schema bundled in the + * {@code java.sql.rowset} module. + * + * A new schema is created for each call so parser instances do not share + * validator implementation state. This can be revisited once the bundled + * schema implementation is safe to share. + * + * @return the WebRowSet schema + * @throws SAXException if the schema cannot be located or parsed + */ + private Schema getSchema() throws SAXException { + URL stdSchema = WebRowSet.class.getResource(WEBROWSET_XSD); + if (stdSchema == null) { + throw new SAXException(MessageFormat.format(resBundle.handleGetObject( + "wrsxmlreader.stdschema").toString(), WEBROWSET_XSD)); } - catch (Throwable e) { - throw new SQLException(MessageFormat.format(resBundle.handleGetObject("wrsxmlreader.readxml").toString() , e.getMessage())); - } + SchemaFactory sf = SchemaFactory.newDefaultInstance(); + sf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); + return sf.newSchema(stdSchema); + } + + /** + * Returns whether WebRowSet XML validation is enabled. + * + * Validation is enabled by default. It is disabled only when the + * {@systemProperty jdk.sql.rowset.webrowsetValidation} is set to + * {@code false}, case-insensitive. + * + * @return {@code true} if XML should be schema-validated + */ + private static boolean validationEnabled() { + return !"false".equalsIgnoreCase( + System.getProperty(VALIDATION_PROPERTY, "true").trim()); } /** diff --git a/src/java.sql.rowset/share/classes/com/sun/rowset/internal/WebRowSetXmlWriter.java b/src/java.sql.rowset/share/classes/com/sun/rowset/internal/WebRowSetXmlWriter.java index 3e419be005a0..426afbf551ac 100644 --- a/src/java.sql.rowset/share/classes/com/sun/rowset/internal/WebRowSetXmlWriter.java +++ b/src/java.sql.rowset/share/classes/com/sun/rowset/internal/WebRowSetXmlWriter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -148,6 +148,7 @@ private void writeRowSet(WebRowSet caller) throws SQLException { writeData(caller); endHeader(); + writer.flush(); } catch (java.io.IOException ex) { throw new SQLException(MessageFormat.format(resBundle.handleGetObject("wrsxmlwriter.ioex").toString(), ex.getMessage())); @@ -321,12 +322,19 @@ private void writeData(WebRowSet caller) throws java.io.IOException { caller.beforeFirst(); caller.setShowDeleted(true); while (caller.next()) { - if (caller.rowDeleted() && caller.rowInserted()) { + boolean rowDeleted = caller.rowDeleted(); + boolean rowInserted = caller.rowInserted(); + boolean rowUpdated = caller.rowUpdated(); + boolean writeUpdateValues = rowDeleted || rowUpdated; + + if (rowDeleted && rowInserted) { beginSection("modifyRow"); - } else if (caller.rowDeleted()) { + } else if (rowDeleted) { beginSection("deleteRow"); - } else if (caller.rowInserted()) { + } else if (rowInserted) { beginSection("insertRow"); + } else if (rowUpdated) { + beginSection("modifyRow"); } else { beginSection("currentRow"); } @@ -338,13 +346,17 @@ private void writeData(WebRowSet caller) throws java.io.IOException { beginTag("columnValue"); writeValue(i, (RowSet)rs); endTag("columnValue"); - beginTag("updateRow"); + beginTag("updateValue"); writeValue(i, caller); - endTag("updateRow"); + endTag("updateValue"); } else { beginTag("columnValue"); writeValue(i, caller); endTag("columnValue"); + if (writeUpdateValues) { + beginTag("updateValue"); + endTag("updateValue"); + } } } @@ -482,7 +494,6 @@ private void endSection(String tag) throws java.io.IOException { } else { ; } - writer.flush(); } private void endSection() throws java.io.IOException { @@ -492,7 +503,6 @@ private void endSection() throws java.io.IOException { String beginTag = getTag(); writer.write("\n"); - writer.flush(); } private void beginTag(String tag) throws java.io.IOException { @@ -513,7 +523,6 @@ private void endTag(String tag) throws java.io.IOException { } else { ; } - writer.flush(); } private void emptyTag(String tag) throws java.io.IOException { @@ -550,8 +559,6 @@ private void writeStringData(String s) throws java.io.IOException { private void writeString(String s) throws java.io.IOException { if (s != null) { writer.write(s); - } else { - writeNull(); } } @@ -595,9 +602,14 @@ private void writeIndent(int tabs) throws java.io.IOException { } private void propString(String tag, String s) throws java.io.IOException { - beginTag(tag); - writeString(s); - endTag(tag); + if (s == null) { + writeIndent(stack.size()); + writer.write("<" + tag + "/>\n"); + } else { + beginTag(tag); + writeString(s); + endTag(tag); + } } private void propInteger(String tag, int i) throws java.io.IOException { diff --git a/src/java.sql.rowset/share/classes/com/sun/rowset/internal/XmlErrorHandler.java b/src/java.sql.rowset/share/classes/com/sun/rowset/internal/XmlErrorHandler.java index 099446c6865c..8bbafd0999c7 100644 --- a/src/java.sql.rowset/share/classes/com/sun/rowset/internal/XmlErrorHandler.java +++ b/src/java.sql.rowset/share/classes/com/sun/rowset/internal/XmlErrorHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,12 +25,10 @@ package com.sun.rowset.internal; -import org.xml.sax.*; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; import org.xml.sax.helpers.DefaultHandler; -import com.sun.rowset.*; -import javax.sql.rowset.*; - /** * An implementation of the DefaultHandler interface, which @@ -38,22 +36,16 @@ * This is the ErrorHandler which helps WebRowSetXmlReader * to handle any errors while reading the xml data. */ - - public class XmlErrorHandler extends DefaultHandler { - public int errorCounter = 0; - - public void error(SAXParseException e) throws SAXException { - errorCounter++; - - } - - public void fatalError(SAXParseException e) throws SAXException { - errorCounter++; - - } - - public void warning(SAXParseException exception) throws SAXException { - - } + @Override + public void error(SAXParseException e) throws SAXException { + throw e; + } + + @Override + public void warning(SAXParseException e) throws SAXException { + // to ensure consistent handling of all XML parsing and validation diagnostics + // warnings are not ignored, though unlikely to happen + throw e; + } } diff --git a/src/java.sql.rowset/share/classes/com/sun/rowset/internal/XmlReaderContentHandler.java b/src/java.sql.rowset/share/classes/com/sun/rowset/internal/XmlReaderContentHandler.java index 73bb39eba985..c70a7bc142a2 100644 --- a/src/java.sql.rowset/share/classes/com/sun/rowset/internal/XmlReaderContentHandler.java +++ b/src/java.sql.rowset/share/classes/com/sun/rowset/internal/XmlReaderContentHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -370,16 +370,21 @@ public class XmlReaderContentHandler extends DefaultHandler { */ private static final int MetaNullTag = 19; - private String [] data = {"currentRow", "columnValue", "insertRow", "deleteRow", "insdel", "updateRow", "null" , "emptyString"}; + private String [] data = {"currentRow", "columnValue", "insertRow", "deleteRow", + "modifyRow", "updateValue", "null" , "emptyString"}; private static final int RowTag = 0; private static final int ColTag = 1; private static final int InsTag = 2; private static final int DelTag = 3; - private static final int InsDelTag = 4; + private static final int ModTag = 4; private static final int UpdTag = 5; private static final int NullTag = 6; private static final int EmptyStringTag = 7; + private static final int InsDelTag = 8; + private static final int UpdRowTag = 9; + + private static final String XSI_NS = "http://www.w3.org/2001/XMLSchema-instance"; /** * A constant indicating the state of this XmlReaderContentHandler @@ -419,6 +424,9 @@ public class XmlReaderContentHandler extends DefaultHandler { private JdbcRowSetResourceBundle resBundle; + private boolean sawRoot; + private final boolean validating; + /** * Constructs a new XmlReaderContentHandler object that will * assist the SAX parser in reading a WebRowSet object in the @@ -431,6 +439,20 @@ public class XmlReaderContentHandler extends DefaultHandler { * @param r the RowSet object in XML format that will be read */ public XmlReaderContentHandler(RowSet r) { + this(r, true); + } + + /** + * Constructs a new {@code XmlReaderContentHandler} with a validation flag. + * When validation is disabled, the handler accepts legacy WebRowSet XML tags + * in addition to the standard tags. + * + * @param r the {@code RowSet} object in XML format that will be read + * @param validating the flag indicating whether schema validation is on + */ + XmlReaderContentHandler(RowSet r, boolean validating) { + this.validating = validating; + // keep the rowset we've been given rs = (WebRowSetImpl)r; @@ -481,21 +503,27 @@ private void initMaps() { items = properties.length; for (i=0;i(); items = colDef.length; for (i=0;i(); items = data.length; for (i=0;iThe XML parser is configured with the standard WebRowSet schema + * explicitly through the JAXP Validation API, so schema validation does + * not depend on xsi:schemaLocation being present in the XML instance + * document. This method therefore performs only an additional consistency + * check on the schema hint provided by the document.

    + * + * @param attributes the attributes of the root webRowSet element + * @throws SAXException if a non-standard schema location is specified + */ + private void checkSchemaLocation(Attributes attributes) throws SAXException { + String schemaLocation = attributes.getValue(XSI_NS, "schemaLocation"); + + // schemaLocation is optional when schema is supplied explicitly + if (schemaLocation == null || schemaLocation.isBlank()) { + return; + } + + String[] tokens = schemaLocation.trim().split("\\s+"); + if (tokens.length % 2 != 0) { + throw new SAXException(MessageFormat.format( + resBundle.handleGetObject("wrsxmlreader.schemaloc").toString(), schemaLocation)); + } + + for (int i = 0; i < tokens.length; i += 2) { + String ns = tokens[i]; + String xsd = tokens[i + 1]; + + if (!isWebRowSetSchema(xsd)) { + throw new SAXException(MessageFormat.format( + resBundle.handleGetObject("wrsxmlreader.schema").toString(), ns, xsd)); + } + } + } + + private static boolean isWebRowSetSchema(String xsd) { + return xsd.equals(WebRowSetXmlReader.WEBROWSET_XSD) + || xsd.endsWith("/" + WebRowSetXmlReader.WEBROWSET_XSD); + } + /** * Sets the value for the given element if name is one of * the array elements in the fields properties, @@ -742,6 +819,8 @@ public void endElement(String uri, String lName, String qName) throws SAXExcepti } break; case RowTag: + case ModTag: + case InsDelTag: try { rs.insertRow(); rs.moveToCurrentRow(); @@ -751,6 +830,8 @@ public void endElement(String uri, String lName, String qName) throws SAXExcepti // rowInserted flagging rs.setOriginalRow(); + // Legacy currentRow elements can carry update tags when + // schema validation is disabled. applyUpdates(); } catch (SQLException ex) { throw new SAXException(MessageFormat.format(resBundle.handleGetObject("xmlrch.errconstr").toString(), ex.getMessage())); @@ -762,6 +843,8 @@ public void endElement(String uri, String lName, String qName) throws SAXExcepti rs.moveToCurrentRow(); rs.next(); rs.setOriginalRow(); + // Legacy deleteRow elements may omit update tags when + // schema validation is disabled. applyUpdates(); rs.deleteRow(); } catch (SQLException ex) { @@ -779,33 +862,20 @@ public void endElement(String uri, String lName, String qName) throws SAXExcepti } break; - case InsDelTag: - try { - rs.insertRow(); - rs.moveToCurrentRow(); - rs.next(); - rs.setOriginalRow(); - applyUpdates(); - } catch (SQLException ex) { - throw new SAXException(MessageFormat.format(resBundle.handleGetObject("xmlrch.errinsdel").toString() , ex.getMessage())); - } - break; - case UpdTag: - try { - if(getNullValue()) - { - insertValue(null); - setNullValue(false); - } else if(getEmptyStringValue()) { - insertValue(""); - setEmptyStringValue(false); - } else { - updates.add(upd); - } - } catch(SQLException ex) { - throw new SAXException(MessageFormat.format(resBundle.handleGetObject("xmlrch.errupdate").toString() , ex.getMessage())); - } + case UpdRowTag: + // Empty updateValue/updateRow placeholders are ignored when + // they carry no value. + if(getNullValue()) + { + addUpdate(null); + setNullValue(false); + } else if(getEmptyStringValue()) { + addUpdate(""); + setEmptyStringValue(false); + } else if(!tempUpdate.isEmpty()) { + addUpdate(tempUpdate); + } break; default: @@ -825,10 +895,7 @@ private void applyUpdates() throws SAXException { while (i.hasNext()) { upd = (Object [])i.next(); idx = ((Integer)upd[0]).intValue(); - - if(!(lastval.equals(upd[1]))){ - insertValue((String)(upd[1])); - } + insertValue((String)(upd[1])); } rs.updateRow(); @@ -841,6 +908,19 @@ private void applyUpdates() throws SAXException { } + /** + * Queues an update value for the current column to be applied when the + * row element is complete. + * + * @param value the update value, can be {@code null} + */ + private void addUpdate(String value) { + upd = new Object[2]; + upd[0] = Integer.valueOf(idx); + upd[1] = value; + updates.add(upd); + } + /** * Sets a property, metadata, or data value with the characters in * the given array of characters, starting with the array element @@ -1027,10 +1107,12 @@ private java.sql.Timestamp getTimestampValue(String s) { private void setPropertyValue(String s) throws SQLException { // find out if we are going to be dealing with a null boolean nullValue = getNullValue(); + // Empty nullable properties preserve the legacy behavior while + // remaining schema-valid. switch(getTag()) { case CommandTag: - if (nullValue) + if (nullValue || s == null || s.isEmpty()) ; //rs.setCommand(null); else rs.setCommand(s); @@ -1042,7 +1124,7 @@ private void setPropertyValue(String s) throws SQLException { rs.setConcurrency(getIntegerValue(s)); break; case DatasourceTag: - if (nullValue) + if (nullValue || s == null || s.isEmpty()) rs.setDataSourceName(null); else rs.setDataSourceName(s); @@ -1129,14 +1211,14 @@ private void setPropertyValue(String s) throws SQLException { rs.setShowDeleted(getBooleanValue(s)); break; case TableNameTag: - if (nullValue) + if (nullValue || s == null || s.isEmpty()) //rs.setTableName(null); ; else rs.setTableName(s); break; case UrlTag: - if (nullValue) + if (nullValue || s == null || s.isEmpty()) rs.setUrl(null); else rs.setUrl(s); @@ -1305,8 +1387,7 @@ private void setDataValue(char[] ch, int start, int len) throws SQLException { tempStr = tempStr.concat(columnValue); break; case UpdTag: - upd = new Object[2]; - + case UpdRowTag: /** * This has been added for handling of special characters. When special * characters are encountered the characters function gets called for @@ -1316,12 +1397,6 @@ private void setDataValue(char[] ch, int start, int len) throws SQLException { **/ tempUpdate = tempUpdate.concat(new String(ch,start,len)); - upd[0] = Integer.valueOf(idx); - upd[1] = tempUpdate; - //updates.add(upd); - - lastval = (String)upd[1]; - //insertValue(ch, start, len); break; case InsTag: @@ -1330,7 +1405,7 @@ private void setDataValue(char[] ch, int start, int len) throws SQLException { private void insertValue(String s) throws SQLException { - if (getNullValue()) { + if (s == null || getNullValue()) { rs.updateNull(idx); return; } diff --git a/src/java.sql.rowset/share/classes/com/sun/rowset/internal/XmlResolver.java b/src/java.sql.rowset/share/classes/com/sun/rowset/internal/XmlResolver.java index a3fa6afc9a2b..856d7d734150 100644 --- a/src/java.sql.rowset/share/classes/com/sun/rowset/internal/XmlResolver.java +++ b/src/java.sql.rowset/share/classes/com/sun/rowset/internal/XmlResolver.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,32 +25,40 @@ package com.sun.rowset.internal; +import com.sun.rowset.JdbcRowSetResourceBundle; import org.xml.sax.*; import org.xml.sax.EntityResolver; import org.xml.sax.InputSource; +import javax.sql.rowset.WebRowSet; +import java.io.IOException; +import java.io.StringReader; +import java.text.MessageFormat; + /** * An implementation of the EntityResolver interface, which * reads and parses an XML formatted WebRowSet object. * This is an implementation of org.xml.sax - * */ public class XmlResolver implements EntityResolver { + //The standard WebRowSet XML Schema as defined in WebRowSet + public static final String STD_SCHEMA_ID = "http://xmlns.jcp.org/xml/ns//jdbc/webrowset.xsd"; - public InputSource resolveEntity(String publicId, String systemId) { - String schemaName = systemId.substring(systemId.lastIndexOf('/')); - - if(systemId.startsWith("http://java.sun.com/xml/ns/jdbc")) { - return new InputSource(this.getClass().getResourceAsStream(schemaName)); - - } else { - // use the default behaviour - return null; - } - - + @Override + public InputSource resolveEntity(String publicId, String systemId) + throws SAXException, IOException { + // accepts the standard schema without resolving it + // the schema is explicitly provided via the validation API + if (WebRowSet.SCHEMA_SYSTEM_ID.equals(systemId) || + STD_SCHEMA_ID.equals(systemId)) { + return new InputSource(new StringReader("")); + } - } + // reports error upon any external entity other than the standard schema + throw new SAXException(MessageFormat.format( + JdbcRowSetResourceBundle.getJdbcRowSetResourceBundle().handleGetObject( + "wrsxmlreader.entity").toString(), systemId)); + } } diff --git a/test/jdk/javax/sql/test/rowset/webrowset/CommonWebRowSetTests.java b/test/jdk/javax/sql/test/rowset/webrowset/CommonWebRowSetTests.java index 023adc8254b0..d8f1ea4ad584 100644 --- a/test/jdk/javax/sql/test/rowset/webrowset/CommonWebRowSetTests.java +++ b/test/jdk/javax/sql/test/rowset/webrowset/CommonWebRowSetTests.java @@ -22,29 +22,27 @@ */ package test.rowset.webrowset; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileReader; -import java.io.InputStreamReader; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.io.OutputStreamWriter; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import test.rowset.cachedrowset.CommonCachedRowSetTests; + +import javax.sql.rowset.WebRowSet; +import java.io.*; import java.math.BigDecimal; +import java.nio.file.Files; +import java.nio.file.Path; import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; import java.util.Arrays; -import javax.sql.rowset.WebRowSet; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.MethodSource; - -import test.rowset.cachedrowset.CommonCachedRowSetTests; public abstract class CommonWebRowSetTests extends CommonCachedRowSetTests { @@ -61,7 +59,39 @@ public abstract class CommonWebRowSetTests extends CommonCachedRowSetTests { = XMLFILEPATH + "INSERTED_COFFEE_ROWS.xml"; protected final String UPDATED_INSERTED_COFFEE_ROWS_XML = XMLFILEPATH + "UPDATED_INSERTED_COFFEE_ROWS.xml"; + private static final String ROWSET_VALIDATION_PROPERTY + = "jdk.sql.rowset.webrowsetValidation"; + + @FunctionalInterface + private interface ThrowingRunnable { + void run() throws Exception; + } + /** + * Runs the given action with the WebRowSet validation system property set + * to the requested value, then restores the previous value. + * + * @param value the temporary property value, or {@code null} to clear it + * @param action the action to run while the property is set + */ + private static synchronized void withRowSetValidation(String value, + ThrowingRunnable action) throws Exception { + String previous = System.getProperty(ROWSET_VALIDATION_PROPERTY); + try { + if (value == null) { + System.clearProperty(ROWSET_VALIDATION_PROPERTY); + } else { + System.setProperty(ROWSET_VALIDATION_PROPERTY, value); + } + action.run(); + } finally { + if (previous == null) { + System.clearProperty(ROWSET_VALIDATION_PROPERTY); + } else { + System.setProperty(ROWSET_VALIDATION_PROPERTY, previous); + } + } + } /* * Utility method to write a WebRowSet XML file via an OutputStream @@ -101,7 +131,7 @@ protected WebRowSet readWebRowSetWithOInputStream(ByteArrayOutputStream baos) th } /* - * Utility method to write a WebRowSet XML file via an Writer + * Utility method to write a WebRowSet XML file via a Writer */ protected ByteArrayOutputStream writeWebRowSetWithOutputStreamWithWriter(WebRowSet rs) throws Exception { ByteArrayOutputStream baos = new ByteArrayOutputStream(); @@ -111,7 +141,7 @@ protected ByteArrayOutputStream writeWebRowSetWithOutputStreamWithWriter(WebRowS } /* - * Utility method to write a WebRowSet XML file via an Writer and populating + * Utility method to write a WebRowSet XML file via a Writer and populating * the WebRowSet via a ResultSet */ protected ByteArrayOutputStream writeWebRowSetWithOutputStreamWithWriter(ResultSet rs) throws Exception { @@ -418,4 +448,249 @@ public void WebRowSetTest0012(WebRowSet wrs1) throws Exception { wrs1.close(); } + /** + * Verifies that an updated row written as XML can be read back with schema + * validation enabled. + */ + @ParameterizedTest(autoCloseArguments = false) + @MethodSource("rowSetType") + public void testUpdatedRows(WebRowSet wrs) throws Exception { + try { + readCoffeeRows(wrs); + wrs.absolute(3); + wrs.updateInt(5, 21); + wrs.updateInt(6, 69); + wrs.updateRow(); + + ByteArrayOutputStream baos = + writeWebRowSetWithOutputStreamWithWriter(wrs); + withRowSetValidation(null, () -> { + // Clearing the property ensures validation is enabled; + // verifies the writer emits schema-compliant + // modifyRow/updateValue XML. + try (WebRowSet wrs1 = + readWebRowSetWithOInputStreamWithReader(baos)) { + assertEquals(COFFEES_ROWS, wrs1.size()); + assertArrayEquals(COFFEES_PRIMARY_KEYS, getPrimaryKeys(wrs1)); + + assertTrue(findRowByPrimaryKey(wrs1, 3, 1)); + assertTrue(wrs1.rowUpdated()); + assertEquals(21, wrs1.getInt(5)); + assertEquals(69, wrs1.getInt(6)); + } + }); + } finally { + wrs.close(); + } + } + + /** + * Verifies that a deleted row written as XML can be read back with schema + * validation enabled. + */ + @ParameterizedTest(autoCloseArguments = false) + @MethodSource("rowSetType") + public void testDeletedRows(WebRowSet wrs) + throws Exception { + try { + readCoffeeRows(wrs); + assertTrue(deleteRowByPrimaryKey(wrs, 2, 1)); + + ByteArrayOutputStream baos = + writeWebRowSetWithOutputStreamWithWriter(wrs); + withRowSetValidation(null, () -> { + // Clearing the property ensures validation is enabled; parsing + // fails if the writer omits schema-required empty updateValue + // elements. + try (WebRowSet wrs1 = + readWebRowSetWithOInputStreamWithReader(baos)) { + Object[] expectedRows = {1, 3, 4, 5}; + assertEquals(COFFEES_ROWS, wrs1.size()); + assertArrayEquals(expectedRows, getPrimaryKeys(wrs1)); + + wrs1.setShowDeleted(true); + assertTrue(findRowByPrimaryKey(wrs1, 2, 1)); + assertTrue(wrs1.rowDeleted()); + assertArrayEquals(COFFEES_PRIMARY_KEYS, getPrimaryKeys(wrs1)); + } + }); + } finally { + wrs.close(); + } + } + + /** + * Verifies that null properties are written as schema-compliant empty + * elements and read back as null. + */ + @ParameterizedTest(autoCloseArguments = false) + @MethodSource("rowSetType") + public void testNullProperties(WebRowSet wrs) throws Exception { + try { + readCoffeeRows(wrs); + assertNull(wrs.getCommand()); + assertNull(wrs.getDataSourceName()); + assertNull(wrs.getUrl()); + + ByteArrayOutputStream baos = + writeWebRowSetWithOutputStreamWithWriter(wrs); + withRowSetValidation(null, () -> { + // Reading with validation enabled fails if the writer emits the + // legacy invalid form for nullable properties. + try (WebRowSet wrs1 = + readWebRowSetWithOInputStreamWithReader(baos)) { + assertEquals(COFFEES_ROWS, wrs1.size()); + assertArrayEquals(COFFEES_PRIMARY_KEYS, getPrimaryKeys(wrs1)); + assertNull(wrs1.getCommand()); + assertNull(wrs1.getDataSourceName()); + assertNull(wrs1.getUrl()); + } + }); + } finally { + wrs.close(); + } + } + + /** + * Verifies that legacy XML is rejected since schema validation is enabled + * by default. + */ + @ParameterizedTest(autoCloseArguments = false) + @MethodSource("rowSetType") + public void testLegacyXmlWithValidation(WebRowSet wrs) + throws Exception { + String legacyXml = getLegacyXml(); + withRowSetValidation(null, () -> { + try { + assertThrows(SQLException.class, + () -> wrs.readXml(new StringReader(legacyXml))); + } finally { + wrs.close(); + } + }); + } + + /** + * Verifies that legacy XML is accepted when schema validation is explicitly + * disabled. + */ + @ParameterizedTest(autoCloseArguments = false) + @MethodSource("rowSetType") + public void testLegacyXmlWithoutValidation(WebRowSet wrs) + throws Exception { + String legacyXml = getLegacyXml(); + withRowSetValidation("false", () -> { + try { + wrs.readXml(new StringReader(legacyXml)); + assertEquals(COFFEES_ROWS, wrs.size()); + assertArrayEquals(COFFEES_PRIMARY_KEYS, getPrimaryKeys(wrs)); + + wrs.beforeFirst(); + boolean foundUpdatedRow = false; + while (wrs.next()) { + if (wrs.getInt(1) == 3) { + foundUpdatedRow = true; + assertTrue(wrs.rowUpdated()); + assertEquals(21, wrs.getInt(5)); + assertEquals(69, wrs.getInt(6)); + } + } + assertTrue(foundUpdatedRow); + } finally { + wrs.close(); + } + }); + } + + /** + * Verifies that legacy null elements are rejected since schema validation + * is enabled by default. + */ + @ParameterizedTest(autoCloseArguments = false) + @MethodSource("rowSetType") + public void testLegacyNullXmlWithValidation(WebRowSet wrs) + throws Exception { + String legacyXml = getLegacyNullXml(); + withRowSetValidation(null, () -> { + try { + // Legacy property values are not valid against the + // standard schema. + assertThrows(SQLException.class, + () -> wrs.readXml(new StringReader(legacyXml))); + } finally { + wrs.close(); + } + }); + } + + /** + * Verifies that legacy null elements are accepted when schema validation is + * explicitly disabled. + */ + @ParameterizedTest(autoCloseArguments = false) + @MethodSource("rowSetType") + public void testLegacyNullXmlWithoutValidation(WebRowSet wrs) + throws Exception { + String legacyXml = getLegacyNullXml(); + withRowSetValidation("false", () -> { + try { + wrs.readXml(new StringReader(legacyXml)); + assertEquals(COFFEES_ROWS, wrs.size()); + assertArrayEquals(COFFEES_PRIMARY_KEYS, getPrimaryKeys(wrs)); + + // With validation disabled, preserve the old reader behavior + // for nullable properties and metadata represented by . + assertNull(wrs.getCommand()); + assertNull(wrs.getDataSourceName()); + assertNull(wrs.getUrl()); + + ResultSetMetaData md = wrs.getMetaData(); + for (int i = 1; i <= md.getColumnCount(); i++) { + assertEquals("", md.getColumnLabel(i)); + assertEquals("", md.getColumnTypeName(i)); + } + } finally { + wrs.close(); + } + }); + } + + /** + * Builds a legacy document from the schema-compliant UPDATED_COFFEE_ROWS_XML + * file. Older WebRowSet XML used currentRow/updateRow for modified rows, + * while the webrowset schema requires modifyRow/updateValue. + */ + private String getLegacyXml() throws Exception { + return Files.readString(Path.of(UPDATED_COFFEE_ROWS_XML)) + .replace("", "") + .replace("", "") + .replace("updateValue", "updateRow"); + } + + /** + * Builds a legacy document from the schema-compliant COFFEE_ROWS_XML file. + * Older WebRowSet XML used nested {@code } elements for nullable + * string properties and metadata, while the webrowset schema requires + * simple string content. + */ + private String getLegacyNullXml() throws Exception { + return Files.readString(Path.of(COFFEE_ROWS_XML)) + .replace("", "") + .replace("", "") + .replace("", "") + .replace("", + "") + .replace("", + ""); + } + + /** + * Reads COFFEE_ROWS_XML and populates the given WebRowSet. + */ + private void readCoffeeRows(WebRowSet wrs) throws Exception { + try (FileInputStream fis = new FileInputStream(COFFEE_ROWS_XML)) { + wrs.readXml(fis); + } + } + } diff --git a/test/jdk/javax/sql/xml/COFFEE_ROWS.xml b/test/jdk/javax/sql/xml/COFFEE_ROWS.xml index e42eb4405b0a..7337206243e9 100644 --- a/test/jdk/javax/sql/xml/COFFEE_ROWS.xml +++ b/test/jdk/javax/sql/xml/COFFEE_ROWS.xml @@ -2,9 +2,9 @@ - + 1008 - + true 1000 0 @@ -20,7 +20,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j ResultSet.TYPE_SCROLL_INSENSITIVE false COFFEES - + com.sun.rowset.providers.RIOptimisticProvider Oracle Corporation @@ -40,7 +40,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j false false 0 - + COF_NAME 0 @@ -48,7 +48,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j 4 - + 2 @@ -59,7 +59,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j false false 0 - + SUP_ID 0 @@ -67,7 +67,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j 12 - + 3 @@ -78,7 +78,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j false false 0 - + PRICE 0 @@ -86,7 +86,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j 4 - + 4 @@ -97,7 +97,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j false false 0 - + SALES 10 @@ -105,7 +105,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j 2 - + 5 @@ -116,7 +116,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j false false 0 - + TOTAL 0 @@ -124,7 +124,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j 4 - + 6 @@ -135,15 +135,15 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j false false 0 - - + + 0 0 4 - + diff --git a/test/jdk/javax/sql/xml/DELETED_COFFEE_ROWS.xml b/test/jdk/javax/sql/xml/DELETED_COFFEE_ROWS.xml index 6fd38ff4e44e..c256461a8695 100644 --- a/test/jdk/javax/sql/xml/DELETED_COFFEE_ROWS.xml +++ b/test/jdk/javax/sql/xml/DELETED_COFFEE_ROWS.xml @@ -4,7 +4,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j SELECT * FROM COFFEES 1008 - + true 1000 0 @@ -40,7 +40,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j false false 0 - + COF_NAME 0 @@ -48,7 +48,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j 4 - + 2 @@ -59,7 +59,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j false false 0 - + SUP_ID 0 @@ -67,7 +67,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j 12 - + 3 @@ -78,7 +78,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j false false 0 - + PRICE 0 @@ -86,7 +86,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j 4 - + 4 @@ -97,7 +97,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j false false 0 - + SALES 10 @@ -105,7 +105,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j 2 - + 5 @@ -116,7 +116,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j false false 0 - + TOTAL 0 @@ -124,7 +124,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j 4 - + 6 @@ -135,15 +135,15 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j false false 0 - - + + 0 0 4 - + @@ -156,12 +156,12 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j 0 - 2 - French_Roast - 49 - 8.99 - 0 - 0 + 2 + French_Roast + 49 + 8.99 + 0 + 0 3 @@ -172,12 +172,12 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j 0 - 4 - Colombian_Decaf - 101 - 8.99 - 0 - 0 + 4 + Colombian_Decaf + 101 + 8.99 + 0 + 0 5 diff --git a/test/jdk/javax/sql/xml/INSERTED_COFFEE_ROWS.xml b/test/jdk/javax/sql/xml/INSERTED_COFFEE_ROWS.xml index a700bab5c6c5..8b6a0112f3bc 100644 --- a/test/jdk/javax/sql/xml/INSERTED_COFFEE_ROWS.xml +++ b/test/jdk/javax/sql/xml/INSERTED_COFFEE_ROWS.xml @@ -2,9 +2,9 @@ - + 1008 - + true 1000 0 @@ -20,7 +20,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j ResultSet.TYPE_SCROLL_INSENSITIVE false COFFEES - + com.sun.rowset.providers.RIOptimisticProvider Oracle Corporation @@ -40,7 +40,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j false false 0 - + COF_NAME 0 @@ -48,7 +48,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j 4 - + 2 @@ -59,7 +59,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j false false 0 - + SUP_ID 0 @@ -67,7 +67,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j 12 - + 3 @@ -78,7 +78,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j false false 0 - + PRICE 0 @@ -86,7 +86,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j 4 - + 4 @@ -97,7 +97,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j false false 0 - + SALES 10 @@ -105,7 +105,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j 2 - + 5 @@ -116,7 +116,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j false false 0 - + TOTAL 0 @@ -124,7 +124,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j 4 - + 6 @@ -135,15 +135,15 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j false false 0 - - + + 0 0 4 - + diff --git a/test/jdk/javax/sql/xml/MODFIED_DELETED_COFFEE_ROWS.xml b/test/jdk/javax/sql/xml/MODFIED_DELETED_COFFEE_ROWS.xml index 2c71a5ffd3ad..d6256bd7cce0 100644 --- a/test/jdk/javax/sql/xml/MODFIED_DELETED_COFFEE_ROWS.xml +++ b/test/jdk/javax/sql/xml/MODFIED_DELETED_COFFEE_ROWS.xml @@ -2,9 +2,9 @@ - + 1008 - + true 1000 0 @@ -20,7 +20,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j ResultSet.TYPE_SCROLL_INSENSITIVE false COFFEES - + com.sun.rowset.providers.RIOptimisticProvider Oracle Corporation @@ -40,7 +40,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j false false 0 - + COF_NAME 0 @@ -48,7 +48,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j 4 - + 2 @@ -59,7 +59,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j false false 0 - + SUP_ID 0 @@ -67,7 +67,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j 12 - + 3 @@ -78,7 +78,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j false false 0 - + PRICE 0 @@ -86,7 +86,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j 4 - + 4 @@ -97,7 +97,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j false false 0 - + SALES 10 @@ -105,7 +105,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j 2 - + 5 @@ -116,7 +116,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j false false 0 - + TOTAL 0 @@ -124,7 +124,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j 4 - + 6 @@ -135,15 +135,15 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j false false 0 - - + + 0 0 4 - + @@ -156,12 +156,12 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j 0 - 2 - French_Roast - 49 - 8.99 - 0 - 0 + 2 + French_Roast + 49 + 8.99 + 0 + 0 3 @@ -172,12 +172,12 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j 0 - 4 - Colombian_Decaf - 101 - 8.99 - 0 - 0 + 4 + Colombian_Decaf + 101 + 8.99 + 0 + 0 5 diff --git a/test/jdk/javax/sql/xml/UPDATED_COFFEE_ROWS.xml b/test/jdk/javax/sql/xml/UPDATED_COFFEE_ROWS.xml index 449ba120b175..8d497e87d817 100644 --- a/test/jdk/javax/sql/xml/UPDATED_COFFEE_ROWS.xml +++ b/test/jdk/javax/sql/xml/UPDATED_COFFEE_ROWS.xml @@ -4,7 +4,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j SELECT * FROM COFFEES 1008 - + true 1000 0 @@ -40,7 +40,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j false false 0 - + COF_NAME 0 @@ -48,7 +48,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j 4 - + 2 @@ -59,7 +59,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j false false 0 - + SUP_ID 0 @@ -67,7 +67,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j 12 - + 3 @@ -78,7 +78,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j false false 0 - + PRICE 0 @@ -86,7 +86,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j 4 - + 4 @@ -97,7 +97,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j false false 0 - + SALES 10 @@ -105,7 +105,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j 2 - + 5 @@ -116,7 +116,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j false false 0 - + TOTAL 0 @@ -124,7 +124,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j 4 - + 6 @@ -135,15 +135,15 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j false false 0 - - + + 0 0 4 - + @@ -163,16 +163,20 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j 0 0 - + 3 + 3 Espresso + Espresso 150 + 150 9.99 + 9.99 0 - 21 + 21 0 - 69 - + 69 + 4 Colombian_Decaf diff --git a/test/jdk/javax/sql/xml/UPDATED_INSERTED_COFFEE_ROWS.xml b/test/jdk/javax/sql/xml/UPDATED_INSERTED_COFFEE_ROWS.xml index 0cfc3c9fc732..9be10b56c6b9 100644 --- a/test/jdk/javax/sql/xml/UPDATED_INSERTED_COFFEE_ROWS.xml +++ b/test/jdk/javax/sql/xml/UPDATED_INSERTED_COFFEE_ROWS.xml @@ -2,9 +2,9 @@ - + 1008 - + true 1000 0 @@ -20,7 +20,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j ResultSet.TYPE_SCROLL_INSENSITIVE false COFFEES - + com.sun.rowset.providers.RIOptimisticProvider Oracle Corporation @@ -40,7 +40,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j false false 0 - + COF_NAME 0 @@ -48,7 +48,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j 4 - + 2 @@ -59,7 +59,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j false false 0 - + SUP_ID 0 @@ -67,7 +67,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j 12 - + 3 @@ -78,7 +78,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j false false 0 - + PRICE 0 @@ -86,7 +86,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j 4 - + 4 @@ -97,7 +97,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j false false 0 - + SALES 10 @@ -105,7 +105,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j 2 - + 5 @@ -116,7 +116,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j false false 0 - + TOTAL 0 @@ -124,7 +124,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j 4 - + 6 @@ -135,15 +135,15 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j false false 0 - - + + 0 0 4 - + @@ -192,10 +192,10 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j Mocha 49 9.99 - 12.99 + 12.99 20 35 - 125 + 125 From 3c9a28d5a035181779e90659d49561a27537eae4 Mon Sep 17 00:00:00 2001 From: Artur Barashev Date: Fri, 29 May 2026 14:29:07 +0000 Subject: [PATCH 013/169] 8384021: Enhance Certificate Constraint Checking Reviewed-by: jnibedita, ahgross, mullan, weijun, rhalade --- .../sun/security/util/HostnameChecker.java | 20 ++- .../sun/security/x509/IPAddressName.java | 147 ++++++++++++------ .../HostnameChecker/TestHostnameChecker.java | 8 +- .../x509/IPAddressName/ConstrainsTest.java | 98 ++++++++---- 4 files changed, 183 insertions(+), 90 deletions(-) diff --git a/src/java.base/share/classes/sun/security/util/HostnameChecker.java b/src/java.base/share/classes/sun/security/util/HostnameChecker.java index b5a6e48e5707..a9fafd43acf7 100644 --- a/src/java.base/share/classes/sun/security/util/HostnameChecker.java +++ b/src/java.base/share/classes/sun/security/util/HostnameChecker.java @@ -125,7 +125,7 @@ private static boolean isIpAddress(String name) { /** * Check if the certificate allows use of the given IP address. - * + *

    * From RFC2818: * In some cases, the URI is specified as an IP address rather than a * hostname. In this case, the iPAddress subjectAltName must be present @@ -140,26 +140,30 @@ private static void matchIP(String expectedIP, X509Certificate cert) } for (List next : subjAltNames) { // For IP address, it needs to be exact match - if (((Integer)next.get(0)).intValue() == ALTNAME_IP) { + if ((int) next.get(0) == ALTNAME_IP) { String ipAddress = (String)next.get(1); + if (expectedIP.equalsIgnoreCase(ipAddress)) { return; } else { - // compare InetAddress objects in order to ensure - // equality between a long IPv6 address and its - // abbreviated form. + // Compare InetAddress objects if literal check failed. + // InetAddress.getByName() normalizes IP address, thus + // the following is insured: + // - Equality between a long IPv6 address and its + // abbreviated form. + // - Equality between IPv4-Mapped IPv6 address and its + // regular IPv4 form. try { if (InetAddress.getByName(expectedIP).equals( InetAddress.getByName(ipAddress))) { return; } - } catch (UnknownHostException e) {} + } catch (UnknownHostException _) {} } } } throw new CertificateException("No subject alternative " + - "names matching " + "IP address " + - expectedIP + " found"); + "names matching IP address " + expectedIP + " found"); } /** diff --git a/src/java.base/share/classes/sun/security/x509/IPAddressName.java b/src/java.base/share/classes/sun/security/x509/IPAddressName.java index f372d349a573..7a26fe3c4a23 100644 --- a/src/java.base/share/classes/sun/security/x509/IPAddressName.java +++ b/src/java.base/share/classes/sun/security/x509/IPAddressName.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -29,6 +29,7 @@ import java.lang.Integer; import java.net.InetAddress; import java.util.Arrays; +import sun.net.util.IPAddressUtil; import sun.security.util.HexDumpEncoder; import sun.security.util.BitArray; import sun.security.util.DerOutputStream; @@ -69,11 +70,12 @@ */ public class IPAddressName implements GeneralNameInterface { private byte[] address; - private final boolean isIPv4; private String name; /** * Create the IPAddressName object from the passed encoded Der value. + *

    + * IPv4-Mapped IPv6 addresses/subnets normalized to IPv4. * * @param derValue the encoded DER IPAddressName. * @exception IOException on error. @@ -84,24 +86,44 @@ public IPAddressName(DerValue derValue) throws IOException { /** * Create the IPAddressName object with the specified octets. + *

    + * A valid address must consist of 4 bytes of address and + * optional 4 bytes of mask, or 16 bytes of address and + * optional 16 bytes of mask. + *

    + * IPv4-Mapped IPv6 addresses/subnets normalized to IPv4. * * @param address the IP address * @throws IOException if address is not a valid IPv4 or IPv6 address */ public IPAddressName(byte[] address) throws IOException { - /* - * A valid address must consist of 4 bytes of address and - * optional 4 bytes of 4 bytes of mask, or 16 bytes of address - * and optional 16 bytes of mask. - */ - if (address.length == 4 || address.length == 8) { - isIPv4 = true; - } else if (address.length == 16 || address.length == 32) { - isIPv4 = false; - } else { - throw new IOException("Invalid IPAddressName"); - } - this.address = address; + // Treat IPv4-Mapped IPv6 address as a regular IPv4. + this.address = switch (address.length) { + case 4, 8 -> address; // IPv4 address/subnet + case 16 -> { + // Single IPv6 address. + byte[] newAddr = IPAddressUtil.convertFromIPv4MappedAddress( + address); + yield newAddr != null ? newAddr : address; + } + case 32 -> { + // IPv6 subnet. + byte[] newAddr = IPAddressUtil.convertFromIPv4MappedAddress( + Arrays.copyOfRange(address, 0, 16)); + + if (newAddr == null) { + yield address; + } + + byte[] ret = new byte[8]; + System.arraycopy(newAddr, 0, ret, 0, 4); + System.arraycopy(ipv4MaskFromIpv6Mask( + Arrays.copyOfRange(address, 16, 32)), + 0, ret, 4, 4); + yield ret; + } + default -> throw new IOException("Invalid IPAddressName"); + }; } /** @@ -118,6 +140,8 @@ public IPAddressName(byte[] address) throws IOException { * of the address. If /n is used, n is a decimal number indicating how many * of the leftmost contiguous bits of the address comprise the prefix for * this subnet. Internally, a mask value is created using the prefix length. + *

    + * IPv4-Mapped IPv6 addresses/subnets normalized to IPv4. * * @param name String form of IPAddressName * @throws IOException if name can not be converted to a valid IPv4 or IPv6 @@ -137,11 +161,9 @@ public IPAddressName(String name) throws IOException { // Parse name into byte-value address components and optional // prefix parseIPv6(name); - isIPv4 = false; } else if (name.indexOf('.') >= 0) { //name is IPv4: uses dots as value separators parseIPv4(name); - isIPv4 = true; } else { throw new IOException("Invalid IPAddress: " + name); } @@ -150,7 +172,7 @@ public IPAddressName(String name) throws IOException { /** * Parse an IPv4 address. * - * @param name IPv4 address with optional mask values + * @param name IPv4 address with an optional mask in dotted IP notation. * @throws IOException on error */ private void parseIPv4(String name) throws IOException { @@ -178,41 +200,76 @@ private void parseIPv4(String name) throws IOException { /** * Parse an IPv6 address. * - * @param name String IPv6 address with optional / - * If / is present, address[] array will - * be 32 bytes long, otherwise 16. + * @param name IPv6 address with an optional mask in CIDR notation. * @throws IOException on error */ - private static final int MASKSIZE = 16; private void parseIPv6(String name) throws IOException { - int slashNdx = name.indexOf('/'); + if (slashNdx == -1) { address = InetAddress.getByName(name).getAddress(); } else { - address = new byte[32]; byte[] base = InetAddress.getByName (name.substring(0, slashNdx)).getAddress(); - System.arraycopy(base, 0, address, 0, 16); - - // append a mask corresponding to the num of prefix bits specified - int prefixLen = Integer.parseInt(name.substring(slashNdx+1)); - if (prefixLen < 0 || prefixLen > 128) { - throw new IOException("IPv6Address prefix length (" + - prefixLen + ") in out of valid range [0,128]"); + byte[] mask = ipv6MaskFromString(name.substring(slashNdx + 1)); + + if (base.length == 16) { + // IPv6 address. + address = new byte[32]; + System.arraycopy(base, 0, address, 0, 16); + System.arraycopy(mask, 0, address, 16, 16); + } else if (base.length == 4) { + // IPv4-Mapped IPv6 address. + address = new byte[8]; + System.arraycopy(base, 0, address, 0, 4); + System.arraycopy(ipv4MaskFromIpv6Mask(mask), 0, address, 4, 4); + } else { + throw new IOException("Invalid IPAddress subnet: " + name); } + } + } + + private static byte[] ipv6MaskFromString(String ipv6Mask) + throws IOException { + int prefixLen; + + try { + prefixLen = Integer.parseInt(ipv6Mask); + } catch (NumberFormatException e) { + throw new IOException(e); + } + + if (prefixLen < 0 || prefixLen > 128) { + throw new IOException("IPv6Address prefix length (" + + prefixLen + ") is out of valid range [0,128]"); + } - // create new bit array initialized to zeros - BitArray bitArray = new BitArray(MASKSIZE * 8); + // create new bit array initialized to zeros + BitArray bitArray = new BitArray(128); - // set all most significant bits up to prefix length - for (int i = 0; i < prefixLen; i++) - bitArray.set(i, true); - byte[] maskArray = bitArray.toByteArray(); + // set all most significant bits up to prefix length + for (int i = 0; i < prefixLen; i++) { + bitArray.set(i, true); + } + + return bitArray.toByteArray(); + } + + private static byte[] ipv4MaskFromIpv6Mask(byte[] ipv6Mask) + throws IOException { + if (ipv6Mask == null || ipv6Mask.length != 16) { + throw new IOException("IPv6 mask must be 16 bytes"); + } - // copy mask bytes into mask portion of address - System.arraycopy(maskArray, 0, address, MASKSIZE, MASKSIZE); + // For a mapped IPv4 subnet, the first 96 bits must be set. + for (int i = 0; i < 12; i++) { + if ((ipv6Mask[i] & 0xFF) != 0xFF) { + throw new IOException("Not an IPv4-mapped IPv6 mask: first 96" + + " bits are not set"); + } } + + return Arrays.copyOfRange(ipv6Mask, 12, 16); } /** @@ -256,8 +313,8 @@ public String getName() throws IOException { if (name != null) return name; - if (isIPv4) { - //IPv4 address or subdomain + if (address.length == 4 || address.length == 8) { + //IPv4 address or subnet byte[] host = new byte[4]; System.arraycopy(address, 0, host, 0, 4); name = InetAddress.getByAddress(host).getHostAddress(); @@ -268,14 +325,14 @@ public String getName() throws IOException { InetAddress.getByAddress(mask).getHostAddress(); } } else { - //IPv6 address or subdomain + //IPv6 address or subnet byte[] host = new byte[16]; System.arraycopy(address, 0, host, 0, 16); name = InetAddress.getByAddress(host).getHostAddress(); if (address.length == 32) { - // IPv6 subdomain: display prefix length + // IPv6 subnet: display prefix length - // copy subdomain into new array and convert to BitArray + // copy subnet into new array and convert to BitArray byte[] maskBytes = new byte[16]; System.arraycopy(address, 16, maskBytes, 0, 16); BitArray ba = new BitArray(16*8, maskBytes); @@ -289,7 +346,7 @@ public String getName() throws IOException { // Verify remaining bits 0 for (; i < 16*8; i++) { if (ba.get(i)) { - throw new IOException("Invalid IPv6 subdomain - set " + + throw new IOException("Invalid IPv6 subnet - set " + "bit " + i + " not contiguous"); } } diff --git a/test/jdk/sun/security/util/HostnameChecker/TestHostnameChecker.java b/test/jdk/sun/security/util/HostnameChecker/TestHostnameChecker.java index 63eb4057f8e6..64f7c5113f67 100644 --- a/test/jdk/sun/security/util/HostnameChecker/TestHostnameChecker.java +++ b/test/jdk/sun/security/util/HostnameChecker/TestHostnameChecker.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2002, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -196,6 +196,7 @@ public static void main(String[] args) throws Exception { check(checker, "altfoo1.com", cert3, true); check(checker, "altfoo2.com", cert3, true); check(checker, "5.6.7.8", cert3, true); + check(checker, "::ffff:5.6.7.8", cert3, true); check(checker, "foo.bar.com", cert4, true); check(checker, "altfoo.bar.com", cert4, false); check(checker, "2001:db8:3c4d:15::1a2f:1a2b", cert5, true); @@ -221,6 +222,7 @@ public static void main(String[] args) throws Exception { check(checker, "altfoo1.com", cert3, true); check(checker, "altfoo2.com", cert3, true); check(checker, "5.6.7.8", cert3, true); + check(checker, "::ffff:5.6.7.8", cert3, true); check(checker, "foo.bar.com", cert4, true); check(checker, "altfoo.bar.com", cert4, false); } @@ -239,11 +241,11 @@ private static void check(HostnameChecker checker, String name, throws Exception { try { checker.match(name, cert); - if (expectedResult == false) { + if (!expectedResult) { throw new Exception("Passed invalid test: " + name); } } catch (CertificateException e) { - if (expectedResult == true) { + if (expectedResult) { throw new Exception("Failed valid test: " + name, e); } } diff --git a/test/jdk/sun/security/x509/IPAddressName/ConstrainsTest.java b/test/jdk/sun/security/x509/IPAddressName/ConstrainsTest.java index 63772eef3351..6ca664fb1e6b 100644 --- a/test/jdk/sun/security/x509/IPAddressName/ConstrainsTest.java +++ b/test/jdk/sun/security/x509/IPAddressName/ConstrainsTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -21,58 +21,88 @@ * questions. */ -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; -import sun.security.x509.GeneralNameInterface; -import sun.security.x509.IPAddressName; +import static jdk.test.lib.Asserts.assertEquals; +import static sun.security.x509.GeneralNameInterface.NAME_MATCH; +import static sun.security.x509.GeneralNameInterface.NAME_NARROWS; +import static sun.security.x509.GeneralNameInterface.NAME_SAME_TYPE; +import static sun.security.x509.GeneralNameInterface.NAME_WIDENS; import java.io.IOException; - -import static org.testng.Assert.assertEquals; +import java.util.List; +import sun.security.x509.IPAddressName; /* * @test * @summary Verify IPAddressName.constrains * @bug 8267617 + * @library /test/lib * @modules java.base/sun.security.x509 - * @run testng ConstrainsTest */ + public class ConstrainsTest { IPAddressName ipv4Addr = new IPAddressName("127.0.0.1"); IPAddressName ipv4Mask = new IPAddressName("127.0.0.0/255.0.0.0"); - IPAddressName ipv6Addr = new IPAddressName("::1"); - IPAddressName ipv6Mask = new IPAddressName("::/0"); + IPAddressName ipv6Addr = new IPAddressName("2001:db8::1"); + IPAddressName ipv6Mask = new IPAddressName("2001:db8::/124"); + + IPAddressName ipv4Addr2 = new IPAddressName("128.0.0.1"); + IPAddressName ipv4NarrowMask = new IPAddressName("127.0.0.0/255.255.0.0"); + IPAddressName ipv4DisjointMask = new IPAddressName("128.0.0.0/255.0.0.0"); + + IPAddressName ipv6Addr2 = new IPAddressName("2001:db8:1::1"); + IPAddressName ipv6NarrowMask = new IPAddressName("2001:db8::/125"); + IPAddressName ipv6DisjointMask = new IPAddressName("2001:db8:1::/124"); public ConstrainsTest() throws IOException { } - @DataProvider(name = "names") - public Object[][] names() { - Object[][] data = { - {ipv4Addr, ipv4Addr, GeneralNameInterface.NAME_MATCH}, - {ipv4Addr, ipv4Mask, GeneralNameInterface.NAME_WIDENS}, - {ipv4Addr, ipv6Addr, GeneralNameInterface.NAME_SAME_TYPE}, - {ipv4Addr, ipv6Mask, GeneralNameInterface.NAME_SAME_TYPE}, - {ipv4Mask, ipv4Addr, GeneralNameInterface.NAME_NARROWS}, - {ipv4Mask, ipv4Mask, GeneralNameInterface.NAME_MATCH}, - {ipv4Mask, ipv6Addr, GeneralNameInterface.NAME_SAME_TYPE}, - {ipv4Mask, ipv6Mask, GeneralNameInterface.NAME_SAME_TYPE}, - {ipv6Addr, ipv4Addr, GeneralNameInterface.NAME_SAME_TYPE}, - {ipv6Addr, ipv4Mask, GeneralNameInterface.NAME_SAME_TYPE}, - {ipv6Addr, ipv6Addr, GeneralNameInterface.NAME_MATCH}, - {ipv6Addr, ipv6Mask, GeneralNameInterface.NAME_WIDENS}, - {ipv6Mask, ipv4Addr, GeneralNameInterface.NAME_SAME_TYPE}, - {ipv6Mask, ipv4Mask, GeneralNameInterface.NAME_SAME_TYPE}, - {ipv6Mask, ipv6Addr, GeneralNameInterface.NAME_NARROWS}, - {ipv6Mask, ipv6Mask, GeneralNameInterface.NAME_MATCH}, - }; - return data; + private Object[][] names() { + return new Object[][]{ + // Basic matching + {ipv4Addr, ipv4Addr, NAME_MATCH}, + {ipv4Addr, ipv4Mask, NAME_WIDENS}, + {ipv4Addr, ipv6Addr, NAME_SAME_TYPE}, + {ipv4Addr, ipv6Mask, NAME_SAME_TYPE}, + {ipv4Mask, ipv4Addr, NAME_NARROWS}, + {ipv4Mask, ipv4Mask, NAME_MATCH}, + {ipv4Mask, ipv6Addr, NAME_SAME_TYPE}, + {ipv4Mask, ipv6Mask, NAME_SAME_TYPE}, + {ipv6Addr, ipv4Addr, NAME_SAME_TYPE}, + {ipv6Addr, ipv4Mask, NAME_SAME_TYPE}, + {ipv6Addr, ipv6Addr, NAME_MATCH}, + {ipv6Addr, ipv6Mask, NAME_WIDENS}, + {ipv6Mask, ipv4Addr, NAME_SAME_TYPE}, + {ipv6Mask, ipv4Mask, NAME_SAME_TYPE}, + {ipv6Mask, ipv6Addr, NAME_NARROWS}, + {ipv6Mask, ipv6Mask, NAME_MATCH}, + + // Same-family host mismatches + {ipv4Addr, ipv4Addr2, NAME_SAME_TYPE}, + {ipv6Addr, ipv6Addr2, NAME_SAME_TYPE}, + + // Host to subnet match, nothing matches + {ipv4Addr2, ipv4Mask, NAME_SAME_TYPE}, + {ipv6Addr2, ipv6Mask, NAME_SAME_TYPE}, + + // Subnet to subnet match: narrow/widen/disjoint + {ipv4Mask, ipv4NarrowMask, NAME_NARROWS}, + {ipv4NarrowMask, ipv4Mask, NAME_WIDENS}, + {ipv4Mask, ipv4DisjointMask, NAME_SAME_TYPE}, + + {ipv6Mask, ipv6NarrowMask, NAME_NARROWS}, + {ipv6NarrowMask, ipv6Mask, NAME_WIDENS}, + {ipv6Mask, ipv6DisjointMask, NAME_SAME_TYPE}, + }; } - @Test(dataProvider = "names") - public void testNameContains(IPAddressName addr1, IPAddressName addr2, int result) throws IOException { - assertEquals(addr1.constrains(addr2), result); + public static void main(String[] args) throws Exception { + List.of(new ConstrainsTest().names()).forEach(v -> testNameContains( + (IPAddressName) v[0], (IPAddressName) v[1], (int) v[2])); } + private static void testNameContains(IPAddressName addr1, + IPAddressName addr2, int result) { + assertEquals(addr1.constrains(addr2), result); + } } From 4820d3f389d1563673c777fa581db60990446c61 Mon Sep 17 00:00:00 2001 From: Emanuel Peter Date: Mon, 1 Jun 2026 13:17:15 +0000 Subject: [PATCH 014/169] 8383689: Enhance Vector Processing Reviewed-by: mschoene, vlivanov, rhalade, kvn, qamai --- src/hotspot/cpu/aarch64/matcher_aarch64.hpp | 5 - src/hotspot/cpu/arm/matcher_arm.hpp | 5 - src/hotspot/cpu/ppc/matcher_ppc.hpp | 5 - src/hotspot/cpu/riscv/matcher_riscv.hpp | 5 - src/hotspot/cpu/s390/matcher_s390.hpp | 5 - src/hotspot/cpu/x86/c2_MacroAssembler_x86.cpp | 106 -------------- src/hotspot/cpu/x86/c2_MacroAssembler_x86.hpp | 9 -- src/hotspot/cpu/x86/matcher_x86.hpp | 13 -- src/hotspot/cpu/x86/x86.ad | 129 +----------------- src/hotspot/share/opto/vectorIntrinsics.cpp | 68 +++++---- src/hotspot/share/opto/vectornode.hpp | 35 +++-- 11 files changed, 73 insertions(+), 312 deletions(-) diff --git a/src/hotspot/cpu/aarch64/matcher_aarch64.hpp b/src/hotspot/cpu/aarch64/matcher_aarch64.hpp index 42bb61f6889a..ede5a4814950 100644 --- a/src/hotspot/cpu/aarch64/matcher_aarch64.hpp +++ b/src/hotspot/cpu/aarch64/matcher_aarch64.hpp @@ -130,11 +130,6 @@ return true; } - // Does target support predicated operation emulation. - static bool supports_vector_predicate_op_emulation(int vopc, int vlen, BasicType bt) { - return false; - } - // Does the CPU supports vector variable rotate instructions? static constexpr bool supports_vector_variable_rotates(void) { return false; diff --git a/src/hotspot/cpu/arm/matcher_arm.hpp b/src/hotspot/cpu/arm/matcher_arm.hpp index a3139d91f302..7980838c5cca 100644 --- a/src/hotspot/cpu/arm/matcher_arm.hpp +++ b/src/hotspot/cpu/arm/matcher_arm.hpp @@ -122,11 +122,6 @@ return VM_Version::has_simd(); } - // Does target support predicated operation emulation. - static bool supports_vector_predicate_op_emulation(int vopc, int vlen, BasicType bt) { - return false; - } - // Does the CPU supports vector variable rotate instructions? static constexpr bool supports_vector_variable_rotates(void) { return false; // not supported diff --git a/src/hotspot/cpu/ppc/matcher_ppc.hpp b/src/hotspot/cpu/ppc/matcher_ppc.hpp index 209d317a0335..356682bb760b 100644 --- a/src/hotspot/cpu/ppc/matcher_ppc.hpp +++ b/src/hotspot/cpu/ppc/matcher_ppc.hpp @@ -130,11 +130,6 @@ return false; } - // Does target support predicated operation emulation. - static bool supports_vector_predicate_op_emulation(int vopc, int vlen, BasicType bt) { - return false; - } - // Does the CPU supports vector variable rotate instructions? static constexpr bool supports_vector_variable_rotates(void) { return false; diff --git a/src/hotspot/cpu/riscv/matcher_riscv.hpp b/src/hotspot/cpu/riscv/matcher_riscv.hpp index bb3d2d015f2d..570bfbf711ad 100644 --- a/src/hotspot/cpu/riscv/matcher_riscv.hpp +++ b/src/hotspot/cpu/riscv/matcher_riscv.hpp @@ -129,11 +129,6 @@ return UseRVV; } - // Does target support predicated operation emulation. - static bool supports_vector_predicate_op_emulation(int vopc, int vlen, BasicType bt) { - return false; - } - // Does the CPU supports vector variable rotate instructions? static bool supports_vector_variable_rotates(void) { return UseZvbb; diff --git a/src/hotspot/cpu/s390/matcher_s390.hpp b/src/hotspot/cpu/s390/matcher_s390.hpp index 188d46438aca..7e006bdc6c7b 100644 --- a/src/hotspot/cpu/s390/matcher_s390.hpp +++ b/src/hotspot/cpu/s390/matcher_s390.hpp @@ -125,11 +125,6 @@ return false; } - // Does target support predicated operation emulation. - static bool supports_vector_predicate_op_emulation(int vopc, int vlen, BasicType bt) { - return false; - } - // Does the CPU supports vector variable rotate instructions? static constexpr bool supports_vector_variable_rotates(void) { return false; diff --git a/src/hotspot/cpu/x86/c2_MacroAssembler_x86.cpp b/src/hotspot/cpu/x86/c2_MacroAssembler_x86.cpp index dcce0aa9c00b..b4ca8648b326 100644 --- a/src/hotspot/cpu/x86/c2_MacroAssembler_x86.cpp +++ b/src/hotspot/cpu/x86/c2_MacroAssembler_x86.cpp @@ -1430,112 +1430,6 @@ void C2_MacroAssembler::vinsert(BasicType typ, XMMRegister dst, XMMRegister src, } } -void C2_MacroAssembler::vgather8b_masked(BasicType elem_bt, XMMRegister dst, - Register base, Register idx_base, - Register mask, Register mask_idx, - Register rtmp, int vlen_enc) { - vpxor(dst, dst, dst, vlen_enc); - if (elem_bt == T_SHORT) { - for (int i = 0; i < 4; i++) { - // dst[i] = mask[i] ? src[idx_base[i]] : 0 - Label skip_load; - btq(mask, mask_idx); - jccb(Assembler::carryClear, skip_load); - movl(rtmp, Address(idx_base, i * 4)); - pinsrw(dst, Address(base, rtmp, Address::times_2), i); - bind(skip_load); - incq(mask_idx); - } - } else { - assert(elem_bt == T_BYTE, "only handles short and byte"); - for (int i = 0; i < 8; i++) { - // dst[i] = mask[i] ? src[idx_base[i]] : 0 - Label skip_load; - btq(mask, mask_idx); - jccb(Assembler::carryClear, skip_load); - movl(rtmp, Address(idx_base, i * 4)); - pinsrb(dst, Address(base, rtmp), i); - bind(skip_load); - incq(mask_idx); - } - } -} - -void C2_MacroAssembler::vgather8b(BasicType elem_bt, XMMRegister dst, - Register base, Register idx_base, - Register rtmp, int vlen_enc) { - vpxor(dst, dst, dst, vlen_enc); - if (elem_bt == T_SHORT) { - for (int i = 0; i < 4; i++) { - // dst[i] = src[idx_base[i]] - movl(rtmp, Address(idx_base, i * 4)); - pinsrw(dst, Address(base, rtmp, Address::times_2), i); - } - } else { - assert(elem_bt == T_BYTE, "only handles short and byte"); - for (int i = 0; i < 8; i++) { - // dst[i] = src[idx_base[i]] - movl(rtmp, Address(idx_base, i * 4)); - pinsrb(dst, Address(base, rtmp), i); - } - } -} - -/* - * Gather using hybrid algorithm, first partially unroll scalar loop - * to accumulate values from gather indices into a quad-word(64bit) slice. - * A slice may hold 8 bytes or 4 short values. This is followed by a vector - * permutation to place the slice into appropriate vector lane - * locations in destination vector. Following pseudo code describes the - * algorithm in detail: - * - * DST_VEC = ZERO_VEC - * PERM_INDEX = {0, 1, 2, 3, 4, 5, 6, 7, 8..} - * TWO_VEC = {2, 2, 2, 2, 2, 2, 2, 2, 2..} - * FOREACH_ITER: - * TMP_VEC_64 = PICK_SUB_WORDS_FROM_GATHER_INDICES - * TEMP_PERM_VEC = PERMUTE TMP_VEC_64 PERM_INDEX - * DST_VEC = DST_VEC OR TEMP_PERM_VEC - * PERM_INDEX = PERM_INDEX - TWO_VEC - * - * With each iteration, doubleword permute indices (0,1) corresponding - * to gathered quadword gets right shifted by two lane positions. - * - */ -void C2_MacroAssembler::vgather_subword(BasicType elem_ty, XMMRegister dst, - Register base, Register idx_base, - Register mask, XMMRegister xtmp1, - XMMRegister xtmp2, XMMRegister temp_dst, - Register rtmp, Register mask_idx, - Register length, int vector_len, int vlen_enc) { - Label GATHER8_LOOP; - assert(is_signed_subword_type(elem_ty), "only handles short and byte"); - movl(length, vector_len); - vpxor(xtmp1, xtmp1, xtmp1, vlen_enc); // xtmp1 = {0, ...} - vpxor(dst, dst, dst, vlen_enc); // dst = {0, ...} - vallones(xtmp2, vlen_enc); - vpsubd(xtmp2, xtmp1, xtmp2, vlen_enc); - vpslld(xtmp2, xtmp2, 1, vlen_enc); // xtmp2 = {2, 2, ...} - load_iota_indices(xtmp1, vector_len * type2aelembytes(elem_ty), T_INT); // xtmp1 = {0, 1, 2, ...} - - bind(GATHER8_LOOP); - // TMP_VEC_64(temp_dst) = PICK_SUB_WORDS_FROM_GATHER_INDICES - if (mask == noreg) { - vgather8b(elem_ty, temp_dst, base, idx_base, rtmp, vlen_enc); - } else { - vgather8b_masked(elem_ty, temp_dst, base, idx_base, mask, mask_idx, rtmp, vlen_enc); - } - // TEMP_PERM_VEC(temp_dst) = PERMUTE TMP_VEC_64(temp_dst) PERM_INDEX(xtmp1) - vpermd(temp_dst, xtmp1, temp_dst, vlen_enc == Assembler::AVX_512bit ? vlen_enc : Assembler::AVX_256bit); - // PERM_INDEX(xtmp1) = PERM_INDEX(xtmp1) - TWO_VEC(xtmp2) - vpsubd(xtmp1, xtmp1, xtmp2, vlen_enc); - // DST_VEC = DST_VEC OR TEMP_PERM_VEC - vpor(dst, dst, temp_dst, vlen_enc); - addptr(idx_base, 32 >> (type2aelembytes(elem_ty) - 1)); - subl(length, 8 >> (type2aelembytes(elem_ty) - 1)); - jcc(Assembler::notEqual, GATHER8_LOOP); -} - void C2_MacroAssembler::vgather(BasicType typ, XMMRegister dst, Register base, XMMRegister idx, XMMRegister mask, int vector_len) { switch(typ) { case T_INT: diff --git a/src/hotspot/cpu/x86/c2_MacroAssembler_x86.hpp b/src/hotspot/cpu/x86/c2_MacroAssembler_x86.hpp index a6471cbd3fdb..761879cbc9ca 100644 --- a/src/hotspot/cpu/x86/c2_MacroAssembler_x86.hpp +++ b/src/hotspot/cpu/x86/c2_MacroAssembler_x86.hpp @@ -509,15 +509,6 @@ void efp16sh(int opcode, XMMRegister dst, XMMRegister src1, XMMRegister src2); - void vgather_subword(BasicType elem_ty, XMMRegister dst, Register base, Register idx_base, Register mask, - XMMRegister xtmp1, XMMRegister xtmp2, XMMRegister xtmp3, Register rtmp, - Register midx, Register length, int vector_len, int vlen_enc); - - void vgather8b_masked(BasicType elem_bt, XMMRegister dst, Register base, Register idx_base, - Register mask, Register midx, Register rtmp, int vlen_enc); - void vgather8b(BasicType elem_bt, XMMRegister dst, Register base, Register idx_base, - Register rtmp, int vlen_enc); - void vector_saturating_op(int opc, BasicType elem_bt, XMMRegister dst, XMMRegister src1, XMMRegister src2, bool is_unsigned, int vlen_enc); void vector_saturating_op(int opc, BasicType elem_bt, XMMRegister dst, XMMRegister src1, Address src2, bool is_unsigned, int vlen_enc); diff --git a/src/hotspot/cpu/x86/matcher_x86.hpp b/src/hotspot/cpu/x86/matcher_x86.hpp index eda8a05d974a..5b012768cb9d 100644 --- a/src/hotspot/cpu/x86/matcher_x86.hpp +++ b/src/hotspot/cpu/x86/matcher_x86.hpp @@ -115,16 +115,6 @@ return (UseAVX >= 2); } - // Does target support predicated operation emulation. - static bool supports_vector_predicate_op_emulation(int vopc, int vlen, BasicType bt) { - switch(vopc) { - case Op_LoadVectorGatherMasked: - return is_subword_type(bt) && VM_Version::supports_avx2(); - default: - return false; - } - } - // Does the CPU supports vector variable rotate instructions? static constexpr bool supports_vector_variable_rotates(void) { return true; @@ -185,9 +175,6 @@ return 7; case Op_MulVL: return VM_Version::supports_avx512vldq() ? 0 : 6; - case Op_LoadVectorGather: - case Op_LoadVectorGatherMasked: - return is_subword_type(ety) ? 50 : 0; case Op_VectorCastF2X: // fall through case Op_VectorCastD2X: return is_floating_point_type(ety) ? 0 : (is_subword_type(ety) ? 35 : 30); diff --git a/src/hotspot/cpu/x86/x86.ad b/src/hotspot/cpu/x86/x86.ad index 62c30a668d7f..fbd01ef59814 100644 --- a/src/hotspot/cpu/x86/x86.ad +++ b/src/hotspot/cpu/x86/x86.ad @@ -3507,16 +3507,6 @@ bool Matcher::match_rule_supported_vector(int opcode, int vlen, BasicType bt) { } break; case Op_LoadVectorGatherMasked: - if (!is_subword_type(bt) && size_in_bits < 512 && !VM_Version::supports_avx512vl()) { - return false; - } - if (is_subword_type(bt) && - ((size_in_bits > 256 && !VM_Version::supports_avx512bw()) || - (size_in_bits < 64) || - (bt == T_SHORT && !VM_Version::supports_bmi2()))) { - return false; - } - break; case Op_StoreVectorScatterMasked: case Op_StoreVectorScatter: if (is_subword_type(bt)) { @@ -3526,10 +3516,9 @@ bool Matcher::match_rule_supported_vector(int opcode, int vlen, BasicType bt) { } // fallthrough case Op_LoadVectorGather: - if (!is_subword_type(bt) && size_in_bits == 64) { + if (is_subword_type(bt)) { return false; - } - if (is_subword_type(bt) && size_in_bits < 64) { + } else if (size_in_bits == 64) { return false; } break; @@ -18379,7 +18368,7 @@ instruct storeV(memory mem, vec src) %{ // ---------------------------------------- Gather ------------------------------------ -// Gather BYTE, SHORT, INT, LONG, FLOAT, DOUBLE +// Gather INT, LONG, FLOAT, DOUBLE instruct gather(legVec dst, memory mem, legVec idx, rRegP tmp, legVec mask) %{ predicate(!VM_Version::supports_avx512vl() && !is_subword_type(Matcher::vector_element_basic_type(n)) && @@ -18436,118 +18425,6 @@ instruct evgather_masked(vec dst, memory mem, vec idx, kReg mask, kReg ktmp, rRe ins_pipe( pipe_slow ); %} -instruct vgather_subwordLE8B(vec dst, memory mem, rRegP idx_base, rRegP tmp, rRegI rtmp) %{ - predicate(is_subword_type(Matcher::vector_element_basic_type(n)) && Matcher::vector_length_in_bytes(n) <= 8); - match(Set dst (LoadVectorGather mem idx_base)); - effect(TEMP tmp, TEMP rtmp); - format %{ "vector_gatherLE8 $dst, $mem, $idx_base\t! using $tmp and $rtmp as TEMP" %} - ins_encode %{ - int vlen_enc = vector_length_encoding(this); - BasicType elem_bt = Matcher::vector_element_basic_type(this); - __ lea($tmp$$Register, $mem$$Address); - __ vgather8b(elem_bt, $dst$$XMMRegister, $tmp$$Register, $idx_base$$Register, $rtmp$$Register, vlen_enc); - %} - ins_pipe( pipe_slow ); -%} - -instruct vgather_subwordGT8B(vec dst, memory mem, rRegP idx_base, rRegP tmp, rRegP idx_base_temp, - vec xtmp1, vec xtmp2, vec xtmp3, rRegI rtmp, rRegI length, rFlagsReg cr) %{ - predicate(is_subword_type(Matcher::vector_element_basic_type(n)) && Matcher::vector_length_in_bytes(n) > 8); - match(Set dst (LoadVectorGather mem idx_base)); - effect(TEMP_DEF dst, TEMP tmp, TEMP idx_base_temp, TEMP xtmp1, TEMP xtmp2, TEMP xtmp3, TEMP rtmp, TEMP length, KILL cr); - format %{ "vector_gatherGT8 $dst, $mem, $idx_base\t! using $tmp, $idx_base_temp, $xtmp1, $xtmp2, $xtmp3, $rtmp and $length as TEMP" %} - ins_encode %{ - int vlen_enc = vector_length_encoding(this); - int vector_len = Matcher::vector_length(this); - BasicType elem_bt = Matcher::vector_element_basic_type(this); - __ lea($tmp$$Register, $mem$$Address); - __ movptr($idx_base_temp$$Register, $idx_base$$Register); - __ vgather_subword(elem_bt, $dst$$XMMRegister, $tmp$$Register, $idx_base_temp$$Register, noreg, $xtmp1$$XMMRegister, - $xtmp2$$XMMRegister, $xtmp3$$XMMRegister, $rtmp$$Register, noreg, $length$$Register, vector_len, vlen_enc); - %} - ins_pipe( pipe_slow ); -%} - -instruct vgather_masked_subwordLE8B_avx3(vec dst, memory mem, rRegP idx_base, kReg mask, rRegL mask_idx, rRegP tmp, rRegI rtmp, rRegL rtmp2, rFlagsReg cr) %{ - predicate(VM_Version::supports_avx512bw() && is_subword_type(Matcher::vector_element_basic_type(n)) && Matcher::vector_length_in_bytes(n) <= 8); - match(Set dst (LoadVectorGatherMasked mem (Binary idx_base mask))); - effect(TEMP mask_idx, TEMP tmp, TEMP rtmp, TEMP rtmp2, KILL cr); - format %{ "vector_masked_gatherLE8 $dst, $mem, $idx_base, $mask\t! using $mask_idx, $tmp, $rtmp and $rtmp2 as TEMP" %} - ins_encode %{ - int vlen_enc = vector_length_encoding(this); - BasicType elem_bt = Matcher::vector_element_basic_type(this); - __ xorq($mask_idx$$Register, $mask_idx$$Register); - __ lea($tmp$$Register, $mem$$Address); - __ kmovql($rtmp2$$Register, $mask$$KRegister); - __ vgather8b_masked(elem_bt, $dst$$XMMRegister, $tmp$$Register, $idx_base$$Register, $rtmp2$$Register, $mask_idx$$Register, $rtmp$$Register, vlen_enc); - %} - ins_pipe( pipe_slow ); -%} - -instruct vgather_masked_subwordGT8B_avx3(vec dst, memory mem, rRegP idx_base, kReg mask, rRegP tmp, rRegP idx_base_temp, - vec xtmp1, vec xtmp2, vec xtmp3, rRegI rtmp, rRegL rtmp2, rRegL mask_idx, rRegI length, rFlagsReg cr) %{ - predicate(VM_Version::supports_avx512bw() && is_subword_type(Matcher::vector_element_basic_type(n)) && Matcher::vector_length_in_bytes(n) > 8); - match(Set dst (LoadVectorGatherMasked mem (Binary idx_base mask))); - effect(TEMP_DEF dst, TEMP tmp, TEMP idx_base_temp, TEMP xtmp1, TEMP xtmp2, TEMP xtmp3, TEMP rtmp, TEMP rtmp2, TEMP mask_idx, TEMP length, KILL cr); - format %{ "vector_gatherGT8_masked $dst, $mem, $idx_base, $mask\t! using $tmp, $idx_base_temp, $xtmp1, $xtmp2, $xtmp3, $rtmp, $rtmp2, $mask_idx and $length as TEMP" %} - ins_encode %{ - int vlen_enc = vector_length_encoding(this); - int vector_len = Matcher::vector_length(this); - BasicType elem_bt = Matcher::vector_element_basic_type(this); - __ xorq($mask_idx$$Register, $mask_idx$$Register); - __ lea($tmp$$Register, $mem$$Address); - __ movptr($idx_base_temp$$Register, $idx_base$$Register); - __ kmovql($rtmp2$$Register, $mask$$KRegister); - __ vgather_subword(elem_bt, $dst$$XMMRegister, $tmp$$Register, $idx_base_temp$$Register, $rtmp2$$Register, $xtmp1$$XMMRegister, - $xtmp2$$XMMRegister, $xtmp3$$XMMRegister, $rtmp$$Register, $mask_idx$$Register, $length$$Register, vector_len, vlen_enc); - %} - ins_pipe( pipe_slow ); -%} - -instruct vgather_masked_subwordLE8B_avx2(vec dst, memory mem, rRegP idx_base, vec mask, rRegI mask_idx, rRegP tmp, rRegI rtmp, rRegI rtmp2, rFlagsReg cr) %{ - predicate(!VM_Version::supports_avx512vlbw() && is_subword_type(Matcher::vector_element_basic_type(n)) && Matcher::vector_length_in_bytes(n) <= 8); - match(Set dst (LoadVectorGatherMasked mem (Binary idx_base mask))); - effect(TEMP mask_idx, TEMP tmp, TEMP rtmp, TEMP rtmp2, KILL cr); - format %{ "vector_masked_gatherLE8 $dst, $mem, $idx_base, $mask\t! using $mask_idx, $tmp, $rtmp and $rtmp2 as TEMP" %} - ins_encode %{ - int vlen_enc = vector_length_encoding(this); - BasicType elem_bt = Matcher::vector_element_basic_type(this); - __ lea($tmp$$Register, $mem$$Address); - __ vpmovmskb($rtmp2$$Register, $mask$$XMMRegister, vlen_enc); - if (elem_bt == T_SHORT) { - __ movl($mask_idx$$Register, 0x55555555); - __ pextl($rtmp2$$Register, $rtmp2$$Register, $mask_idx$$Register); - } - __ xorl($mask_idx$$Register, $mask_idx$$Register); - __ vgather8b_masked(elem_bt, $dst$$XMMRegister, $tmp$$Register, $idx_base$$Register, $rtmp2$$Register, $mask_idx$$Register, $rtmp$$Register, vlen_enc); - %} - ins_pipe( pipe_slow ); -%} - -instruct vgather_masked_subwordGT8B_avx2(vec dst, memory mem, rRegP idx_base, vec mask, rRegP tmp, rRegP idx_base_temp, - vec xtmp1, vec xtmp2, vec xtmp3, rRegI rtmp, rRegI rtmp2, rRegI mask_idx, rRegI length, rFlagsReg cr) %{ - predicate(!VM_Version::supports_avx512vlbw() && is_subword_type(Matcher::vector_element_basic_type(n)) && Matcher::vector_length_in_bytes(n) > 8); - match(Set dst (LoadVectorGatherMasked mem (Binary idx_base mask))); - effect(TEMP_DEF dst, TEMP tmp, TEMP idx_base_temp, TEMP xtmp1, TEMP xtmp2, TEMP xtmp3, TEMP rtmp, TEMP rtmp2, TEMP mask_idx, TEMP length, KILL cr); - format %{ "vector_gatherGT8_masked $dst, $mem, $idx_base, $mask\t! using $tmp, $idx_base_temp, $xtmp1, $xtmp2, $xtmp3, $rtmp, $rtmp2, $mask_idx and $length as TEMP" %} - ins_encode %{ - int vlen_enc = vector_length_encoding(this); - int vector_len = Matcher::vector_length(this); - BasicType elem_bt = Matcher::vector_element_basic_type(this); - __ lea($tmp$$Register, $mem$$Address); - __ movptr($idx_base_temp$$Register, $idx_base$$Register); - __ vpmovmskb($rtmp2$$Register, $mask$$XMMRegister, vlen_enc); - if (elem_bt == T_SHORT) { - __ movl($mask_idx$$Register, 0x55555555); - __ pextl($rtmp2$$Register, $rtmp2$$Register, $mask_idx$$Register); - } - __ xorl($mask_idx$$Register, $mask_idx$$Register); - __ vgather_subword(elem_bt, $dst$$XMMRegister, $tmp$$Register, $idx_base_temp$$Register, $rtmp2$$Register, $xtmp1$$XMMRegister, - $xtmp2$$XMMRegister, $xtmp3$$XMMRegister, $rtmp$$Register, $mask_idx$$Register, $length$$Register, vector_len, vlen_enc); - %} - ins_pipe( pipe_slow ); -%} - // ====================Scatter======================================= // Scatter INT, LONG, FLOAT, DOUBLE diff --git a/src/hotspot/share/opto/vectorIntrinsics.cpp b/src/hotspot/share/opto/vectorIntrinsics.cpp index 0b5bfd5e8d2e..3427769f05cb 100644 --- a/src/hotspot/share/opto/vectorIntrinsics.cpp +++ b/src/hotspot/share/opto/vectorIntrinsics.cpp @@ -279,7 +279,6 @@ bool LibraryCallKit::arch_supports_vector(int sopc, int num_elem, BasicType type is_supported = Matcher::match_rule_supported_vector_masked(sopc, num_elem, type); } } - is_supported |= Matcher::supports_vector_predicate_op_emulation(sopc, num_elem, type); if (!is_supported) { non_product_log_if_needed("Rejected vector mask predicate using (%s,%s,%d) because architecture does not support it", @@ -1366,31 +1365,55 @@ bool LibraryCallKit::inline_vector_gather_scatter(bool is_scatter) { } // Check that the vector holding indices is supported by architecture - // For sub-word gathers expander receive index array. - if (!is_subword_type(elem_bt) && !arch_supports_vector(Op_LoadVector, idx_num_elem, T_INT, VecMaskNotUsed)) { + if (!arch_supports_vector(Op_LoadVector, idx_num_elem, T_INT, VecMaskNotUsed)) { log_if_needed(" ** not supported: arity=%d op=%s/loadindex vlen=%d etype=int is_masked_op=%d", is_scatter, is_scatter ? "scatter" : "gather", idx_num_elem, is_masked_op ? 1 : 0); return false; // not supported } + // Currently, subword is not supported. + // Note: we used to have an x64 subword gather implementation before, but it was buggy. + // JDK-8318650 had introduced the subword gather implementation: + // - We loaded from indexMap once to perform checkIndex + // - And then we loaded again from indexMap in the LoadVectorGather(Masked) + // backend implementation. But if a second thread mutated the indexMap, + // we would not have checked the index again, and possibly performed out + // of bounds accesses. + // - The reason for using indexMap directly was that subword gather requires + // an int-index per subword element, and so the indices vector would have + // to be larger than the output vector, or we would need multiple index + // vectors. Just loading from indexMap seemed like an elegant solution, + // but unfortunetely, it leads to TOCTOU bugs. + // + // JDK-8383689 removed subword gather again. + // + // In the future, we could consider re-implementing subword gather. We would + // have to ensure to only load the indices once, and perform checkIndex and + // indexing from that same index vector. We may also want to change the API, + // so that one can provide an index vector, rather than a indexMap array. + // Since vectors are immutable, the indexCheck and indexing would trivially + // be consuming the same indices. And the API user could compute arbitrary + // indices, and would not have to round-trip them via an indexMap array. + // However: so far, no hardware we are aware of can actually perform + // vectorized subword gather/scatter. So the implementation would actually + // need to be a scalar emulation. The JDK-8318650 performed scalar loads + // from the indexMap. A re-implementation would instead have to extract + // elements from the index vector. The performance impact has not yet been + // investigated. + if (is_subword_type(elem_bt)) { + log_if_needed(" ** not supported: subword type etype=%s", + type2name(elem_bt)); + return false; // not supported + } + Node* base = argument(6); Node* offset = ConvL2X(argument(7)); // Save state and restore on bailout SavedState old_state(this); - Node* addr = nullptr; - if (!is_subword_type(elem_bt)) { - addr = make_unsafe_address(base, offset, elem_bt, true); - } else { - assert(!is_scatter, "Only supports gather operation for subword types now"); - uint header = arrayOopDesc::base_offset_in_bytes(elem_bt); - assert(offset->is_Con() && offset->bottom_type()->is_long()->get_con() == header, - "offset must be the array base offset"); - Node* index = argument(15); - addr = array_element_address(base, index, elem_bt); - } + Node* addr = make_unsafe_address(base, offset, elem_bt, true); const TypeAryPtr* arr_type = gvn().type(addr)->isa_aryptr(); @@ -1417,18 +1440,13 @@ bool LibraryCallKit::inline_vector_gather_scatter(bool is_scatter) { } // Get the indexes for gather/scatter. - Node* indexes = nullptr; + // Note: we have already loaded the indices from indexMap into a vector, + // and performed checkIndex on it. We must now use that same vector, + // and not reload from the indexMap again. const TypeInstPtr* vbox_idx_type = TypeInstPtr::make_exact(TypePtr::NotNull, vbox_idx_klass); - if (is_subword_type(elem_bt)) { - Node* indexMap = argument(16); - Node* indexM = argument(17); - indexes = array_element_address(indexMap, indexM, T_INT); - } else { - // Get the first index vector. - indexes = unbox_vector(argument(9), vbox_idx_type, T_INT, idx_num_elem); - if (indexes == nullptr) { - return false; - } + Node* indexes = unbox_vector(argument(9), vbox_idx_type, T_INT, idx_num_elem); + if (indexes == nullptr) { + return false; } // Get the vector mask value. diff --git a/src/hotspot/share/opto/vectornode.hpp b/src/hotspot/share/opto/vectornode.hpp index 4657050af6ef..6ed37b70798b 100644 --- a/src/hotspot/share/opto/vectornode.hpp +++ b/src/hotspot/share/opto/vectornode.hpp @@ -1214,15 +1214,19 @@ class LoadVectorNode : public LoadNode { #endif }; -// Load Vector from memory via index map +// Gather: +// indices: int-vector of indices +// adr: memory address (e.g. array base plus offset) +// output[i] = adr[indices[i]] +// +// Note: assumes that indices are all in-bounds, performs no index checking. class LoadVectorGatherNode : public LoadVectorNode { public: LoadVectorGatherNode(Node* c, Node* mem, Node* adr, const TypePtr* at, const TypeVect* vt, Node* indices) : LoadVectorNode(c, mem, adr, at, vt) { init_class_id(Class_LoadVectorGather); add_req(indices); - DEBUG_ONLY(bool is_subword = is_subword_type(vt->element_basic_type())); - assert(is_subword || indices->bottom_type()->is_vect(), "indices must be in vector"); + assert(indices->bottom_type()->is_vect(), "indices must be in vector"); assert(req() == MemNode::ValueIn + 1, "match_edge expects that index input is in MemNode::ValueIn"); } @@ -1277,8 +1281,13 @@ class StoreVectorNode : public StoreNode { #endif }; -// Store Vector into memory via index map - class StoreVectorScatterNode : public StoreVectorNode { +// Scatter: +// indices: int-vector of indices +// adr: memory address (e.g. array base plus offset) +// adr[indices[i]] = val[i] +// +// Note: assumes that indices are all in-bounds, performs no index checking. +class StoreVectorScatterNode : public StoreVectorNode { public: enum { Indices = 4 }; StoreVectorScatterNode(Node* c, Node* mem, Node* adr, const TypePtr* at, Node* val, Node* indices) @@ -1338,7 +1347,12 @@ class LoadVectorMaskedNode : public LoadVectorNode { } }; -// Load Vector from memory via index map under the influence of a predicate register(mask). +// Masked Gather: +// indices: int-vector of indices +// adr: memory address (e.g. array base plus offset) +// output[i] = mask[i] ? adr[indices[i]] : 0 +// +// Note: assumes that indices are all in-bounds, performs no index checking. class LoadVectorGatherMaskedNode : public LoadVectorNode { public: LoadVectorGatherMaskedNode(Node* c, Node* mem, Node* adr, const TypePtr* at, const TypeVect* vt, Node* indices, Node* mask) @@ -1347,7 +1361,7 @@ class LoadVectorGatherMaskedNode : public LoadVectorNode { add_req(indices); add_req(mask); assert(req() == MemNode::ValueIn + 2, "match_edge expects that last input is in MemNode::ValueIn+1"); - assert(is_subword_type(vt->element_basic_type()) || indices->bottom_type()->is_vect(), "indices must be in vector"); + assert(indices->bottom_type()->is_vect(), "indices must be in vector"); } virtual int Opcode() const; @@ -1360,7 +1374,12 @@ class LoadVectorGatherMaskedNode : public LoadVectorNode { } }; -// Store Vector into memory via index map under the influence of a predicate register(mask). +// Masked Scatter: +// indices: int-vector of indices +// adr: memory address (e.g. array base plus offset) +// if (mask[i]) { adr[indices[i]] = val[i] } +// +// Note: assumes that indices are all in-bounds, performs no index checking. class StoreVectorScatterMaskedNode : public StoreVectorNode { public: enum { Indices = 4, From db055baa2ac01615b0df5f2fe93c44eccfdbc6a4 Mon Sep 17 00:00:00 2001 From: Volkan Yazici Date: Tue, 2 Jun 2026 09:58:51 +0000 Subject: [PATCH 015/169] 8385260: Enhance SOCKS5 support Reviewed-by: aefimov, rhalade, djelinski, alanb, dfuchs --- .../classes/java/net/SocksSocketImpl.java | 41 +++++++++++++++---- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/src/java.base/share/classes/java/net/SocksSocketImpl.java b/src/java.base/share/classes/java/net/SocksSocketImpl.java index 71794d7a0a66..5f8b974e12b6 100644 --- a/src/java.base/share/classes/java/net/SocksSocketImpl.java +++ b/src/java.base/share/classes/java/net/SocksSocketImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -143,13 +143,27 @@ private boolean authenticate(byte method, InputStream in, if (userName == null) return false; out.write(1); - out.write(userName.length()); - out.write(userName.getBytes(StandardCharsets.ISO_8859_1)); - if (password != null) { - out.write(password.length()); - out.write(password.getBytes(StandardCharsets.ISO_8859_1)); - } else + + var userNameBytes = userName.getBytes(StandardCharsets.ISO_8859_1); + // RFC 1929 allows usernames of at most 255 bytes + if (userNameBytes.length > 0xFF) { + throw new SocketException("SOCKS: username is too long"); + } + out.write(userNameBytes.length); + out.write(userNameBytes); + + if (password == null) { out.write(0); + } else { + var passwordBytes = password.getBytes(StandardCharsets.ISO_8859_1); + // RFC 1929 allows password of at most 255 bytes + if (passwordBytes.length > 0xFF) { + throw new SocketException("SOCKS: password is too long"); + } + out.write(passwordBytes.length); + out.write(passwordBytes); + } + out.flush(); byte[] data = new byte[2]; int i = readSocksReply(in, data, deadlineMillis); @@ -241,6 +255,15 @@ protected void connect(SocketAddress endpoint, int timeout) throws IOException { if (!(endpoint instanceof InetSocketAddress epoint)) throw new IllegalArgumentException("Unsupported address type"); + byte[] domainNameBytes = null; + if (epoint.isUnresolved()) { + domainNameBytes = epoint.getHostName().getBytes(StandardCharsets.ISO_8859_1); + // RFC 1928 allows FQDNs of at most 255 bytes + if (domainNameBytes.length > 0xFF) { + throw new SocketException("SOCKS: host name is too long"); + } + } + if (server == null) { // This is the general case // server is not null only when the socket was created with a @@ -366,8 +389,8 @@ protected void connect(SocketAddress endpoint, int timeout) throws IOException { /* Test for IPV4/IPV6/Unresolved */ if (epoint.isUnresolved()) { out.write(DOMAIN_NAME); - out.write(epoint.getHostName().length()); - out.write(epoint.getHostName().getBytes(StandardCharsets.ISO_8859_1)); + out.write(domainNameBytes.length); + out.write(domainNameBytes); out.write((epoint.getPort() >> 8) & 0xff); out.write((epoint.getPort() >> 0) & 0xff); } else if (epoint.getAddress() instanceof Inet6Address) { From c251c01efd05fdfffe1e107b122d5f18129c5efb Mon Sep 17 00:00:00 2001 From: Alexander Zuev Date: Tue, 2 Jun 2026 21:37:31 +0000 Subject: [PATCH 016/169] 8384455: Enhance DataFlavor Reviewed-by: rhalade, azvegint, skoivu, prr --- .../java/awt/datatransfer/DataFlavor.java | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/src/java.datatransfer/share/classes/java/awt/datatransfer/DataFlavor.java b/src/java.datatransfer/share/classes/java/awt/datatransfer/DataFlavor.java index cfb89118ff71..5eb12e4b8731 100644 --- a/src/java.datatransfer/share/classes/java/awt/datatransfer/DataFlavor.java +++ b/src/java.datatransfer/share/classes/java/awt/datatransfer/DataFlavor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -33,7 +33,6 @@ import java.io.InputStreamReader; import java.io.ObjectInput; import java.io.ObjectOutput; -import java.io.OptionalDataException; import java.io.Reader; import java.io.Serial; import java.io.StringReader; @@ -1320,19 +1319,7 @@ public synchronized void readExternal(ObjectInput is) throws IOException , Class } } - try { - representationClass = (Class)is.readObject(); - } catch (OptionalDataException ode) { - if (!ode.eof || ode.length != 0) { - throw ode; - } - // Ensure backward compatibility. - // Old versions didn't write the representation class to the stream. - if (rcn != null) { - representationClass = - DataFlavor.tryToLoadClass(rcn, getClass().getClassLoader()); - } - } + representationClass = (Class)is.readObject(); } /** From 64d86d84aa5e23b4981fa0a667a6cd2bc83cfc45 Mon Sep 17 00:00:00 2001 From: Artur Barashev Date: Wed, 3 Jun 2026 18:27:31 +0000 Subject: [PATCH 017/169] 8383679: Enhance Certificate Name Checks Reviewed-by: ahgross, jnibedita, mullan, weijun, rhalade --- .../sun/security/x509/NameConstraintsExtension.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/java.base/share/classes/sun/security/x509/NameConstraintsExtension.java b/src/java.base/share/classes/sun/security/x509/NameConstraintsExtension.java index 5b34a7267967..07989fea25b5 100644 --- a/src/java.base/share/classes/sun/security/x509/NameConstraintsExtension.java +++ b/src/java.base/share/classes/sun/security/x509/NameConstraintsExtension.java @@ -449,7 +449,14 @@ public boolean verify(X509Certificate cert) throws IOException { } } else { if (!hasNameType(altNames, GeneralNameInterface.NAME_DNS)) { - altNames.add(new GeneralName(new DNSName(cn))); + // Replacing wildcard character '*' with 'z' to check + // the domain name template validity. + new DNSName(cn.replace('*', 'z')); + // If the above line doesn't throw, use Der constructor + // which performs no format checks: we want to accept + // DNS name with any wildcard here. + altNames.add(new GeneralName(new DNSName(new DerValue( + DerValue.tag_IA5String, cn)))); } } } catch (IOException ioe) { From e7322d8822d31072e290a6915ac7be0d2b945481 Mon Sep 17 00:00:00 2001 From: Joe Wang Date: Thu, 4 Jun 2026 00:52:02 +0000 Subject: [PATCH 018/169] 8383699: Enhance XPath Expressions Reviewed-by: aefimov, rhalade, lancea, naoto, jlu --- .../apache/xpath/internal/XPathContext.java | 32 +++++++++++++++++-- .../functions/FuncSystemProperty.java | 27 ++++++++++++++-- .../xpath/internal/jaxp/XPathFactoryImpl.java | 6 ++-- .../xpath/internal/jaxp/XPathImplUtil.java | 5 +-- .../internal/res/XPATHErrorResources.java | 9 ++++-- .../jdk/xml/internal/JdkConstants.java | 7 ++++ .../jdk/xml/internal/JdkXmlFeatures.java | 15 +++++++-- src/java.xml/share/classes/module-info.java | 32 ++++++++++++++++--- 8 files changed, 115 insertions(+), 18 deletions(-) diff --git a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/XPathContext.java b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/XPathContext.java index 1ab774f69f63..27669d89567b 100644 --- a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/XPathContext.java +++ b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/XPathContext.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2026, Oracle and/or its affiliates. All rights reserved. */ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -49,6 +49,8 @@ import javax.xml.transform.ErrorListener; import javax.xml.transform.SourceLocator; import javax.xml.transform.URIResolver; +import jdk.xml.internal.JdkXmlConfig; +import jdk.xml.internal.JdkXmlFeatures; import org.xml.sax.XMLReader; /** @@ -56,7 +58,7 @@ * *

    This class extends DTMManager but does not directly implement it.

    * @xsl.usage advanced - * @LastModified: Jan 2019 + * @LastModified: June 2026 */ public class XPathContext extends DTMManager // implements ExpressionContext { @@ -94,6 +96,11 @@ public class XPathContext extends DTMManager // implements ExpressionContext private boolean m_overrideDefaultParser; + /** + * JDK XML feature settings + */ + private JdkXmlFeatures m_jdkXmlFeatures; + /** * Though XPathContext context extends * the DTMManager, it really is a proxy for this object, which @@ -128,6 +135,27 @@ public boolean isSecureProcessing() return m_isSecureProcessing; } + /** + * Sets the JDK XML feature settings for this XPath evaluation. + */ + public void setJdkXmlFeatures(JdkXmlFeatures features) + { + m_jdkXmlFeatures = features; + } + + /** + * {@return the JDK XML feature settings for this XPath evaluation} + * + * The settings are initialized from the global configuration if needed. + */ + public JdkXmlFeatures getJdkXmlFeatures() + { + if (m_jdkXmlFeatures == null) { + m_jdkXmlFeatures = JdkXmlConfig.getInstance(false).getXMLFeatures(true); + } + return m_jdkXmlFeatures; + } + /** * Get an instance of a DTM, loaded with the content from the * specified source. If the unique flag is true, a new instance will diff --git a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/functions/FuncSystemProperty.java b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/functions/FuncSystemProperty.java index 0beccb5132bf..b6f8c87d61e2 100644 --- a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/functions/FuncSystemProperty.java +++ b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/functions/FuncSystemProperty.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2026, Oracle and/or its affiliates. All rights reserved. */ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -20,19 +20,22 @@ package com.sun.org.apache.xpath.internal.functions; +import com.sun.org.apache.xalan.internal.res.XSLMessages; import com.sun.org.apache.xpath.internal.XPathContext; import com.sun.org.apache.xpath.internal.objects.XObject; import com.sun.org.apache.xpath.internal.objects.XString; import com.sun.org.apache.xpath.internal.res.XPATHErrorResources; -import java.io.BufferedInputStream; import java.io.InputStream; import java.util.Properties; +import javax.xml.transform.TransformerException; +import jdk.xml.internal.JdkConstants; +import jdk.xml.internal.JdkXmlFeatures; import jdk.xml.internal.SecuritySupport; /** * Execute the SystemProperty() function. * @xsl.usage advanced - * @LastModified: Nov 2024 + * @LastModified: June 2026 */ public class FuncSystemProperty extends FunctionOneArg { @@ -96,6 +99,7 @@ public XObject execute(XPathContext xctxt) throws javax.xml.transform.Transforme new Object[]{ namespace, fullName }); //"Don't currently do anything with namespace "+namespace+" in property: "+fullName); + checkSysPropAccess(xctxt, fullName); result = System.getProperty(propName); if (null == result) { @@ -106,6 +110,7 @@ public XObject execute(XPathContext xctxt) throws javax.xml.transform.Transforme } else { + checkSysPropAccess(xctxt, fullName); result = System.getProperty(fullName); if (null == result) @@ -131,6 +136,22 @@ public XObject execute(XPathContext xctxt) throws javax.xml.transform.Transforme return new XString(result); } + /** + * Checks whether XPath may access non-XSLT-defined system properties. + */ + private void checkSysPropAccess(XPathContext xctxt, String name) + throws TransformerException + { + JdkXmlFeatures features = xctxt.getJdkXmlFeatures(); + + if (!features.getFeature(JdkXmlFeatures.XmlFeature.JDK_XPATH_SYSTEM_PROPERTY)) { + String fmsg = XSLMessages.createXPATHMessage( + XPATHErrorResources.ER_NON_XSLT_SYSTEM_PROPERTY, + new Object[] {name, JdkConstants.XPATH_ALLOW_NON_XSLT_SYS_PROPS}); + throw new TransformerException(fmsg); + } + } + /** * Retrieve a property bundle from XSLT_PROPERTIES * diff --git a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/jaxp/XPathFactoryImpl.java b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/jaxp/XPathFactoryImpl.java index 2f4d2ade5453..56a09b645e4f 100644 --- a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/jaxp/XPathFactoryImpl.java +++ b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/jaxp/XPathFactoryImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2026, Oracle and/or its affiliates. All rights reserved. */ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -35,7 +35,7 @@ * * @author Ramesh Mandava * - * @LastModified: June 2025 + * @LastModified: June 2026 */ public class XPathFactoryImpl extends XPathFactory { @@ -185,6 +185,8 @@ public void setFeature(String name, boolean value) if (value && _featureManager != null) { _featureManager.setFeature(JdkXmlFeatures.XmlFeature.ENABLE_EXTENSION_FUNCTION, JdkProperty.State.FSP, false); + _featureManager.setFeature(JdkXmlFeatures.XmlFeature.JDK_XPATH_SYSTEM_PROPERTY, + JdkProperty.State.FSP, false); _xmlSecMgr.setSecureProcessing(value); } diff --git a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/jaxp/XPathImplUtil.java b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/jaxp/XPathImplUtil.java index 3de72f3f68bb..585822dcbd81 100644 --- a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/jaxp/XPathImplUtil.java +++ b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/jaxp/XPathImplUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -56,7 +56,7 @@ * This class contains several utility methods used by XPathImpl and * XPathExpressionImpl * - * @LastModified: June 2025 + * @LastModified: June 2026 */ class XPathImplUtil { XPathFunctionResolver functionResolver; @@ -94,6 +94,7 @@ XObject eval(Object contextItem, com.sun.org.apache.xpath.internal.XPath xpath) xpathSupport = new com.sun.org.apache.xpath.internal.XPathContext(); } + xpathSupport.setJdkXmlFeatures(featureManager); xpathSupport.setVarStack(new JAXPVariableStack(variableResolver)); XObject xobj; diff --git a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/res/XPATHErrorResources.java b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/res/XPATHErrorResources.java index 716a4bcf63bd..55a106aa67ed 100644 --- a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/res/XPATHErrorResources.java +++ b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/res/XPATHErrorResources.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved. */ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -31,7 +31,7 @@ * Also you need to update the count of messages(MAX_CODE)or * the count of warnings(MAX_WARNING) [ Information purpose only] * @xsl.usage advanced - * @LastModified: Apr 2025 + * @LastModified: June 2026 */ public class XPATHErrorResources extends ListResourceBundle { @@ -327,6 +327,7 @@ public class XPATHErrorResources extends ListResourceBundle public static final String ER_GETTING_UNKNOWN_PROPERTY = "ER_GETTING_UNKNOWN_PROPERTY"; public static final String ER_XPATH_GROUP_LIMIT = "ER_XPATH_GROUP_LIMIT"; public static final String ER_XPATH_OPERATOR_LIMIT = "ER_XPATH_OPERATOR_LIMIT"; + public static final String ER_NON_XSLT_SYSTEM_PROPERTY = "ER_NON_XSLT_SYSTEM_PROPERTY"; //END: Keys needed for exception messages of JAXP 1.3 XPath API implementation @@ -862,6 +863,10 @@ public class XPATHErrorResources extends ListResourceBundle "JAXP0801002: the compiler encountered an XPath expression containing " + "''{0}'' operators that exceeds the ''{1}'' limit set by ''{2}''."}, + { ER_NON_XSLT_SYSTEM_PROPERTY, + "Access to non-XSLT system property ''{0}'' is not allowed. Set " + + "''{1}'' to ''true'' to allow it."}, + //END: Definitions of error keys used in exception messages of JAXP 1.3 XPath API implementation // Warnings... diff --git a/src/java.xml/share/classes/jdk/xml/internal/JdkConstants.java b/src/java.xml/share/classes/jdk/xml/internal/JdkConstants.java index 927d19bc3b35..6ef32fb6d282 100644 --- a/src/java.xml/share/classes/jdk/xml/internal/JdkConstants.java +++ b/src/java.xml/share/classes/jdk/xml/internal/JdkConstants.java @@ -220,6 +220,13 @@ public final class JdkConstants { */ public static final String XPATH_TOTALOP_LIMIT = "jdk.xml.xpathTotalOpLimit"; + /** + * jdk.xml.allowNonXSLTSysProps: controls whether XPath's system-property() + * function is permitted to access non-XSLT-defined JVM system properties. + */ + public static final String XPATH_ALLOW_NON_XSLT_SYS_PROPS = + "jdk.xml.allowNonXSLTSysProps"; + /** * JDK TransformerFactory and Transformer attribute that specifies a class * loader that will be used for extension functions class loading diff --git a/src/java.xml/share/classes/jdk/xml/internal/JdkXmlFeatures.java b/src/java.xml/share/classes/jdk/xml/internal/JdkXmlFeatures.java index 7016d2227ea3..fb516d17e24f 100644 --- a/src/java.xml/share/classes/jdk/xml/internal/JdkXmlFeatures.java +++ b/src/java.xml/share/classes/jdk/xml/internal/JdkXmlFeatures.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -28,6 +28,8 @@ import javax.xml.XMLConstants; import jdk.xml.internal.JdkProperty.ImplPropMap; import jdk.xml.internal.JdkProperty.State; + +import static jdk.xml.internal.JdkConstants.XPATH_ALLOW_NON_XSLT_SYS_PROPS; import static jdk.xml.internal.JdkXmlUtils.SP_USE_CATALOG; /** @@ -72,7 +74,16 @@ public static enum XmlFeature { * FSP: not enforced by FSP. */ JDK_OVERRIDE_PARSER(ImplPropMap.OVERRIDEPARSER, null, null, false, - null, null, false, false, true, false); + null, null, false, false, true, false), + + /** + * Feature allowNonXSLTSysProps: controls whether XPath's system-property() + * function is permitted to access non-XSLT-defined JVM system properties. + * FSP: when FSP is on, accessing non-XSLT-defined JVM system properties is disabled + */ + JDK_XPATH_SYSTEM_PROPERTY(null, XPATH_ALLOW_NON_XSLT_SYS_PROPS, + XPATH_ALLOW_NON_XSLT_SYS_PROPS, false, + null, null, false, false, true, true); private final ImplPropMap implMap; private final String name; diff --git a/src/java.xml/share/classes/module-info.java b/src/java.xml/share/classes/module-info.java index a27354ff6a60..19bb39475fd9 100644 --- a/src/java.xml/share/classes/module-info.java +++ b/src/java.xml/share/classes/module-info.java @@ -863,7 +863,7 @@ * 9 * * - * jdk.xml.extensionClassLoader + * {@systemProperty jdk.xml.extensionClassLoader} * Sets a non-null ClassLoader instance to be used for loading XSLTC java * extension functions. * @@ -877,7 +877,7 @@ * 9 * * - * jdk.xml.xpathExprGrpLimit + * {@systemProperty jdk.xml.xpathExprGrpLimit} * Limits the number of groups an XPath expression can contain. * * yes @@ -894,14 +894,14 @@ * 19 * * - * jdk.xml.xpathExprOpLimit + * {@systemProperty jdk.xml.xpathExprOpLimit} * Limits the number of operators an XPath expression can contain. * * 100 * 100 * * - * jdk.xml.xpathTotalOpLimit + * {@systemProperty jdk.xml.xpathTotalOpLimit} * Limits the total number of XPath operators in an XSL Stylesheet. * * 10000 @@ -911,6 +911,28 @@ * * * + * {@systemProperty jdk.xml.allowNonXSLTSysProps} + * controls whether XPath's {@code system-property()} function is permitted to + * access non-XSLT-defined JVM system properties in addition to the standard XSLT-defined + * properties supported by the JDK. Attempts to access non-XSLT-defined properties will + * fail with an XPathExpressionException during XPath expression evaluation. + * + * yes + * Boolean + * + * true or false. True allows access to non-XSLT JVM system properties; False + * restricts access to only XSLT-defined properties. + * + * false + * false + * Yes + * + * XPath + * + * Method 2 + * 27 + * + * * {@systemProperty jdk.xml.enableExtensionFunctions} * Determines whether extension functions in the Transform API are to be allowed. * The extension functions in the XPath API are not affected by this property. @@ -1263,7 +1285,7 @@ * {@code http://www.oracle.com/xml/jaxp/properties/xsltcIsStandalone} * * - * {@code jdk.xml.extensionClassLoader} + * {@systemProperty jdk.xml.extensionClassLoader} * {@code jdk.xml.transform.extensionClassLoader} * * From 1f2116007286b2332fd1d208c0ab5e1a210aebe7 Mon Sep 17 00:00:00 2001 From: Phil Race Date: Thu, 4 Jun 2026 21:12:37 +0000 Subject: [PATCH 019/169] 8384664: Enhance Image Handling Reviewed-by: rhalade, jdv, kizune --- .../classes/sun/lwawt/macosx/CImage.java | 29 ++++++++++--------- .../macosx/native/libawt_lwawt/awt/CImage.m | 5 ++++ 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/src/java.desktop/macosx/classes/sun/lwawt/macosx/CImage.java b/src/java.desktop/macosx/classes/sun/lwawt/macosx/CImage.java index d3f262852559..6cf49b5b9ff9 100644 --- a/src/java.desktop/macosx/classes/sun/lwawt/macosx/CImage.java +++ b/src/java.desktop/macosx/classes/sun/lwawt/macosx/CImage.java @@ -83,7 +83,6 @@ public static CImage createFromImage(final Image image, CTrayIcon.IconObserver o } public static final class Creator { - CTrayIcon.IconObserver observer; Creator() { } @@ -131,7 +130,7 @@ public Image createImageFromName(final String name) { return createImageUsingNativeSize(nativeCreateNSImageFromImageName(name)); } - private static int[] imageToArray(Image image, boolean prepareImage, CTrayIcon.IconObserver observer) { + private static BufferedImage getAsBufferedImage(Image image, boolean prepareImage, CTrayIcon.IconObserver observer) { if (image == null) return null; if (prepareImage && !(image instanceof BufferedImage)) { @@ -163,17 +162,18 @@ private static int[] imageToArray(Image image, boolean prepareImage, CTrayIcon.I g2.drawImage(image, 0, 0, observer); g2.dispose(); - return ((DataBufferInt)bimg.getRaster().getDataBuffer()).getData(); + return bimg; } public byte[] getPlatformImageBytes(final Image image) { - int[] buffer = imageToArray(image, false, null); + BufferedImage bimg = getAsBufferedImage(image, false, null); - if (buffer == null) { + if (bimg == null) { return null; } + int[] buffer = ((DataBufferInt)bimg.getRaster().getDataBuffer()).getData(); - return nativeGetPlatformImageBytes(buffer, image.getWidth(null), image.getHeight(null)); + return nativeGetPlatformImageBytes(buffer, bimg.getWidth(), bimg.getHeight()); } /** @@ -205,11 +205,13 @@ private CImage createFromImage(final Image image, final boolean prepareImage, CT return createFromImages(resolutionVariants, prepareImage); } - int[] buffer = imageToArray(image, prepareImage, observer); - if (buffer == null) { + BufferedImage bimg = getAsBufferedImage(image, prepareImage, observer); + + if (bimg == null) { return null; } - return new CImage(nativeCreateNSImageFromArray(buffer, image.getWidth(null), image.getHeight(null))); + int[] buffer = ((DataBufferInt)bimg.getRaster().getDataBuffer()).getData(); + return new CImage(nativeCreateNSImageFromArray(buffer, bimg.getWidth(), bimg.getHeight())); } public CImage createFromImages(final List images) { @@ -230,13 +232,14 @@ private CImage createFromImages(final List images, final boolean prepareI num = 0; for (final Image img : images) { - buffers[num] = imageToArray(img, prepareImage, null); - if (buffers[num] == null) { + BufferedImage bimg = getAsBufferedImage(img, prepareImage, null); + if (bimg == null) { // Unable to process the image continue; } - w[num] = img.getWidth(null); - h[num] = img.getHeight(null); + buffers[num] = ((DataBufferInt)bimg.getRaster().getDataBuffer()).getData(); + w[num] = bimg.getWidth(); + h[num] = bimg.getHeight(); num++; } diff --git a/src/java.desktop/macosx/native/libawt_lwawt/awt/CImage.m b/src/java.desktop/macosx/native/libawt_lwawt/awt/CImage.m index fa534fff2759..3c5bb10cd253 100644 --- a/src/java.desktop/macosx/native/libawt_lwawt/awt/CImage.m +++ b/src/java.desktop/macosx/native/libawt_lwawt/awt/CImage.m @@ -75,6 +75,11 @@ static NSBitmapImageRep* CImage_CreateImageRep(JNIEnv *env, jintArray buffer, jint width, jint height) { + jlong bufferLength = (*env)->GetArrayLength(env, buffer); + if (((jlong)width * height) > bufferLength) { + return 0L; + } + NSBitmapImageRep* imageRep = [[[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL pixelsWide:width pixelsHigh:height From 4458d9bbad5360ea9c812ce22633c5d971933da4 Mon Sep 17 00:00:00 2001 From: Alexey Semenyuk Date: Mon, 8 Jun 2026 16:31:14 +0000 Subject: [PATCH 020/169] 8384468: Enhance jpackage Packaging Reviewed-by: rhalade, kcr, almatvee --- make/modules/jdk.jpackage/Lib.gmk | 3 +- .../linux/native/applauncher/ExecCommand.c | 313 ++++++++++++++++++ .../linux/native/applauncher/ExecCommand.h | 49 +++ .../linux/native/applauncher/LinuxLauncher.c | 36 +- .../linux/native/applauncher/LinuxPackage.c | 234 +++++++------ .../linux/native/applauncher/LinuxPackage.h | 21 +- .../linux/native/libapplauncher/Executor.cpp | 61 ++-- .../linux/native/libapplauncher/Executor.h | 4 +- .../libapplauncher/LinuxLauncherLib.cpp | 22 +- .../linux/native/libapplauncher/Package.cpp | 72 +--- .../linux/native/libapplauncher/Package.h | 15 +- .../share/native/applauncher/JvmLauncher.h | 8 +- .../share/native/applauncher/JvmLauncherLib.c | 26 +- 13 files changed, 600 insertions(+), 264 deletions(-) create mode 100644 src/jdk.jpackage/linux/native/applauncher/ExecCommand.c create mode 100644 src/jdk.jpackage/linux/native/applauncher/ExecCommand.h diff --git a/make/modules/jdk.jpackage/Lib.gmk b/make/modules/jdk.jpackage/Lib.gmk index 86b11bdafeea..cee8309c679d 100644 --- a/make/modules/jdk.jpackage/Lib.gmk +++ b/make/modules/jdk.jpackage/Lib.gmk @@ -55,8 +55,8 @@ $(eval $(call SetupJdkExecutable, BUILD_JPACKAGEAPPLAUNCHER, \ EXTRA_SRC := common, \ INCLUDE_FILES := $(JPACKAGEAPPLAUNCHER_INCLUDE_FILES), \ OPTIMIZATION := SIZE, \ + DISABLED_WARNINGS_clang_ExecCommand.c := format-nonliteral, \ DISABLED_WARNINGS_clang_JvmLauncherLib.c := format-nonliteral, \ - DISABLED_WARNINGS_clang_LinuxPackage.c := format-nonliteral, \ DISABLED_WARNINGS_clang_Log.cpp := unused-const-variable, \ CFLAGS_FILTER_OUT := -MD, \ CXXFLAGS_FILTER_OUT := -MD, \ @@ -93,6 +93,7 @@ ifeq ($(call isTargetOs, linux), true) LINK_TYPE := C++, \ OPTIMIZATION := SIZE, \ DISABLED_WARNINGS_gcc_Log.cpp := unused-const-variable, \ + DISABLED_WARNINGS_clang_ExecCommand.c := format-nonliteral, \ DISABLED_WARNINGS_clang_JvmLauncherLib.c := format-nonliteral, \ DISABLED_WARNINGS_clang_tstrings.cpp := format-nonliteral, \ LD_SET_ORIGIN := false, \ diff --git a/src/jdk.jpackage/linux/native/applauncher/ExecCommand.c b/src/jdk.jpackage/linux/native/applauncher/ExecCommand.c new file mode 100644 index 000000000000..d75e1109081c --- /dev/null +++ b/src/jdk.jpackage/linux/native/applauncher/ExecCommand.c @@ -0,0 +1,313 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ExecCommand.h" +#include "JvmLauncher.h" + + +static bool logCommandLine(const char* format, const char* const argv[]) { + char* formattedCommandLine = NULL; + int formattedCommandLineLength = 0; + const char* arg; + int i; + bool success = false; + + for (i = 0; (arg = argv[i]) != NULL; i++) { + /* Count trailing whitespace */ + formattedCommandLineLength += strlen(arg) + 1; + if (strchr(arg, ' ') != NULL) { + /* Enclose the argument into single quotes */ + formattedCommandLineLength += 2; + } + } + + if (!formattedCommandLineLength) { + /* Empty command line is an error */ + goto cleanup; + } + + formattedCommandLine = malloc(formattedCommandLineLength + 1 /* \0 */); + if (!formattedCommandLine) { + JP_LOG_ERRNO; + goto cleanup; + } + + formattedCommandLine[0] = '\0'; + for (i = 0; (arg = argv[i]) != NULL; i++) { + if (strchr(arg, ' ') != NULL) { + strcat(formattedCommandLine, "'"); + strcat(formattedCommandLine, arg); + strcat(formattedCommandLine, "'"); + } else { + strcat(formattedCommandLine, arg); + } + strcat(formattedCommandLine, " "); + } + + /* Trim trailing whitespace */ + formattedCommandLine[formattedCommandLineLength - 1] = '\0'; + + JP_LOG_TRACE(format, formattedCommandLine); + + success = true; + +cleanup: + free(formattedCommandLine); + + return success; +} + +static bool invokeCallback( + FILE* stream, + ExecCommandCallbackType callback, + void* callbackData) { + + char* strBufBegin = NULL; + char* strBufEnd = NULL; + char* strBufNextChar = NULL; + char* strNewBufBegin = NULL; + size_t strBufCapacity = 0; + int callbackMode = EXEC_CALLBACK_USE; + int c; + ptrdiff_t charOffset; + int success = false; + int tailCharCount = 0; + + for (;;) { + c = fgetc(stream); + if (EOF == c || '\n' == c) { + if (EXEC_CALLBACK_USE == callbackMode + && strBufBegin != strBufNextChar) { + *strBufNextChar = 0; + JP_LOG_TRACE("execCommand: [%s]", strBufBegin); + callbackMode = (*callback)(callbackData, strBufBegin); + strBufNextChar = strBufBegin; + } + + if (EOF == c) { + break; + } + + continue; + } + + if (EXEC_CALLBACK_USE != callbackMode) { + /* Fetch the stream */ + for (; EOF != c; c = fgetc(stream)) { + tailCharCount++; + } + if (tailCharCount) { + JP_LOG_TRACE( + "execCommand: fetched %d trailing chars", + tailCharCount); + } + break; + } + + if (strBufNextChar == strBufEnd) { + /* Double buffer size */ + if (strBufCapacity) { + strBufCapacity = strBufCapacity * 2; + } else { + strBufCapacity = 1; + } + + if (strBufNextChar && strBufBegin) { + charOffset = strBufNextChar - strBufBegin; + } else { + charOffset = 0; + } + + strNewBufBegin = realloc( strBufBegin, + /* Account for the terminal '\0' */ + strBufCapacity + 1); + if (!strNewBufBegin) { + JP_LOG_ERRNO; + goto cleanup; + } + + strBufNextChar = strNewBufBegin + charOffset; + strBufEnd = strNewBufBegin + strBufCapacity; + strBufBegin = strNewBufBegin; + } + + *strBufNextChar++ = (char)c; + } + + success = (EXEC_CALLBACK_ERROR != callbackMode); + +cleanup: + if (strBufBegin) { + free(strBufBegin); + } + + return success; +} + + +int execCommand( + const char* const argv[], + ExecCommandCallbackType callback, + void* callbackData) { + + int pipefd[] = { -1, -1 }; + pid_t cpid = -1; + FILE* stream = NULL; + int exitCode = -1; + int devNull = -1; + int savedStderr = -1; + int savedErrno = 0; + bool callbackSuccess = false; + int childExitCode = 1; + int waitpidStatus = -1; + + if (!logCommandLine("execCommand: cmdline=[%s]", argv)) { + return -1; + } + + if (pipe(pipefd) == -1) { + JP_LOG_ERRNO; + goto cleanup; + } + + cpid = fork(); + if (cpid == -1) { + JP_LOG_ERRNO; + } else if (cpid == 0) /* Child process */ { + /* Close unused read end */ + closePipeEnd(pipefd, 0); + + /* Save original stderr */ + if ((savedStderr = dup(STDERR_FILENO)) == -1) { + JP_LOG_ERRNO; + goto cleanupChild; + } + + /* Redirect stdout of the child process into the pipe's end */ + if (dup2(pipefd[1], STDOUT_FILENO) == -1) { + JP_LOG_ERRNO; + goto cleanupChild; + } + + devNull = open("/dev/null", O_WRONLY); + if (devNull == -1) { + JP_LOG_ERRNO; + goto cleanupChild; + } + + /* Silence stderr in the child process */ + if (dup2(devNull, STDERR_FILENO) == -1) { + JP_LOG_ERRNO; + goto cleanupChild; + } + + childExitCode = 0; + +cleanupChild: + if (devNull != -1) { + close(devNull); + } + + closePipeEnd(pipefd, 0); + + if (childExitCode == 0) { + execvp(argv[0], (char* const*)argv); + + /* + Normally, execvp() doesn't return. + If control flow reaches this point, execvp() failed. + Restore stderr to make JP_LOG_ERRNO macro work and report error. + */ + savedErrno = errno; + dup2(savedStderr, STDERR_FILENO); + errno = savedErrno; + JP_LOG_ERRNO; + + close(savedStderr); + + childExitCode = 127; /* Command not found */ + } + + _exit(childExitCode); + } + + /* Close unused write end */ + closePipeEnd(pipefd, 1); + + stream = fdopen(pipefd[0], "r"); + if (!stream) { + JP_LOG_ERRNO; + goto cleanup; + } + pipefd[0] = -1; + + callbackSuccess = invokeCallback(stream, callback, callbackData); + if (!callbackSuccess) { + JP_LOG_TRACE("Callback failed"); + goto cleanup; + } + +cleanup: + if (stream) { + fclose(stream); + } + + closePipeEnd(pipefd, 0); + closePipeEnd(pipefd, 1); + + if (cpid > 0) { + while (waitpid(cpid, &waitpidStatus, 0) == -1 && errno == EINTR) { + } + + if (WIFEXITED(waitpidStatus)) { + exitCode = WEXITSTATUS(waitpidStatus); + } + } + + JP_LOG_TRACE("execCommand: exit=%d", exitCode); + + if (exitCode == 0 && !callbackSuccess) { + exitCode = 1; + } + + return exitCode; +} + + +void closePipeEnd(int* pipefd, int idx) { + if (pipefd[idx] >= 0) { + close(pipefd[idx]); + pipefd[idx] = -1; + } +} diff --git a/src/jdk.jpackage/linux/native/applauncher/ExecCommand.h b/src/jdk.jpackage/linux/native/applauncher/ExecCommand.h new file mode 100644 index 000000000000..1bce66feeea4 --- /dev/null +++ b/src/jdk.jpackage/linux/native/applauncher/ExecCommand.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#ifndef ExecCommand_h +#define ExecCommand_h + +#ifdef __cplusplus +extern "C" { +#endif + +#define EXEC_CALLBACK_IGNORE 0 +#define EXEC_CALLBACK_USE 1 +#define EXEC_CALLBACK_ERROR 2 + +typedef int (*ExecCommandCallbackType)(void*, char*); + +int execCommand(const char* const argv[], + ExecCommandCallbackType callback, + void* callbackData); + +void closePipeEnd(int* pipefd, int idx); + +#ifdef __cplusplus +} +#endif + +#endif /* #ifndef ExecCommand_h */ diff --git a/src/jdk.jpackage/linux/native/applauncher/LinuxLauncher.c b/src/jdk.jpackage/linux/native/applauncher/LinuxLauncher.c index 7db42bc2dbd6..96e6abe98c81 100644 --- a/src/jdk.jpackage/linux/native/applauncher/LinuxLauncher.c +++ b/src/jdk.jpackage/linux/native/applauncher/LinuxLauncher.c @@ -35,31 +35,40 @@ #include #include "JvmLauncher.h" #include "LinuxPackage.h" +#include "ExecCommand.h" #define STATUS_FAILURE 1 -typedef JvmlLauncherHandle (*JvmlLauncherAPI_CreateFunType)(int argc, char *argv[]); +typedef JvmlLauncherHandle (*JvmlLauncherAPI_CreateFunType)( + const PackageDesc* pkg, + int argc, + char* argv[]); static int appArgc; static char **appArgv; static JvmlLauncherData* initJvmlLauncherData(int* size) { - char* launcherLibPath = 0; + JvmLauncherDesc* jvmLauncherDesc = 0; + PackageDesc pkg; void* jvmLauncherLibHandle = 0; JvmlLauncherAPI_GetAPIFunc getApi = 0; - JvmlLauncherAPI_CreateFunType createJvmlLauncher = 0; + JvmlLauncherAPI_CreateFunType createJvmLauncher = 0; JvmlLauncherAPI* api = 0; JvmlLauncherHandle jvmLauncherHandle = 0; JvmlLauncherData* result = 0; - launcherLibPath = getJvmLauncherLibPath(); - if (!launcherLibPath) { + jvmLauncherDesc = getJvmLauncherDesc(); + if (!jvmLauncherDesc) { goto cleanup; } - jvmLauncherLibHandle = dlopen(launcherLibPath, RTLD_NOW | RTLD_LOCAL); + pkg.name = jvmLauncherDesc->packageName; + pkg.type = jvmLauncherDesc->packageType; + + jvmLauncherLibHandle = dlopen( jvmLauncherDesc->jvmLauncherLibPath, + RTLD_NOW | RTLD_LOCAL); if (!jvmLauncherLibHandle) { JP_LOG_ERRMSG(dlerror()); goto cleanup; @@ -77,13 +86,13 @@ static JvmlLauncherData* initJvmlLauncherData(int* size) { goto cleanup; } - createJvmlLauncher = dlsym(jvmLauncherLibHandle, "jvmLauncherCreate"); - if (!createJvmlLauncher) { + createJvmLauncher = dlsym(jvmLauncherLibHandle, "jvmLauncherCreate"); + if (!createJvmLauncher) { JP_LOG_ERRMSG(dlerror()); goto cleanup; } - jvmLauncherHandle = (*createJvmlLauncher)(appArgc, appArgv); + jvmLauncherHandle = (*createJvmLauncher)(&pkg, appArgc, appArgv); if (!jvmLauncherHandle) { goto cleanup; } @@ -99,7 +108,7 @@ static JvmlLauncherData* initJvmlLauncherData(int* size) { if (jvmLauncherLibHandle) { dlclose(jvmLauncherLibHandle); } - free(launcherLibPath); + freeJvmLauncherDesc(jvmLauncherDesc); return result; } @@ -170,13 +179,6 @@ static ssize_t writeFully(const int fd, const void* buf, const size_t len) { return (ssize_t)nWritten; } -static void closePipeEnd(int* pipefd, int idx) { - if (pipefd[idx] >= 0) { - close(pipefd[idx]); - pipefd[idx] = -1; - } -} - static void initJvmlLauncherDataPointers(void* baseAddress, JvmlLauncherData* jvmLauncherData) { diff --git a/src/jdk.jpackage/linux/native/applauncher/LinuxPackage.c b/src/jdk.jpackage/linux/native/applauncher/LinuxPackage.c index d346e241035a..31d2208d7a30 100644 --- a/src/jdk.jpackage/linux/native/applauncher/LinuxPackage.c +++ b/src/jdk.jpackage/linux/native/applauncher/LinuxPackage.c @@ -23,16 +23,16 @@ * questions. */ -#include #include -#include #include #include #include #include #include +#include #include "JvmLauncher.h" #include "LinuxPackage.h" +#include "ExecCommand.h" static char* getModulePath(void) { @@ -56,19 +56,9 @@ static char* getModulePath(void) { } -# define PACKAGE_TYPE_UNKNOWN 0 -# define PACKAGE_TYPE_RPM 1 -# define PACKAGE_TYPE_DEB 2 - -typedef struct { - char* name; - int type; -} PackageDesc; - - static void freePackageDesc(PackageDesc* desc) { if (desc) { - free(desc->name); + free((void*)desc->name); free(desc); } } @@ -91,116 +81,73 @@ static PackageDesc* createPackageDesc(void) { } -static PackageDesc* initPackageDesc(PackageDesc* desc, const char* str, - int type) { +static bool initPackageDesc(PackageDesc* desc, const char* str, int type) { char *newStr = strdup(str); if (!newStr) { JP_LOG_ERRNO; - return 0; + return false; } - free(desc->name); + free((void*)desc->name); desc->name = newStr; desc->type = type; - return desc; + return true; } -#define POPEN_CALLBACK_USE 1 -#define POPEN_CALLBACK_IGNORE 0 - -typedef int (*popenCallbackType)(void*, char*); - -static int popenCommand(const char* cmdlineFormat, const char* arg, - popenCallbackType callback, void* callbackData) { - char* cmdline = 0; - FILE *stream = 0; - const size_t cmdlineLenth = strlen(cmdlineFormat) + strlen(arg); - char* strBufBegin = 0; - char* strBufEnd = 0; - char* strBufNextChar = 0; - char* strNewBufBegin = 0; - size_t strBufCapacity = 0; - int callbackMode = POPEN_CALLBACK_USE; - int exitCode = -1; - int c; - ptrdiff_t char_offset; - - cmdline = malloc(cmdlineLenth + 1 /* \0 */); - if (!cmdline) { - JP_LOG_ERRNO; - goto cleanup; - } +static JvmLauncherDesc* createJvmLauncherDesc(void) { + JvmLauncherDesc* result = 0; -#if defined(__GNUC__) && __GNUC__ >= 5 -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wformat-nonliteral" -#endif - if (0 > snprintf(cmdline, cmdlineLenth, cmdlineFormat, arg)) { + result = malloc(sizeof(JvmLauncherDesc)); + if (!result) { JP_LOG_ERRNO; goto cleanup; } -#if defined(__GNUC__) && __GNUC__ >= 5 -#pragma GCC diagnostic pop -#endif - JP_LOG_TRACE("popen: (%s)", cmdline); + result->packageType = PACKAGE_TYPE_UNKNOWN; + result->packageName = NULL; + result->jvmLauncherLibPath = NULL; - stream = popen(cmdline, "r"); - if (!stream) { - JP_LOG_ERRNO; - goto cleanup; - } +cleanup: + return result; +} - for (;;) { - c = fgetc(stream); - if((EOF == c || '\n' == c)) { - if (POPEN_CALLBACK_USE == callbackMode - && strBufBegin != strBufNextChar) { - *strBufNextChar = 0; - JP_LOG_TRACE("popen: [%s]", strBufBegin); - callbackMode = (*callback)(callbackData, strBufBegin); - strBufNextChar = strBufBegin; - } - - if (EOF == c) { - break; - } - - continue; - } - if (strBufNextChar == strBufEnd) { - /* Double buffer size */ - strBufCapacity = strBufCapacity * 2 + 1; - char_offset = strBufNextChar - strBufBegin; - strNewBufBegin = realloc(strBufBegin, strBufCapacity); - if (!strNewBufBegin) { - JP_LOG_ERRNO; - goto cleanup; - } - - strBufNextChar = strNewBufBegin + char_offset; - strBufEnd = strNewBufBegin + strBufCapacity; - strBufBegin = strNewBufBegin; - } +static bool initJvmLauncherDesc( + JvmLauncherDesc* desc, + const PackageDesc* packageDesc, + const char* jvmLauncherLibPath) { - *strBufNextChar++ = (char)c; - } + char* newJvmLauncherLibPath = NULL; + char* newPackageName = NULL; -cleanup: - if (stream) { - exitCode = pclose(stream); + newJvmLauncherLibPath = strdup(jvmLauncherLibPath); + if (!newJvmLauncherLibPath) { + JP_LOG_ERRNO; + return false; } - if (strBufBegin) { - free(strBufBegin); + if (packageDesc && packageDesc->name) { + newPackageName = strdup(packageDesc->name); + if (!newPackageName) { + JP_LOG_ERRNO; + free(newJvmLauncherLibPath); + return false; + } } - free(cmdline); + free((void*)desc->packageName); + free((void*)desc->jvmLauncherLibPath); - JP_LOG_TRACE("popen: exit: %d", exitCode); - return exitCode; + desc->packageName = newPackageName; + if (packageDesc) { + desc->packageType = packageDesc->type; + } else { + desc->packageType = PACKAGE_TYPE_UNKNOWN; + } + desc->jvmLauncherLibPath = newJvmLauncherLibPath; + + return true; } @@ -221,8 +168,11 @@ static char* concat(const char *x, const char *y) { static int initRpmPackage(void* desc, char* str) { - initPackageDesc((PackageDesc*)desc, str, PACKAGE_TYPE_RPM); - return POPEN_CALLBACK_IGNORE; + if (initPackageDesc((PackageDesc*)desc, str, PACKAGE_TYPE_RPM)) { + return EXEC_CALLBACK_IGNORE; + } else { + return EXEC_CALLBACK_ERROR; + } } @@ -231,8 +181,12 @@ static int initDebPackage(void* desc, char* str) { if (colonChrPos) { *colonChrPos = 0; } - initPackageDesc((PackageDesc*)desc, str, PACKAGE_TYPE_DEB); - return POPEN_CALLBACK_IGNORE; + + if (initPackageDesc((PackageDesc*)desc, str, PACKAGE_TYPE_DEB)) { + return EXEC_CALLBACK_IGNORE; + } else { + return EXEC_CALLBACK_ERROR; + } } @@ -248,34 +202,38 @@ static int findLauncherLib(void* launcherLibPath, char* str) { buf = strdup(str); if (!buf) { JP_LOG_ERRNO; + return EXEC_CALLBACK_ERROR; } else { *(char**)launcherLibPath = buf; } - return POPEN_CALLBACK_IGNORE; + return EXEC_CALLBACK_IGNORE; } - return POPEN_CALLBACK_USE; + return EXEC_CALLBACK_USE; } static PackageDesc* findOwnerOfFile(const char* path) { - int popenStatus = -1; + int execStatus = -1; PackageDesc* pkg = 0; + const char* rpmOwner[] = { + "rpm", "--queryformat", "%{NAME}", "-qf", path, NULL + }; + const char* debOwner[] = { + "dpkg", "-S", path, NULL + }; pkg = createPackageDesc(); if (!pkg) { return 0; } - popenStatus = popenCommand( - "rpm --queryformat '%{NAME}' -qf '%s' 2>/dev/null", path, - initRpmPackage, pkg); - if (popenStatus) { + execStatus = execCommand(rpmOwner, initRpmPackage, pkg); + if (execStatus) { pkg->type = PACKAGE_TYPE_UNKNOWN; - popenStatus = popenCommand("dpkg -S '%s' 2>/dev/null", path, - initDebPackage, pkg); + execStatus = execCommand(debOwner, initDebPackage, pkg); } - if (popenStatus) { + if (execStatus) { pkg->type = PACKAGE_TYPE_UNKNOWN; } @@ -292,13 +250,25 @@ static PackageDesc* findOwnerOfFile(const char* path) { } -char* getJvmLauncherLibPath(void) { +void freeJvmLauncherDesc(JvmLauncherDesc* desc) { + if (desc) { + free((void*)desc->packageName); + free((void*)desc->jvmLauncherLibPath); + free(desc); + } +} + + +JvmLauncherDesc* getJvmLauncherDesc(void) { char* modulePath = 0; char* appImageDir = 0; char* launcherLibPath = 0; - const char* pkgQueryCmd = 0; - int popenStatus = -1; + const char* pkgQueryCmd[] = { + NULL, NULL, NULL, NULL + }; PackageDesc* pkg = 0; + JvmLauncherDesc* result = 0; + bool resultReady = false; modulePath = getModulePath(); if (!modulePath) { @@ -314,27 +284,45 @@ char* getJvmLauncherLibPath(void) { launcherLibPath = concat(appImageDir, "/lib" LAUNCHER_LIB_NAME); } else { if (PACKAGE_TYPE_RPM == pkg->type) { - pkgQueryCmd = "rpm -ql '%s' 2>/dev/null"; + pkgQueryCmd[0] = "rpm"; + pkgQueryCmd[1] = "-ql"; + pkgQueryCmd[2] = pkg->name; } else if (PACKAGE_TYPE_DEB == pkg->type) { - pkgQueryCmd = "dpkg -L '%s' 2>/dev/null"; + pkgQueryCmd[0] = "dpkg"; + pkgQueryCmd[1] = "-L"; + pkgQueryCmd[2] = pkg->name; } else { /* Should never happen */ JP_LOG_ERRMSG("Internal error"); goto cleanup; } - popenStatus = popenCommand(pkgQueryCmd, pkg->name, findLauncherLib, - &launcherLibPath); - if (popenStatus) { - free(launcherLibPath); - launcherLibPath = NULL; + if (execCommand(pkgQueryCmd, findLauncherLib, &launcherLibPath)) { goto cleanup; } } + if (!launcherLibPath) { + goto cleanup; + } + + result = createJvmLauncherDesc(); + if (result && initJvmLauncherDesc(result, pkg, launcherLibPath)) { + JP_LOG_TRACE("JvmLauncherDesc(%s|%d|%s)", + result->packageName ? result->packageName : "(null)", + result->packageType, + result->jvmLauncherLibPath); + resultReady = true; + } + cleanup: free(modulePath); freePackageDesc(pkg); + if (!resultReady) { + freeJvmLauncherDesc(result); + result = NULL; + } + free(launcherLibPath); - return launcherLibPath; + return result; } diff --git a/src/jdk.jpackage/linux/native/applauncher/LinuxPackage.h b/src/jdk.jpackage/linux/native/applauncher/LinuxPackage.h index 24ec18910666..af862502a07b 100644 --- a/src/jdk.jpackage/linux/native/applauncher/LinuxPackage.h +++ b/src/jdk.jpackage/linux/native/applauncher/LinuxPackage.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -30,7 +30,24 @@ extern "C" { #endif -char* getJvmLauncherLibPath(void); +#define PACKAGE_TYPE_UNKNOWN 0 +#define PACKAGE_TYPE_RPM 1 +#define PACKAGE_TYPE_DEB 2 + +typedef struct { + const char* name; + int type; +} PackageDesc; + +typedef struct { + const char* packageName; + int packageType; + const char* jvmLauncherLibPath; +} JvmLauncherDesc; + +void freeJvmLauncherDesc(JvmLauncherDesc* desc); + +JvmLauncherDesc* getJvmLauncherDesc(void); #ifdef __cplusplus } diff --git a/src/jdk.jpackage/linux/native/libapplauncher/Executor.cpp b/src/jdk.jpackage/linux/native/libapplauncher/Executor.cpp index d8a1755ac88e..5caf01f193cf 100644 --- a/src/jdk.jpackage/linux/native/libapplauncher/Executor.cpp +++ b/src/jdk.jpackage/linux/native/libapplauncher/Executor.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,49 +23,36 @@ * questions. */ -#include -#include #include "Executor.h" -#include "Log.h" #include "ErrorHandling.h" +#include "ExecCommand.h" -int executeCommandLineAndReadStdout(const std::string& cmd, - CommandOutputConsumer& consumer) { - FILE * stream = popen(cmd.c_str(), "r"); - if (!stream) { - JP_THROW(tstrings::any() << "popen(" << cmd - << ") failed. Error: " << lastCRTError()); +namespace { + +int callbackAdapter(void* callback, char* str) { + JP_TRY; + if (reinterpret_cast(callback)->accept(str)) { + return EXEC_CALLBACK_IGNORE; + } else { + return EXEC_CALLBACK_USE; } + JP_CATCH_ALL; + return EXEC_CALLBACK_ERROR; +} - LOG_TRACE(tstrings::any() << "Reading output of [" << cmd << "] command"); +} // namespace - try { - bool useConsumer = true; - std::string buf; - for (;;) { - const int c = fgetc(stream); - if(c == EOF) { - if (useConsumer && !buf.empty()) { - LOG_TRACE(tstrings::any() << "Next line: [" << buf << "]"); - consumer.accept(buf); - } - break; - } +int executeCommandLineAndReadStdout(const tstring_array& cmd, + CommandOutputConsumer& consumer) { - if (c == '\n' && useConsumer) { - LOG_TRACE(tstrings::any() << "Next line: [" << buf << "]"); - useConsumer = !consumer.accept(buf); - buf.clear(); - } else { - buf.push_back(static_cast(c)); - } - } - return pclose(stream); - } catch (...) { - if (stream) { - pclose(stream); - } - throw; + std::vector argv; + tstring_array::const_iterator it = cmd.begin(); + tstring_array::const_iterator end = cmd.end(); + for (; it != end; ++it) { + argv.push_back(it->c_str()); } + argv.push_back(NULL); + + return execCommand(argv.data(), callbackAdapter, &consumer); } diff --git a/src/jdk.jpackage/linux/native/libapplauncher/Executor.h b/src/jdk.jpackage/linux/native/libapplauncher/Executor.h index 44a5f0c7e26c..423289c7af03 100644 --- a/src/jdk.jpackage/linux/native/libapplauncher/Executor.h +++ b/src/jdk.jpackage/linux/native/libapplauncher/Executor.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -38,7 +38,7 @@ class CommandOutputConsumer { }; }; -int executeCommandLineAndReadStdout(const std::string& cmd, +int executeCommandLineAndReadStdout(const tstring_array& cmd, CommandOutputConsumer& consumer); #endif // #ifndef EXECUTOR_H diff --git a/src/jdk.jpackage/linux/native/libapplauncher/LinuxLauncherLib.cpp b/src/jdk.jpackage/linux/native/libapplauncher/LinuxLauncherLib.cpp index 561e2a942bb0..8430f3149bb8 100644 --- a/src/jdk.jpackage/linux/native/libapplauncher/LinuxLauncherLib.cpp +++ b/src/jdk.jpackage/linux/native/libapplauncher/LinuxLauncherLib.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -45,11 +45,16 @@ size_t hash(const std::string& str) { } Jvm* jvmLauncher; +PackageDesc pkgDesc = {}; void launchApp() { const tstring launcherPath = SysInfo::getProcessModulePath(); - const Package ownerPackage = Package::findOwnerOfFile(launcherPath); + LOG_TRACE(tstrings::any() << "PackageDesc(" + << (pkgDesc.name ? pkgDesc.name : "(null)") + << "|" << pkgDesc.type << ")"); + + const Package ownerPackage = Package(pkgDesc); AppLauncher appLauncher; appLauncher.addJvmLibName(_T("lib/libjli.so")); @@ -100,12 +105,12 @@ void launchApp() { tistringstream iss(launchInfo); iss.exceptions(std::ios::failbit | std::ios::badbit); - size_t hash = 0; - iss >> hash; + size_t launchInfoHash = 0; + iss >> launchInfoHash; launchInfo = ""; - if (thisHash != hash) { + if (thisHash != launchInfoHash) { // This launcher execution is the result of execve() call from // within JVM. // This means all JVM arguments are already configured in launcher @@ -133,10 +138,15 @@ void launchApp() { extern "C" { -JNIEXPORT JvmlLauncherHandle jvmLauncherCreate(int argc, char *argv[]) { +JNIEXPORT JvmlLauncherHandle jvmLauncherCreate( + const PackageDesc* pkg, + int argc, + char* argv[]) { + SysInfo::argc = argc; SysInfo::argv = argv; jvmLauncher = 0; + pkgDesc = *pkg; app::launch(std::nothrow, launchApp); JvmlLauncherHandle jlh = 0; diff --git a/src/jdk.jpackage/linux/native/libapplauncher/Package.cpp b/src/jdk.jpackage/linux/native/libapplauncher/Package.cpp index 3665d7e14a00..fe99a723c4a5 100644 --- a/src/jdk.jpackage/linux/native/libapplauncher/Package.cpp +++ b/src/jdk.jpackage/linux/native/libapplauncher/Package.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -33,67 +33,9 @@ Package::Package(): type(Unknown) { } -namespace { -class FirstLineConsumer : public CommandOutputConsumer { -public: - FirstLineConsumer(): processed(false) { - } - - virtual bool accept(const std::string& line) { - if (!processed) { - value = line; - processed = true; - } - return processed; - }; - - std::string getValue() const { - if (!processed) { - JP_THROW("No output captured"); - } - return value; - } - -private: - bool processed; - std::string value; -}; - - -std::string findOwnerOfFile(const std::nothrow_t&, const std::string& cmdline, - const std::string& path) { - try { - FirstLineConsumer consumer; - int exitCode = executeCommandLineAndReadStdout( - cmdline + " \'" + path + "\' 2>/dev/null", consumer); - if (exitCode == 0) { - return consumer.getValue(); - } - } catch (...) { - } - return ""; -} - -} // namespace - -Package Package::findOwnerOfFile(const std::string& path) { - Package result; - result.theName = ::findOwnerOfFile(std::nothrow, - "rpm --queryformat '%{NAME}' -qf", path); - if (!result.theName.empty()) { - result.type = RPM; - } else { - tstring_array components = tstrings::split(::findOwnerOfFile( - std::nothrow, "dpkg -S", path), ":"); - if (!components.empty()) { - result.theName = components.front(); - if (!result.theName.empty()) { - result.type = DEB; - } - } - } - - return result; +Package::Package(const PackageDesc& desc): + type(static_cast(desc.type)), + theName(desc.name ? desc.name : "") { } @@ -119,7 +61,7 @@ class AppLauncherInitializer : public CommandOutputConsumer { return !appDir.empty() && !runtimeDir.empty(); }; - void apply(AppLauncher& launcher) { + void apply(AppLauncher& launcher) const { launcher.setDefaultRuntimePath(runtimeDir); launcher.setAppDir(appDir); } @@ -136,10 +78,10 @@ void Package::initAppLauncher(AppLauncher& appLauncher) const { int exitCode = -1; if (type == RPM) { exitCode = executeCommandLineAndReadStdout( - "rpm -ql \'" + theName + "\'", consumer); + { "rpm", "-ql", theName }, consumer); } else if (type == DEB) { exitCode = executeCommandLineAndReadStdout( - "dpkg -L \'" + theName + "\'", consumer); + { "dpkg", "-L", theName }, consumer); } if (exitCode == 0) { diff --git a/src/jdk.jpackage/linux/native/libapplauncher/Package.h b/src/jdk.jpackage/linux/native/libapplauncher/Package.h index 4c25d2c7bc1d..7cb7f26db734 100644 --- a/src/jdk.jpackage/linux/native/libapplauncher/Package.h +++ b/src/jdk.jpackage/linux/native/libapplauncher/Package.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -28,6 +28,7 @@ #include "tstrings.h" +#include "LinuxPackage.h" class AppLauncher; @@ -37,16 +38,20 @@ class Package { public: Package(); - std::string name() const { + explicit Package(const PackageDesc& desc); + + const std::string& name() const { return theName; } void initAppLauncher(AppLauncher& appLauncher) const; - static Package findOwnerOfFile(const std::string& path); - private: - enum Type { Unknown, RPM, DEB }; + enum Type { + Unknown = PACKAGE_TYPE_UNKNOWN, + RPM = PACKAGE_TYPE_RPM, + DEB = PACKAGE_TYPE_DEB + }; Type type; std::string theName; diff --git a/src/jdk.jpackage/share/native/applauncher/JvmLauncher.h b/src/jdk.jpackage/share/native/applauncher/JvmLauncher.h index ab8a9d792a42..4bcaa7f08465 100644 --- a/src/jdk.jpackage/share/native/applauncher/JvmLauncher.h +++ b/src/jdk.jpackage/share/native/applauncher/JvmLauncher.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -86,11 +86,11 @@ JvmlLauncherData* jvmLauncherCreateJvmlLauncherData(JvmlLauncherAPI* api, JvmlLauncherHandle h, int* size); int jvmLauncherStartJvm(JvmlLauncherData* jvmArgs, void* JLI_Launch); -void jvmLauncherLog(const char* format, ...); +void jvmLauncherLog(const char* file, int line, const char* format, ...); -#define JP_LOG_ERRMSG(msg) do { jvmLauncherLog((msg)); } while (0) +#define JP_LOG_ERRMSG(msg) do { jvmLauncherLog(__FILE__, __LINE__, (msg)); } while (0) #define JP_LOG_ERRNO JP_LOG_ERRMSG(strerror(errno)) -#define JP_LOG_TRACE jvmLauncherLog +#define JP_LOG_TRACE(format, ...) jvmLauncherLog(__FILE__, __LINE__, format, ##__VA_ARGS__) #ifdef __cplusplus diff --git a/src/jdk.jpackage/share/native/applauncher/JvmLauncherLib.c b/src/jdk.jpackage/share/native/applauncher/JvmLauncherLib.c index 6d3c9f3dd546..fb765ca99025 100644 --- a/src/jdk.jpackage/share/native/applauncher/JvmLauncherLib.c +++ b/src/jdk.jpackage/share/native/applauncher/JvmLauncherLib.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -98,6 +98,27 @@ static void dumpJvmlLauncherData(const JvmlLauncherData* jvmArgs) { } +static const char* filename(const char* path) { + const char* slash = strrchr(path, '/'); +#ifdef _WIN32 + const char* backslash = strrchr(path, '\\'); +#else + const char* backslash = NULL; +#endif + const char* sep; + + if (slash == NULL) { + sep = backslash; + } else if (backslash == NULL) { + sep = slash; + } else { + sep = slash > backslash ? slash : backslash; + } + + return sep ? sep + 1 : path; +} + + int jvmLauncherStartJvm(JvmlLauncherData* jvmArgs, void* JLI_Launch) { int i; int exitCode; @@ -135,7 +156,7 @@ int jvmLauncherStartJvm(JvmlLauncherData* jvmArgs, void* JLI_Launch) { } -void jvmLauncherLog(const char* format, ...) { +void jvmLauncherLog(const char* file, int line, const char* format, ...) { const char *withLog = getenv("JPACKAGE_DEBUG"); if (!withLog || strcmp(withLog, "true")) { return; @@ -151,6 +172,7 @@ void jvmLauncherLog(const char* format, ...) { #ifdef LINUX fprintf(stderr, "[%d]: ", getpid()); #endif + fprintf(stderr, "%s:%d: ", filename(file), line); vfprintf(stderr, format, args); fprintf(stderr, "\n"); #if defined(__GNUC__) && __GNUC__ >= 5 From cd3d04b6dcbb24fac0a4fc6078862150a810f033 Mon Sep 17 00:00:00 2001 From: Serguei Spitsyn Date: Mon, 8 Jun 2026 22:32:33 +0000 Subject: [PATCH 021/169] 8385249: Enhance diagnostic sessions Reviewed-by: fmatte, rhalade, kevinw --- .../linux/native/libattach/VirtualMachineImpl.c | 13 +++++++++---- .../macosx/native/libattach/VirtualMachineImpl.c | 15 ++++++++++----- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/jdk.attach/linux/native/libattach/VirtualMachineImpl.c b/src/jdk.attach/linux/native/libattach/VirtualMachineImpl.c index df4fc54cc966..53d3f743a912 100644 --- a/src/jdk.attach/linux/native/libattach/VirtualMachineImpl.c +++ b/src/jdk.attach/linux/native/libattach/VirtualMachineImpl.c @@ -141,13 +141,14 @@ JNIEXPORT void JNICALL Java_sun_tools_attach_VirtualMachineImpl_checkPermissions memset(&sb, 0, sizeof(struct stat)); /* - * Check that the path is owned by the effective uid/gid of this - * process. Also check that group/other access is not allowed. + * Check that the path itself (not following symbolic links) is + * owned by the effective uid/gid of this process and that it is + * a socket. Also check that group/other access is not allowed. */ uid = geteuid(); gid = getegid(); - res = stat(p, &sb); + res = lstat(p, &sb); if (res != 0) { /* save errno */ res = errno; @@ -156,7 +157,11 @@ JNIEXPORT void JNICALL Java_sun_tools_attach_VirtualMachineImpl_checkPermissions if (res == 0) { char msg[100]; jboolean isError = JNI_FALSE; - if (sb.st_uid != uid && uid != ROOT_UID) { + if (!S_ISSOCK(sb.st_mode)) { + snprintf(msg, sizeof(msg), + "file is expected to be a socket: S_ISSOCK(sb.st_mode): %d", S_ISSOCK(sb.st_mode)); + isError = JNI_TRUE; + } else if (sb.st_uid != uid && uid != ROOT_UID) { snprintf(msg, sizeof(msg), "file should be owned by the current user (which is %d) but is owned by %d", uid, sb.st_uid); isError = JNI_TRUE; diff --git a/src/jdk.attach/macosx/native/libattach/VirtualMachineImpl.c b/src/jdk.attach/macosx/native/libattach/VirtualMachineImpl.c index 5b2579eba39c..e99d872ad1ac 100644 --- a/src/jdk.attach/macosx/native/libattach/VirtualMachineImpl.c +++ b/src/jdk.attach/macosx/native/libattach/VirtualMachineImpl.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -180,13 +180,14 @@ JNIEXPORT void JNICALL Java_sun_tools_attach_VirtualMachineImpl_checkPermissions memset(&sb, 0, sizeof(struct stat)); /* - * Check that the path is owned by the effective uid/gid of this - * process. Also check that group/other access is not allowed. + * Check that the path itself (not following symbolic links) is + * owned by the effective uid/gid of this process and that it is + * a socket. Also check that group/other access is not allowed. */ uid = geteuid(); gid = getegid(); - res = stat(p, &sb); + res = lstat(p, &sb); if (res != 0) { /* save errno */ res = errno; @@ -195,7 +196,11 @@ JNIEXPORT void JNICALL Java_sun_tools_attach_VirtualMachineImpl_checkPermissions if (res == 0) { char msg[100]; jboolean isError = JNI_FALSE; - if (sb.st_uid != uid && uid != ROOT_UID) { + if (!S_ISSOCK(sb.st_mode)) { + snprintf(msg, sizeof(msg), + "file is expected to be a socket: S_ISSOCK(sb.st_mode): %d", S_ISSOCK(sb.st_mode)); + isError = JNI_TRUE; + } else if (sb.st_uid != uid && uid != ROOT_UID) { snprintf(msg, sizeof(msg), "file should be owned by the current user (which is %d) but is owned by %d", uid, sb.st_uid); isError = JNI_TRUE; From c1e81159189112ce1123ce92bb756a6c56ca13d6 Mon Sep 17 00:00:00 2001 From: Kevin Walls Date: Wed, 10 Jun 2026 15:07:16 +0000 Subject: [PATCH 022/169] 8384930: Enhance jstatd Reviewed-by: dfuchs, skoivu --- .../classes/sun/tools/jstatd/Jstatd.java | 7 +++---- .../sun/tools/jstatd/RemoteHostImpl.java | 19 ++++++++++++++++--- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/jdk.jstatd/share/classes/sun/tools/jstatd/Jstatd.java b/src/jdk.jstatd/share/classes/sun/tools/jstatd/Jstatd.java index cfd1212a67ab..3d6d4ff12752 100644 --- a/src/jdk.jstatd/share/classes/sun/tools/jstatd/Jstatd.java +++ b/src/jdk.jstatd/share/classes/sun/tools/jstatd/Jstatd.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2004, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -149,10 +149,9 @@ public static void main(String[] args) { name.append("/").append(rminame); try { - remoteHost = new RemoteHostImpl(rmiPort); ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(rmiFilterPattern); - RemoteHost stub = (RemoteHost) UnicastRemoteObject.exportObject( - remoteHost, rmiPort, filter); + remoteHost = new RemoteHostImpl(rmiPort, filter); + RemoteHost stub = (RemoteHost) UnicastRemoteObject.exportObject(remoteHost, rmiPort, filter); bind(name.toString(), stub); System.out.println("jstatd started (bound to " + name.toString() + ")"); System.out.flush(); diff --git a/src/jdk.jstatd/share/classes/sun/tools/jstatd/RemoteHostImpl.java b/src/jdk.jstatd/share/classes/sun/tools/jstatd/RemoteHostImpl.java index 5db7834e8f1e..53ca1da7f05c 100644 --- a/src/jdk.jstatd/share/classes/sun/tools/jstatd/RemoteHostImpl.java +++ b/src/jdk.jstatd/share/classes/sun/tools/jstatd/RemoteHostImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2004, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,6 +26,7 @@ package sun.tools.jstatd; import java.util.*; +import java.util.concurrent.ConcurrentHashMap; import java.nio.*; import java.io.*; import java.net.*; @@ -52,13 +53,20 @@ public class RemoteHostImpl implements RemoteHost, HostListener { private Set activeVms; private static RemoteVm rvm; private final int rmiPort; + private final ObjectInputFilter filter; + private final Map exportedVMs = new ConcurrentHashMap<>(); public RemoteHostImpl() throws MonitorException { this(0); } public RemoteHostImpl(int rmiPort) throws MonitorException { + this(0, null); + } + + public RemoteHostImpl(int rmiPort, ObjectInputFilter filter) throws MonitorException { this.rmiPort = rmiPort; + this.filter = filter; try { monitoredHost = MonitoredHost.getMonitoredHost("localhost"); } catch (URISyntaxException e) { } @@ -78,7 +86,8 @@ public RemoteVm attachVm(int lvmid) throws RemoteException, MonitorException { VmIdentifier vmid = new VmIdentifier(vmidStr); MonitoredVm mvm = monitoredHost.getMonitoredVm(vmid); rvm = new RemoteVmImpl((BufferedMonitoredVm)mvm); - stub = (RemoteVm) UnicastRemoteObject.exportObject(rvm, rmiPort); + stub = (RemoteVm) UnicastRemoteObject.exportObject(rvm, rmiPort, filter); + exportedVMs.put(stub, (RemoteVmImpl) rvm); } catch (URISyntaxException e) { throw new RuntimeException("Malformed VmIdentifier URI: " @@ -88,7 +97,11 @@ public RemoteVm attachVm(int lvmid) throws RemoteException, MonitorException { } public void detachVm(RemoteVm rvm) throws RemoteException { - rvm.detach(); + // Do not invoke on caller-supplied Object. + RemoteVm v = exportedVMs.remove(rvm); + if (v != null) { + v.detach(); + } } public int[] activeVms() throws MonitorException { From 84ecb95e01c1ef7804c82d65cc199609bd466488 Mon Sep 17 00:00:00 2001 From: Phil Race Date: Tue, 16 Jun 2026 21:34:47 +0000 Subject: [PATCH 023/169] 8386187: Enhance font rendering Reviewed-by: psadhukhan, rhalade, jdv --- .../share/native/libfontmanager/freetypeScaler.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/java.desktop/share/native/libfontmanager/freetypeScaler.c b/src/java.desktop/share/native/libfontmanager/freetypeScaler.c index 3bd2b3d07ae5..b294d8f396d4 100644 --- a/src/java.desktop/share/native/libfontmanager/freetypeScaler.c +++ b/src/java.desktop/share/native/libfontmanager/freetypeScaler.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2007, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -864,10 +864,12 @@ static void CopyGrey4ToGrey8(const void* srcImage, int srcRowBytes, srcRow += srcRowBytes; dstRow += dstRowBytes; - for (i = 0; i < width; i++) { + for (i = 0; i < width; i += 2) { srcValue = *src8++; - *dstByte++ = Grey4ToAlpha255(srcValue & 0x0f); *dstByte++ = Grey4ToAlpha255(srcValue >> 4); + if (i < width-1) { + *dstByte++ = Grey4ToAlpha255(srcValue & 0x0f); + } } } } From be5205ef2667942a172ae9ed1c1fe00f2d56d7ff Mon Sep 17 00:00:00 2001 From: Michael McMahon Date: Mon, 22 Jun 2026 14:47:58 +0000 Subject: [PATCH 024/169] 8383745: Enhance HTTP Server Reviewed-by: dfuchs, rhalade, djelinski --- .../sun/net/httpserver/simpleserver/FileServerHandler.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/jdk.httpserver/share/classes/sun/net/httpserver/simpleserver/FileServerHandler.java b/src/jdk.httpserver/share/classes/sun/net/httpserver/simpleserver/FileServerHandler.java index 08ea357b7f4b..1da06ed9397c 100644 --- a/src/jdk.httpserver/share/classes/sun/net/httpserver/simpleserver/FileServerHandler.java +++ b/src/jdk.httpserver/share/classes/sun/net/httpserver/simpleserver/FileServerHandler.java @@ -25,6 +25,7 @@ package sun.net.httpserver.simpleserver; +import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -221,7 +222,8 @@ private Path mapToPath(HttpExchange exchange, Path root) { // resolve each path segment against the root Path path = root; for (var segment : pathSegment) { - if (!URIPathSegment.isSupported(segment)) { + if (!URIPathSegment.isSupported(segment) || segment.contains(File.separator)) { + // checking for separator rules out anomalies like UNCs on Windows return null; // stop resolution, null results in 404 response } path = path.resolve(segment); From a5142bd728911f1baf414eaf360a39893066b7d6 Mon Sep 17 00:00:00 2001 From: Jayathirth D V Date: Tue, 23 Jun 2026 06:39:10 +0000 Subject: [PATCH 025/169] 8386552: Enhance GIF loading Reviewed-by: rhalade, prr, psadhukhan --- .../sun/imageio/plugins/gif/GIFImageReader.java | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/gif/GIFImageReader.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/gif/GIFImageReader.java index 69c1bc63f3f6..e584769f3e8f 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/gif/GIFImageReader.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/gif/GIFImageReader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -35,6 +35,7 @@ import java.awt.image.MultiPixelPackedSampleModel; import java.awt.image.PixelInterleavedSampleModel; import java.awt.image.SampleModel; +import java.io.ByteArrayOutputStream; import java.io.EOFException; import java.io.IOException; import java.nio.ByteOrder; @@ -650,7 +651,8 @@ private int locateImage(int imageIndex) throws IIOException { // Read blocks of 1-255 bytes, stop at a 0-length block private byte[] concatenateBlocks() throws IOException { - byte[] data = new byte[0]; + ByteArrayOutputStream buffer = new ByteArrayOutputStream(255); + byte[] data = new byte[255]; while (true) { int length = stream.readUnsignedByte(); if (length == 0) { @@ -660,15 +662,10 @@ private byte[] concatenateBlocks() throws IOException { stream.skipBytes(length); continue; } - byte[] subBlockData = - ReaderUtil.staggeredReadByteStream(stream, length); - byte[] newData = new byte[data.length + length]; - System.arraycopy(data, 0, newData, 0, data.length); - System.arraycopy(subBlockData, 0, newData, - data.length, length); - data = newData; + stream.readFully(data, 0, length); + buffer.write(data, 0, length); } - return data; + return buffer.toByteArray(); } // Stream must be positioned at start of metadata for 'currIndex' From 5d6bd8f24d933a3a525d6199e177eb7b71b18e75 Mon Sep 17 00:00:00 2001 From: Kevin Walls Date: Tue, 23 Jun 2026 10:07:47 +0000 Subject: [PATCH 026/169] 8387075: RemoteHostImpl constructor correction (jstatd) Reviewed-by: dfuchs --- .../share/classes/sun/tools/jstatd/RemoteHostImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jdk.jstatd/share/classes/sun/tools/jstatd/RemoteHostImpl.java b/src/jdk.jstatd/share/classes/sun/tools/jstatd/RemoteHostImpl.java index 53ca1da7f05c..9f050fbbb99c 100644 --- a/src/jdk.jstatd/share/classes/sun/tools/jstatd/RemoteHostImpl.java +++ b/src/jdk.jstatd/share/classes/sun/tools/jstatd/RemoteHostImpl.java @@ -61,7 +61,7 @@ public RemoteHostImpl() throws MonitorException { } public RemoteHostImpl(int rmiPort) throws MonitorException { - this(0, null); + this(rmiPort, null); } public RemoteHostImpl(int rmiPort, ObjectInputFilter filter) throws MonitorException { From 01bf14b163282e2f1df5198e7d393f1473256a67 Mon Sep 17 00:00:00 2001 From: Jan Lahoda Date: Wed, 24 Jun 2026 04:59:50 +0000 Subject: [PATCH 027/169] 8384736: Enhance JShell Reviewed-by: rhalade, dbalek, asotona --- .../execution/JdiDefaultExecutionControl.java | 69 +++++++--- .../jdk/jshell/execution/JdiInitiator.java | 3 +- .../execution/RemoteExecutionControl.java | 40 +++++- .../execution/StreamingExecutionControl.java | 55 +++++--- .../jdk/jshell/RemoteAgentHandshakeTest.java | 126 ++++++++++++++++++ 5 files changed, 255 insertions(+), 38 deletions(-) create mode 100644 test/langtools/jdk/jshell/RemoteAgentHandshakeTest.java diff --git a/src/jdk.jshell/share/classes/jdk/jshell/execution/JdiDefaultExecutionControl.java b/src/jdk.jshell/share/classes/jdk/jshell/execution/JdiDefaultExecutionControl.java index 1df6dc5191b3..b4489a8071cb 100644 --- a/src/jdk.jshell/share/classes/jdk/jshell/execution/JdiDefaultExecutionControl.java +++ b/src/jdk.jshell/share/classes/jdk/jshell/execution/JdiDefaultExecutionControl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -36,7 +36,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.function.Consumer; import com.sun.jdi.BooleanValue; import com.sun.jdi.ClassNotLoadedException; import com.sun.jdi.Field; @@ -48,8 +47,12 @@ import com.sun.jdi.VMDisconnectedException; import com.sun.jdi.VirtualMachine; import java.io.PrintStream; +import java.security.SecureRandom; import java.time.Duration; +import java.util.Arrays; +import java.util.Base64; import java.util.Optional; +import java.util.concurrent.CompletableFuture; import jdk.jshell.JShellConsole; import jdk.jshell.execution.JdiDefaultExecutionControl.JdiStarter.TargetDescription; import jdk.jshell.spi.ExecutionControl; @@ -103,6 +106,7 @@ public class JdiDefaultExecutionControl extends JdiExecutionControl { * @throws IOException if there are errors in set-up */ static ExecutionControl create(ExecutionEnv env, Map parameters, String remoteAgent, int timeout, JdiStarter starter) throws IOException { + Process processToStop = null; try (final ServerSocket listener = new ServerSocket(0, 1, InetAddress.getLoopbackAddress())) { // timeout on I/O-socket listener.setSoTimeout(timeout); @@ -146,15 +150,30 @@ public void closeDown() { }; // Set-up the JDI connection - TargetDescription target = starter.start(augmentedEnv, parameters, port); + TargetDescription target = starter.start(augmentedEnv, parameters, -1); VirtualMachine vm = target.vm(); - Process process = target.process(); + Process process = processToStop = target.process(); + byte[] secret; + + OutputStream stdout = process.getOutputStream(); + + //send the port number: + stdout.write(("" + port + "\n").getBytes()); + stdout.flush(); + + //generate a secret that will help us validate the correct remote is attached: + secret = new byte[64]; + new SecureRandom().nextBytes(secret); + stdout.write((Base64.getEncoder().encodeToString(secret) + "\n").getBytes()); + stdout.flush(); + + CompletableFuture agentStopped = new CompletableFuture<>(); - List> deathListeners = new ArrayList<>(); Util.detectJdiExitEvent(vm, s -> { - for (Consumer h : deathListeners) { - h.accept(s); - } + agentStopped.complete(s); + }); + process.onExit().thenRun(() -> { + agentStopped.complete("Remote process died."); }); // Set-up the commands/reslts on the socket. Piggy-back snippet @@ -162,6 +181,17 @@ public void closeDown() { Socket socket = listener.accept(); // out before in -- match remote creation so we don't hang OutputStream out = socket.getOutputStream(); + InputStream in = socket.getInputStream(); + + socket.setSoTimeout(timeout); + + //verify the secret: + byte[] receivedSecret = in.readNBytes(secret.length); + + if (!Arrays.equals(secret, receivedSecret)) { + throw new IOException("Incorrect secret received."); + } + Map outputs = new HashMap<>(); outputs.put("out", env.userOut()); outputs.put("err", env.userErr()); @@ -176,10 +206,17 @@ public void closeDown() { outputs.put("consoleInput", consoleOutput); input.put("consoleOutput", consoleOutput.sinkInput); } - return remoteInputOutput(socket.getInputStream(), out, outputs, input, + ExecutionControl result = remoteInputOutput(in, out, outputs, input, (objIn, objOut) -> new JdiDefaultExecutionControl(env, - objOut, objIn, vm, process, remoteAgent, deathListeners)); - } + objOut, objIn, vm, process, remoteAgent, + agentStopped)); + processToStop = null; + return result; + } finally { + if (processToStop != null) { + processToStop.destroy(); + } +} } /** @@ -191,7 +228,7 @@ public void closeDown() { private JdiDefaultExecutionControl(ExecutionEnv env, ObjectOutput cmdout, ObjectInput cmdin, VirtualMachine vm, Process process, String remoteAgent, - List> deathListeners) { + CompletableFuture agentStopped) { super(cmdout, cmdin); this.vm = vm; this.process = process; @@ -199,8 +236,10 @@ private JdiDefaultExecutionControl(ExecutionEnv env, // We have now succeeded in establishing the connection. // If there is an exit now it propagates all the way up // and the VM should be disposed of. - deathListeners.add(s -> env.closeDown()); - deathListeners.add(s -> disposeVM()); + agentStopped.thenAccept(s -> { + env.closeDown(); + disposeVM(); + }); } /** @@ -356,7 +395,7 @@ public interface JdiStarter { * * @param env the execution context * @param parameters additional execution parameters - * @param port the port to which the remote process should connect + * @param port the value is always {@code -1} * @return a description of the started external process * @throws RuntimeException if the process cannot be started * @throws Error if the process cannot be started diff --git a/src/jdk.jshell/share/classes/jdk/jshell/execution/JdiInitiator.java b/src/jdk.jshell/share/classes/jdk/jshell/execution/JdiInitiator.java index fd908745d81d..13497f91edce 100644 --- a/src/jdk.jshell/share/classes/jdk/jshell/execution/JdiInitiator.java +++ b/src/jdk.jshell/share/classes/jdk/jshell/execution/JdiInitiator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -216,6 +216,7 @@ protected void runListenProcess(String jdiAddress, ProcessBuilder pb = new ProcessBuilder(args); pb.redirectError(crashErrorFile); pb.redirectOutput(crashOutputFile); + pb.redirectInput(ProcessBuilder.Redirect.PIPE); process = pb.start(); setupVM.processStarted(process); diff --git a/src/jdk.jshell/share/classes/jdk/jshell/execution/RemoteExecutionControl.java b/src/jdk.jshell/share/classes/jdk/jshell/execution/RemoteExecutionControl.java index 3add8dd7a2b3..75c6e17846cd 100644 --- a/src/jdk.jshell/share/classes/jdk/jshell/execution/RemoteExecutionControl.java +++ b/src/jdk.jshell/share/classes/jdk/jshell/execution/RemoteExecutionControl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -24,11 +24,15 @@ */ package jdk.jshell.execution; +import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; import java.io.OutputStream; import java.io.PrintStream; +import java.io.Reader; import java.lang.reflect.Method; import java.net.Socket; +import java.util.Base64; import java.util.HashMap; import java.util.Map; @@ -45,6 +49,12 @@ * {@link DirectExecutionControl} which adds stop support controlled by * an external process. Designed to work with {@link JdiDefaultExecutionControl}. * + *

    The initial handshake with {@link JdiDefaultExecutionControl} is as follows: + * the first command line argument is {@code -1}. The port number to which the + * agent should connect is passed on the first line on stdin. On the second line + * of stdin, an Base64-encoded byte array is sent, which should be sent to + * the socket immediately after the connection is established. + * * @author Jan Lahoda * @author Robert Field * @since 9 @@ -61,9 +71,24 @@ public class RemoteExecutionControl extends DirectExecutionControl implements Ex */ public static void main(String[] args) throws Exception { String loopBack = null; - Socket socket = new Socket(loopBack, Integer.parseInt(args[0])); + int commandLinePort = Integer.parseInt(args[0]); + int port; + byte[] secret; + if (commandLinePort == (-1)) { + Reader r = new InputStreamReader(System.in); + port = Integer.parseInt(readLine(r)); + secret = Base64.getDecoder().decode(readLine(r)); + } else { + port = commandLinePort; + secret = null; + } + Socket socket = new Socket(loopBack, port); InputStream inStream = socket.getInputStream(); OutputStream outStream = socket.getOutputStream(); + if (secret != null) { + outStream.write(secret); + outStream.flush(); + } Map> outputs = new HashMap<>(); outputs.put("out", st -> System.setOut(new PrintStream(st, true, System.out.charset()))); outputs.put("err", st -> System.setErr(new PrintStream(st, true, System.err.charset()))); @@ -74,6 +99,17 @@ public static void main(String[] args) throws Exception { forwardExecutionControlAndIO(new RemoteExecutionControl(), inStream, outStream, outputs, input); } + private static String readLine(Reader r) throws IOException { + StringBuilder line = new StringBuilder(); + int read; + + while ((read = r.read()) != (-1) && read != '\n') { + line.append((char)read); + } + + return line.toString(); + } + // These three variables are used by the main JShell process in interrupting // the running process. Access is via JDI, so the reference is not visible // to code inspection. diff --git a/src/jdk.jshell/share/classes/jdk/jshell/execution/StreamingExecutionControl.java b/src/jdk.jshell/share/classes/jdk/jshell/execution/StreamingExecutionControl.java index d4df40dac9a7..5f5372b780fc 100644 --- a/src/jdk.jshell/share/classes/jdk/jshell/execution/StreamingExecutionControl.java +++ b/src/jdk.jshell/share/classes/jdk/jshell/execution/StreamingExecutionControl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,6 +27,8 @@ import java.io.EOFException; import java.io.IOException; import java.io.ObjectInput; +import java.io.ObjectInputFilter; +import java.io.ObjectInputStream; import java.io.ObjectOutput; import jdk.jshell.JShellException; import jdk.jshell.spi.ExecutionControl; @@ -45,16 +47,23 @@ public class StreamingExecutionControl implements ExecutionControl { private final ObjectOutput out; private final ObjectInput in; + private final ObjectInputFilterImpl filter = new ObjectInputFilterImpl(); /** * Creates an instance. * * @param out the output for commands - * @param in the input for command responses + * @param in the input for command responses; must be an {@link ObjectInputStream} + * @throws IllegalArgumentException if {@code in} is not an {@linkplain ObjectInputStream} */ public StreamingExecutionControl(ObjectOutput out, ObjectInput in) { this.out = out; this.in = in; + if (in instanceof ObjectInputStream ois) { + ois.setObjectInputFilter(filter); + } else { + throw new IllegalArgumentException("The 'in' parameter must be an ObjectInputStream."); + } } @Override @@ -162,27 +171,16 @@ public void stop() } /** - * @throws ExecutionControl.UserException {@inheritDoc} - * @throws ExecutionControl.ResolutionException {@inheritDoc} - * @throws ExecutionControl.StoppedException {@inheritDoc} - * @throws ExecutionControl.EngineTerminationException {@inheritDoc} - * @throws ExecutionControl.NotImplementedException {@inheritDoc} + * {@inheritDoc} + * + * @apiNote This method always throws {@link NotImplementedException}. + * + * @throws ExecutionControl.NotImplementedException always thrown. */ @Override public Object extensionCommand(String command, Object arg) - throws RunException, EngineTerminationException, InternalException { - try { - writeCommand(command); - out.writeObject(arg); - out.flush(); - // Retrieve and report results from the remote agent. - readAndReportExecutionResult(); - Object result = in.readObject(); - return result; - } catch (IOException | ClassNotFoundException ex) { - throw new EngineTerminationException("Exception transmitting remote extensionCommand: " - + command + " -- " + ex); - } + throws NotImplementedException { + throw new NotImplementedException("extensionCommand not implemented."); } /** @@ -379,4 +377,21 @@ private ResolutionException readResolutionException() throws IOException, ClassN StackTraceElement[] elems = (StackTraceElement[]) in.readObject(); return new ResolutionException(id, elems); } + + private static final class ObjectInputFilterImpl implements ObjectInputFilter { + + @Override + public ObjectInputFilter.Status checkInput(ObjectInputFilter.FilterInfo filterInfo) { + if (filterInfo.serialClass() instanceof Class clazz) { + while (clazz.isArray()) { + clazz = clazz.componentType(); + } + return clazz == StackTraceElement.class || + clazz == boolean.class ? Status.ALLOWED + : Status.REJECTED; + } + + return Status.ALLOWED; + } + } } diff --git a/test/langtools/jdk/jshell/RemoteAgentHandshakeTest.java b/test/langtools/jdk/jshell/RemoteAgentHandshakeTest.java new file mode 100644 index 000000000000..0f0ba0c9a963 --- /dev/null +++ b/test/langtools/jdk/jshell/RemoteAgentHandshakeTest.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test 8384736 + * @summary Verify the correct handshake is used for remote agents. + * @run junit RemoteAgentHandshakeTest + */ + +import java.time.Duration; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import jdk.jfr.consumer.RecordingStream; +import jdk.jshell.JShell; +import jdk.jshell.execution.JdiExecutionControlProvider; +import jdk.jshell.execution.RemoteExecutionControl; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class RemoteAgentHandshakeTest { + + @Test + public void testCustomAgentOldHandshake() throws Exception { + assertThrows(IllegalStateException.class, () -> { + try (JShell jshell = + JShell.builder() + .executionEngine(new JdiExecutionControlProvider(), + Map.of(JdiExecutionControlProvider.PARAM_REMOTE_AGENT, + OldHandShake.class.getName())) + .build()) { + jshell.eval("1"); + } + }); + } + + @Test + public void testCustomAgentNewHandshake() throws Exception { + try (JShell jshell = + JShell.builder() + .executionEngine(new JdiExecutionControlProvider(), + Map.of(JdiExecutionControlProvider.PARAM_REMOTE_AGENT, + NewHandShake.class.getName())) + .build()) { + jshell.eval("1"); + } + } + + @Test + public void testDefaultAgentUsesNewHandshake() throws Exception { + try (var rs = new RecordingStream()) { + rs.enable("jdk.ProcessStart"); + rs.enable("jdk.CPULoad").withPeriod(Duration.ofMillis(10)); + + CompletableFuture command = new CompletableFuture<>(); + + rs.onEvent("jdk.ProcessStart", evt -> { + String commandLine = evt.getValue("command"); + + if (commandLine.contains("RemoteExecutionControl")) { + command.complete(commandLine); + } + }); + + CountDownLatch running = new CountDownLatch(1); + + rs.onEvent("jdk.CPULoad", evt -> { + running.countDown(); + }); + + rs.startAsync(); + + running.await(); + + try (JShell jshell = JShell.create()) { + jshell.eval("1"); + } + + String commandLine = command.get(10, TimeUnit.SECONDS); + if (!commandLine.endsWith(" -1")) { + throw new AssertionError("Does not contain a correct port: '" + commandLine + "'"); + } + } + } + + public static class OldHandShake extends RemoteExecutionControl { + public static void main(String[] args) throws Exception { + if (Integer.parseInt(args[0]) <= 0) { + throw new AssertionError("Unexpected port number!"); + } + + RemoteExecutionControl.main(args); + } + } + + public static class NewHandShake extends RemoteExecutionControl { + public static void main(String[] args) throws Exception { + if (Integer.parseInt(args[0]) != (-1)) { + throw new AssertionError("Unexpected port number!"); + } + + RemoteExecutionControl.main(args); + } + } +} From 70b65c2d2c7cae3d8cd054eae54554a121462a44 Mon Sep 17 00:00:00 2001 From: Jaikiran Pai Date: Wed, 24 Jun 2026 10:25:50 +0000 Subject: [PATCH 028/169] 8383047: Enhance LDAP logins Reviewed-by: rhalade, aefimov, djelinski, vyazici --- .../security/auth/module/JndiLoginModule.java | 157 ++++++++++-------- 1 file changed, 87 insertions(+), 70 deletions(-) diff --git a/src/jdk.security.auth/share/classes/com/sun/security/auth/module/JndiLoginModule.java b/src/jdk.security.auth/share/classes/com/sun/security/auth/module/JndiLoginModule.java index 4694134e3880..12a687f83d16 100644 --- a/src/jdk.security.auth/share/classes/com/sun/security/auth/module/JndiLoginModule.java +++ b/src/jdk.security.auth/share/classes/com/sun/security/auth/module/JndiLoginModule.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,12 +25,23 @@ package com.sun.security.auth.module; -import javax.security.auth.*; -import javax.security.auth.callback.*; -import javax.security.auth.login.*; -import javax.security.auth.spi.*; -import javax.naming.*; -import javax.naming.directory.*; +import javax.naming.InitialContext; +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; +import javax.naming.directory.Attribute; +import javax.naming.directory.Attributes; +import javax.naming.directory.BasicAttributes; +import javax.naming.directory.DirContext; +import javax.naming.directory.SearchResult; +import javax.security.auth.Subject; +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.NameCallback; +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.callback.UnsupportedCallbackException; +import javax.security.auth.login.FailedLoginException; +import javax.security.auth.login.LoginException; +import javax.security.auth.spi.LoginModule; import java.util.Map; import java.util.LinkedList; @@ -200,9 +211,8 @@ public class JndiLoginModule implements LoginModule { private static final String CRYPT = "{crypt}"; private static final String USER_PWD = "userPassword"; - private static final String USER_UID = "uidNumber"; - private static final String USER_GID = "gidNumber"; - private static final String GROUP_ID = "gidNumber"; + private static final String UID_NUMBER = "uidNumber"; + private static final String GID_NUMBER = "gidNumber"; private static final String NAME = "javax.security.auth.login.name"; private static final String PWD = "javax.security.auth.login.password"; @@ -381,7 +391,7 @@ public boolean login() throws LoginException { */ public boolean commit() throws LoginException { - if (succeeded == false) { + if (!succeeded) { return false; } else { if (subject.isReadOnly()) { @@ -389,11 +399,11 @@ public boolean commit() throws LoginException { throw new LoginException ("Subject is Readonly"); } // add Principals to the Subject - if (!subject.getPrincipals().contains(userPrincipal)) + if (userPrincipal != null && !subject.getPrincipals().contains(userPrincipal)) subject.getPrincipals().add(userPrincipal); - if (!subject.getPrincipals().contains(UIDPrincipal)) + if (UIDPrincipal != null && !subject.getPrincipals().contains(UIDPrincipal)) subject.getPrincipals().add(UIDPrincipal); - if (!subject.getPrincipals().contains(GIDPrincipal)) + if (GIDPrincipal != null && !subject.getPrincipals().contains(GIDPrincipal)) subject.getPrincipals().add(GIDPrincipal); for (int i = 0; i < supplementaryGroups.size(); i++) { if (!subject.getPrincipals().contains @@ -436,9 +446,9 @@ public boolean abort() throws LoginException { System.out.println("\t\t[JndiLoginModule]: " + "aborted authentication failed"); - if (succeeded == false) { + if (!succeeded) { return false; - } else if (succeeded == true && commitSucceeded == false) { + } else if (!commitSucceeded) { // Clean out state succeeded = false; @@ -510,34 +520,25 @@ public boolean logout() throws LoginException { * @param getPasswdFromSharedState boolean that tells this method whether * to retrieve the password from the sharedState. */ - private void attemptAuthentication(boolean getPasswdFromSharedState) - throws LoginException { - + private void attemptAuthentication(boolean getPasswdFromSharedState) throws LoginException { String encryptedPassword = null; - // first get the username and password getUsernamePassword(getPasswdFromSharedState); - + if (username == null) { + throw new FailedLoginException("No username provided"); + } try { - // get the user's passwd entry from the user provider URL InitialContext iCtx = new InitialContext(); ctx = (DirContext)iCtx.lookup(userProvider); - - /* - SearchControls controls = new SearchControls - (SearchControls.ONELEVEL_SCOPE, - 0, - 5000, - new String[] { USER_PWD }, - false, - false); - */ - - SearchControls controls = new SearchControls(); - NamingEnumeration ne = ctx.search("", - "(uid=" + username + ")", - controls); + // Search for the user with the given "uid" and obtain all their attributes. + // Then verify the user's password attribute matches with the one set on + // this LoginModule + NamingEnumeration ne = ctx.search("", new BasicAttributes("uid", username)); + // Create an exception instance beforehand, to use it in the case of a failed login due + // to a missing user or due to an incorrect credential. It's OK for it to go unused + // in the case of a successful authentication. + final FailedLoginException failedLogin = new FailedLoginException("Login failed"); if (ne.hasMore()) { SearchResult result = ne.next(); Attributes attributes = result.getAttributes(); @@ -559,14 +560,17 @@ private void attemptAuthentication(boolean getPasswdFromSharedState) // authentication for LDAP is recommended only for secure // channels. For nonsecure channels, SSL is recommended. + final boolean passwordMatches; Attribute pwd = attributes.get(USER_PWD); - String encryptedPwd = new String((byte[])pwd.get(), UTF_8); - encryptedPassword = encryptedPwd.substring(CRYPT.length()); - - // check the password - if (verifyPassword - (encryptedPassword, new String(password)) == true) { - + if (pwd == null) { + passwordMatches = false; + } else { + String encryptedPwd = new String((byte[]) pwd.get(), UTF_8); + encryptedPassword = encryptedPwd.substring(CRYPT.length()); + // check the password + passwordMatches = verifyPassword(encryptedPassword, new String(password)); + } + if (passwordMatches) { // authentication succeeded if (debug) System.out.println("\t\t[JndiLoginModule] " + @@ -577,7 +581,7 @@ private void attemptAuthentication(boolean getPasswdFromSharedState) if (debug) System.out.println("\t\t[JndiLoginModule] " + "attemptAuthentication() failed"); - throw new FailedLoginException("Login incorrect"); + throw failedLogin; } // save input as shared state only if @@ -592,27 +596,36 @@ private void attemptAuthentication(boolean getPasswdFromSharedState) // create the user principal userPrincipal = new UnixPrincipal(username); - // get the UID - Attribute uid = attributes.get(USER_UID); - String uidNumber = (String)uid.get(); - UIDPrincipal = new UnixNumericUserPrincipal(uidNumber); - if (debug && uidNumber != null) { - System.out.println("\t\t[JndiLoginModule] " + - "user: '" + username + "' has UID: " + + // get the uidNumber + Attribute uid = attributes.get(UID_NUMBER); + if (uid == null) { + UIDPrincipal = null; + } else { + String uidNumber = (String) uid.get(); + UIDPrincipal = uidNumber == null + ? null + : new UnixNumericUserPrincipal(uidNumber); + if (debug && UIDPrincipal != null) { + System.out.println("\t\t[JndiLoginModule] " + + "user: '" + username + "' has uidNumber: " + uidNumber); + } } - // get the GID - Attribute gid = attributes.get(USER_GID); - String gidNumber = (String)gid.get(); - GIDPrincipal = new UnixNumericGroupPrincipal - (gidNumber, true); - if (debug && gidNumber != null) { - System.out.println("\t\t[JndiLoginModule] " + - "user: '" + username + "' has GID: " + + Attribute gid = attributes.get(GID_NUMBER); + if (gid == null) { + GIDPrincipal = null; + } else { + String gidNumber = (String) gid.get(); + GIDPrincipal = gidNumber == null + ? null + : new UnixNumericGroupPrincipal(gidNumber, true); + if (debug && GIDPrincipal != null) { + System.out.println("\t\t[JndiLoginModule] " + + "user: '" + username + "' has gidNumber: " + gidNumber); + } } - // get the supplementary groups from the group provider URL ctx = (DirContext)iCtx.lookup(groupProvider); ne = ctx.search("", new BasicAttributes("memberUid", username)); @@ -621,13 +634,19 @@ private void attemptAuthentication(boolean getPasswdFromSharedState) result = ne.next(); attributes = result.getAttributes(); - gid = attributes.get(GROUP_ID); - String suppGid = (String)gid.get(); - if (!gidNumber.equals(suppGid)) { + gid = attributes.get(GID_NUMBER); + if (gid == null) { + continue; + } + String suppGid = (String) gid.get(); + if (suppGid == null) { + continue; + } + if (GIDPrincipal == null || !GIDPrincipal.getName().equals(suppGid)) { UnixNumericGroupPrincipal suppPrincipal = new UnixNumericGroupPrincipal(suppGid, false); supplementaryGroups.add(suppPrincipal); - if (debug && suppGid != null) { + if (debug) { System.out.println("\t\t[JndiLoginModule] " + "user: '" + username + "' has Supplementary Group: " + @@ -641,17 +660,15 @@ private void attemptAuthentication(boolean getPasswdFromSharedState) if (debug) { System.out.println("\t\t[JndiLoginModule]: User not found"); } - throw new FailedLoginException("User not found"); + throw failedLogin; } } catch (NamingException ne) { - // bad username if (debug) { - System.out.println("\t\t[JndiLoginModule]: User not found"); + System.out.println("\t\t[JndiLoginModule]: exception when finding user"); ne.printStackTrace(); } - throw new FailedLoginException("User not found"); + throw new FailedLoginException("Login failed"); } - // authentication succeeded } From da7c28fa49a8e9486c53a41961a4286b770696a3 Mon Sep 17 00:00:00 2001 From: Justin Lu Date: Wed, 24 Jun 2026 16:31:37 +0000 Subject: [PATCH 029/169] 8386783: WebRowSet empty elements read value of previous element Reviewed-by: naoto, joehw, lancea --- .../rowset/RowSetResourceBundle.properties | 4 ---- .../rowset/internal/WebRowSetXmlReader.java | 8 ++++++-- .../internal/XmlReaderContentHandler.java | 19 ++++++++++++++----- .../com/sun/rowset/internal/XmlResolver.java | 10 +++++----- .../webrowset/CommonWebRowSetTests.java | 6 +++++- 5 files changed, 30 insertions(+), 17 deletions(-) diff --git a/src/java.sql.rowset/share/classes/com/sun/rowset/RowSetResourceBundle.properties b/src/java.sql.rowset/share/classes/com/sun/rowset/RowSetResourceBundle.properties index ecb246e24463..54ae42cf991a 100644 --- a/src/java.sql.rowset/share/classes/com/sun/rowset/RowSetResourceBundle.properties +++ b/src/java.sql.rowset/share/classes/com/sun/rowset/RowSetResourceBundle.properties @@ -138,10 +138,6 @@ syncrsimpl.valtores = Value to be resolved can either be in the database or in c #WebRowSetXmlReader exception wrsxmlreader.readxml = readXML : {0} -wrsxmlreader.stdschema = readXML : Cannot locate standard WebRowSet schema: {0}. -wrsxmlreader.schemaloc = readXML : Malformed xsi:schemaLocation: {0}. -wrsxmlreader.schema = readXML : Non-standard schema specified for namespace {0} : {1}. -wrsxmlreader.entity = readXML : external entity {0} other than the standard schema is not allowed. #WebRowSetXmlWriter exceptions wrsxmlwriter.ioex = IOException : {0} diff --git a/src/java.sql.rowset/share/classes/com/sun/rowset/internal/WebRowSetXmlReader.java b/src/java.sql.rowset/share/classes/com/sun/rowset/internal/WebRowSetXmlReader.java index 2c105bd4ae88..67d0db98c7f1 100644 --- a/src/java.sql.rowset/share/classes/com/sun/rowset/internal/WebRowSetXmlReader.java +++ b/src/java.sql.rowset/share/classes/com/sun/rowset/internal/WebRowSetXmlReader.java @@ -54,6 +54,11 @@ public class WebRowSetXmlReader implements XmlReader, Serializable { static final String WEBROWSET_XSD = "webrowset.xsd"; private static final String VALIDATION_PROPERTY = "jdk.sql.rowset.webrowsetValidation"; + // Error messages + private static final String STD_SCHEMA_MESSAGE = + "readXML : Cannot locate standard WebRowSet schema: %s.".formatted(WEBROWSET_XSD); + + private JdbcRowSetResourceBundle resBundle; public WebRowSetXmlReader(){ @@ -167,8 +172,7 @@ private void readXml(WebRowSet caller, InputSource is) throws SQLException { private Schema getSchema() throws SAXException { URL stdSchema = WebRowSet.class.getResource(WEBROWSET_XSD); if (stdSchema == null) { - throw new SAXException(MessageFormat.format(resBundle.handleGetObject( - "wrsxmlreader.stdschema").toString(), WEBROWSET_XSD)); + throw new SAXException(STD_SCHEMA_MESSAGE); } SchemaFactory sf = SchemaFactory.newDefaultInstance(); diff --git a/src/java.sql.rowset/share/classes/com/sun/rowset/internal/XmlReaderContentHandler.java b/src/java.sql.rowset/share/classes/com/sun/rowset/internal/XmlReaderContentHandler.java index c70a7bc142a2..8e1222a889ff 100644 --- a/src/java.sql.rowset/share/classes/com/sun/rowset/internal/XmlReaderContentHandler.java +++ b/src/java.sql.rowset/share/classes/com/sun/rowset/internal/XmlReaderContentHandler.java @@ -84,7 +84,7 @@ public class XmlReaderContentHandler extends DefaultHandler { private String propertyValue; private String metaDataValue; - private int tag; + private int tag = -1; private int state; private WebRowSetImpl rs; @@ -386,6 +386,10 @@ public class XmlReaderContentHandler extends DefaultHandler { private static final String XSI_NS = "http://www.w3.org/2001/XMLSchema-instance"; + // Error messages + private static final String SCHEMA_LOC_MESSAGE = "readXML : Malformed xsi:schemaLocation: %s."; + private static final String SCHEMA_MESSAGE = "readXML : Non-standard schema specified for namespace %s : %s."; + /** * A constant indicating the state of this XmlReaderContentHandler * object in which it has not yet been called by the SAX parser and therefore @@ -660,8 +664,7 @@ private void checkSchemaLocation(Attributes attributes) throws SAXException { String[] tokens = schemaLocation.trim().split("\\s+"); if (tokens.length % 2 != 0) { - throw new SAXException(MessageFormat.format( - resBundle.handleGetObject("wrsxmlreader.schemaloc").toString(), schemaLocation)); + throw new SAXException(SCHEMA_LOC_MESSAGE.formatted(schemaLocation)); } for (int i = 0; i < tokens.length; i += 2) { @@ -669,8 +672,7 @@ private void checkSchemaLocation(Attributes attributes) throws SAXException { String xsd = tokens[i + 1]; if (!isWebRowSetSchema(xsd)) { - throw new SAXException(MessageFormat.format( - resBundle.handleGetObject("wrsxmlreader.schema").toString(), ns, xsd)); + throw new SAXException(SCHEMA_MESSAGE.formatted(ns, xsd)); } } } @@ -941,6 +943,13 @@ public void characters(char[] ch, int start, int length) throws SAXException { try { switch (getState()) { case PROPERTIES: + + // If tag is -1, startElement has not registered a valid tag + // Thus, we are in whitespace between elements, and should exit + if (tag == -1) { + break; + } + propertyValue = new String(ch, start, length); /** diff --git a/src/java.sql.rowset/share/classes/com/sun/rowset/internal/XmlResolver.java b/src/java.sql.rowset/share/classes/com/sun/rowset/internal/XmlResolver.java index 856d7d734150..96edbfa1f195 100644 --- a/src/java.sql.rowset/share/classes/com/sun/rowset/internal/XmlResolver.java +++ b/src/java.sql.rowset/share/classes/com/sun/rowset/internal/XmlResolver.java @@ -25,7 +25,6 @@ package com.sun.rowset.internal; -import com.sun.rowset.JdbcRowSetResourceBundle; import org.xml.sax.*; import org.xml.sax.EntityResolver; @@ -34,7 +33,6 @@ import javax.sql.rowset.WebRowSet; import java.io.IOException; import java.io.StringReader; -import java.text.MessageFormat; /** * An implementation of the EntityResolver interface, which @@ -44,6 +42,10 @@ public class XmlResolver implements EntityResolver { //The standard WebRowSet XML Schema as defined in WebRowSet public static final String STD_SCHEMA_ID = "http://xmlns.jcp.org/xml/ns//jdbc/webrowset.xsd"; + // Error messages + private static final String ENTITY_MESSAGE = + "readXML : external entity %s other than the standard schema is not allowed."; + @Override public InputSource resolveEntity(String publicId, String systemId) @@ -57,8 +59,6 @@ public InputSource resolveEntity(String publicId, String systemId) } // reports error upon any external entity other than the standard schema - throw new SAXException(MessageFormat.format( - JdbcRowSetResourceBundle.getJdbcRowSetResourceBundle().handleGetObject( - "wrsxmlreader.entity").toString(), systemId)); + throw new SAXException(ENTITY_MESSAGE.formatted(systemId)); } } diff --git a/test/jdk/javax/sql/test/rowset/webrowset/CommonWebRowSetTests.java b/test/jdk/javax/sql/test/rowset/webrowset/CommonWebRowSetTests.java index d8f1ea4ad584..79839c223240 100644 --- a/test/jdk/javax/sql/test/rowset/webrowset/CommonWebRowSetTests.java +++ b/test/jdk/javax/sql/test/rowset/webrowset/CommonWebRowSetTests.java @@ -578,10 +578,14 @@ public void testLegacyXmlWithValidation(WebRowSet wrs) @MethodSource("rowSetType") public void testLegacyXmlWithoutValidation(WebRowSet wrs) throws Exception { - String legacyXml = getLegacyXml(); + String legacyXml = getLegacyXml() + // Exercise the path where an empty element follows a value bearing + // element. This is for JDK-8386783 + .replace("jdbc:derby://localhost:1527/testDB;create=true", ""); withRowSetValidation("false", () -> { try { wrs.readXml(new StringReader(legacyXml)); + assertNull(wrs.getUrl()); assertEquals(COFFEES_ROWS, wrs.size()); assertArrayEquals(COFFEES_PRIMARY_KEYS, getPrimaryKeys(wrs)); From 7d3ef9a8c0e825089f37206234fe51d9bfd7d426 Mon Sep 17 00:00:00 2001 From: Joe Wang Date: Thu, 25 Jun 2026 16:47:04 +0000 Subject: [PATCH 030/169] 8384031: Enhance XML Processing Reviewed-by: rhalade, naoto, lancea, jlu, aefimov --- .../impl/xs/SchemaNamespaceSupport.java | 7 +- .../internal/util/NamespaceSupport.java | 185 +++++++++--------- .../MultipleScopeNamespaceSupport.java | 52 ++--- 3 files changed, 118 insertions(+), 126 deletions(-) diff --git a/src/java.xml/share/classes/com/sun/org/apache/xerces/internal/impl/xs/SchemaNamespaceSupport.java b/src/java.xml/share/classes/com/sun/org/apache/xerces/internal/impl/xs/SchemaNamespaceSupport.java index aca31d1e7704..79577c3ef509 100644 --- a/src/java.xml/share/classes/com/sun/org/apache/xerces/internal/impl/xs/SchemaNamespaceSupport.java +++ b/src/java.xml/share/classes/com/sun/org/apache/xerces/internal/impl/xs/SchemaNamespaceSupport.java @@ -1,6 +1,5 @@ /* - * reserved comment block - * DO NOT REMOVE OR ALTER! + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. */ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -32,6 +31,7 @@ * * @author Neil Graham, IBM * + * @LastModified: June 2026 */ public class SchemaNamespaceSupport extends NamespaceSupport { @@ -50,6 +50,7 @@ public SchemaNamespaceSupport(SchemaNamespaceSupport nSupport) { if (fContext.length <= fCurrentContext) fContext = new int[fCurrentContext+1]; System.arraycopy(nSupport.fContext, 0, fContext, 0, fCurrentContext+1); + rebuildNamespaceTable(); } // end constructor /** @@ -76,6 +77,7 @@ public void setEffectiveContext (String [] namespaceDecls) { System.arraycopy(namespaceDecls, 0, fNamespace, fNamespaceSize, namespaceDecls.length); fNamespaceSize = newSize; + rebuildNamespaceTable(); } // setEffectiveContext(String):void /** @@ -108,6 +110,7 @@ public void makeGlobal() { if (fCurrentContext >= 3) { fCurrentContext = 3; fNamespaceSize = fContext[3]; + rebuildNamespaceTable(); } } // makeGlobal } // class NamespaceSupport diff --git a/src/java.xml/share/classes/com/sun/org/apache/xerces/internal/util/NamespaceSupport.java b/src/java.xml/share/classes/com/sun/org/apache/xerces/internal/util/NamespaceSupport.java index 05eba5b2560c..30cfb3d7db49 100644 --- a/src/java.xml/share/classes/com/sun/org/apache/xerces/internal/util/NamespaceSupport.java +++ b/src/java.xml/share/classes/com/sun/org/apache/xerces/internal/util/NamespaceSupport.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2026, Oracle and/or its affiliates. All rights reserved. */ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -23,6 +23,7 @@ import com.sun.org.apache.xerces.internal.xni.NamespaceContext; import java.util.ArrayList; import java.util.Enumeration; +import java.util.IdentityHashMap; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; @@ -35,10 +36,17 @@ * * @author Andy Clark, IBM * - * @LastModified: Oct 2017 + * @LastModified: June 2026 */ public class NamespaceSupport implements NamespaceContext { + // + // Constants + // + + /** No previous active namespace declaration exists for the prefix. */ + protected static final int NO_PREVIOUS_DECLARATION = -1; + // // Data // @@ -57,6 +65,17 @@ public class NamespaceSupport implements NamespaceContext { /** The top of the namespace information array. */ protected int fNamespaceSize; + /** + * Previous active declaration for the prefix at the same tuple index in + * {@link #fNamespace}, or {@link #NO_PREVIOUS_DECLARATION} if none exists. + * This allows {@link #popContext()} to restore {@link #fNamespaceTable} + * to the previous active binding without searching ancestor contexts. + */ + protected int[] fNamespacePrev = new int[16]; + + /** Active namespace declaration indexes, keyed by prefix identity. */ + protected IdentityHashMap fNamespaceTable = new IdentityHashMap<>(); + // NOTE: The constructor depends on the initial context size // being at least 1. -Ac @@ -111,14 +130,19 @@ public void reset() { // reset namespace and context info fNamespaceSize = 0; fCurrentContext = 0; + fNamespaceTable.clear(); // bind "xml" prefix to the XML uri + fNamespacePrev[0] = NO_PREVIOUS_DECLARATION; fNamespace[fNamespaceSize++] = XMLSymbols.PREFIX_XML; fNamespace[fNamespaceSize++] = NamespaceContext.XML_URI; + fNamespaceTable.put(XMLSymbols.PREFIX_XML, 0); // bind "xmlns" prefix to the XMLNS uri + fNamespacePrev[1] = NO_PREVIOUS_DECLARATION; fNamespace[fNamespaceSize++] = XMLSymbols.PREFIX_XMLNS; fNamespace[fNamespaceSize++] = NamespaceContext.XMLNS_URI; + fNamespaceTable.put(XMLSymbols.PREFIX_XMLNS, 2); fContext[fCurrentContext] = fNamespaceSize; //++fCurrentContext; @@ -148,8 +172,19 @@ public void pushContext() { * @see com.sun.org.apache.xerces.internal.xni.NamespaceContext#popContext() */ public void popContext() { - fNamespaceSize = fContext[fCurrentContext--]; - //System.out.println("Calling popContext, fCurrentContext = " + fCurrentContext); + final int context = fContext[fCurrentContext]; + for (int prefixIndex = fNamespaceSize - 2; prefixIndex >= context; prefixIndex -= 2) { + final String prefix = fNamespace[prefixIndex]; + final int prev = fNamespacePrev[prefixIndex >> 1]; + if (prev == NO_PREVIOUS_DECLARATION) { + fNamespaceTable.remove(prefix); + } + else { + fNamespaceTable.put(prefix, prev); + } + } + fNamespaceSize = context; + fCurrentContext--; } // popContext() /** @@ -161,18 +196,14 @@ public boolean declarePrefix(String prefix, String uri) { return false; } + final int activeIndex = fNamespaceTable.getOrDefault(prefix, NO_PREVIOUS_DECLARATION); + // see if prefix already exists in current context - for (int i = fNamespaceSize; i > fContext[fCurrentContext]; i -= 2) { - if (fNamespace[i - 2] == prefix) { - // REVISIT: [Q] Should the new binding override the - // previously declared binding or should it - // it be ignored? -Ac - // NOTE: The SAX2 "NamespaceSupport" helper allows - // re-bindings with the new binding overwriting - // the previous binding. -Ac - fNamespace[i - 1] = uri; - return true; - } + if (activeIndex >= fContext[fCurrentContext]) { + // A duplicate declaration in the current context overwrites the + // existing binding in place, preserving the active table entry. + fNamespace[activeIndex + 1] = uri; + return true; } // resize array, if needed @@ -180,9 +211,15 @@ public boolean declarePrefix(String prefix, String uri) { String[] namespacearray = new String[fNamespaceSize * 2]; System.arraycopy(fNamespace, 0, namespacearray, 0, fNamespaceSize); fNamespace = namespacearray; + + int[] namespacePrev = new int[namespacearray.length >> 1]; + System.arraycopy(fNamespacePrev, 0, namespacePrev, 0, fNamespacePrev.length); + fNamespacePrev = namespacePrev; } // bind prefix to uri in current context + fNamespacePrev[fNamespaceSize >> 1] = activeIndex; + fNamespaceTable.put(prefix, fNamespaceSize); fNamespace[fNamespaceSize++] = prefix; fNamespace[fNamespaceSize++] = uri; @@ -190,20 +227,30 @@ public boolean declarePrefix(String prefix, String uri) { } // declarePrefix(String,String):boolean + /** + * Rebuilds the active namespace table from {@link #fNamespace}. Subclasses + * that directly replace or truncate namespace tuples must call this method. + */ + protected void rebuildNamespaceTable() { + if (fNamespacePrev.length < (fNamespace.length >> 1)) { + fNamespacePrev = new int[fNamespace.length >> 1]; + } + fNamespaceTable.clear(); + for (int i = 0; i < fNamespaceSize; i += 2) { + final String prefix = fNamespace[i]; + fNamespacePrev[i >> 1] = fNamespaceTable.getOrDefault(prefix, NO_PREVIOUS_DECLARATION); + fNamespaceTable.put(prefix, i); + } + } + /** * @see com.sun.org.apache.xerces.internal.xni.NamespaceContext#getURI(String) */ public String getURI(String prefix) { // find prefix in current context - for (int i = fNamespaceSize; i > 0; i -= 2) { - if (fNamespace[i - 2] == prefix) { - return fNamespace[i - 1]; - } - } - - // prefix not found - return null; + final int active = fNamespaceTable.getOrDefault(prefix, NO_PREVIOUS_DECLARATION); + return active != NO_PREVIOUS_DECLARATION ? fNamespace[active + 1] : null; } // getURI(String):String @@ -241,71 +288,46 @@ public String getDeclaredPrefixAt(int index) { } // getDeclaredPrefixAt(int):String public Iterator getPrefixes(){ - int count = 0; - if (fPrefixes.length < (fNamespace.length/2)) { - // resize prefix array - String[] prefixes = new String[fNamespaceSize]; - fPrefixes = prefixes; - } - String prefix = null; - boolean unique = true; - for (int i = 2; i < (fNamespaceSize-2); i += 2) { - prefix = fNamespace[i + 2]; - for (int k=0;k getAllPrefixes() { - int count = 0; - if (fPrefixes.length < (fNamespace.length/2)) { - // resize prefix array - String[] prefixes = new String[fNamespaceSize]; - fPrefixes = prefixes; - } - String prefix = null; - boolean unique = true; - for (int i = 2; i < (fNamespaceSize-2); i += 2) { - prefix = fNamespace[i + 2]; - for (int k=0;k getPrefixes(String uri){ - int count = 0; - String prefix = null; - boolean unique = true; List prefixList = new ArrayList<>(); - for (int i = fNamespaceSize; i >0 ; i -= 2) { - if(fNamespace[i-1] == uri){ - if(!prefixList.contains(fNamespace[i-2])) - prefixList.add(fNamespace[i-2]); + for (int i = fNamespaceSize; i > 0; i -= 2) { + final int prefixIndex = i - 2; + if (fNamespace[prefixIndex + 1] == uri) { + final String prefix = fNamespace[prefixIndex]; + if (fNamespaceTable.getOrDefault(prefix, NO_PREVIOUS_DECLARATION) == prefixIndex) { + prefixList.add(prefix); + } } } return prefixList; } + protected int collectPrefixes(int start, int end) { + if (fPrefixes.length < (fNamespace.length / 2)) { + // resize prefix array + fPrefixes = new String[fNamespace.length / 2]; + } + int count = 0; + for (int i = start; i < end; i += 2) { + if (fNamespacePrev[i >> 1] < start) { + fPrefixes[count++] = fNamespace[i]; + } + } + return count; + } + /* * non-NamespaceContext methods */ @@ -321,14 +343,7 @@ public List getPrefixes(String uri){ public boolean containsPrefix(String prefix) { // find prefix in context - for (int i = fNamespaceSize; i > 0; i -= 2) { - if (fNamespace[i - 2] == prefix) { - return true; - } - } - - // prefix not found - return false; + return fNamespaceTable.containsKey(prefix); } /** @@ -340,16 +355,8 @@ public boolean containsPrefix(String prefix) { * @return true if the given prefix exists in the current context */ public boolean containsPrefixInCurrentContext(String prefix) { - // find prefix in current context - for (int i = fContext[fCurrentContext]; i < fNamespaceSize; i += 2) { - if (fNamespace[i] == prefix) { - return true; - } - } - - // prefix not found - return false; + return fNamespaceTable.getOrDefault(prefix, NO_PREVIOUS_DECLARATION) >= fContext[fCurrentContext]; } protected final class IteratorPrefixes implements Iterator { diff --git a/src/java.xml/share/classes/com/sun/org/apache/xerces/internal/xinclude/MultipleScopeNamespaceSupport.java b/src/java.xml/share/classes/com/sun/org/apache/xerces/internal/xinclude/MultipleScopeNamespaceSupport.java index 7b506b72fa66..3d29a7466fc5 100644 --- a/src/java.xml/share/classes/com/sun/org/apache/xerces/internal/xinclude/MultipleScopeNamespaceSupport.java +++ b/src/java.xml/share/classes/com/sun/org/apache/xerces/internal/xinclude/MultipleScopeNamespaceSupport.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2026, Oracle and/or its affiliates. All rights reserved. */ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -37,7 +37,7 @@ * * @author Peter McCracken, IBM * - * @LastModified: Oct 2017 + * @LastModified: June 2026 */ public class MultipleScopeNamespaceSupport extends NamespaceSupport { @@ -66,29 +66,7 @@ public MultipleScopeNamespaceSupport(NamespaceContext context) { * @see com.sun.org.apache.xerces.internal.xni.NamespaceContext#getAllPrefixes() */ public Enumeration getAllPrefixes() { - int count = 0; - if (fPrefixes.length < (fNamespace.length / 2)) { - // resize prefix array - String[] prefixes = new String[fNamespaceSize]; - fPrefixes = prefixes; - } - String prefix = null; - boolean unique = true; - for (int i = fContext[fScope[fCurrentScope]]; - i <= (fNamespaceSize - 2); - i += 2) { - prefix = fNamespace[i]; - for (int k = 0; k < count; k++) { - if (fPrefixes[k] == prefix) { - unique = false; - break; - } - } - if (unique) { - fPrefixes[count++] = prefix; - } - unique = true; - } + int count = collectPrefixes(fContext[fScope[fCurrentScope]], fNamespaceSize); return new Prefixes(fPrefixes, count); } @@ -131,10 +109,10 @@ public String getPrefix(String uri, int start, int end) { return XMLSymbols.PREFIX_XMLNS; } - // find uri in current context + // find uri in the requested context range within the current scope for (int i = start; i > end; i -= 2) { if (fNamespace[i - 1] == uri) { - if (getURI(fNamespace[i - 2]) == uri) + if (getURI(fNamespace[i - 2], start, end) == uri) return fNamespace[i - 2]; } } @@ -152,15 +130,18 @@ public String getURI(String prefix, int start, int end) { return NamespaceContext.XMLNS_URI; } - // find prefix in current context - for (int i = start; i > end; i -= 2) { - if (fNamespace[i - 2] == prefix) { - return fNamespace[i - 1]; - } - } + int prefixIndex = getPrefixIndex(prefix, start, end); + return prefixIndex != NO_PREVIOUS_DECLARATION + ? fNamespace[prefixIndex + 1] : null; + } - // prefix not found - return null; + private int getPrefixIndex(String prefix, int start, int end) { + final Integer active = fNamespaceTable.get(prefix); + int prefixIndex = active != null ? active : NO_PREVIOUS_DECLARATION; + while (prefixIndex >= start) { + prefixIndex = fNamespacePrev[prefixIndex >> 1]; + } + return prefixIndex >= end ? prefixIndex : NO_PREVIOUS_DECLARATION; } /** @@ -170,6 +151,7 @@ public String getURI(String prefix, int start, int end) { public void reset() { fCurrentContext = fScope[fCurrentScope]; fNamespaceSize = fContext[fCurrentContext]; + rebuildNamespaceTable(); } /** From 9f4e850777aa60db3ebbcdfaa91fe58af53d87b8 Mon Sep 17 00:00:00 2001 From: Jan Lahoda Date: Fri, 26 Jun 2026 02:37:41 +0000 Subject: [PATCH 031/169] 8387235: JShell tests failing Reviewed-by: dbalek, asotona --- .../jdk/jshell/execution/JdiDefaultExecutionControl.java | 2 ++ test/langtools/jdk/jshell/RemoteAgentHandshakeTest.java | 2 ++ test/langtools/jdk/jshell/UserJdiUserRemoteTest.java | 5 +++-- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/jdk.jshell/share/classes/jdk/jshell/execution/JdiDefaultExecutionControl.java b/src/jdk.jshell/share/classes/jdk/jshell/execution/JdiDefaultExecutionControl.java index b4489a8071cb..49521748b3b8 100644 --- a/src/jdk.jshell/share/classes/jdk/jshell/execution/JdiDefaultExecutionControl.java +++ b/src/jdk.jshell/share/classes/jdk/jshell/execution/JdiDefaultExecutionControl.java @@ -192,6 +192,8 @@ public void closeDown() { throw new IOException("Incorrect secret received."); } + socket.setSoTimeout(0); + Map outputs = new HashMap<>(); outputs.put("out", env.userOut()); outputs.put("err", env.userErr()); diff --git a/test/langtools/jdk/jshell/RemoteAgentHandshakeTest.java b/test/langtools/jdk/jshell/RemoteAgentHandshakeTest.java index 0f0ba0c9a963..bb44ff275bb4 100644 --- a/test/langtools/jdk/jshell/RemoteAgentHandshakeTest.java +++ b/test/langtools/jdk/jshell/RemoteAgentHandshakeTest.java @@ -49,6 +49,7 @@ public void testCustomAgentOldHandshake() throws Exception { .executionEngine(new JdiExecutionControlProvider(), Map.of(JdiExecutionControlProvider.PARAM_REMOTE_AGENT, OldHandShake.class.getName())) + .remoteVMOptions("-classpath", System.getProperty("test.classes")) .build()) { jshell.eval("1"); } @@ -62,6 +63,7 @@ public void testCustomAgentNewHandshake() throws Exception { .executionEngine(new JdiExecutionControlProvider(), Map.of(JdiExecutionControlProvider.PARAM_REMOTE_AGENT, NewHandShake.class.getName())) + .remoteVMOptions("-classpath", System.getProperty("test.classes")) .build()) { jshell.eval("1"); } diff --git a/test/langtools/jdk/jshell/UserJdiUserRemoteTest.java b/test/langtools/jdk/jshell/UserJdiUserRemoteTest.java index 094c705bce64..28edb4c26b05 100644 --- a/test/langtools/jdk/jshell/UserJdiUserRemoteTest.java +++ b/test/langtools/jdk/jshell/UserJdiUserRemoteTest.java @@ -37,6 +37,7 @@ import jdk.jshell.spi.ExecutionControl; import jdk.jshell.spi.ExecutionControl.ExecutionControlException; import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -63,8 +64,8 @@ public void testVarValue() { @Test public void testExtension() throws ExecutionControlException { assertEval("42;"); - Object res = currentEC.extensionCommand("FROG", "test"); - assertEquals("ribbit", res); + Assertions.assertThrows(ExecutionControl.NotImplementedException.class, + () -> currentEC.extensionCommand("FROG", "test")); } @Test From e0b15f85dfeeb7ff740fd3fc4eda8d3b48ba208d Mon Sep 17 00:00:00 2001 From: Volkan Yazici Date: Fri, 26 Jun 2026 06:58:11 +0000 Subject: [PATCH 032/169] 8386033: Enhance QUIC connections Reviewed-by: rhalade, aefimov, skoivu, dfuchs, djelinski --- .../net/http/quic/QuicConnectionImpl.java | 21 +++++++++++++------ .../net/http/quic/frames/AckFrame.java | 4 ++-- .../net/http/quic/frames/StreamFrame.java | 9 +++++++- .../http/quic/packets/QuicPacketDecoder.java | 20 +++++++++--------- 4 files changed, 35 insertions(+), 19 deletions(-) diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicConnectionImpl.java b/src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicConnectionImpl.java index ed14fc49f19d..f225afe6d0b1 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicConnectionImpl.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicConnectionImpl.java @@ -1122,12 +1122,16 @@ public void increaseProcessedData(long diff) { } private long bumpMaxData() { - long newMaxData = processedData + desiredBufferSize; + var processedData = this.processedData; + long newMaxData = processedData >= MAX_VL_INTEGER - desiredBufferSize + ? MAX_VL_INTEGER + : processedData + desiredBufferSize; long maxData = this.maxData; - if (newMaxData - maxData < (desiredBufferSize / 5)) { - return 0; - } - while (maxData < newMaxData) { + while (maxData < newMaxData) { // CAS-loop on `maxData` + if (newMaxData != MAX_VL_INTEGER && // Do not skip the final clamp-to-max bump + newMaxData - maxData < (desiredBufferSize / 5)) { // Skip small ordinary bumps + break; + } if (MAX_RCV_DATA.compareAndSet(this, maxData, newMaxData)) return newMaxData; maxData = this.maxData; @@ -1136,7 +1140,12 @@ private long bumpMaxData() { } public boolean needSendMaxData() { - return maxData - processedData < desiredBufferSize/2; + long maxData = this.maxData; + // `maxData` is the value that was already sent to the peer. + // `bumpMaxData()` clamps `maxData` at `MAX_VL_INTEGER`. + // Once `maxData == MAX_VL_INTEGER`, there is no larger legal value to advertise. + return maxData < MAX_VL_INTEGER && + maxData - processedData < (desiredBufferSize / 2); } String logTag() { return logTag.get(); } diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/quic/frames/AckFrame.java b/src/java.net.http/share/classes/jdk/internal/net/http/quic/frames/AckFrame.java index 09d05f910661..338e690ce0e3 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/quic/frames/AckFrame.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/quic/frames/AckFrame.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -84,7 +84,7 @@ public final class AckFrame extends QuicFrame { null, type, QuicTransportErrors.FRAME_ENCODING_ERROR); } - var ackRanges = new ArrayList(ackRangeCount + 1); + var ackRanges = new ArrayList(); AckRange first = AckRange.of(0, firstAckRange); ackRanges.addFirst(first); for (int i=1; i <= ackRangeCount; i++) { diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/quic/frames/StreamFrame.java b/src/java.net.http/share/classes/jdk/internal/net/http/quic/frames/StreamFrame.java index b597e53c06c3..693eb059815c 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/quic/frames/StreamFrame.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/quic/frames/StreamFrame.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -29,7 +29,9 @@ import java.util.Objects; import jdk.internal.net.http.common.Utils; +import jdk.internal.net.http.quic.VariableLengthEncoder; import jdk.internal.net.http.quic.packets.QuicPacketEncoder; +import jdk.internal.net.quic.QuicTransportErrors; import jdk.internal.net.quic.QuicTransportException; /** @@ -69,6 +71,11 @@ public final class StreamFrame extends QuicFrame { } else { length = -1; } + if (length > VariableLengthEncoder.MAX_ENCODED_INTEGER - offset) { + throw new QuicTransportException( + "Maximum stream frame offset exceeded", + null, type, QuicTransportErrors.FRAME_ENCODING_ERROR); + } if (length == -1) { int remaining = buffer.remaining(); streamData = Utils.sliceOrCopy(buffer, buffer.position(), remaining); diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/quic/packets/QuicPacketDecoder.java b/src/java.net.http/share/classes/jdk/internal/net/http/quic/packets/QuicPacketDecoder.java index 233a1a35778b..fc4984a30a2e 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/quic/packets/QuicPacketDecoder.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/quic/packets/QuicPacketDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -888,26 +888,24 @@ private static final class IncomingInitialPacket final int size; final int length; - final int tokenLength; final long packetNumber; final byte[] token; final List frames; IncomingInitialPacket(QuicConnectionId sourceId, QuicConnectionId destinationId, int version, - int tokenLength, byte[] token, int length, + byte[] token, int length, long packetNumber, List frames, int size) { super(sourceId, destinationId, version); this.size = size; this.length = length; - this.tokenLength = tokenLength; this.token = token; this.packetNumber = packetNumber; this.frames = List.copyOf(frames); } @Override - public int tokenLength() { return tokenLength; } + public int tokenLength() { return token != null ? token.length : 0; } @Override public byte[] token() { return token; } @@ -968,7 +966,7 @@ static IncomingInitialPacket decode(PacketReader reader, CodingContext context) } // Get number of bytes needed to store the length of the token - var tokenLength = (int) reader.readTokenLength(); + var tokenLength = reader.readTokenLength(); if (debug.on()) { debug.log("IncomingInitialPacket.decode(token-length(%d), %s)", tokenLength, reader); @@ -1051,7 +1049,7 @@ static IncomingInitialPacket decode(PacketReader reader, CodingContext context) assert size == reader.bytesRead() : size - reader.bytesRead(); return new IncomingInitialPacket(sourceID, destinationID, - version, tokenLength, token, (int)packetLength, packetNumber, frames, size); + version, token, (int)packetLength, packetNumber, frames, size); } } @@ -1572,16 +1570,18 @@ public long readPacketLength() { return packetLength; } - public long readTokenLength() { + private long readTokenLength() { return readVariableLength(); } - public byte[] readToken(int tokenLength) { + public byte[] readToken(long tokenLength) { // Check to ensure that tokenLength is within valid range if (tokenLength < 0 || tokenLength > buffer.remaining()) { throw new BufferUnderflowException(); } - byte[] token = tokenLength > 0 ? new byte[tokenLength] : null; + // `int` conversion is safe due to the `> buffer.remaining()` check above. + int tokenLengthInt = Math.toIntExact(tokenLength); + byte[] token = tokenLength > 0 ? new byte[tokenLengthInt] : null; if (tokenLength > 0) { buffer.get(token); } From 17e41a6f7b6aa979f37212776b3225934f68da05 Mon Sep 17 00:00:00 2001 From: Hai-May Chao Date: Fri, 26 Jun 2026 17:37:56 +0000 Subject: [PATCH 033/169] 8384771: Enhance TLS handshakes Reviewed-by: mschoene, rhalade, weijun, jnimeh --- .../classes/sun/security/x509/URIName.java | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/java.base/share/classes/sun/security/x509/URIName.java b/src/java.base/share/classes/sun/security/x509/URIName.java index c6d0d1790569..4a021ccbea59 100644 --- a/src/java.base/share/classes/sun/security/x509/URIName.java +++ b/src/java.base/share/classes/sun/security/x509/URIName.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -29,6 +29,7 @@ import java.net.URI; import java.net.URISyntaxException; +import sun.net.util.IPAddressUtil; import sun.security.util.*; /** @@ -131,14 +132,21 @@ public URIName(String name) throws IOException { try { hostDNS = new DNSName(host); } catch (IOException ioe) { - // Not a valid DNSName; see if it is a valid IPv4 - // IPAddressName - try { - hostIP = new IPAddressName(host); - } catch (Exception ioe2) { + // Not a valid DNSName; only accept host as an + // IPv4 literal. + byte[] ipV4 = IPAddressUtil.textToNumericFormatV4(host); + if (ipV4 != null) { + try { + hostIP = new IPAddressName(ipV4); + } catch (IOException ioe2) { + throw new IOException("invalid URI name (host " + + "portion is not a valid DNSName or IPv4 address" + + "):" + name, ioe2); + } + } else { throw new IOException("invalid URI name (host " + - "portion is not a valid DNSName, IPv4 address," + - " or IPv6 address):" + name); + "portion is not a valid DNSName or IPv4 address" + + "):" + name); } } } From 098ed73f1b8a7a78c472ee04ba731bd0af86157d Mon Sep 17 00:00:00 2001 From: Alexander Zuev Date: Sat, 27 Jun 2026 04:23:15 +0000 Subject: [PATCH 034/169] 8385476: Enhance MIDI file handling Reviewed-by: mschoene, psadhukhan, rhalade, prr --- .../media/sound/StandardMidiFileReader.java | 57 ++++++++++++++----- 1 file changed, 44 insertions(+), 13 deletions(-) diff --git a/src/java.desktop/share/classes/com/sun/media/sound/StandardMidiFileReader.java b/src/java.desktop/share/classes/com/sun/media/sound/StandardMidiFileReader.java index 0581b0ce3ca6..7f89ec1e422e 100644 --- a/src/java.desktop/share/classes/com/sun/media/sound/StandardMidiFileReader.java +++ b/src/java.desktop/share/classes/com/sun/media/sound/StandardMidiFileReader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,6 +26,7 @@ package com.sun.media.sound; import java.io.BufferedInputStream; +import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.EOFException; import java.io.File; @@ -229,6 +230,10 @@ final class SMFParser { private static final boolean DEBUG = false; + private static final int MAX_ALLOWED_ALLOC = 1024*1024; // Maximum allowed memory preallocation + + private static final int TRACK_BUFFER_SIZE = 10*1024; // Read large tracks in 10 KB chunks + int tracks; // number of tracks DataInputStream stream; // the stream to read from @@ -286,19 +291,45 @@ boolean nextTrack() throws IOException, InvalidMidiDataException { } } // now read track in a byte array - try { - trackData = new byte[trackLength]; - } catch (final OutOfMemoryError oom) { - throw new IOException("Track length too big", oom); - } - try { - // $$fb 2003-08-20: fix for 4910986: MIDI file parser breaks up on http connection - stream.readFully(trackData); - } catch (EOFException eof) { - if (!STRICT_PARSER) { - return false; + if (trackLength <= MAX_ALLOWED_ALLOC) { + try { + trackData = new byte[trackLength]; + } catch (final OutOfMemoryError oom) { + throw new IOException("Track length too big", oom); + } + try { + // $$fb 2003-08-20: fix for 4910986: MIDI file parser breaks up on http connection + stream.readFully(trackData); + } catch (EOFException eof) { + if (!STRICT_PARSER) { + return false; + } + throw new EOFException("invalid MIDI file"); + } + } else { + try { + byte[] buffer = new byte[TRACK_BUFFER_SIZE]; + ByteArrayOutputStream out = new ByteArrayOutputStream(); + long bytesRead = 0; + + while (bytesRead < trackLength) { + int bytesToRead = (int) Math.min(buffer.length, trackLength - bytesRead); + try { + stream.readFully(buffer, 0, bytesToRead); + } catch (EOFException eof) { + if (!STRICT_PARSER) { + return false; + } + throw new EOFException("invalid MIDI file"); + } + + out.write(buffer, 0, bytesToRead); + bytesRead += bytesToRead; + } + trackData = out.toByteArray(); + } catch (final OutOfMemoryError oom) { + throw new IOException("Track length too big", oom); } - throw new EOFException("invalid MIDI file"); } pos = 0; return true; From c6000549c7b1035c0e1ae42993e24c4f1c10a3ed Mon Sep 17 00:00:00 2001 From: Daniel Fuchs Date: Wed, 1 Jul 2026 14:37:58 +0000 Subject: [PATCH 035/169] 8385209: Enhance HTTP client caching Reviewed-by: rhalade, michaelm, djelinski --- .../net/http/AuthenticationFilter.java | 104 ++++++++++++------ .../jdk/internal/net/http/HttpClientImpl.java | 2 +- .../jdk/internal/net/http/RedirectFilter.java | 47 +++----- 3 files changed, 85 insertions(+), 68 deletions(-) diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/AuthenticationFilter.java b/src/java.net.http/share/classes/jdk/internal/net/http/AuthenticationFilter.java index 278e2bf23b14..05f0a4cb7c2d 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/AuthenticationFilter.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/AuthenticationFilter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -49,7 +49,7 @@ * Implementation of Http Basic authentication. */ class AuthenticationFilter implements HeaderFilter { - volatile MultiExchange exchange; + private static final Base64.Encoder encoder = Base64.getEncoder(); static final int DEFAULT_RETRY_LIMIT = 3; @@ -67,12 +67,12 @@ class AuthenticationFilter implements HeaderFilter { // A public no-arg constructor is required by FilterFactory public AuthenticationFilter() {} - private PasswordAuthentication getCredentials(String header, + private PasswordAuthentication getCredentials(HttpClientImpl client, + String header, boolean proxy, HttpRequestImpl req) throws IOException { - HttpClientImpl client = exchange.client(); java.net.Authenticator auth = client.authenticator() .orElseThrow(() -> new IOException("No authenticator set")); @@ -144,33 +144,51 @@ private URI getProxyURI(HttpRequestImpl r) { } @Override - public void request(HttpRequestImpl r, MultiExchange e) throws IOException { + public void request(HttpRequestImpl r, MultiExchange exchange) throws IOException { // use preemptive authentication if an entry exists. - Cache cache = getCache(e); - this.exchange = e; + Cache cache = getCache(exchange.client()); + AuthInfo serverAuth = exchange.serverauth; + AuthInfo proxyAuth = exchange.proxyauth; + + if (serverAuth != null && !sameOriginAndScheme(serverAuth.requestor, r.uri())) { + serverAuth = exchange.serverauth = null; + } + if (proxyAuth != null && !sameOriginAndScheme(proxyAuth.requestor, getProxyURI(r))) { + proxyAuth = exchange.proxyauth = null; + } // Proxy - if (exchange.proxyauth == null) { + if (proxyAuth == null) { URI proxyURI = getProxyURI(r); if (proxyURI != null) { CacheEntry ca = cache.get(proxyURI, true); if (ca != null) { - exchange.proxyauth = new AuthInfo(true, ca.scheme, null, ca, ca.isUTF8); + exchange.proxyauth = new AuthInfo(true, ca.scheme, proxyURI, null, ca, ca.isUTF8); addBasicCredentials(r, true, ca.value, ca.isUTF8); } } } // Server - if (exchange.serverauth == null) { - CacheEntry ca = cache.get(r.uri(), false); + if (serverAuth == null) { + URI requestor = r.uri(); + CacheEntry ca = cache.get(requestor, false); if (ca != null) { - exchange.serverauth = new AuthInfo(true, ca.scheme, null, ca, ca.isUTF8); + exchange.serverauth = new AuthInfo(true, ca.scheme, requestor, null, ca, ca.isUTF8); addBasicCredentials(r, false, ca.value, ca.isUTF8); } } } + private static boolean sameOriginAndScheme(URI uri, URI other) { + // in this context we don't need to use equalsIgnoreCase. + if (uri == other) return true; + if (uri == null || other == null) return false; + if (!Objects.equals(uri.getScheme(), other.getScheme())) return false; + if (!Objects.equals(uri.getRawAuthority(), other.getRawAuthority())) return false; + return true; + } + // TODO: refactor into per auth scheme class private static void addBasicCredentials(HttpRequestImpl r, boolean proxy, @@ -202,6 +220,7 @@ private static void addBasicCredentials(HttpRequestImpl r, static class AuthInfo { final boolean fromcache; final String scheme; + final URI requestor; int retries; PasswordAuthentication credentials; // used in request CacheEntry cacheEntry; // if used @@ -209,19 +228,24 @@ static class AuthInfo { AuthInfo(boolean fromcache, String scheme, - PasswordAuthentication credentials, boolean isUTF8) { + URI requestor, + PasswordAuthentication credentials, + boolean isUTF8) { this.fromcache = fromcache; this.scheme = scheme; this.credentials = credentials; + this.requestor = requestor; this.retries = 1; this.isUTF8 = isUTF8; } AuthInfo(boolean fromcache, String scheme, + URI requestor, PasswordAuthentication credentials, - CacheEntry ca, boolean isUTF8) { - this(fromcache, scheme, credentials, isUTF8); + CacheEntry ca, + boolean isUTF8) { + this(fromcache, scheme, requestor, credentials, isUTF8); assert credentials == null || (ca != null && ca.value == null); cacheEntry = ca; } @@ -230,7 +254,9 @@ AuthInfo retryWithCredentials(PasswordAuthentication pw, boolean isUTF8) { // If the info was already in the cache we need to create a new // instance with fromCache==false so that it's put back in the // cache if authentication succeeds - AuthInfo res = fromcache ? new AuthInfo(false, scheme, pw, isUTF8) : this; + AuthInfo res = fromcache + ? new AuthInfo(false, scheme, requestor, pw, isUTF8) + : this; res.credentials = Objects.requireNonNull(pw); res.retries = retries; return res; @@ -239,7 +265,9 @@ AuthInfo retryWithCredentials(PasswordAuthentication pw, boolean isUTF8) { @Override public HttpRequestImpl response(Response r) throws IOException { - Cache cache = getCache(exchange); + MultiExchange exchange = r.exchange.multi; + HttpClientImpl client = exchange.client(); + Cache cache = getCache(client); int status = r.statusCode(); HttpHeaders hdrs = r.headers(); HttpRequestImpl req = r.request(); @@ -253,28 +281,37 @@ public HttpRequestImpl response(Response r) throws IOException { } if (status != PROXY_UNAUTHORIZED) { - if (exchange.proxyauth != null && !exchange.proxyauth.fromcache) { - AuthInfo au = exchange.proxyauth; + AuthInfo au = exchange.proxyauth; + if (au != null && !au.fromcache) { URI proxyURI = getProxyURI(req); if (proxyURI != null) { - exchange.proxyauth = null; - cache.store(au.scheme, proxyURI, true, au.credentials, au.isUTF8); + if (sameOriginAndScheme(au.requestor, proxyURI)) { + exchange.proxyauth = null; + cache.store(au.scheme, au.requestor, true, au.credentials, au.isUTF8); + } } } if (status != UNAUTHORIZED) { + au = exchange.serverauth; // check if any authentication succeeded for first time - if (exchange.serverauth != null && !exchange.serverauth.fromcache) { - AuthInfo au = exchange.serverauth; - cache.store(au.scheme, req.uri(), false, au.credentials, au.isUTF8); + if (au != null && !au.fromcache) { + URI requestor = req.uri(); + if (sameOriginAndScheme(au.requestor, requestor)) { + cache.store(au.scheme, au.requestor, false, au.credentials, au.isUTF8); + } } + assert status != PROXY_UNAUTHORIZED && status != UNAUTHORIZED; return null; } + assert status == UNAUTHORIZED; } + assert status == PROXY_UNAUTHORIZED || status == UNAUTHORIZED; + boolean proxy = status == PROXY_UNAUTHORIZED; String authname = proxy ? "Proxy-Authenticate" : "WWW-Authenticate"; List authvals = hdrs.allValues(authname); - if (authvals.isEmpty() && exchange.client().authenticator().isPresent()) { + if (authvals.isEmpty() && client.authenticator().isPresent()) { throw new IOException(authname + " header missing for response code " + status); } String authval = null; @@ -310,16 +347,17 @@ public HttpRequestImpl response(Response r) throws IOException { } AuthInfo au = proxy ? exchange.proxyauth : exchange.serverauth; - if (au == null) { + URI requestor = proxy ? getProxyURI(req) : req.uri(); + if (au == null || !sameOriginAndScheme(au.requestor, requestor)) { // if no authenticator, let the user deal with 407/401 - if (exchange.client().authenticator().isEmpty()) return null; + if (client.authenticator().isEmpty()) return null; - PasswordAuthentication pw = getCredentials(authval, proxy, req); + PasswordAuthentication pw = getCredentials(client, authval, proxy, req); if (pw == null) { throw new IOException("No credentials provided"); } // No authentication in request. Get credentials from user - au = new AuthInfo(false, "Basic", pw, isUTF8); + au = new AuthInfo(false, "Basic",requestor, pw, isUTF8); if (proxy) { exchange.proxyauth = au; } else { @@ -332,16 +370,17 @@ public HttpRequestImpl response(Response r) throws IOException { throw new IOException("too many authentication attempts. Limit: " + retry_limit); } else { + assert sameOriginAndScheme(au.requestor, requestor); // we sent credentials, but they were rejected if (au.fromcache) { cache.remove(au.cacheEntry); } // if no authenticator, let the user deal with 407/401 - if (exchange.client().authenticator().isEmpty()) return null; + if (client.authenticator().isEmpty()) return null; // try again - PasswordAuthentication pw = getCredentials(authval, proxy, req); + PasswordAuthentication pw = getCredentials(client, authval, proxy, req); if (pw == null) { throw new IOException("No credentials provided"); } @@ -362,8 +401,7 @@ public HttpRequestImpl response(Response r) throws IOException { // be garbage collected when no longer referenced. static final WeakHashMap caches = new WeakHashMap<>(); - static synchronized Cache getCache(MultiExchange exchange) { - HttpClientImpl client = exchange.client(); + static synchronized Cache getCache(HttpClientImpl client) { Cache c = caches.get(client); if (c == null) { c = new Cache(); diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/HttpClientImpl.java b/src/java.net.http/share/classes/jdk/internal/net/http/HttpClientImpl.java index aacfb27e99ad..1fdc5ffc7994 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/HttpClientImpl.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/HttpClientImpl.java @@ -1839,8 +1839,8 @@ public String toString() { } private void initFilters() { - addFilter(AuthenticationFilter.class); addFilter(RedirectFilter.class); + addFilter(AuthenticationFilter.class); if (this.cookieHandler != null) { addFilter(CookieFilter.class); } diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/RedirectFilter.java b/src/java.net.http/share/classes/jdk/internal/net/http/RedirectFilter.java index a3c588a8f922..a88860232136 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/RedirectFilter.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/RedirectFilter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -28,23 +28,15 @@ import java.io.IOException; import java.io.UncheckedIOException; import java.net.URI; -import java.net.http.HttpClient; +import java.net.http.HttpClient.Redirect; import java.net.http.HttpHeaders; -import java.util.concurrent.locks.ReentrantLock; import jdk.internal.net.http.common.Log; import jdk.internal.net.http.common.Utils; class RedirectFilter implements HeaderFilter { - private final ReentrantLock stateLock = new ReentrantLock(); - HttpRequestImpl request; - HttpClientImpl client; - HttpClient.Redirect policy; - String method; - MultiExchange exchange; static final int DEFAULT_MAX_REDIRECTS = 5; - URI uri; /* * NOT_MODIFIED status code results from a conditional GET where @@ -62,28 +54,11 @@ public RedirectFilter() {} @Override public void request(HttpRequestImpl r, MultiExchange e) throws IOException { - stateLock.lock(); - try { - this.request = r; - this.client = e.client(); - this.policy = client.followRedirects(); - - this.method = r.method(); - this.uri = r.uri(); - this.exchange = e; - } finally { - stateLock.unlock(); - } } @Override public HttpRequestImpl response(Response r) throws IOException { - stateLock.lock(); - try { - return handleResponse(r); - } finally { - stateLock.unlock(); - } + return handleResponse(r); } private static String redirectedMethod(int statusCode, String orig) { @@ -120,7 +95,9 @@ private static boolean isRedirecting(int statusCode) { */ private HttpRequestImpl handleResponse(Response r) { int rcode = r.statusCode(); - if (rcode == 200 || policy == HttpClient.Redirect.NEVER) { + MultiExchange exchange = r.exchange.multi; + Redirect policy = exchange.client.followRedirects(); + if (rcode == 200 || policy == Redirect.NEVER) { return null; } @@ -128,10 +105,12 @@ private HttpRequestImpl handleResponse(Response r) { return null; if (isRedirecting(rcode)) { - URI redir = getRedirectedURI(r.headers()); - String newMethod = redirectedMethod(rcode, method); + HttpRequestImpl request = r.request(); + URI from = request.uri(); + URI redir = getRedirectedURI(from, r.headers()); + String newMethod = redirectedMethod(rcode, request.method()); Log.logTrace("response code: {0}, redirected URI: {1}", rcode, redir); - if (canRedirect(redir) && ++exchange.numberOfRedirects < max_redirects) { + if (canRedirect(from, redir, policy) && ++exchange.numberOfRedirects < max_redirects) { Log.logTrace("redirect to: {0} with method: {1}", redir, newMethod); return HttpRequestImpl.newInstanceForRedirection(redir, newMethod, request, rcode != 303); } else { @@ -142,7 +121,7 @@ private HttpRequestImpl handleResponse(Response r) { return null; } - private URI getRedirectedURI(HttpHeaders headers) { + private URI getRedirectedURI(URI uri, HttpHeaders headers) { URI redirectedURI; redirectedURI = headers.firstValue("Location") .map(URI::create) @@ -155,7 +134,7 @@ private URI getRedirectedURI(HttpHeaders headers) { return redirectedURI; } - private boolean canRedirect(URI redir) { + private boolean canRedirect(URI uri, URI redir, Redirect policy) { String newScheme = redir.getScheme(); String oldScheme = uri.getScheme(); return switch (policy) { From 4e627bb421da3dd8c4d3b29dde26fbeb2f23d576 Mon Sep 17 00:00:00 2001 From: Weijun Wang Date: Tue, 7 Jul 2026 14:31:41 +0000 Subject: [PATCH 036/169] 8387152: Enhance krb5 tokens Reviewed-by: rhalade, valeriep, semery --- .../sun/security/jgss/krb5/CipherHelper.java | 246 ++++-------------- .../sun/security/jgss/krb5/Krb5Context.java | 132 ++-------- .../security/jgss/krb5/MessageToken_v2.java | 34 ++- .../sun/security/jgss/krb5/WrapToken_v2.java | 4 +- 4 files changed, 87 insertions(+), 329 deletions(-) diff --git a/src/java.security.jgss/share/classes/sun/security/jgss/krb5/CipherHelper.java b/src/java.security.jgss/share/classes/sun/security/jgss/krb5/CipherHelper.java index 20bf6a147533..54f74353100c 100644 --- a/src/java.security.jgss/share/classes/sun/security/jgss/krb5/CipherHelper.java +++ b/src/java.security.jgss/share/classes/sun/security/jgss/krb5/CipherHelper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2004, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -39,6 +39,7 @@ import java.security.MessageDigest; import java.security.GeneralSecurityException; import java.security.NoSuchAlgorithmException; + import sun.security.krb5.*; import sun.security.krb5.internal.crypto.Aes128Sha2; import sun.security.krb5.internal.crypto.Aes256Sha2; @@ -47,6 +48,7 @@ import sun.security.krb5.internal.crypto.Aes256; import sun.security.krb5.internal.crypto.ArcFourHmac; import sun.security.krb5.internal.crypto.EType; +import sun.security.util.ByteArrays; class CipherHelper { @@ -587,34 +589,43 @@ void decryptData(WrapToken token, byte[] ciphertext, int cStart, int cLen, // decrypt data in the new GSS tokens void decryptData(WrapToken_v2 token, byte[] ciphertext, int cStart, int cLen, byte[] plaintext, int pStart, int key_usage) - throws GSSException { - - /* - Krb5Token.debug("decryptData : ciphertext = " + - Krb5Token.getHexBytes(ciphertext)); - */ + throws GSSException { + byte[] decrypted; switch (etype) { case EncryptedData.ETYPE_AES128_CTS_HMAC_SHA1_96: - aes128Decrypt(token, ciphertext, cStart, cLen, - plaintext, pStart, key_usage); - break; + decrypted = aes128Decrypt(ciphertext, cStart, cLen, key_usage); + break; case EncryptedData.ETYPE_AES256_CTS_HMAC_SHA1_96: - aes256Decrypt(token, ciphertext, cStart, cLen, - plaintext, pStart, key_usage); - break; + decrypted = aes256Decrypt(ciphertext, cStart, cLen, key_usage); + break; case EncryptedData.ETYPE_AES128_CTS_HMAC_SHA256_128: - aes128Sha2Decrypt(token, ciphertext, cStart, cLen, - plaintext, pStart, key_usage); - break; + decrypted = aes128Sha2Decrypt(ciphertext, cStart, cLen, key_usage); + break; case EncryptedData.ETYPE_AES256_CTS_HMAC_SHA384_192: - aes256Sha2Decrypt(token, ciphertext, cStart, cLen, - plaintext, pStart, key_usage); - break; + decrypted = aes256Sha2Decrypt(ciphertext, cStart, cLen, key_usage); + break; default: - throw new GSSException(GSSException.FAILURE, -1, + throw new GSSException(GSSException.FAILURE, -1, "Unsupported etype: " + etype); - } + } + byte[] outerHeader = token.getTokenHeader().clone(); + outerHeader[MessageToken_v2.TOKEN_RRC_POS] = 0; + outerHeader[MessageToken_v2.TOKEN_RRC_POS + 1] = 0; + int len = decrypted.length - WrapToken_v2.CONFOUNDER_SIZE - + WrapToken_v2.TOKEN_HEADER_SIZE - token.getEc(); + if (len < 0) { + throw new GSSException(GSSException.DEFECTIVE_TOKEN); + } + if (!ByteArrays.isEqual( + outerHeader, + 0, WrapToken_v2.TOKEN_HEADER_SIZE, + decrypted, + decrypted.length - WrapToken_v2.TOKEN_HEADER_SIZE, decrypted.length)) { + throw new GSSException(GSSException.DEFECTIVE_TOKEN); + } + System.arraycopy(decrypted, WrapToken_v2.CONFOUNDER_SIZE, + plaintext, pStart, len); } void decryptData(WrapToken token, InputStream cipherStream, int cLen, @@ -667,44 +678,6 @@ void decryptData(WrapToken token, InputStream cipherStream, int cLen, } } - void decryptData(WrapToken_v2 token, InputStream cipherStream, int cLen, - byte[] plaintext, int pStart, int key_usage) - throws GSSException, IOException { - - // Read encrypted data from stream - byte[] ciphertext = new byte[cLen]; - try { - Krb5Token.readFully(cipherStream, ciphertext, 0, cLen); - } catch (IOException e) { - GSSException ge = new GSSException( - GSSException.DEFECTIVE_TOKEN, -1, - "Cannot read complete token"); - ge.initCause(e); - throw ge; - } - switch (etype) { - case EncryptedData.ETYPE_AES128_CTS_HMAC_SHA1_96: - aes128Decrypt(token, ciphertext, 0, cLen, - plaintext, pStart, key_usage); - break; - case EncryptedData.ETYPE_AES256_CTS_HMAC_SHA1_96: - aes256Decrypt(token, ciphertext, 0, cLen, - plaintext, pStart, key_usage); - break; - case EncryptedData.ETYPE_AES128_CTS_HMAC_SHA256_128: - aes128Sha2Decrypt(token, ciphertext, 0, cLen, - plaintext, pStart, key_usage); - break; - case EncryptedData.ETYPE_AES256_CTS_HMAC_SHA384_192: - aes256Sha2Decrypt(token, ciphertext, 0, cLen, - plaintext, pStart, key_usage); - break; - default: - throw new GSSException(GSSException.FAILURE, -1, - "Unsupported etype: " + etype); - } - } - void encryptData(WrapToken token, byte[] confounder, byte[] plaintext, int start, int len, byte[] padding, OutputStream os) throws GSSException, IOException { @@ -824,45 +797,6 @@ void encryptData(WrapToken token, byte[] confounder, byte[] plaintext, } } - /* - * Encrypt data in the new GSS tokens - * - * Wrap Tokens (with confidentiality) - * { Encrypt(16-byte confounder | plaintext | 16-byte token_header) | - * 12-byte HMAC } - * where HMAC is on {16-byte confounder | plaintext | 16-byte token_header} - * HMAC is not encrypted; it is appended at the end. - */ - int encryptData(WrapToken_v2 token, byte[] confounder, byte[] tokenHeader, - byte[] plaintext, int pStart, int pLen, byte[] ciphertext, int cStart, - int key_usage) throws GSSException { - - byte[] ctext; - switch (etype) { - case EncryptedData.ETYPE_AES128_CTS_HMAC_SHA1_96: - ctext = aes128Encrypt(confounder, tokenHeader, - plaintext, pStart, pLen, key_usage); - break; - case EncryptedData.ETYPE_AES256_CTS_HMAC_SHA1_96: - ctext = aes256Encrypt(confounder, tokenHeader, - plaintext, pStart, pLen, key_usage); - break; - case EncryptedData.ETYPE_AES128_CTS_HMAC_SHA256_128: - ctext = aes128Sha2Encrypt(confounder, tokenHeader, - plaintext, pStart, pLen, key_usage); - break; - case EncryptedData.ETYPE_AES256_CTS_HMAC_SHA384_192: - ctext = aes256Sha2Encrypt(confounder, tokenHeader, - plaintext, pStart, pLen, key_usage); - break; - default: - throw new GSSException(GSSException.FAILURE, -1, - "Unsupported etype: " + etype); - } - System.arraycopy(ctext, 0, ciphertext, cStart, ctext.length); - return ctext.length; - } - // --------------------- DES methods /** @@ -1408,14 +1342,11 @@ private byte[] aes128Sha2Encrypt(byte[] confounder, byte[] tokenHeader, } } - private void aes128Decrypt(WrapToken_v2 token, byte[] ciphertext, - int cStart, int cLen, byte[] plaintext, int pStart, int key_usage) - throws GSSException { - - byte[] ptext; + private byte[] aes128Decrypt(byte[] ciphertext, + int cStart, int cLen, int key_usage) throws GSSException { try { - ptext = Aes128.decryptRaw(keybytes, key_usage, + return Aes128.decryptRaw(keybytes, key_usage, ZERO_IV_AES, ciphertext, cStart, cLen); } catch (GeneralSecurityException e) { GSSException ge = new GSSException(GSSException.FAILURE, -1, @@ -1423,36 +1354,13 @@ private void aes128Decrypt(WrapToken_v2 token, byte[] ciphertext, ge.initCause(e); throw ge; } - - /* - Krb5Token.debug("\naes128Decrypt in: " + - Krb5Token.getHexBytes(ciphertext, cStart, cLen)); - Krb5Token.debug("\naes128Decrypt plain: " + - Krb5Token.getHexBytes(ptext)); - Krb5Token.debug("\naes128Decrypt ptext: " + - Krb5Token.getHexBytes(ptext)); - */ - - // Strip out confounder and token header - int len = ptext.length - WrapToken_v2.CONFOUNDER_SIZE - - WrapToken_v2.TOKEN_HEADER_SIZE; - System.arraycopy(ptext, WrapToken_v2.CONFOUNDER_SIZE, - plaintext, pStart, len); - - /* - Krb5Token.debug("\naes128Decrypt plaintext: " + - Krb5Token.getHexBytes(plaintext, pStart, len)); - */ } - private void aes128Sha2Decrypt(WrapToken_v2 token, byte[] ciphertext, - int cStart, int cLen, byte[] plaintext, int pStart, int key_usage) - throws GSSException { - - byte[] ptext; + private byte[] aes128Sha2Decrypt(byte[] ciphertext, + int cStart, int cLen, int key_usage) throws GSSException { try { - ptext = Aes128Sha2.decryptRaw(keybytes, key_usage, + return Aes128Sha2.decryptRaw(keybytes, key_usage, ZERO_IV_AES, ciphertext, cStart, cLen); } catch (GeneralSecurityException e) { GSSException ge = new GSSException(GSSException.FAILURE, -1, @@ -1460,26 +1368,6 @@ private void aes128Sha2Decrypt(WrapToken_v2 token, byte[] ciphertext, ge.initCause(e); throw ge; } - - /* - Krb5Token.debug("\naes128Sha2Decrypt in: " + - Krb5Token.getHexBytes(ciphertext, cStart, cLen)); - Krb5Token.debug("\naes128Sha2Decrypt plain: " + - Krb5Token.getHexBytes(ptext)); - Krb5Token.debug("\naes128Sha2Decrypt ptext: " + - Krb5Token.getHexBytes(ptext)); - */ - - // Strip out confounder and token header - int len = ptext.length - WrapToken_v2.CONFOUNDER_SIZE - - WrapToken_v2.TOKEN_HEADER_SIZE; - System.arraycopy(ptext, WrapToken_v2.CONFOUNDER_SIZE, - plaintext, pStart, len); - - /* - Krb5Token.debug("\naes128Sha2Decrypt plaintext: " + - Krb5Token.getHexBytes(plaintext, pStart, len)); - */ } private byte[] aes256Encrypt(byte[] confounder, byte[] tokenHeader, @@ -1546,13 +1434,11 @@ private byte[] aes256Sha2Encrypt(byte[] confounder, byte[] tokenHeader, } } - private void aes256Decrypt(WrapToken_v2 token, byte[] ciphertext, - int cStart, int cLen, byte[] plaintext, int pStart, int key_usage) - throws GSSException { + private byte[] aes256Decrypt(byte[] ciphertext, + int cStart, int cLen, int key_usage) throws GSSException { - byte[] ptext; try { - ptext = Aes256.decryptRaw(keybytes, key_usage, + return Aes256.decryptRaw(keybytes, key_usage, ZERO_IV_AES, ciphertext, cStart, cLen); } catch (GeneralSecurityException e) { GSSException ge = new GSSException(GSSException.FAILURE, -1, @@ -1560,36 +1446,13 @@ private void aes256Decrypt(WrapToken_v2 token, byte[] ciphertext, ge.initCause(e); throw ge; } - - /* - Krb5Token.debug("\naes256Decrypt in: " + - Krb5Token.getHexBytes(ciphertext, cStart, cLen)); - Krb5Token.debug("\naes256Decrypt plain: " + - Krb5Token.getHexBytes(ptext)); - Krb5Token.debug("\naes256Decrypt ptext: " + - Krb5Token.getHexBytes(ptext)); - */ - - // Strip out confounder and token header - int len = ptext.length - WrapToken_v2.CONFOUNDER_SIZE - - WrapToken_v2.TOKEN_HEADER_SIZE; - System.arraycopy(ptext, WrapToken_v2.CONFOUNDER_SIZE, - plaintext, pStart, len); - - /* - Krb5Token.debug("\naes128Decrypt plaintext: " + - Krb5Token.getHexBytes(plaintext, pStart, len)); - */ - } - private void aes256Sha2Decrypt(WrapToken_v2 token, byte[] ciphertext, - int cStart, int cLen, byte[] plaintext, int pStart, int key_usage) - throws GSSException { + private byte[] aes256Sha2Decrypt(byte[] ciphertext, + int cStart, int cLen, int key_usage) throws GSSException { - byte[] ptext; try { - ptext = Aes256Sha2.decryptRaw(keybytes, key_usage, + return Aes256Sha2.decryptRaw(keybytes, key_usage, ZERO_IV_AES, ciphertext, cStart, cLen); } catch (GeneralSecurityException e) { GSSException ge = new GSSException(GSSException.FAILURE, -1, @@ -1597,27 +1460,6 @@ private void aes256Sha2Decrypt(WrapToken_v2 token, byte[] ciphertext, ge.initCause(e); throw ge; } - - /* - Krb5Token.debug("\naes256Sha2Decrypt in: " + - Krb5Token.getHexBytes(ciphertext, cStart, cLen)); - Krb5Token.debug("\naes256Sha2Decrypt plain: " + - Krb5Token.getHexBytes(ptext)); - Krb5Token.debug("\naes256Sha2Decrypt ptext: " + - Krb5Token.getHexBytes(ptext)); - */ - - // Strip out confounder and token header - int len = ptext.length - WrapToken_v2.CONFOUNDER_SIZE - - WrapToken_v2.TOKEN_HEADER_SIZE; - System.arraycopy(ptext, WrapToken_v2.CONFOUNDER_SIZE, - plaintext, pStart, len); - - /* - Krb5Token.debug("\naes256Sha2Decrypt plaintext: " + - Krb5Token.getHexBytes(plaintext, pStart, len)); - */ - } /** diff --git a/src/java.security.jgss/share/classes/sun/security/jgss/krb5/Krb5Context.java b/src/java.security.jgss/share/classes/sun/security/jgss/krb5/Krb5Context.java index b118c9ee2157..d6a5de796721 100644 --- a/src/java.security.jgss/share/classes/sun/security/jgss/krb5/Krb5Context.java +++ b/src/java.security.jgss/share/classes/sun/security/jgss/krb5/Krb5Context.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -904,39 +904,6 @@ public final byte[] wrap(byte[] inBuf, int offset, int len, } } - public final int wrap(byte[] inBuf, int inOffset, int len, - byte[] outBuf, int outOffset, - MessageProp msgProp) throws GSSException { - - if (state != STATE_DONE) - throw new GSSException(GSSException.NO_CONTEXT, -1, - "Wrap called in invalid state!"); - - int retVal = 0; - try { - if (cipherHelper.getProto() == 0) { - WrapToken token = - new WrapToken(this, msgProp, inBuf, inOffset, len); - retVal = token.encode(outBuf, outOffset); - } else if (cipherHelper.getProto() == 1) { - WrapToken_v2 token = - new WrapToken_v2(this, msgProp, inBuf, inOffset, len); - retVal = token.encode(outBuf, outOffset); - } - if (DEBUG != null) { - DEBUG.println("Krb5Context.wrap: token=[" - + getHexBytes(outBuf, outOffset, retVal) - + "]"); - } - return retVal; - } catch (IOException e) { - GSSException gssException = - new GSSException(GSSException.FAILURE, -1, e.getMessage()); - gssException.initCause(e); - throw gssException; - } - } - public final void wrap(byte[] inBuf, int offset, int len, OutputStream os, MessageProp msgProp) throws GSSException { @@ -994,79 +961,35 @@ public final void wrap(InputStream is, OutputStream os, public final byte[] unwrap(byte[] inBuf, int offset, int len, MessageProp msgProp) - throws GSSException { - - if (DEBUG != null) { - DEBUG.println("Krb5Context.unwrap: token=[" - + getHexBytes(inBuf, offset, len) - + "]"); - } - - if (state != STATE_DONE) { - throw new GSSException(GSSException.NO_CONTEXT, -1, - " Unwrap called in invalid state!"); - } - - byte[] data = null; - if (cipherHelper.getProto() == 0) { - WrapToken token = - new WrapToken(this, inBuf, offset, len, msgProp); - data = token.getData(); - setSequencingAndReplayProps(token, msgProp); - } else if (cipherHelper.getProto() == 1) { - WrapToken_v2 token = - new WrapToken_v2(this, inBuf, offset, len, msgProp); - data = token.getData(); - setSequencingAndReplayProps(token, msgProp); - } + throws GSSException { - return data; + if (DEBUG != null) { + DEBUG.println("Krb5Context.unwrap: token=[" + + getHexBytes(inBuf, offset, len) + + "]"); } - public final int unwrap(byte[] inBuf, int inOffset, int len, - byte[] outBuf, int outOffset, - MessageProp msgProp) throws GSSException { - - if (state != STATE_DONE) + if (state != STATE_DONE) { throw new GSSException(GSSException.NO_CONTEXT, -1, - "Unwrap called in invalid state!"); + " Unwrap called in invalid state!"); + } + byte[] data = null; if (cipherHelper.getProto() == 0) { WrapToken token = - new WrapToken(this, inBuf, inOffset, len, msgProp); - len = token.getData(outBuf, outOffset); + new WrapToken(this, inBuf, offset, len, msgProp); + data = token.getData(); setSequencingAndReplayProps(token, msgProp); } else if (cipherHelper.getProto() == 1) { WrapToken_v2 token = - new WrapToken_v2(this, inBuf, inOffset, len, msgProp); - len = token.getData(outBuf, outOffset); + new WrapToken_v2(this, inBuf, offset, len, msgProp); + data = token.getData(); setSequencingAndReplayProps(token, msgProp); } - return len; - } - public final int unwrap(InputStream is, - byte[] outBuf, int outOffset, - MessageProp msgProp) throws GSSException { - - if (state != STATE_DONE) - throw new GSSException(GSSException.NO_CONTEXT, -1, - "Unwrap called in invalid state!"); - - int len = 0; - if (cipherHelper.getProto() == 0) { - WrapToken token = new WrapToken(this, is, msgProp); - len = token.getData(outBuf, outOffset); - setSequencingAndReplayProps(token, msgProp); - } else if (cipherHelper.getProto() == 1) { - WrapToken_v2 token = new WrapToken_v2(this, is, msgProp); - len = token.getData(outBuf, outOffset); - setSequencingAndReplayProps(token, msgProp); - } - return len; + return data; } - public final void unwrap(InputStream is, OutputStream os, MessageProp msgProp) throws GSSException { @@ -1120,31 +1043,6 @@ public final byte[] getMIC(byte[] inMsg, int offset, int len, } } - private int getMIC(byte[] inMsg, int offset, int len, - byte[] outBuf, int outOffset, - MessageProp msgProp) - throws GSSException { - - int retVal = 0; - try { - if (cipherHelper.getProto() == 0) { - MicToken token = - new MicToken(this, msgProp, inMsg, offset, len); - retVal = token.encode(outBuf, outOffset); - } else if (cipherHelper.getProto() == 1) { - MicToken_v2 token = - new MicToken_v2(this, msgProp, inMsg, offset, len); - retVal = token.encode(outBuf, outOffset); - } - return retVal; - } catch (IOException e) { - GSSException gssException = - new GSSException(GSSException.FAILURE, -1, e.getMessage()); - gssException.initCause(e); - throw gssException; - } - } - /* * Checksum calculation requires a byte[]. Hence, might as well pass * a byte[] into the MicToken constructor. However, writing the diff --git a/src/java.security.jgss/share/classes/sun/security/jgss/krb5/MessageToken_v2.java b/src/java.security.jgss/share/classes/sun/security/jgss/krb5/MessageToken_v2.java index 784e4803149b..5eb2dcd265b1 100644 --- a/src/java.security.jgss/share/classes/sun/security/jgss/krb5/MessageToken_v2.java +++ b/src/java.security.jgss/share/classes/sun/security/jgss/krb5/MessageToken_v2.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2004, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -102,7 +102,9 @@ abstract class MessageToken_v2 extends Krb5Token { private static final int TOKEN_ID_POS = 0; private static final int TOKEN_FLAG_POS = 2; private static final int TOKEN_EC_POS = 4; - private static final int TOKEN_RRC_POS = 6; + + // Accessed by CipherHelper to normalize RRC + public static final int TOKEN_RRC_POS = 6; /** * The size of the random confounder used in a WrapToken. @@ -235,7 +237,14 @@ abstract class MessageToken_v2 extends Krb5Token { } if (tokenId == Krb5Token.WRAP_ID_v2) { - rotate(); + // According to RFC 4121, we should rotate by RRC. However, Windows + // rotates by RRC + EC in DCE-style tokens, and MIT krb5 follows it. + // (https://github.com/krb5/krb5/blob/07818f1/src/lib/gssapi/krb5/k5sealv3iov.c#L119) + // Non-DCE-style tokens observed from Windows shows EC = 0, and normal + // MIT krb5 build hardcoded EC = 0 for non-DCE-style tokens. + // (https://github.com/krb5/krb5/blob/07818f1/src/lib/gssapi/krb5/k5sealv3.c#L130), + // In this case, adding EC does not change the effective rotation. + rotate(prop.getPrivacy() ? (rrc + ec) : rrc); } if (tokenId == Krb5Token.MIC_ID_v2 || @@ -374,13 +383,13 @@ public final boolean verifySign(byte[] data, int offset, int len) * Our implementation does not do any rotates when sending, only * when receiving, we rotate left as per the RRC count, to revert it. */ - private void rotate() { - if (rrc % tokenDataLen != 0) { - rrc = rrc % tokenDataLen; + private void rotate(int rotateNum) { + rotateNum = rotateNum % tokenDataLen; + if (rotateNum != 0) { byte[] newBytes = new byte[tokenDataLen]; - System.arraycopy(tokenData, rrc, newBytes, 0, tokenDataLen-rrc); - System.arraycopy(tokenData, 0, newBytes, tokenDataLen-rrc, rrc); + System.arraycopy(tokenData, rotateNum, newBytes, 0, tokenDataLen-rotateNum); + System.arraycopy(tokenData, 0, newBytes, tokenDataLen-rotateNum, rotateNum); tokenData = newBytes; } @@ -490,6 +499,15 @@ protected final byte[] getTokenHeader() { return (tokenHeader.getBytes()); } + /** + * Returns the EC value for the message token. + * @return the EC value + * @see CipherHelper#decryptData(WrapToken_v2, byte[], int, int, byte[], int, int) + */ + public final int getEc() { + return ec; + } + // ******************************************* // // I N N E R C L A S S E S F O L L O W // ******************************************* // diff --git a/src/java.security.jgss/share/classes/sun/security/jgss/krb5/WrapToken_v2.java b/src/java.security.jgss/share/classes/sun/security/jgss/krb5/WrapToken_v2.java index f25dfb464b1b..96f1c78f21ee 100644 --- a/src/java.security.jgss/share/classes/sun/security/jgss/krb5/WrapToken_v2.java +++ b/src/java.security.jgss/share/classes/sun/security/jgss/krb5/WrapToken_v2.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2004, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -130,7 +130,7 @@ public int getData(byte[] dataBuf, int dataBufOffset) cipherHelper.decryptData(this, tokenData, 0, tokenDataLen, dataBuf, dataBufOffset, getKeyUsage()); - return tokenDataLen - CONFOUNDER_SIZE - + return tokenDataLen - CONFOUNDER_SIZE - getEc() - TOKEN_HEADER_SIZE - cipherHelper.getChecksumLength(); } else { From 42c4f841c8188b51a2e02e8616f7705be26cac2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Jeli=C5=84ski?= Date: Wed, 8 Jul 2026 08:45:59 +0000 Subject: [PATCH 037/169] 8386515: Enhance HTTP server parsing Reviewed-by: rhalade, dfuchs, michaelm, vyazici --- .../classes/sun/net/httpserver/Request.java | 80 ++++++++----------- .../sun/net/httpserver/ServerImpl.java | 9 ++- 2 files changed, 41 insertions(+), 48 deletions(-) diff --git a/src/jdk.httpserver/share/classes/sun/net/httpserver/Request.java b/src/jdk.httpserver/share/classes/sun/net/httpserver/Request.java index 75eeb29f015b..ee05ed29d6a0 100644 --- a/src/jdk.httpserver/share/classes/sun/net/httpserver/Request.java +++ b/src/jdk.httpserver/share/classes/sun/net/httpserver/Request.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -79,7 +79,7 @@ public OutputStream outputStream() { */ public String readLine() throws IOException { - boolean gotCR = false, gotLF = false; + boolean gotLF = false; pos = 0; lineBuf = new StringBuffer(); long lsize = 32; @@ -106,34 +106,18 @@ public String readLine() throws IOException { if (c == -1) { return null; } - if (gotCR) { - if (c == LF) { - gotLF = true; - } else { - gotCR = false; - consume(CR); - if (firstClearRequest && offset == 0) { - if (c < FIRST_CHAR) { - throw new ProtocolException("Unexpected start of request line"); - } - offset++; - } - consume(c); - lsize = lsize + 2; - } + if (c == CR || c == LF) { + checkCRLF(c); + gotLF = true; } else { - if (c == CR) { - gotCR = true; - } else { - if (firstClearRequest && offset == 0) { - if (c < FIRST_CHAR) { - throw new ProtocolException("Unexpected start of request line"); - } - offset++; + if (firstClearRequest && offset == 0) { + if (c < FIRST_CHAR) { + throw new ProtocolException("Unexpected start of request line"); } - consume(c); - lsize = lsize + 1; + offset++; } + consume(c); + lsize = lsize + 1; } if (maxReqHeaderSize > 0 && lsize > maxReqHeaderSize) { throw new IOException("Maximum header (" + @@ -161,7 +145,16 @@ public String requestLine() { } Headers hdrs = null; - @SuppressWarnings("fallthrough") + + void checkCRLF(int firstc) throws IOException { + if (firstc == CR) { + int c = is.read(); + if (c != LF) { + throw new ProtocolException("Invalid CR in request header"); + } + } + } + Headers headers() throws IOException { if (hdrs != null) { return hdrs; @@ -175,20 +168,15 @@ Headers headers() throws IOException { // check for empty headers if (firstc == CR || firstc == LF) { - int c = is.read(); - if (c == CR || c == LF) { - return hdrs; - } - s[0] = (char)firstc; - len = 1; - firstc = c; + checkCRLF(firstc); + return hdrs; } long hsize = startLine.length() + 32L; while (firstc != LF && firstc != CR && firstc >= 0) { int keyend = -1; int c; - boolean inKey = firstc > ' '; + boolean inKey = firstc != ' ' && firstc != '\t'; s[len++] = (char) firstc; hsize = hsize + 1; parseloop:{ @@ -201,26 +189,24 @@ Headers headers() throws IOException { : Long.MAX_VALUE; while ((c = is.read()) >= 0) { switch (c) { - /*fallthrough*/ case ':': - if (inKey && len > 0) + if (inKey && len > 0) { keyend = len; - inKey = false; + inKey = false; + } break; case '\t': c = ' '; - case ' ': - inKey = false; break; case CR: case LF: - firstc = is.read(); - if (c == CR && firstc == LF) { - firstc = is.read(); - if (firstc == CR) - firstc = is.read(); + checkCRLF(c); + if (inKey) { + throw new ProtocolException("Missing field separator"); } - if (firstc == LF || firstc == CR || firstc > ' ') + firstc = is.read(); + checkCRLF(firstc); + if (firstc != ' ' && firstc != '\t') break parseloop; /* continuation */ c = ' '; diff --git a/src/jdk.httpserver/share/classes/sun/net/httpserver/ServerImpl.java b/src/jdk.httpserver/share/classes/sun/net/httpserver/ServerImpl.java index f0a8efe1a6b1..cfc02c3910f7 100644 --- a/src/jdk.httpserver/share/classes/sun/net/httpserver/ServerImpl.java +++ b/src/jdk.httpserver/share/classes/sun/net/httpserver/ServerImpl.java @@ -790,7 +790,14 @@ public void run() { start = space+1; String version = requestLine.substring(start); - Headers headers = req.headers(); + Headers headers; + try { + headers = req.headers(); + } catch (ProtocolException pe) { + reject(Code.HTTP_BAD_REQUEST, requestLine, + "Failed to parse headers: " + pe.getMessage()); + return; + } /* check key for illegal characters */ for (var k : headers.keySet()) { if (!isValidName(k)) { From 8dd230157ffcd59ac9528876bd44a822f864e057 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Jeli=C5=84ski?= Date: Wed, 8 Jul 2026 12:46:45 +0000 Subject: [PATCH 038/169] 8386535: Improve HTTP headers Reviewed-by: dfuchs, rhalade, michaelm, vyazici --- .../classes/sun/net/httpserver/Request.java | 33 ++++++++----------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/src/jdk.httpserver/share/classes/sun/net/httpserver/Request.java b/src/jdk.httpserver/share/classes/sun/net/httpserver/Request.java index ee05ed29d6a0..c0e6131456b3 100644 --- a/src/jdk.httpserver/share/classes/sun/net/httpserver/Request.java +++ b/src/jdk.httpserver/share/classes/sun/net/httpserver/Request.java @@ -37,7 +37,6 @@ */ class Request { - static final int BUF_LEN = 2048; static final byte CR = 13; static final byte LF = 10; static final byte FIRST_CHAR = 32; @@ -60,11 +59,6 @@ class Request { } while ("".equals(startLine)); } - - char[] buf = new char[BUF_LEN]; - int pos; - StringBuffer lineBuf; - public InputStream inputStream() { return is; } @@ -80,7 +74,7 @@ public OutputStream outputStream() { public String readLine() throws IOException { boolean gotLF = false; - pos = 0; lineBuf = new StringBuffer(); + StringBuilder lineBuf = new StringBuilder(); long lsize = 32; // For the first request that comes on a clear connection @@ -116,7 +110,8 @@ public String readLine() throws IOException { } offset++; } - consume(c); + checkCtl(c); + lineBuf.append((char)c); lsize = lsize + 1; } if (maxReqHeaderSize > 0 && lsize > maxReqHeaderSize) { @@ -125,16 +120,7 @@ public String readLine() throws IOException { ServerConfig.getMaxReqHeaderSize() + "."); } } - lineBuf.append(buf, 0, pos); - return new String(lineBuf); - } - - private void consume(int c) throws IOException { - if (pos == BUF_LEN) { - lineBuf.append(buf); - pos = 0; - } - buf[pos++] = (char)c; + return lineBuf.toString(); } /** @@ -155,6 +141,13 @@ void checkCRLF(int firstc) throws IOException { } } + void checkCtl(int c) throws IOException { + if (c == 0x7f || + c < ' ' && c != CR && c!= LF && c != '\t') { + throw new ProtocolException("Invalid character in request header"); + } + } + Headers headers() throws IOException { if (hdrs != null) { return hdrs; @@ -177,6 +170,7 @@ Headers headers() throws IOException { int keyend = -1; int c; boolean inKey = firstc != ' ' && firstc != '\t'; + checkCtl(firstc); s[len++] = (char) firstc; hsize = hsize + 1; parseloop:{ @@ -188,6 +182,7 @@ Headers headers() throws IOException { ? maxReqHeaderSize - hsize - 32 : Long.MAX_VALUE; while ((c = is.read()) >= 0) { + checkCtl(c); switch (c) { case ':': if (inKey && len > 0) { @@ -241,7 +236,7 @@ Headers headers() throws IOException { } String v; if (keyend >= len) - v = new String(); + v = ""; else v = String.copyValueOf(s, keyend, len - keyend); From 72a42f15b597bfefb1091fe9a0e360de4812a58c Mon Sep 17 00:00:00 2001 From: Weijun Wang Date: Wed, 8 Jul 2026 16:22:26 +0000 Subject: [PATCH 039/169] 8387217: Enhance krb5 client Reviewed-by: rhalade, semery, valeriep --- .../sun/security/krb5/KrbAsReqBuilder.java | 4 +-- .../security/krb5/internal/ETypeInfo2.java | 5 ++- .../security/krb5/internal/KDCReqBody.java | 2 +- .../sun/security/krb5/internal/PAData.java | 31 +++++++++++++------ test/lib/jdk/test/lib/security/DerUtils.java | 14 +++++---- 5 files changed, 36 insertions(+), 20 deletions(-) diff --git a/src/java.security.jgss/share/classes/sun/security/krb5/KrbAsReqBuilder.java b/src/java.security.jgss/share/classes/sun/security/krb5/KrbAsReqBuilder.java index 0d1375756874..c65a92e6b62d 100644 --- a/src/java.security.jgss/share/classes/sun/security/krb5/KrbAsReqBuilder.java +++ b/src/java.security.jgss/share/classes/sun/security/krb5/KrbAsReqBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -357,7 +357,7 @@ private KrbAsReqBuilder send() throws KrbException, IOException { preAuthFailedOnce = true; KRBError kerr = ke.getError(); int paEType = PAData.getPreferredEType(kerr.getPA(), - EType.getDefaults("default_tkt_enctypes")[0]); + req.getMessage().reqBody.eType); if (password == null) { EncryptionKey[] ks = Krb5Util.keysFromJavaxKeyTab(ktab, cname); pakey = EncryptionKey.findKey(paEType, ks); diff --git a/src/java.security.jgss/share/classes/sun/security/krb5/internal/ETypeInfo2.java b/src/java.security.jgss/share/classes/sun/security/krb5/internal/ETypeInfo2.java index c1abbaa3f52f..c9a6d096743a 100644 --- a/src/java.security.jgss/share/classes/sun/security/krb5/internal/ETypeInfo2.java +++ b/src/java.security.jgss/share/classes/sun/security/krb5/internal/ETypeInfo2.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2009, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -165,4 +165,7 @@ public byte[] getParams() { return ((s2kparams == null) ? null : s2kparams.clone()); } + public boolean hasParams() { + return s2kparams != null; + } } diff --git a/src/java.security.jgss/share/classes/sun/security/krb5/internal/KDCReqBody.java b/src/java.security.jgss/share/classes/sun/security/krb5/internal/KDCReqBody.java index 8579dfdac412..d0354095a5be 100644 --- a/src/java.security.jgss/share/classes/sun/security/krb5/internal/KDCReqBody.java +++ b/src/java.security.jgss/share/classes/sun/security/krb5/internal/KDCReqBody.java @@ -77,9 +77,9 @@ public class KDCReqBody { public KerberosTime till; public KerberosTime rtime; //optional public HostAddresses addresses; //optional + public int[] eType = null; //a sequence; not optional private int nonce; - private int[] eType = null; //a sequence; not optional private EncryptedData encAuthorizationData; //optional private Ticket[] additionalTickets; //optional diff --git a/src/java.security.jgss/share/classes/sun/security/krb5/internal/PAData.java b/src/java.security.jgss/share/classes/sun/security/krb5/internal/PAData.java index bcbae770505d..4b95bfc6b09e 100644 --- a/src/java.security.jgss/share/classes/sun/security/krb5/internal/PAData.java +++ b/src/java.security.jgss/share/classes/sun/security/krb5/internal/PAData.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -188,13 +188,13 @@ public static PAData[] parseSequence(DerInputStream data, * * (This is useful when PA-DATAs from KRB-ERROR and AS-REP are combined). * - * @return the etype, or defaultEType if not enough info + * @return the etype, or requestedETypes[0] if not enough info * @throws Asn1Exception|IOException if there is an encoding error */ - public static int getPreferredEType(PAData[] pas, int defaultEType) + public static int getPreferredEType(PAData[] pas, int[] requestedETypes) throws IOException, Asn1Exception { - if (pas == null) return defaultEType; + if (pas == null) return requestedETypes[0]; DerValue d = null, d2 = null; for (PAData p: pas) { @@ -209,23 +209,34 @@ public static int getPreferredEType(PAData[] pas, int defaultEType) } } if (d2 != null) { + ArrayList offered = new ArrayList<>(); while (d2.data.available() > 0) { DerValue value = d2.data.getDerValue(); ETypeInfo2 tmp = new ETypeInfo2(value); - if (EType.isNewer(tmp.getEType()) || tmp.getParams() == null) { + if (EType.isNewer(tmp.getEType()) || !tmp.hasParams()) { // we don't support non-null s2kparams for old etypes - return tmp.getEType(); + offered.add(tmp.getEType()); + } + } + for (int etype : requestedETypes) { + if (offered.contains(etype)) { + return etype; } } } if (d != null) { + ArrayList offered = new ArrayList<>(); while (d.data.available() > 0) { DerValue value = d.data.getDerValue(); - ETypeInfo tmp = new ETypeInfo(value); - return tmp.getEType(); + offered.add(new ETypeInfo(value).getEType()); + } + for (int etype : requestedETypes) { + if (offered.contains(etype)) { + return etype; + } } } - return defaultEType; + return requestedETypes[0]; } /** @@ -280,7 +291,7 @@ public static SaltAndParams getSaltAndParams(int eType, PAData[] pas) DerValue value = d2.data.getDerValue(); ETypeInfo2 tmp = new ETypeInfo2(value); if (tmp.getEType() == eType && - (EType.isNewer(eType) || tmp.getParams() == null)) { + (EType.isNewer(eType) || !tmp.hasParams())) { // we don't support non-null s2kparams for old etypes return new SaltAndParams(tmp.getSalt(), tmp.getParams()); } diff --git a/test/lib/jdk/test/lib/security/DerUtils.java b/test/lib/jdk/test/lib/security/DerUtils.java index 2323065b5952..ed59dabf7d41 100644 --- a/test/lib/jdk/test/lib/security/DerUtils.java +++ b/test/lib/jdk/test/lib/security/DerUtils.java @@ -30,6 +30,7 @@ import sun.security.util.ObjectIdentifier; import java.io.IOException; +import java.util.Locale; public class DerUtils { /** @@ -37,9 +38,10 @@ public class DerUtils { *

    * The location of the inner DerValue is expressed as a string, in which * each character is a step from the outer DerValue into the inner one. - * If it's a number n, the n'th element (starting from 0) of a sequence - * is the next step. If it's 'c', the content of an OctetString parsed - * as a DerValue is the next step. Note that n cannot be bigger than 9. + * If it's a number n (in uppercase HEX, from 0 to F), the n'th element + * of a sequence is the next step. If it's 'c', the content of an OCTET + * STRING parsed as a DerValue is the next step. Note that n cannot be + * bigger than 15 (HEX "F"). *

    * Attention: do not reuse the return value. DerValue is mutable and * reading it advances a pointer inside. @@ -80,7 +82,7 @@ public static DerValue innerDerValue(byte[] data, String location) } else { DerInputStream ins = v.getData(); // skip n DerValue in the sequence - for (int i = 0; i < step - '0'; i++) { + for (int i = 0; i < Integer.parseInt(step + "", 16); i++) { ins.getDerValue(); } if (ins.available() > 0) { @@ -183,8 +185,8 @@ private static DerOutputStream modify0(byte[] data, String now, String target, } else { for (int i = 0; ; i++) { // We only support locations of one digit now - if (i > 9) throw new IllegalStateException("Too big " + i); - String pos = now + i; + if (i > 15) throw new IllegalStateException("Too big " + i); + String pos = now + Integer.toHexString(i).toUpperCase(Locale.ROOT); var sub = DerUtils.innerDerValue(data, pos); // current value if (sub == null) break; // at the end if (target.equals(pos)) { // the one we want to change From dff917e5657efac503974da4b4ff5184ced6f1c1 Mon Sep 17 00:00:00 2001 From: Prasanta Sadhukhan Date: Sun, 12 Jul 2026 04:15:05 +0000 Subject: [PATCH 040/169] 8386013: Enhance EventListenerList Reviewed-by: kizune, rhalade, prr, skoivu --- .../classes/javax/swing/event/EventListenerList.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/java.desktop/share/classes/javax/swing/event/EventListenerList.java b/src/java.desktop/share/classes/javax/swing/event/EventListenerList.java index 234a58de4ece..c894f434e2fa 100644 --- a/src/java.desktop/share/classes/javax/swing/event/EventListenerList.java +++ b/src/java.desktop/share/classes/javax/swing/event/EventListenerList.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -301,9 +301,12 @@ private void readObject(ObjectInputStream s) ClassLoader cl = Thread.currentThread().getContextClassLoader(); EventListener l = (EventListener)s.readObject(); String name = (String) listenerTypeOrNull; - @SuppressWarnings("unchecked") - Class tmp = (Class)Class.forName(name, true, cl); - add(tmp, l); + Class c = Class.forName(name, false, cl); + if (EventListener.class.isAssignableFrom(c)) { + @SuppressWarnings("unchecked") + Class tmp = (Class) c; + add(tmp, l); + } } } From 192660424689742b94698b99ad4b6221711f24f5 Mon Sep 17 00:00:00 2001 From: Artur Barashev Date: Mon, 13 Jul 2026 13:43:38 +0000 Subject: [PATCH 041/169] 8386923: Enhance TLS Extensions Reviewed-by: ahgross, jnibedita, pkumaraswamy, rhalade, hchao, jnimeh, weijun, mullan --- .../share/classes/sun/security/ssl/SSLExtensions.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/java.base/share/classes/sun/security/ssl/SSLExtensions.java b/src/java.base/share/classes/sun/security/ssl/SSLExtensions.java index c325890a3b66..5c596932dc27 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLExtensions.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLExtensions.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -68,8 +68,17 @@ final class SSLExtensions { } encodedLength = len + 2; // 2: the length of the extensions. + Set extIdSet = new HashSet<>(); // To check for duplicates. + while (len > 0) { int extId = Record.getInt16(m); + + if (!extIdSet.add(extId)) { + throw hm.handshakeContext.conContext.fatal( + Alert.ILLEGAL_PARAMETER, + "Duplicate extension with ID: " + extId); + } + int extLen = Record.getInt16(m); if (extLen > m.remaining()) { throw hm.handshakeContext.conContext.fatal( From b0413bda33fe360d61afa14c302221da77a27086 Mon Sep 17 00:00:00 2001 From: Michael McMahon Date: Tue, 14 Jul 2026 17:09:32 +0000 Subject: [PATCH 042/169] 8384872: Enhance HTTP Client Reviewed-by: rhalade, dfuchs, vyazici, skoivu --- .../jdk/internal/net/http/CheckFilename.java | 76 +++++++++++++++++++ .../net/http/ResponseBodyHandlers.java | 2 +- 2 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 src/java.net.http/share/classes/jdk/internal/net/http/CheckFilename.java diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/CheckFilename.java b/src/java.net.http/share/classes/jdk/internal/net/http/CheckFilename.java new file mode 100644 index 000000000000..fc2b3c428f8a --- /dev/null +++ b/src/java.net.http/share/classes/jdk/internal/net/http/CheckFilename.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.net.http; + +import java.util.Set; +import java.util.List; +import java.util.Locale; +import jdk.internal.util.OperatingSystem; + +/** + * Checks for disallowed filenames that may be provided by a server + * which we do not want to write to. On Windows we disallow any filename + * that looks like a DOS legacy device. No restrictions on other platforms. + */ +final class CheckFilename { + + private CheckFilename() { + throw new InternalError(); + } + + private static final boolean isWindows = OperatingSystem.isWindows(); + + /** + * Reserved names documented at: + * https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file + * and other Microsoft api documentation + */ + private static final Set WINDOWS_RESERVED_NAMES = + Set.of("AUX", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", + "COM7", "COM8", "COM9", "COM¹", "COM²", "COM³", "CON", "CONIN$", + "CONOUT$", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", + "LPT9", "LPT¹", "LPT²", "LPT³", "NUL", "PRN"); + + /** + * name must be the final component of the pathname only. + * Possibly including an extension. + */ + public static boolean isAllowed(String name) { + if (!isWindows) + return true; + + // Microsoft documents reserved device names followed by extensions as equivalent to the device name. + // `CON.txt`, `NUL.tar.gz`, `COM1.txt`, etc. should not be allowed. + int dot = name.indexOf('.'); + if (dot != -1) { + name = name.substring(0, dot); + } + name = name.toUpperCase(Locale.ROOT) + .stripTrailing(); + + return !WINDOWS_RESERVED_NAMES.contains(name); + } +} diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/ResponseBodyHandlers.java b/src/java.net.http/share/classes/jdk/internal/net/http/ResponseBodyHandlers.java index a74c632c2185..d1f5b21e054f 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/ResponseBodyHandlers.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/ResponseBodyHandlers.java @@ -320,7 +320,7 @@ public BodySubscriber apply(ResponseInfo responseInfo) { filenameParam = filenameParam.trim(); - if (PROHIBITED.contains(filenameParam)) { + if (PROHIBITED.contains(filenameParam) || !CheckFilename.isAllowed(filenameParam)) { throw unchecked(responseInfo, "Prohibited Content-Disposition filename parameter:" + filenameParam); From 5fcdf381b411cd375590d7b2de345792675ee6b7 Mon Sep 17 00:00:00 2001 From: Stuart Marks Date: Wed, 15 Jul 2026 16:33:23 +0000 Subject: [PATCH 043/169] 8384654: Enhance Template Handling Reviewed-by: ahgross, rhalade, joehw, naoto, jlu --- .../xsltc/compiler/util/ErrorMessages.java | 5 ++++ .../xsltc/compiler/util/ErrorMsg.java | 3 ++- .../internal/xsltc/trax/TemplatesImpl.java | 11 +++++++- .../jdk/xml/internal/JdkConstants.java | 5 ++++ .../jdk/xml/internal/JdkXmlFeatures.java | 22 ++++++++++++---- src/java.xml/share/classes/module-info.java | 26 +++++++++++++++++-- 6 files changed, 63 insertions(+), 9 deletions(-) diff --git a/src/java.xml/share/classes/com/sun/org/apache/xalan/internal/xsltc/compiler/util/ErrorMessages.java b/src/java.xml/share/classes/com/sun/org/apache/xalan/internal/xsltc/compiler/util/ErrorMessages.java index a3875969bb7e..4c5f249326f8 100644 --- a/src/java.xml/share/classes/com/sun/org/apache/xalan/internal/xsltc/compiler/util/ErrorMessages.java +++ b/src/java.xml/share/classes/com/sun/org/apache/xalan/internal/xsltc/compiler/util/ErrorMessages.java @@ -1037,6 +1037,11 @@ public Object[][] getContents() "smaller templates." }, + {ErrorMsg.DESERIALIZE_TRANSLET_ERR, + "Support for deserializing TemplatesImpl is disabled. This can be " + + "overridden by setting the ''jdk.xml.enableTemplatesImplDeserialization'' " + + "property to ''true''."}, + {ErrorMsg.XPATH_GROUP_LIMIT, "JAXP0801001: the compiler encountered an XPath expression containing " + "''{0}'' groups that exceeds the ''{1}'' limit set by ''{2}''."}, diff --git a/src/java.xml/share/classes/com/sun/org/apache/xalan/internal/xsltc/compiler/util/ErrorMsg.java b/src/java.xml/share/classes/com/sun/org/apache/xalan/internal/xsltc/compiler/util/ErrorMsg.java index 8ef4c9c86b60..ad63a44c57a7 100644 --- a/src/java.xml/share/classes/com/sun/org/apache/xalan/internal/xsltc/compiler/util/ErrorMsg.java +++ b/src/java.xml/share/classes/com/sun/org/apache/xalan/internal/xsltc/compiler/util/ErrorMsg.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2026, Oracle and/or its affiliates. All rights reserved. */ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -174,6 +174,7 @@ public final class ErrorMsg { public static final String XPATH_GROUP_LIMIT = "XPATH_GROUP_LIMIT"; public static final String XPATH_OPERATOR_LIMIT = "XPATH_OPERATOR_LIMIT"; public static final String XPATH_TOTAL_OPERATOR_LIMIT = "XPATH_TOTAL_OPERATOR_LIMIT"; + public static final String DESERIALIZE_TRANSLET_ERR = "DESERIALIZE_TEMPLATES_ERR"; // All error messages are localized and are stored in resource bundles. // This array and the following 4 strings are read from that bundle. diff --git a/src/java.xml/share/classes/com/sun/org/apache/xalan/internal/xsltc/trax/TemplatesImpl.java b/src/java.xml/share/classes/com/sun/org/apache/xalan/internal/xsltc/trax/TemplatesImpl.java index 71aad15b09f6..cef94e450e90 100644 --- a/src/java.xml/share/classes/com/sun/org/apache/xalan/internal/xsltc/trax/TemplatesImpl.java +++ b/src/java.xml/share/classes/com/sun/org/apache/xalan/internal/xsltc/trax/TemplatesImpl.java @@ -58,6 +58,7 @@ import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.URIResolver; import jdk.xml.internal.JdkConstants; +import jdk.xml.internal.JdkXmlFeatures; import jdk.xml.internal.XMLSecurityManager; @@ -261,6 +262,14 @@ public TemplatesImpl() { } private void readObject(ObjectInputStream is) throws IOException, ClassNotFoundException { + TransformerFactoryImpl tfactory = new TransformerFactoryImpl(); + + if (!tfactory.getFeature( + JdkXmlFeatures.XmlFeature.ENABLE_TEMPLATESIMPL_DESERIALIZATION.apiProperty())) { + ErrorMsg err = new ErrorMsg(ErrorMsg.DESERIALIZE_TRANSLET_ERR); + throw new UnsupportedOperationException(err.toString()); + } + // We have to read serialized fields first. ObjectInputStream.GetField gf = is.readFields(); _name = (String)gf.get("_name", null); @@ -275,7 +284,7 @@ private void readObject(ObjectInputStream is) _uriResolver = (URIResolver) is.readObject(); } - _tfactory = new TransformerFactoryImpl(); + _tfactory = tfactory; } diff --git a/src/java.xml/share/classes/jdk/xml/internal/JdkConstants.java b/src/java.xml/share/classes/jdk/xml/internal/JdkConstants.java index 6ef32fb6d282..be0e7ec021a3 100644 --- a/src/java.xml/share/classes/jdk/xml/internal/JdkConstants.java +++ b/src/java.xml/share/classes/jdk/xml/internal/JdkConstants.java @@ -435,4 +435,9 @@ public final class JdkConstants { public static final int CDATA_CHUNK_SIZE_DEFAULT = SecuritySupport.getJAXPSystemProperty(Integer.class, CDATA_CHUNK_SIZE, "0"); + /** + * Enable TemplatesImpl deserialization. + */ + public static final String SP_ENABLE_TEMPLATESIMPL_DESERIALIZATION = + "jdk.xml.enableTemplatesImplDeserialization"; } diff --git a/src/java.xml/share/classes/jdk/xml/internal/JdkXmlFeatures.java b/src/java.xml/share/classes/jdk/xml/internal/JdkXmlFeatures.java index fb516d17e24f..13e69ac10c77 100644 --- a/src/java.xml/share/classes/jdk/xml/internal/JdkXmlFeatures.java +++ b/src/java.xml/share/classes/jdk/xml/internal/JdkXmlFeatures.java @@ -31,6 +31,7 @@ import static jdk.xml.internal.JdkConstants.XPATH_ALLOW_NON_XSLT_SYS_PROPS; import static jdk.xml.internal.JdkXmlUtils.SP_USE_CATALOG; +import static jdk.xml.internal.JdkConstants.SP_ENABLE_TEMPLATESIMPL_DESERIALIZATION; /** * This class manages JDK's XML Features. Previously added features and properties @@ -83,7 +84,16 @@ public static enum XmlFeature { */ JDK_XPATH_SYSTEM_PROPERTY(null, XPATH_ALLOW_NON_XSLT_SYS_PROPS, XPATH_ALLOW_NON_XSLT_SYS_PROPS, false, - null, null, false, false, true, true); + null, null, false, false, true, true), + + /* + * Feature TemplatesImplDeserialization + * FSP: TemplatesImpl deserialization is enforced by FSP. When FSP is on, + * TemplatesImpl deserialization is disabled. + */ + ENABLE_TEMPLATESIMPL_DESERIALIZATION(null, + SP_ENABLE_TEMPLATESIMPL_DESERIALIZATION, SP_ENABLE_TEMPLATESIMPL_DESERIALIZATION, + false, null, null, false, false, true, true); private final ImplPropMap implMap; private final String name; @@ -103,16 +113,18 @@ public static enum XmlFeature { * parameters. * @param name the name of the feature * @param nameSP the name of the System Property + * @param differ * @param nameOld the name of the corresponding legacy property * @param nameOldSP the system property of the legacy property - * @param value the value of the feature - * @param hasSystem a flag to indicate whether the feature is supported + * @param valueDefault the default value of the feature + * @param valueEnforced the FSP-enforced value of the feature + * @param hasSystem a flag indicating whether a system property is supported for the feature * @param enforced a flag indicating whether the feature is * FSP (Feature_Secure_Processing) enforced * with a System property */ XmlFeature(ImplPropMap implMap, String name, String nameSP, boolean differ, - String nameOld, String nameOldSP, boolean value, boolean valueEnforced, + String nameOld, String nameOldSP, boolean valueDefault, boolean valueEnforced, boolean hasSystem, boolean enforced) { this.implMap = implMap; if (implMap != null) { @@ -127,7 +139,7 @@ public static enum XmlFeature { this.nameOldSP = nameOldSP; } this.differ = differ; - this.valueDefault = value; + this.valueDefault = valueDefault; this.valueEnforced = valueEnforced; this.hasSystem = hasSystem; this.enforced = enforced; diff --git a/src/java.xml/share/classes/module-info.java b/src/java.xml/share/classes/module-info.java index 19bb39475fd9..fb2f2fe602d7 100644 --- a/src/java.xml/share/classes/module-info.java +++ b/src/java.xml/share/classes/module-info.java @@ -1061,6 +1061,24 @@ * 22 * * + * {@systemProperty jdk.xml.enableTemplatesImplDeserialization} + * Enables or disables deserialization of the JDK's implementation of the + * {@link javax.xml.transform.Templates} interface. + * + * yes + * boolean + * true or false. True indicates that deserialization is allowed. False indicates that + * deserialization is disallowed. + * false + * false + * Yes + * + * Transform + * + * [8] + * 7 + * + * * {@systemProperty jdk.xml.resource.access} * Defines allowed access to external resources by specifying a list * of URI patterns, each following the syntax:

    @@ -1152,7 +1170,7 @@ * * {@code *} * {@code *} - * No [8] + * No [9] * * DOM
    * SAX
    @@ -1207,7 +1225,11 @@ * they are set to deny or ignore, other properties that regulate a part or an * aspect of DTD shall have no effect. *

    - * [8] In the current release, the state of the {@code jdk.xml.resource.access} + * [8] This property can be set only via a system property or in the + * JAXP Configuration File. Setting this property + * on a transformer factory or on a {@code Transformer} instance has no effect. + *

    + * [9] In the current release, the state of the {@code jdk.xml.resource.access} * property does not change when {@link javax.xml.XMLConstants#FEATURE_SECURE_PROCESSING FEATURE_SECURE_PROCESSING} * (FSP) is enabled. In future releases, it will transition to a restrictive setting * when FSP is enabled. From b54d299b081e3e579e62eb512e297d164b1322d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Jeli=C5=84ski?= Date: Thu, 16 Jul 2026 19:05:13 +0000 Subject: [PATCH 044/169] 8386525: Enhance HTTP Server Handler Reviewed-by: rhalade, michaelm, dfuchs --- .../sun/net/httpserver/ServerConfig.java | 8 +++++++- .../sun/net/httpserver/ServerImpl.java | 20 +++++++++++++++++++ .../ContextPathMatcherPathPrefixTest.java | 3 +-- test/jdk/com/sun/net/httpserver/Test10.java | 14 +++++++++---- test/jdk/com/sun/net/httpserver/Test3.java | 10 +++++----- test/jdk/com/sun/net/httpserver/Test4.java | 10 +++++----- test/jdk/com/sun/net/httpserver/Test5.java | 10 +++++----- .../com/sun/net/httpserver/bugs/B6361557.java | 13 ++++++++++-- .../com/sun/net/httpserver/bugs/B6393710.java | 4 +++- .../com/sun/net/httpserver/bugs/B6433018.java | 3 ++- .../httpserver/bugs/TruncatedRequestBody.java | 6 ++++-- 11 files changed, 73 insertions(+), 28 deletions(-) diff --git a/src/jdk.httpserver/share/classes/sun/net/httpserver/ServerConfig.java b/src/jdk.httpserver/share/classes/sun/net/httpserver/ServerConfig.java index 3eda0eb1a484..3bc836186cda 100644 --- a/src/jdk.httpserver/share/classes/sun/net/httpserver/ServerConfig.java +++ b/src/jdk.httpserver/share/classes/sun/net/httpserver/ServerConfig.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -71,6 +71,7 @@ class ServerConfig { // the value of the TCP_NODELAY socket-level option private static final boolean noDelay; + private static final boolean hostValidationDisabled; static { @@ -116,6 +117,7 @@ class ServerConfig { debug = Boolean.getBoolean("sun.net.httpserver.debug"); noDelay = Boolean.getBoolean("sun.net.httpserver.nodelay"); + hostValidationDisabled = Boolean.getBoolean("sun.net.httpserver.disableHostValidation"); } @@ -219,4 +221,8 @@ static long getReqRspTimerScheduleMillis() { static boolean noDelay() { return noDelay; } + + static boolean isHostValidationDisabled() { + return hostValidationDisabled; + } } diff --git a/src/jdk.httpserver/share/classes/sun/net/httpserver/ServerImpl.java b/src/jdk.httpserver/share/classes/sun/net/httpserver/ServerImpl.java index cfc02c3910f7..6339e41a3f45 100644 --- a/src/jdk.httpserver/share/classes/sun/net/httpserver/ServerImpl.java +++ b/src/jdk.httpserver/share/classes/sun/net/httpserver/ServerImpl.java @@ -110,6 +110,7 @@ class ServerImpl { static final long REQ_RSP_TIMER_SCHEDULE = ServerConfig.getReqRspTimerScheduleMillis(); static final long MAX_REQ_TIME = getTimeMillis(ServerConfig.getMaxReqTime()); static final long MAX_RSP_TIME = getTimeMillis(ServerConfig.getMaxRspTime()); + static final boolean HOST_VALIDATION_DISABLED = ServerConfig.isHostValidationDisabled();; static final boolean reqRspTimeoutEnabled = MAX_REQ_TIME != -1 || MAX_RSP_TIME != -1; // the maximum idle duration for a connection which is currently idle but has served // some request in the past @@ -847,6 +848,25 @@ public void run() { requestCompleted(connection); } } + /* check if there's a single Host header */ + if (!HOST_VALIDATION_DISABLED) { + List hostValueList = headers.get("Host"); + if (hostValueList == null || hostValueList.isEmpty()) { + if (!version.equalsIgnoreCase("http/1.0")) { + reject(400, requestLine, "Missing Host header"); + return; + } + } else if (hostValueList.size() > 1) { + reject(400, requestLine, "Multiple Host headers"); + return; + } else { + String host = hostValueList.get(0); + if (host.indexOf(',') != -1) { + reject(400, requestLine, "Multiple Host header values"); + return; + } + } + } ctx = contexts.findContext(protocol, uri.getPath()); if (ctx == null) { reject(Code.HTTP_NOT_FOUND, diff --git a/test/jdk/com/sun/net/httpserver/ContextPathMatcherPathPrefixTest.java b/test/jdk/com/sun/net/httpserver/ContextPathMatcherPathPrefixTest.java index 6b2606365858..3d302793fedf 100644 --- a/test/jdk/com/sun/net/httpserver/ContextPathMatcherPathPrefixTest.java +++ b/test/jdk/com/sun/net/httpserver/ContextPathMatcherPathPrefixTest.java @@ -222,8 +222,7 @@ private void expect0(int statusCode, String requestLinePrefix) throws IOExceptio // Write the request byte[] requestBytes = String - // `Connection: close` is required for `BufferedReader::readLine` to work. - .format("%s HTTP/1.1\r\nConnection: close\r\n\r\n", requestLinePrefix) + .format("%s HTTP/1.0\r\n\r\n", requestLinePrefix) .getBytes(CHARSET); outputStream.write(requestBytes); outputStream.flush(); diff --git a/test/jdk/com/sun/net/httpserver/Test10.java b/test/jdk/com/sun/net/httpserver/Test10.java index a256acbca118..5eb8b1a7cd3f 100644 --- a/test/jdk/com/sun/net/httpserver/Test10.java +++ b/test/jdk/com/sun/net/httpserver/Test10.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,9 +25,11 @@ * @test * @bug 7005016 * @summary pit jdk7 b121 sqe test jhttp/HttpServer150013 failing - * @run main/othervm -Dsun.net.httpserver.clockTick=1000 -Dsun.net.httpserver.idleInterval=3 Test10 * @run main/othervm -Dsun.net.httpserver.clockTick=1000 -Dsun.net.httpserver.idleInterval=3 - * -Djava.net.preferIPv6Addresses Test10 + * -Dsun.net.httpserver.disableHostValidation=true Test10 + * @run main/othervm -Dsun.net.httpserver.clockTick=1000 -Dsun.net.httpserver.idleInterval=3 + * -Djava.net.preferIPv6Addresses + * -Dsun.net.httpserver.disableHostValidation=true Test10 */ import com.sun.net.httpserver.*; @@ -42,6 +44,7 @@ */ public class Test10 extends Test { + static volatile boolean handlerInvoked; public static void main (String[] args) throws Exception { System.out.print ("Test10: "); Handler handler = new Handler(); @@ -66,10 +69,10 @@ public static void main (String[] args) throws Exception { } static class Handler implements HttpHandler { - volatile int invocation = 0; public void handle (HttpExchange t) throws IOException { + handlerInvoked = true; InputStream is = t.getRequestBody(); while (is.read() != -1); Headers map = t.getRequestHeaders(); @@ -89,6 +92,9 @@ public static void doClient (int port) throws Exception { int c; byte[] b = new byte [1024]; while ((c=is.read(b)) != -1) ; + if (!handlerInvoked) { + throw new RuntimeException("Handler not invoked"); + } is.close(); socket.close(); } diff --git a/test/jdk/com/sun/net/httpserver/Test3.java b/test/jdk/com/sun/net/httpserver/Test3.java index bbb307dc9f27..50c364c9cc40 100644 --- a/test/jdk/com/sun/net/httpserver/Test3.java +++ b/test/jdk/com/sun/net/httpserver/Test3.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -125,13 +125,13 @@ static void checkBody (InputStream is, String cmp) throws IOException { "HTTP/1.1 200 OK.*Xtest: 3.*"; public static void doClient (int port) throws Exception { - String s = "GET /test/1.html HTTP/1.1\r\nContent-length: 20\r\n"+ + String s = "GET /test/1.html HTTP/1.1\r\nHost: localhost\r\nContent-length: 20\r\n"+ "\r\n" +body1 + - "GET /test/2.html HTTP/1.1\r\nContent-length: 30\r\n"+ + "GET /test/2.html HTTP/1.1\r\nHost: localhost\r\nContent-length: 30\r\n"+ "\r\n"+ body2 + - "GET /test/3.html HTTP/1.1\r\nContent-length: 10\r\n"+ + "GET /test/3.html HTTP/1.1\r\nHost: localhost\r\nContent-length: 10\r\n"+ "\r\n"+ body3 + - "GET /test/4.html HTTP/1.1\r\nContent-length: 10\r\n"+ + "GET /test/4.html HTTP/1.1\r\nHost: localhost\r\nContent-length: 10\r\n"+ "\r\n"+body4; Socket socket = new Socket (InetAddress.getLoopbackAddress(), port); diff --git a/test/jdk/com/sun/net/httpserver/Test4.java b/test/jdk/com/sun/net/httpserver/Test4.java index ba2f5b4ec825..033425fdad75 100644 --- a/test/jdk/com/sun/net/httpserver/Test4.java +++ b/test/jdk/com/sun/net/httpserver/Test4.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -124,13 +124,13 @@ static void checkBody (InputStream is, String cmp) throws IOException { "HTTP/1.1 200 OK.*Xtest: 3.*"; public static void doClient (int port) throws Exception { - String s = "GET /test/1.html HTTP/1.1\r\nContent-length: 20\r\n"+ + String s = "GET /test/1.html HTTP/1.1\r\nHost: localhost\r\nContent-length: 20\r\n"+ "\r\n" +body1 + - "GET /test/2.html HTTP/1.1\r\nContent-length: 30\r\n"+ + "GET /test/2.html HTTP/1.1\r\nHost: localhost\r\nContent-length: 30\r\n"+ "\r\n"+ body2 + - "GET /test/3.html HTTP/1.1\r\nContent-length: 10\r\n"+ + "GET /test/3.html HTTP/1.1\r\nHost: localhost\r\nContent-length: 10\r\n"+ "\r\n"+ body3 + - "GET /test/4.html HTTP/1.1\r\nContent-length: 10\r\n"+ + "GET /test/4.html HTTP/1.1\r\nHost: localhost\r\nContent-length: 10\r\n"+ "\r\n"+body4; Socket socket = new Socket (InetAddress.getLoopbackAddress(), port); diff --git a/test/jdk/com/sun/net/httpserver/Test5.java b/test/jdk/com/sun/net/httpserver/Test5.java index 68b7d7b357bc..91d18a7d934d 100644 --- a/test/jdk/com/sun/net/httpserver/Test5.java +++ b/test/jdk/com/sun/net/httpserver/Test5.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -123,13 +123,13 @@ static void checkBody (InputStream is, String cmp) throws IOException { "HTTP/1.1 200 OK.*Xtest: 3.*"; public static void doClient (int port) throws Exception { - String s = "GET /test/1.html HTTP/1.1\r\nContent-length: 20\r\n"+ + String s = "GET /test/1.html HTTP/1.1\r\nHost: localhost\r\nContent-length: 20\r\n"+ "\r\n" +body1 + - "GET /test/2.html HTTP/1.1\r\nContent-length: 30\r\n"+ + "GET /test/2.html HTTP/1.1\r\nHost: localhost\r\nContent-length: 30\r\n"+ "\r\n"+ body2 + - "GET /test/3.html HTTP/1.1\r\nContent-length: 10\r\n"+ + "GET /test/3.html HTTP/1.1\r\nHost: localhost\r\nContent-length: 10\r\n"+ "\r\n"+ body3 + - "GET /test/4.html HTTP/1.1\r\nContent-length: 10\r\n"+ + "GET /test/4.html HTTP/1.1\r\nHost: localhost\r\nContent-length: 10\r\n"+ "\r\n"+body4; Socket socket = new Socket (InetAddress.getLoopbackAddress(), port); diff --git a/test/jdk/com/sun/net/httpserver/bugs/B6361557.java b/test/jdk/com/sun/net/httpserver/bugs/B6361557.java index a92bb9adfdc5..a8c274e9b100 100644 --- a/test/jdk/com/sun/net/httpserver/bugs/B6361557.java +++ b/test/jdk/com/sun/net/httpserver/bugs/B6361557.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2006, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2006, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -36,6 +36,8 @@ import java.nio.*; import java.nio.channels.*; import java.net.*; +import java.util.concurrent.atomic.AtomicInteger; + import static com.sun.net.httpserver.HttpExchange.RSPBODY_EMPTY; /** @@ -49,12 +51,14 @@ public class B6361557 { public static boolean error = false; static final int NUM = 1000; + static final AtomicInteger count = new AtomicInteger(0); static class Handler implements HttpHandler { int invocation = 1; public void handle (HttpExchange t) throws IOException { + count.getAndIncrement(); InputStream is = t.getRequestBody(); Headers map = t.getRequestHeaders(); Headers rmap = t.getResponseHeaders(); @@ -65,7 +69,9 @@ public void handle (HttpExchange t) } } - final static String request = "GET /test/foo.html HTTP/1.1\r\nContent-length: 0\r\n\r\n"; + final static String request = "GET /test/foo.html HTTP/1.1\r\n" + + "Host: localhost\r\n" + + "Content-length: 0\r\n\r\n"; final static ByteBuffer requestBuf = ByteBuffer.wrap(request.getBytes()); public static void main (String[] args) throws Exception { @@ -127,6 +133,9 @@ public static void main (String[] args) throws Exception { break; } } + if (count.get() != NUM) { + throw new RuntimeException("Unexpected number of handled requests: " +count.get()); + } server.stop(0); selector.close(); executor.shutdown (); diff --git a/test/jdk/com/sun/net/httpserver/bugs/B6393710.java b/test/jdk/com/sun/net/httpserver/bugs/B6393710.java index 68a3bac259f3..7d7206eaa817 100644 --- a/test/jdk/com/sun/net/httpserver/bugs/B6393710.java +++ b/test/jdk/com/sun/net/httpserver/bugs/B6393710.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2006, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2006, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -58,11 +58,13 @@ public class B6393710 { */ static String cmd = "POST /test/foo HTTP/1.1"+CRLF+ + "Host: localhost"+CRLF+ "Content-Length: 22"+CRLF+ "Pragma: no-cache"+CRLF+ "Cache-Control: no-cache"+CRLF+ CRLF+ ""+ "POST /test/foo HTTP/1.1"+CRLF+ + "Host: localhost"+CRLF+ "Content-Length: 22"+CRLF+ "Pragma: no-cache"+CRLF+ "Authorization: Basic ZnJlZDpmcmVkcGFzc3dvcmQ="+CRLF+ diff --git a/test/jdk/com/sun/net/httpserver/bugs/B6433018.java b/test/jdk/com/sun/net/httpserver/bugs/B6433018.java index 1f6876bf8727..ad8c863cd5f9 100644 --- a/test/jdk/com/sun/net/httpserver/bugs/B6433018.java +++ b/test/jdk/com/sun/net/httpserver/bugs/B6433018.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2006, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2006, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -46,6 +46,7 @@ public class B6433018 { static final String cmd = "POST /test/item HTTP/1.1"+CRLF+ + "Host: araku:9999"+CRLF+ "Keep-Alive: 300"+CRLF+ "Proxy-Connection: keep-alive"+CRLF+ "Content-Type: text/xml"+CRLF+ diff --git a/test/jdk/com/sun/net/httpserver/bugs/TruncatedRequestBody.java b/test/jdk/com/sun/net/httpserver/bugs/TruncatedRequestBody.java index 95a3d1787df8..1ea1e62be8bf 100644 --- a/test/jdk/com/sun/net/httpserver/bugs/TruncatedRequestBody.java +++ b/test/jdk/com/sun/net/httpserver/bugs/TruncatedRequestBody.java @@ -112,7 +112,8 @@ public static void main(String[] args) throws IOException, InterruptedException // Test 1: fixed length Socket sock = new Socket(loopback, port); - String s1 = "POST /foo HTTP/1.1\r\nContent-length: 200000\r\n" + String s1 = "POST /foo HTTP/1.1\r\nHost: localhost\r\n" + + "Content-length: 200000\r\n" + "\r\nfoo bar99"; OutputStream os = sock.getOutputStream(); @@ -123,7 +124,8 @@ public static void main(String[] args) throws IOException, InterruptedException // Test 2: chunked - String s2 = "POST /foo HTTP/1.1\r\nTransfer-encoding: chunked\r\n\r\n" + + String s2 = "POST /foo HTTP/1.1\r\nHost: localhost\r\n" + + "Transfer-encoding: chunked\r\n\r\n" + "100\r\nFoo bar"; sock = new Socket(loopback, port); os = sock.getOutputStream(); From bf0cc850a441e467706969d5da502d7938d59c99 Mon Sep 17 00:00:00 2001 From: Weijun Wang Date: Fri, 17 Jul 2026 16:24:55 +0000 Subject: [PATCH 045/169] 8387238: Enhance Kerberos validation Reviewed-by: rhalade, valeriep, semery --- .../share/classes/sun/security/krb5/KrbApReq.java | 2 +- .../share/classes/sun/security/krb5/KrbAsRep.java | 7 +++++-- .../share/classes/sun/security/krb5/KrbAsReqBuilder.java | 2 +- .../classes/sun/security/krb5/internal/crypto/EType.java | 6 +++--- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/java.security.jgss/share/classes/sun/security/krb5/KrbApReq.java b/src/java.security.jgss/share/classes/sun/security/krb5/KrbApReq.java index 8682619096d9..7d32bf9a9f64 100644 --- a/src/java.security.jgss/share/classes/sun/security/krb5/KrbApReq.java +++ b/src/java.security.jgss/share/classes/sun/security/krb5/KrbApReq.java @@ -510,7 +510,7 @@ private void createMessage(APOptions apOptions, // Check that key is one of the permitted types private static void checkPermittedEType(int target) throws KrbException { int[] etypes = EType.getDefaults("permitted_enctypes"); - if (!EType.isSupported(target, etypes)) { + if (!EType.isOneOf(target, etypes)) { throw new KrbException(EType.toString(target) + " encryption type not in permitted_enctypes list"); } diff --git a/src/java.security.jgss/share/classes/sun/security/krb5/KrbAsRep.java b/src/java.security.jgss/share/classes/sun/security/krb5/KrbAsRep.java index 2b4bd4da2668..2393284ebf2c 100644 --- a/src/java.security.jgss/share/classes/sun/security/krb5/KrbAsRep.java +++ b/src/java.security.jgss/share/classes/sun/security/krb5/KrbAsRep.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -53,11 +53,14 @@ final class KrbAsRep extends KrbKdcRep { // message, created by initiator after calling // the decrypt() method - KrbAsRep(byte[] ibuf) throws + KrbAsRep(byte[] ibuf, int[] requestedETypes) throws KrbException, Asn1Exception, IOException { DerValue encoding = new DerValue(ibuf); try { rep = new ASRep(encoding); + if (!EType.isOneOf(rep.encPart.getEType(), requestedETypes)) { + throw new KrbException("AS-REP enc_part not using requested etype"); + } } catch (Asn1Exception e) { rep = null; KRBError err = new KRBError(encoding); diff --git a/src/java.security.jgss/share/classes/sun/security/krb5/KrbAsReqBuilder.java b/src/java.security.jgss/share/classes/sun/security/krb5/KrbAsReqBuilder.java index c65a92e6b62d..5bf3bc503dd4 100644 --- a/src/java.security.jgss/share/classes/sun/security/krb5/KrbAsReqBuilder.java +++ b/src/java.security.jgss/share/classes/sun/security/krb5/KrbAsReqBuilder.java @@ -344,7 +344,7 @@ private KrbAsReqBuilder send() throws KrbException, IOException { } try { req = build(pakey, referralsState); - rep = new KrbAsRep(comm.send(req)); + rep = new KrbAsRep(comm.send(req), req.getMessage().reqBody.eType); return this; } catch (KrbException ke) { if (!preAuthFailedOnce && ( diff --git a/src/java.security.jgss/share/classes/sun/security/krb5/internal/crypto/EType.java b/src/java.security.jgss/share/classes/sun/security/krb5/internal/crypto/EType.java index ee385affb9e8..874a68fa6d9b 100644 --- a/src/java.security.jgss/share/classes/sun/security/krb5/internal/crypto/EType.java +++ b/src/java.security.jgss/share/classes/sun/security/krb5/internal/crypto/EType.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -293,7 +293,7 @@ public static int[] getDefaults(String configName, EncryptionKey[] keys) } } - public static boolean isSupported(int eTypeConst, int[] config) { + public static boolean isOneOf(int eTypeConst, int[] config) { for (int i = 0; i < config.length; i++) { if (eTypeConst == config[i]) { return true; @@ -303,7 +303,7 @@ public static boolean isSupported(int eTypeConst, int[] config) { } public static boolean isSupported(int eTypeConst) { - return isSupported(eTypeConst, supportedETypes); + return isOneOf(eTypeConst, supportedETypes); } /** From 3365a373ba1b95d547720cb34369b72f63def412 Mon Sep 17 00:00:00 2001 From: Brent Christian Date: Fri, 17 Jul 2026 20:44:30 +0000 Subject: [PATCH 046/169] 8384746: Enhance Preferences Reviewed-by: rhalade, jpai, naoto --- .../unix/native/libprefs/FileSystemPreferences.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/java.prefs/unix/native/libprefs/FileSystemPreferences.c b/src/java.prefs/unix/native/libprefs/FileSystemPreferences.c index 4dc4137da2a7..542438ef3086 100644 --- a/src/java.prefs/unix/native/libprefs/FileSystemPreferences.c +++ b/src/java.prefs/unix/native/libprefs/FileSystemPreferences.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -67,7 +67,6 @@ Java_java_util_prefs_FileSystemPreferences_lockFile0(JNIEnv *env, int fd, rc; int result[2] = {0, 0}; jintArray javaResult = NULL; - int old_umask; struct flock fl; if (!fname) @@ -86,10 +85,12 @@ Java_java_util_prefs_FileSystemPreferences_lockFile0(JNIEnv *env, fd = open(fname, O_RDONLY, 0); result[1] = errno; } else { - old_umask = umask(0); fd = open(fname, O_WRONLY|O_CREAT, permission); result[1] = errno; - umask(old_umask); + if (fd >= 0) { + /* lock file created; set correct permissions */ + fchmod(fd, permission); /* assume will succeed, ignore retval & errno */ + } } if (fd < 0) { From 85b119e6a78accb5f0c6676f0b6500059deea8d1 Mon Sep 17 00:00:00 2001 From: Xueming Shen Date: Sat, 25 Jul 2026 00:53:12 +0000 Subject: [PATCH 047/169] 8384688: Enhance jmod handling Reviewed-by: lancea, alanb --- .../classes/jdk/internal/jmod/JmodFile.java | 53 ++++++++++++++++++- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/src/java.base/share/classes/jdk/internal/jmod/JmodFile.java b/src/java.base/share/classes/jdk/internal/jmod/JmodFile.java index 987d68f0e66b..74906d2b308f 100644 --- a/src/java.base/share/classes/jdk/internal/jmod/JmodFile.java +++ b/src/java.base/share/classes/jdk/internal/jmod/JmodFile.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -113,7 +113,10 @@ private Entry(ZipEntry e) { this.zipEntry = e; this.section = section(name.substring(0, i)); - this.name = name.substring(i+1); + this.name = name.substring(i + 1); + if (invalidEntryName(this.name)) { + throw new RuntimeException("invalid jmod entry: " + name); + } } /** @@ -168,6 +171,52 @@ static Section section(String name) { return NAME_TO_SECTION.get(name); } + private static boolean invalidEntryName(String name) { + // JMOD entries are stored as section/name ZIP entries using '/' + // as the separator. Reject absolute paths, path names that contain + // '\' or '.' or '..' elements, and Windows drive-letter path elements. + if (name.startsWith("/") || name.indexOf('\\') >= 0) { + return true; + } + return hasDotOrDotDot(name) || hasWinDrive(name); + } + + private static boolean hasDotOrDotDot(String name) { + int index = 0; + while (index < name.length()) { + int start = index; + while (index < name.length() && name.charAt(index) != '/') { + index++; + } + if (name.charAt(start) == '.') { + int length = index - start; + if (length == 1 || (length == 2 && name.charAt(start + 1) == '.')) { + return true; + } + } + index++; + } + return false; + } + + private static boolean isWinDriveLetter(char c) { + return ((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z')); + } + + private static boolean hasWinDrive(String name) { + int i = 0; + while (i + 1 < name.length()) { + if (name.charAt(i + 1) == ':' && isWinDriveLetter(name.charAt(i))) { + return true; + } + i = name.indexOf('/', i); + if (i < 0) { + return false; + } + i++; + } + return false; + } } private final Path file; From e185170dd1a1706d2af1e93ac9c337f0200151e2 Mon Sep 17 00:00:00 2001 From: Volkan Yazici Date: Wed, 26 Aug 2026 17:41:31 +0000 Subject: [PATCH 048/169] 8391087: com/sun/net/httpserver/property/DrainAmountPropertyTest.java fails with AssertionError Reviewed-by: djelinski, dfuchs --- .../sun/net/httpserver/property/DrainAmountPropertyTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/jdk/com/sun/net/httpserver/property/DrainAmountPropertyTest.java b/test/jdk/com/sun/net/httpserver/property/DrainAmountPropertyTest.java index 0b060178b6c9..e3368b0304ab 100644 --- a/test/jdk/com/sun/net/httpserver/property/DrainAmountPropertyTest.java +++ b/test/jdk/com/sun/net/httpserver/property/DrainAmountPropertyTest.java @@ -170,7 +170,7 @@ private static void sendRequest( throws IOException { LOGGER.info("Sending request (target=%s, bodyLength=%s)".formatted(requestTarget, requestBodyLength)); var socketOutput = clientSocket.getOutputStream(); - var request = "%s %s HTTP/1.1%s\r\n\r\n".formatted( + var request = "%s %s HTTP/1.1%s\r\nHost: localhost\r\n\r\n".formatted( requestMethod, requestTarget, requestBodyLength == null ? "" : "\r\nContent-Length: " + requestBodyLength); socketOutput.write(request.getBytes(US_ASCII)); From 624e4263a16082fe6d217d87eb5f91ef42c2c0c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20H=C3=A4ssig?= Date: Mon, 7 Sep 2026 12:26:22 +0000 Subject: [PATCH 049/169] 8390400: Enhance C2 comparisons Reviewed-by: skoivu, rhalade, thartmann, dlong --- src/hotspot/cpu/x86/x86.ad | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/hotspot/cpu/x86/x86.ad b/src/hotspot/cpu/x86/x86.ad index fbd01ef59814..383947fa7849 100644 --- a/src/hotspot/cpu/x86/x86.ad +++ b/src/hotspot/cpu/x86/x86.ad @@ -10687,10 +10687,12 @@ instruct compareAndExchangeB( effect(KILL cr); format %{ "cmpxchgb $mem_ptr,$newval\t# " - "If rax == $mem_ptr then store $newval into $mem_ptr\n\t" %} + "If rax == $mem_ptr then store $newval into $mem_ptr, " + "else store $mem_ptr into rax; rax -> byte\n\t" %} ins_encode %{ __ lock(); __ cmpxchgb($newval$$Register, $mem_ptr$$Address); + __ narrow_subword_type($oldval$$Register, T_BYTE); %} ins_pipe( pipe_cmpxchg ); %} @@ -10704,10 +10706,12 @@ instruct compareAndExchangeS( effect(KILL cr); format %{ "cmpxchgw $mem_ptr,$newval\t# " - "If rax == $mem_ptr then store $newval into $mem_ptr\n\t" %} + "If rax == $mem_ptr then store $newval into $mem_ptr, " + "else store $mem_ptr into rax; rax -> short\n\t" %} ins_encode %{ __ lock(); __ cmpxchgw($newval$$Register, $mem_ptr$$Address); + __ narrow_subword_type($oldval$$Register, T_SHORT); %} ins_pipe( pipe_cmpxchg ); %} @@ -10721,7 +10725,8 @@ instruct compareAndExchangeI( effect(KILL cr); format %{ "cmpxchgl $mem_ptr,$newval\t# " - "If rax == $mem_ptr then store $newval into $mem_ptr\n\t" %} + "If rax == $mem_ptr then store $newval into $mem_ptr, " + "else store $mem_ptr into rax\n\t" %} ins_encode %{ __ lock(); __ cmpxchgl($newval$$Register, $mem_ptr$$Address); @@ -10738,7 +10743,8 @@ instruct compareAndExchangeL( effect(KILL cr); format %{ "cmpxchgq $mem_ptr,$newval\t# " - "If rax == $mem_ptr then store $newval into $mem_ptr\n\t" %} + "If rax == $mem_ptr then store $newval into $mem_ptr, " + "else store $mem_ptr into rax\n\t" %} ins_encode %{ __ lock(); __ cmpxchgq($newval$$Register, $mem_ptr$$Address); @@ -10755,7 +10761,8 @@ instruct compareAndExchangeN( effect(KILL cr); format %{ "cmpxchgl $mem_ptr,$newval\t# " - "If rax == $mem_ptr then store $newval into $mem_ptr\n\t" %} + "If rax == $mem_ptr then store $newval into $mem_ptr, " + "else store $mem_ptr into rax\n\t" %} ins_encode %{ __ lock(); __ cmpxchgl($newval$$Register, $mem_ptr$$Address); @@ -10773,7 +10780,8 @@ instruct compareAndExchangeP( effect(KILL cr); format %{ "cmpxchgq $mem_ptr,$newval\t# " - "If rax == $mem_ptr then store $newval into $mem_ptr\n\t" %} + "If rax == $mem_ptr then store $newval into $mem_ptr, " + "else store $mem_ptr into rax\n\t" %} ins_encode %{ __ lock(); __ cmpxchgq($newval$$Register, $mem_ptr$$Address); From 98c4d2f7b92f2bef85c624adf07306e41ba4622c Mon Sep 17 00:00:00 2001 From: Amit Kumar Date: Thu, 15 Oct 2026 04:40:51 +0000 Subject: [PATCH 050/169] 8388189: [s390x] Implement post-call NOPs Reviewed-by: hdhiman, rrich --- src/hotspot/cpu/ppc/macroAssembler_ppc.cpp | 2 +- src/hotspot/cpu/s390/assembler_s390.hpp | 9 ++++ .../continuationFreezeThaw_s390.inline.hpp | 2 +- src/hotspot/cpu/s390/frame_s390.hpp | 12 ++++- src/hotspot/cpu/s390/frame_s390.inline.hpp | 26 ++++++---- src/hotspot/cpu/s390/macroAssembler_s390.cpp | 10 ++-- src/hotspot/cpu/s390/nativeInst_s390.cpp | 20 ++++++++ src/hotspot/cpu/s390/nativeInst_s390.hpp | 50 ++++++++++++++++--- .../linux_s390/javaThread_linux_s390.cpp | 6 ++- .../os_cpu/linux_s390/os_linux_s390.cpp | 16 +++--- 10 files changed, 118 insertions(+), 35 deletions(-) diff --git a/src/hotspot/cpu/ppc/macroAssembler_ppc.cpp b/src/hotspot/cpu/ppc/macroAssembler_ppc.cpp index e3294587f0d8..e2151a6f9545 100644 --- a/src/hotspot/cpu/ppc/macroAssembler_ppc.cpp +++ b/src/hotspot/cpu/ppc/macroAssembler_ppc.cpp @@ -1278,7 +1278,7 @@ void MacroAssembler::post_call_nop() { relocate(post_call_nop_Relocation::spec()); InlineSkippedInstructionsCounter skipCounter(this); Assembler::emit_int32(Assembler::CMPLI_OPCODE | Assembler::opp_u_field(1, 9, 9)); - assert(is_post_call_nop(*(int*)(pc() - 4)), "post call not not found"); + assert(is_post_call_nop(*(int*)(pc() - 4)), "post call nop not found"); } int MacroAssembler::ic_check_size() { diff --git a/src/hotspot/cpu/s390/assembler_s390.hpp b/src/hotspot/cpu/s390/assembler_s390.hpp index 11c65ec91bc2..f3407d3af1cf 100644 --- a/src/hotspot/cpu/s390/assembler_s390.hpp +++ b/src/hotspot/cpu/s390/assembler_s390.hpp @@ -3281,6 +3281,15 @@ class Assembler : public AbstractAssembler { static bool is_z_nop(address x) { return is_z_nop(* (short *) x); } + // A post-call NOP is encoded as CLFI SP, imm32 (RIL-a format, 6 bytes). + // Byte 0 = 0xC2 (opcode1), byte 1 = 0xFF (R1=SP=15 in bits 39..36, + // opcode2=0xF in bits 35..32). The 4-byte imm32 carries the payload. + // Result goes only to the condition code, which is caller-scratch after a call. + static bool is_post_call_nop(address x) { + // Check the first 2 bytes for the CLFI SP opcode signature (0xC2 0xFF). + // Only 2 bytes are read to avoid a page-boundary out-of-bounds access. + return (*(unsigned short*) x) == (unsigned short)((0xc2 << 8) | 0xff); + } static bool is_z_illtrap(address x) { return *(uint8_t*)x == 0u; } diff --git a/src/hotspot/cpu/s390/continuationFreezeThaw_s390.inline.hpp b/src/hotspot/cpu/s390/continuationFreezeThaw_s390.inline.hpp index 725d3623128a..a7d186c0517e 100644 --- a/src/hotspot/cpu/s390/continuationFreezeThaw_s390.inline.hpp +++ b/src/hotspot/cpu/s390/continuationFreezeThaw_s390.inline.hpp @@ -331,7 +331,7 @@ inline void Thaw::patch_caller_links(intptr_t* sp, intptr_t* bottom) { callers_sp = _cont.entryFP(); } else { assert(!Interpreter::contains(pc), "sp:" PTR_FORMAT " pc:" PTR_FORMAT, p2i(sp), p2i(pc)); - CodeBlob* cb = CodeCache::find_blob(pc); + CodeBlob* cb = CodeCache::find_blob_fast(pc); callers_sp = sp + cb->frame_size(); } // set the back link diff --git a/src/hotspot/cpu/s390/frame_s390.hpp b/src/hotspot/cpu/s390/frame_s390.hpp index 2946ed60d3e7..9256e9ae0d3b 100644 --- a/src/hotspot/cpu/s390/frame_s390.hpp +++ b/src/hotspot/cpu/s390/frame_s390.hpp @@ -489,10 +489,19 @@ // Mark a frame as not fully initialized. Must not be used for frames in the valid back chain. void mark_not_fully_initialized() const { DEBUG_ONLY(own_abi()->callers_sp = NOT_FULLY_INITIALIZED;) } + enum class kind { + unknown, // The frame's pc is not necessarily in the CodeCache. + // CodeCache::find_blob_fast() can yield wrong results here and must not be used. + code_blob, // The frame's pc is known to be in the CodeCache but not likely in an nmethod. + // CodeCache::find_blob_fast() is correct but not faster. + nmethod // This frame is likely from an nmethod. + // CodeCache lookup is optimized via NativePostCallNop payload. + }; + private: // Initialize frame members (_pc and _sp must be given) - inline void setup(); + inline void setup(kind knd); public: @@ -500,6 +509,7 @@ inline frame(intptr_t* sp, intptr_t* fp, address pc); // To be used, if sp was not extended to match callee's calling convention. inline frame(intptr_t* sp, address pc, intptr_t* unextended_sp = nullptr, intptr_t* fp = nullptr, CodeBlob* cb = nullptr); + inline frame(intptr_t* sp, address pc, kind knd); inline frame(intptr_t* sp, intptr_t* unextended_sp, intptr_t* fp, address pc, CodeBlob* cb, const ImmutableOopMap* oop_map, bool on_heap); inline frame(intptr_t* sp, intptr_t* unextended_sp, intptr_t* fp, address pc, CodeBlob* cb, const ImmutableOopMap* oop_map = nullptr); diff --git a/src/hotspot/cpu/s390/frame_s390.inline.hpp b/src/hotspot/cpu/s390/frame_s390.inline.hpp index 7acdd01a9c91..08f86de40e21 100644 --- a/src/hotspot/cpu/s390/frame_s390.inline.hpp +++ b/src/hotspot/cpu/s390/frame_s390.inline.hpp @@ -35,14 +35,14 @@ // Inline functions for z/Architecture frames: // Initialize frame members (_sp must be given) -inline void frame::setup() { +inline void frame::setup(kind knd) { if (_pc == nullptr) { _pc = (address)own_abi()->return_pc; assert(_pc != nullptr, "must have PC"); } if (_cb == nullptr) { - _cb = CodeCache::find_blob(_pc); + _cb = (knd == kind::nmethod) ? CodeCache::find_blob_fast(_pc) : CodeCache::find_blob(_pc); } if (_unextended_sp == nullptr) { @@ -99,20 +99,26 @@ inline frame::frame() : _sp(nullptr), _pc(nullptr), _cb(nullptr), _oop_map(nullp inline frame::frame(intptr_t* sp, address pc, intptr_t* unextended_sp, intptr_t* fp, CodeBlob* cb) : _sp(sp), _pc(pc), _cb(cb), _oop_map(nullptr), _on_heap(false), DEBUG_ONLY(_frame_index(-1) COMMA) _unextended_sp(unextended_sp), _fp(fp) { - setup(); + setup(kind::nmethod); } -inline frame::frame(intptr_t* sp) : frame(sp, nullptr) {} +inline frame::frame(intptr_t* sp) : frame(sp, nullptr, kind::nmethod) {} inline frame::frame(intptr_t* sp, intptr_t* fp, address pc) : _sp(sp), _pc(pc), _cb(nullptr), _oop_map(nullptr), _deopt_state(unknown), _on_heap(false), DEBUG_ONLY(_frame_index(-1) COMMA) _unextended_sp(nullptr), _fp(fp) { - setup(); + setup(kind::nmethod); +} + +inline frame::frame(intptr_t* sp, address pc, kind knd) + : _sp(sp), _pc(pc), _cb(nullptr), _oop_map(nullptr), + _on_heap(false), DEBUG_ONLY(_frame_index(-1) COMMA) _unextended_sp(sp), _fp(nullptr) { + setup(knd); } inline frame::frame(intptr_t* sp, intptr_t* unextended_sp, intptr_t* fp, address pc, CodeBlob* cb, const ImmutableOopMap* oop_map) :_sp(sp), _pc(pc), _cb(cb), _oop_map(oop_map), _on_heap(false), DEBUG_ONLY(_frame_index(-1) COMMA) _unextended_sp(unextended_sp), _fp(fp) { - setup(); + setup(kind::nmethod); } inline frame::frame(intptr_t* sp, intptr_t* unextended_sp, intptr_t* fp, address pc, CodeBlob* cb, const ImmutableOopMap* oop_map, bool on_heap) @@ -120,7 +126,7 @@ inline frame::frame(intptr_t* sp, intptr_t* unextended_sp, intptr_t* fp, address // In thaw, non-heap frames use this constructor to pass oop_map. I don't know why. assert(_on_heap || _cb != nullptr, "these frames are always heap frames"); if (cb != nullptr) { - setup(); + setup(kind::nmethod); } } @@ -129,7 +135,7 @@ inline frame::frame(intptr_t* sp, intptr_t* unextended_sp, intptr_t* fp, address inline frame::frame(void* sp, void* pc, void* unextended_sp) : _sp((intptr_t*)sp), _pc((address)pc), _cb(nullptr), _oop_map(nullptr), _on_heap(false), DEBUG_ONLY(_frame_index(-1) COMMA) _unextended_sp((intptr_t*)unextended_sp) { - setup(); + setup(kind::unknown); } #endif @@ -405,7 +411,7 @@ inline frame frame::sender_raw(RegisterMap* map) const { // Must be native-compiled frame, i.e. the marshaling code for native // methods that exists in the core system. - return frame(sender_sp(), sender_pc()); + return frame(sender_sp(), sender_pc(), kind::code_blob); } inline frame frame::sender_for_compiled_frame(RegisterMap *map) const { @@ -452,7 +458,7 @@ inline frame frame::sender_for_compiled_frame(RegisterMap *map) const { return Continuation::continuation_bottom_sender(map->thread(), *this, sender_sp); } } - return frame(sender_sp, sender_pc); + return frame(sender_sp, sender_pc, kind::nmethod); } template diff --git a/src/hotspot/cpu/s390/macroAssembler_s390.cpp b/src/hotspot/cpu/s390/macroAssembler_s390.cpp index b3e1b223bf0e..c4688c0546b1 100644 --- a/src/hotspot/cpu/s390/macroAssembler_s390.cpp +++ b/src/hotspot/cpu/s390/macroAssembler_s390.cpp @@ -6928,10 +6928,12 @@ void MacroAssembler::post_call_nop() { if (!Continuations::enabled()) { return; } - nop(); - // TODO: - // 1. https://bugs.openjdk.org/browse/JDK-8300002 - // 2. https://bugs.openjdk.org/browse/JDK-8290965 + // We use CLFI SP, imm32 to encode post call nops. + // Refer to NativePostCallNop for details. + relocate(post_call_nop_Relocation::spec()); + InlineSkippedInstructionsCounter skipCounter(this); + z_clfi(Z_SP, 0); + assert(is_post_call_nop(pc() - 6), "post call nop not found"); } void MacroAssembler::push_cont_fastpath() { diff --git a/src/hotspot/cpu/s390/nativeInst_s390.cpp b/src/hotspot/cpu/s390/nativeInst_s390.cpp index 3520e9a34935..0a49826adf2f 100644 --- a/src/hotspot/cpu/s390/nativeInst_s390.cpp +++ b/src/hotspot/cpu/s390/nativeInst_s390.cpp @@ -634,6 +634,26 @@ void NativeGeneralJump::replace_mt_safe(address instr_addr, address code_buffer) void NativeDeoptInstruction::verify() { } +bool NativePostCallNop::patch(int32_t oopmap_slot, int32_t cb_offset) { + if (((oopmap_slot & 0xff) != oopmap_slot) || ((cb_offset & 0xffffff) != cb_offset)) { + return false; // cannot encode + } + uint32_t data = ((uint32_t)oopmap_slot << 24) | (uint32_t)cb_offset; + assert(data != 0, "must be"); + // Write the 4-byte payload into the imm32 field of the CLFI instruction + // (bytes 2..5). s390 is big-endian so a direct 32-bit write is correct. + *(uint32_t*) addr_at(displacement_offset) = data; + ICache::invalidate_range(addr_at(0), instruction_size); + + int32_t oopmap_slot_dec, cb_offset_dec; + assert(check(), "pcn not recognized after patch"); + assert(decode(oopmap_slot_dec, cb_offset_dec), "encoding failed"); + assert(oopmap_slot == oopmap_slot_dec, "oopmap slot encoding is wrong"); + assert(cb_offset == cb_offset_dec, "cb offset encoding is wrong"); + + return true; // successfully encoded +} + void NativePostCallNop::make_deopt() { NativeDeoptInstruction::insert(addr_at(0)); } diff --git a/src/hotspot/cpu/s390/nativeInst_s390.hpp b/src/hotspot/cpu/s390/nativeInst_s390.hpp index 0ba97830bb70..adc0395d4889 100644 --- a/src/hotspot/cpu/s390/nativeInst_s390.hpp +++ b/src/hotspot/cpu/s390/nativeInst_s390.hpp @@ -653,16 +653,50 @@ class NativeGeneralJump: public NativeInstruction { }; class NativePostCallNop: public NativeInstruction { + + // Post-call NOPs are encoded as CLFI R0, imm32 (RIL-a format, 6 bytes): + // + // Bit |0 7|8 11|12 15|16 47| + // +---------+------+------+--------------------------+ + // | 0xC2 | R1 | 0xF | I2 (imm32) | + // +---------+------+------+--------------------------+ + // | opcode1 | 0x0 |op2=F | oopmap_slot[31:24] | + // | |(R0) | | | cb_offset[23:0] | + // + // Byte 0 = 0xC2 (opcode1, from CLFI_ZOPC bits 47..40) + // Byte 1 = 0x0F (R1=R0=0x0 in bits 39..36; opcode2=0xF in bits 35..32) + // Bytes 2..5 = imm32 payload + // + // CLFI sets only the condition code — no GPR is written. + // The R1 field is fixed to R0 (encoding 0), allowing check() to + // recognize the instruction by testing only the first two bytes. + public: - enum z_specific_constants { - // The check reads a 2-byte nop instruction. Since s390 nop is 2 bytes (BCR instruction), - // we can safely read it in a single stage without risk of out-of-bounds access. - // The nop instruction is checked by is_nop() which reads a short (2 bytes). - first_check_size = 2 + + enum z_pcn_constants { + // Only the first 2 bytes are needed to identify the instruction. + // This avoids an out-of-bounds read if the return address is at a + // page boundary (same rationale as x86 first_check_size). + first_check_size = 2, + instruction_size = 6, // CLFI is a 6-byte RIL-a instruction + displacement_offset = 2 // imm32 payload starts at byte 2 }; - bool check() const { return is_nop(); } - bool decode(int32_t& oopmap_slot, int32_t& cb_offset) const { return false; } - bool patch(int32_t oopmap_slot, int32_t cb_offset) { Unimplemented(); return false; } + + bool check() const { return Assembler::is_post_call_nop(addr_at(0)); } + + bool decode(int32_t& oopmap_slot, int32_t& cb_offset) const { + // s390 is big-endian; read the 4-byte imm32 at byte offset 2. + // Use uint32_t to avoid signed integer arithmetic on the raw bit pattern. + uint32_t data = *(uint32_t*) addr_at(displacement_offset); + if (data == 0) { + return false; // no payload encoded yet + } + cb_offset = (int32_t)(data & 0xffffff); + oopmap_slot = (int32_t)(data >> 24); // top 8 bits, no masking needed + return true; + } + + bool patch(int32_t oopmap_slot, int32_t cb_offset); void make_deopt(); }; diff --git a/src/hotspot/os_cpu/linux_s390/javaThread_linux_s390.cpp b/src/hotspot/os_cpu/linux_s390/javaThread_linux_s390.cpp index 6643187170ab..f63bdb62dff5 100644 --- a/src/hotspot/os_cpu/linux_s390/javaThread_linux_s390.cpp +++ b/src/hotspot/os_cpu/linux_s390/javaThread_linux_s390.cpp @@ -47,7 +47,8 @@ frame JavaThread::pd_last_frame() { } #endif - return frame(sp, pc); + // Likely the frame of a RuntimeStub (e.g. after a call to the VM). + return frame(sp, pc, frame::kind::code_blob); } bool JavaThread::pd_get_top_frame_for_profiling(frame* fr_addr, void* ucontext, bool isInJava) { @@ -71,7 +72,8 @@ bool JavaThread::pd_get_top_frame_for_profiling(frame* fr_addr, void* ucontext, return false; } - frame ret_frame((intptr_t*)uc->uc_mcontext.gregs[15/*Z_SP*/], pc); + // pc is from ucontext — could be anywhere (native, compiled, unknown). + frame ret_frame((intptr_t*)uc->uc_mcontext.gregs[15/*Z_SP*/], pc, frame::kind::unknown); if (ret_frame.fp() == nullptr) { // The found frame does not have a valid frame pointer. diff --git a/src/hotspot/os_cpu/linux_s390/os_linux_s390.cpp b/src/hotspot/os_cpu/linux_s390/os_linux_s390.cpp index cbb06d2d07d8..17aa7fd84841 100644 --- a/src/hotspot/os_cpu/linux_s390/os_linux_s390.cpp +++ b/src/hotspot/os_cpu/linux_s390/os_linux_s390.cpp @@ -145,14 +145,14 @@ frame os::fetch_frame_from_context(const void* ucVoid) { // compiled frame stack bang return fetch_compiled_frame_from_context(ucVoid); } - return frame(sp, epc); + return frame(sp, epc, frame::kind::unknown); } frame os::fetch_compiled_frame_from_context(const void* ucVoid) { const ucontext_t* uc = (const ucontext_t*)ucVoid; intptr_t* sp = os::Linux::ucontext_get_sp(uc); address lr = ucontext_get_lr(uc); - return frame(sp, lr); + return frame(sp, lr, frame::kind::unknown); } intptr_t* os::fetch_bcp_from_context(const void* ucVoid) { @@ -171,23 +171,23 @@ frame os::get_sender_for_C_frame(frame* fr) { // of gpr10. if ((Interpreter::code() != nullptr && Interpreter::contains(fr->pc())) || (CodeCache::contains(fr->pc()) && !StubRoutines::contains(fr->pc()))) { - return frame(fr->sender_sp(), fr->sender_pc()); + return frame(fr->sender_sp(), fr->sender_pc(), frame::kind::unknown); } else { if (StubRoutines::contains(fr->pc())) { StubCodeDesc* desc = StubCodeDesc::desc_for(fr->pc()); if (desc && !strcmp(desc->name(),"call_stub")) { - return frame(fr->sender_sp(), fr->callstub_sender_pc()); + return frame(fr->sender_sp(), fr->callstub_sender_pc(), frame::kind::unknown); } else { - return frame(fr->sender_sp(), fr->sender_pc()); + return frame(fr->sender_sp(), fr->sender_pc(), frame::kind::unknown); } } else { intptr_t* sender_sp = fr->sender_sp(); address sender_fp = (address)*sender_sp; ptrdiff_t entry_len = sender_fp - (address)sender_sp; if (entry_len < frame::z_abi_160_size) { - return frame(sender_sp, fr->sender_pc()); + return frame(sender_sp, fr->sender_pc(), frame::kind::unknown); } else { - return frame(sender_sp, fr->native_sender_pc()); + return frame(sender_sp, fr->native_sender_pc(), frame::kind::unknown); } } } @@ -200,7 +200,7 @@ frame os::current_frame() { assert (csp != nullptr, "sp should not be null"); // Pass a dummy pc. This way we don't have to load it from the // stack, since we don't know in which slot we can find it. - frame topframe(csp, (address)0x8); + frame topframe(csp, (address)0x8, frame::kind::unknown); if (os::is_first_C_frame(&topframe)) { // Stack is not walkable. return frame(); From ca6e6b55dae1dff51babbd63eb34e3bef4bcc8ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Jeli=C5=84ski?= Date: Thu, 15 Oct 2026 06:28:34 +0000 Subject: [PATCH 051/169] 8394512: Remove support for pre-Vista SDKs Reviewed-by: alanb --- src/java.base/windows/native/libnio/ch/Net.c | 31 ++----------------- ...ndowsAsynchronousServerSocketChannelImpl.c | 27 ++-------------- .../ch/WindowsAsynchronousSocketChannelImpl.c | 24 ++------------ 3 files changed, 9 insertions(+), 73 deletions(-) diff --git a/src/java.base/windows/native/libnio/ch/Net.c b/src/java.base/windows/native/libnio/ch/Net.c index aff73441cfd7..d9300ee2028d 100644 --- a/src/java.base/windows/native/libnio/ch/Net.c +++ b/src/java.base/windows/native/libnio/ch/Net.c @@ -37,31 +37,6 @@ #include "sun_nio_ch_Net.h" #include "sun_nio_ch_PollArrayWrapper.h" -/** - * Definitions to allow for building with older SDK include files. - */ - -#ifndef MCAST_BLOCK_SOURCE - -#define MCAST_BLOCK_SOURCE 43 -#define MCAST_UNBLOCK_SOURCE 44 -#define MCAST_JOIN_SOURCE_GROUP 45 -#define MCAST_LEAVE_SOURCE_GROUP 46 - -#endif /* MCAST_BLOCK_SOURCE */ - -struct my_ip_mreq_source { - IN_ADDR imr_multiaddr; - IN_ADDR imr_sourceaddr; - IN_ADDR imr_interface; -}; - -struct my_group_source_req { - ULONG gsr_interface; - SOCKADDR_STORAGE gsr_group; - SOCKADDR_STORAGE gsr_source; -}; - /** * Copy IPv6 address as jbytearray to target */ @@ -455,7 +430,7 @@ Java_sun_nio_ch_Net_joinOrDrop4(JNIEnv *env, jobject this, jboolean join, jobjec jint group, jint interf, jint source) { struct ip_mreq mreq; - struct my_ip_mreq_source mreq_source; + struct ip_mreq_source mreq_source; int opt, n, optlen; void* optval; @@ -487,7 +462,7 @@ JNIEXPORT jint JNICALL Java_sun_nio_ch_Net_blockOrUnblock4(JNIEnv *env, jobject this, jboolean block, jobject fdo, jint group, jint interf, jint source) { - struct my_ip_mreq_source mreq_source; + struct ip_mreq_source mreq_source; int n; int opt = (block) ? IP_BLOCK_SOURCE : IP_UNBLOCK_SOURCE; @@ -518,7 +493,7 @@ static int setGroupSourceReqOption(JNIEnv* env, jint index, jbyteArray source) { - struct my_group_source_req req; + struct group_source_req req; struct sockaddr_in6* sin6; req.gsr_interface = (ULONG)index; diff --git a/src/java.base/windows/native/libnio/ch/WindowsAsynchronousServerSocketChannelImpl.c b/src/java.base/windows/native/libnio/ch/WindowsAsynchronousServerSocketChannelImpl.c index 2c6de95ac65f..90737969c4a5 100644 --- a/src/java.base/windows/native/libnio/ch/WindowsAsynchronousServerSocketChannelImpl.c +++ b/src/java.base/windows/native/libnio/ch/WindowsAsynchronousServerSocketChannelImpl.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,6 +25,7 @@ #include #include +#include #include "jni.h" #include "jni_util.h" @@ -36,29 +37,7 @@ #include "sun_nio_ch_WindowsAsynchronousServerSocketChannelImpl.h" -#ifndef WSAID_ACCEPTEX -#define WSAID_ACCEPTEX {0xb5367df1,0xcbac,0x11cf,{0x95,0xca,0x00,0x80,0x5f,0x48,0xa1,0x92}} -#endif - -#ifndef SO_UPDATE_ACCEPT_CONTEXT -#define SO_UPDATE_ACCEPT_CONTEXT 0x700B -#endif - - -typedef BOOL (*AcceptEx_t) -( - SOCKET sListenSocket, - SOCKET sAcceptSocket, - PVOID lpOutputBuffer, - DWORD dwReceiveDataLength, - DWORD dwLocalAddressLength, - DWORD dwRemoteAddressLength, - LPDWORD lpdwBytesReceived, - LPOVERLAPPED lpOverlapped -); - - -static AcceptEx_t AcceptEx_func; +static LPFN_ACCEPTEX AcceptEx_func; JNIEXPORT void JNICALL diff --git a/src/java.base/windows/native/libnio/ch/WindowsAsynchronousSocketChannelImpl.c b/src/java.base/windows/native/libnio/ch/WindowsAsynchronousSocketChannelImpl.c index b325c34c9d85..b91dde31e3e4 100644 --- a/src/java.base/windows/native/libnio/ch/WindowsAsynchronousSocketChannelImpl.c +++ b/src/java.base/windows/native/libnio/ch/WindowsAsynchronousSocketChannelImpl.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,6 +25,7 @@ #include #include +#include #include #include "jni.h" @@ -36,26 +37,7 @@ #include "sun_nio_ch_WindowsAsynchronousSocketChannelImpl.h" -#ifndef WSAID_CONNECTEX -#define WSAID_CONNECTEX {0x25a207b9,0xddf3,0x4660,{0x8e,0xe9,0x76,0xe5,0x8c,0x74,0x06,0x3e}} -#endif - -#ifndef SO_UPDATE_CONNECT_CONTEXT -#define SO_UPDATE_CONNECT_CONTEXT 0x7010 -#endif - -typedef BOOL (PASCAL *ConnectEx_t) -( - SOCKET s, - const struct sockaddr* name, - int namelen, - PVOID lpSendBuffer, - DWORD dwSendDataLength, - LPDWORD lpdwBytesSent, - LPOVERLAPPED lpOverlapped -); - -static ConnectEx_t ConnectEx_func; +static LPFN_CONNECTEX ConnectEx_func; JNIEXPORT void JNICALL From 00f4e1dfe37abdca1c5ded6726dca9c4d168b240 Mon Sep 17 00:00:00 2001 From: Emanuel Peter Date: Thu, 15 Oct 2026 07:23:14 +0000 Subject: [PATCH 052/169] 8393645: [REDO] C2 VerifyIterativeGVN: fix many missing optimization/notification cases for arithmetic nodes Reviewed-by: qamai, chagedorn --- src/hotspot/share/opto/addnode.cpp | 4 + src/hotspot/share/opto/loopopts.cpp | 2 + src/hotspot/share/opto/node.cpp | 4 + src/hotspot/share/opto/phaseX.cpp | 553 ++++++++---------- src/hotspot/share/opto/superword.cpp | 6 + .../c2/igvn/TestMissingAndOrIdealization.java | 66 +++ .../TestMissingNestedMulIdealization.java | 64 ++ ...stMissingVariableTimesConIdealization.java | 75 +++ .../loopopts/superword/TestReductions.java | 13 +- 9 files changed, 467 insertions(+), 320 deletions(-) create mode 100644 test/hotspot/jtreg/compiler/c2/igvn/TestMissingAndOrIdealization.java create mode 100644 test/hotspot/jtreg/compiler/c2/igvn/TestMissingNestedMulIdealization.java create mode 100644 test/hotspot/jtreg/compiler/c2/igvn/TestMissingVariableTimesConIdealization.java diff --git a/src/hotspot/share/opto/addnode.cpp b/src/hotspot/share/opto/addnode.cpp index aa523fe9be80..4a6659db8794 100644 --- a/src/hotspot/share/opto/addnode.cpp +++ b/src/hotspot/share/opto/addnode.cpp @@ -1610,6 +1610,8 @@ Node* MaxLNode::Ideal(PhaseGVN* phase, bool can_reshape) { } if (can_reshape) { return fold_subI_no_underflow_pattern(this, phase); + } else { + phase->record_for_igvn(this); } return nullptr; } @@ -1642,6 +1644,8 @@ Node* MinLNode::Ideal(PhaseGVN* phase, bool can_reshape) { } if (can_reshape) { return fold_subI_no_underflow_pattern(this, phase); + } else { + phase->record_for_igvn(this); } return nullptr; } diff --git a/src/hotspot/share/opto/loopopts.cpp b/src/hotspot/share/opto/loopopts.cpp index 77ccf3b31bdd..d5332dbef541 100644 --- a/src/hotspot/share/opto/loopopts.cpp +++ b/src/hotspot/share/opto/loopopts.cpp @@ -612,6 +612,8 @@ Node* PhaseIdealLoop::remix_address_expressions(Node* n) { n_op == Op_MulD) { if (n2_loop == n_loop) { assert(n1_loop != n_loop, ""); + // IGVN may want to swap the edges back later, to commute (canonicalize) the operation. + _igvn.rehash_node_delayed(n); n->swap_edges(1, 2); } } diff --git a/src/hotspot/share/opto/node.cpp b/src/hotspot/share/opto/node.cpp index d16edc38e239..163b9ee89783 100644 --- a/src/hotspot/share/opto/node.cpp +++ b/src/hotspot/share/opto/node.cpp @@ -1238,6 +1238,10 @@ bool Node::has_special_unique_user() const { } else if (op == Op_LoadUS && n->Opcode() == Op_LShiftI) { // Condition for RShiftI(LShiftI(LoadUS(...), 16), 16) => LoadS(...), see RShiftINode::Ideal return true; + } else if ((op == Op_LoadB || op == Op_LoadS) && n->Opcode() == Op_AndI) { + // AndINode::Ideal turns AndI(LoadB/S) into AndI(LoadUB/US), if the LoadB + // only has a single use. + return true; } else if (op == Op_AddL) { // Condition for convL2I(addL(x,y)) ==> addI(convL2I(x),convL2I(y)) return n->Opcode() == Op_ConvL2I && n->in(1) == this; diff --git a/src/hotspot/share/opto/phaseX.cpp b/src/hotspot/share/opto/phaseX.cpp index ffd9ccf19161..51f83f816ffb 100644 --- a/src/hotspot/share/opto/phaseX.cpp +++ b/src/hotspot/share/opto/phaseX.cpp @@ -440,22 +440,34 @@ PhaseRenumberLive::PhaseRenumberLive(PhaseGVN* gvn, assert(worklist.is_subset_of(_useful), "only useful nodes should still be in the worklist"); + // While we don't need to rehash, some optimizations are sensitive to the + // relative ordering of node IDs, for example AddNode::commute wants to keep + // in1 < in2. Therefore, we keep the relative ordering stable. + uint next_idx = 0; + for (int old_idx = 0; old_idx < _old2new_map.length(); old_idx++) { + if (_useful.member_set().test(old_idx)) { + _old2new_map.at_put(old_idx, next_idx++); + } + } + assert(next_idx == _useful.size(), "must map all useful nodes"); + // Iterate over the set of live nodes. for (uint current_idx = 0; current_idx < _useful.size(); current_idx++) { Node* n = _useful.at(current_idx); - const Type* type = gvn->type_or_null(n); - _new_type_array.map(current_idx, type); + uint old_idx = n->_idx; + int new_idx = _old2new_map.at(old_idx); + assert(new_idx >= 0, "every useful node must have new idx"); - assert(_old2new_map.at(n->_idx) == -1, "already seen"); - _old2new_map.at_put(n->_idx, current_idx); + const Type* type = gvn->type_or_null(n); + _new_type_array.map(new_idx, type); if (old_node_note_array != nullptr) { - Node_Notes* nn = C->locate_node_notes(old_node_note_array, n->_idx); - C->set_node_notes_at(current_idx, nn); + Node_Notes* nn = C->locate_node_notes(old_node_note_array, old_idx); + C->set_node_notes_at(new_idx, nn); } - n->set_idx(current_idx); // Update node ID. + n->set_idx(new_idx); // Update node ID. if (update_embedded_ids(n) < 0) { _delayed.push(n); // has embedded IDs; handle later @@ -1391,6 +1403,51 @@ void PhaseIterGVN::verify_Ideal_for(Node* n, bool can_reshape, bool deep_revisit // because there are known cases where Ideal can optimize after IGVN. // Some may be expected and cannot be fixed, and others should be fixed. switch (n->Opcode()) { + // BoolNode::Ideal canonicalizes integer comparisons, but only if "!is_counted_loop_exit_test". + // We only optimize if: + // Cmp -> Bool -> If + // And not if: + // Cmp -> Bool -> CountedLoopEnd + // In loop unswitching, we turn a CountedLoopEnd into an If. + // We would now have to notify the input of the modified node, + // which we don't do so far. + // One simple hack would be to just push the Bool node on + // the worklist in that optimization, in an ad-hoc fashon. + // But more principled would probably be to do this as part + // of replace_input_of, which calls set_req_X, which already + // has some notification logic, for example using + // Node::has_special_unique_user. There are also other places + // the graph could be updated and lose the CountedLoopEnd node, + // and in those places we also use Node::has_special_unique_user, + // and other related code. + // I would suggest we refactor and extend that notification + // code in a future RFE. + // + // Found with: + // -XX:VerifyIterativeGVN=1110 + // compiler/loopopts/TestBaseCountedEndLoopUnswitchCandidate.java + case Op_Bool: + return; + + // I have encountered some issues with AddHF. + // It could be due to issues arising from JDK-8388873, we should + // check again after that is fixed. + // + // Found on aarch64 with: + // -XX:-TieredCompilation -XX:VerifyIterativeGVN=1110 + // compiler/c2/irTests/TestFloat16ScalarOperations.java + case Op_AddHF: + return; + + // In XorI/LNode::Ideal, we optimize Xor(x, -1), but + // only if is_used_in_only_arithmetic: so we would + // need notification if the Xor only has arithmetic + // nodes. I think this also calls for a generalization + // of Node::has_special_unique_user. + case Op_XorI: + case Op_XorL: + return; + // RegionNode::Ideal does "Skip around the useless IF diamond". // 245 IfTrue === 244 // 258 If === 245 257 @@ -1410,182 +1467,6 @@ void PhaseIterGVN::verify_Ideal_for(Node* n, bool can_reshape, bool deep_revisit case Op_Region: return; - // In AddNode::Ideal, we call "commute", which swaps the inputs so - // that smaller idx are first. Tracking it back, it led me to - // PhaseIdealLoop::remix_address_expressions which swapped the edges. - // - // Example: - // Before PhaseIdealLoop::remix_address_expressions - // 154 AddI === _ 12 144 - // After PhaseIdealLoop::remix_address_expressions - // 154 AddI === _ 144 12 - // After AddNode::Ideal - // 154 AddI === _ 12 144 - // - // I suspect that the node should be added to the IGVN worklist after - // PhaseIdealLoop::remix_address_expressions - // - // This is the only case I looked at, there may be others. Found like this: - // java -XX:VerifyIterativeGVN=0100 -Xbatch --version - // - // The following hit the same logic in PhaseIdealLoop::remix_address_expressions. - // - // Note: currently all of these fail also for other reasons, for example - // because of "commute" doing the reordering with the phi below. Once - // that is resolved, we can come back to this issue here. - // - // case Op_AddD: - // case Op_AddI: - // case Op_AddL: - // case Op_AddF: - // case Op_MulI: - // case Op_MulL: - // case Op_MulF: - // case Op_MulD: - // if (n->in(1)->_idx > n->in(2)->_idx) { - // // Expect "commute" to revert this case. - // return false; - // } - // break; // keep verifying - - // AddFPNode::Ideal calls "commute", which can reorder the inputs for this: - // Check for tight loop increments: Loop-phi of Add of loop-phi - // It wants to take the phi into in(1): - // 471 Phi === 435 38 390 - // 390 AddF === _ 471 391 - // - // Other Associative operators are also affected equally. - // - // Investigate why this does not happen earlier during IGVN. - // - // Found with: - // test/hotspot/jtreg/compiler/loopopts/superword/ReductionPerf.java - // -XX:VerifyIterativeGVN=1110 - case Op_AddD: - //case Op_AddI: // Also affected for other reasons, see case further down. - //case Op_AddL: // Also affected for other reasons, see case further down. - case Op_AddF: - case Op_MulI: - case Op_MulL: - case Op_MulF: - case Op_MulD: - case Op_MinF: - case Op_MinD: - case Op_MaxF: - case Op_MaxD: - // XorINode::Ideal - // Found with: - // compiler/intrinsics/chacha/TestChaCha20.java - // -XX:VerifyIterativeGVN=1110 - case Op_XorI: - case Op_XorL: - // It seems we may have similar issues with the HF cases. - // Found with aarch64: - // compiler/vectorization/TestFloat16VectorOperations.java - // -XX:VerifyIterativeGVN=1110 - case Op_AddHF: - case Op_MulHF: - case Op_MaxHF: - case Op_MinHF: - return; - - // In MulNode::Ideal the edges can be swapped to help value numbering: - // - // // We are OK if right is a constant, or right is a load and - // // left is a non-constant. - // if( !(t2->singleton() || - // (in(2)->is_Load() && !(t1->singleton() || in(1)->is_Load())) ) ) { - // if( t1->singleton() || // Left input is a constant? - // // Otherwise, sort inputs (commutativity) to help value numbering. - // (in(1)->_idx > in(2)->_idx) ) { - // swap_edges(1, 2); - // - // Why was this not done earlier during IGVN? - // - // Found with: - // test/hotspot/jtreg/gc/stress/gcbasher/TestGCBasherWithG1.java - // -XX:VerifyIterativeGVN=1110 - case Op_AndI: - // Same for AndL. - // Found with: - // compiler/intrinsics/bigInteger/MontgomeryMultiplyTest.java - // -XX:VerifyIterativeGVN=1110 - case Op_AndL: - return; - - // SubLNode::Ideal does transform like: - // Convert "c1 - (y+c0)" into "(c1-c0) - y" - // - // In IGVN before verification: - // 8423 ConvI2L === _ 3519 [[ 8424 ]] #long:-2 - // 8422 ConvI2L === _ 8399 [[ 8424 ]] #long:3..256:www - // 8424 AddL === _ 8422 8423 [[ 8383 ]] !orig=[8382] - // 8016 ConL === 0 [[ 8383 ]] #long:0 - // 8383 SubL === _ 8016 8424 [[ 8156 ]] !orig=[8154] - // - // And then in verification: - // 8338 ConL === 0 [[ 8339 8424 ]] #long:-2 <----- Was constant folded. - // 8422 ConvI2L === _ 8399 [[ 8424 ]] #long:3..256:www - // 8424 AddL === _ 8422 8338 [[ 8383 ]] !orig=[8382] - // 8016 ConL === 0 [[ 8383 ]] #long:0 - // 8383 SubL === _ 8016 8424 [[ 8156 ]] !orig=[8154] - // - // So the form changed from: - // c1 - (y + [8423 ConvI2L]) - // to - // c1 - (y + -2) - // but the SubL was not added to the IGVN worklist. Investigate why. - // There could be other issues too. - // - // There seems to be a related AddL IGVN optimization that triggers - // the same SubL optimization, so investigate that too. - // - // Found with: - // java -XX:VerifyIterativeGVN=0100 -Xcomp --version - case Op_SubL: - return; - - // SubINode::Ideal does - // Convert "x - (y+c0)" into "(x-y) - c0" AND - // Convert "c1 - (y+c0)" into "(c1-c0) - y" - // - // Investigate why this does not yet happen during IGVN. - // - // Found with: - // test/hotspot/jtreg/compiler/c2/IVTest.java - // -XX:VerifyIterativeGVN=1110 - case Op_SubI: - return; - - // AddNode::IdealIL does transform like: - // Convert x + (con - y) into "(x - y) + con" - // - // In IGVN before verification: - // 8382 ConvI2L - // 8381 ConvI2L === _ 791 [[ 8383 ]] #long:0 - // 8383 SubL === _ 8381 8382 - // 8168 ConvI2L - // 8156 AddL === _ 8168 8383 [[ 8158 ]] - // - // And then in verification: - // 8424 AddL - // 8016 ConL === 0 [[ 8383 ]] #long:0 <--- Was constant folded. - // 8383 SubL === _ 8016 8424 - // 8168 ConvI2L - // 8156 AddL === _ 8168 8383 [[ 8158 ]] - // - // So the form changed from: - // x + (ConvI2L(0) - [8382 ConvI2L]) - // to - // x + (0 - [8424 AddL]) - // but the AddL was not added to the IGVN worklist. Investigate why. - // There could be other issues, too. For example with "commute", see above. - // - // Found with: - // java -XX:VerifyIterativeGVN=0100 -Xcomp --version - case Op_AddL: - return; - // SubTypeCheckNode::Ideal calls SubTypeCheckNode::verify_helper, which does // Node* cmp = phase->transform(new CmpPNode(subklass, in(SuperKlass))); // record_for_cleanup(cmp, phase); @@ -1683,20 +1564,6 @@ void PhaseIterGVN::verify_Ideal_for(Node* n, bool can_reshape, bool deep_revisit case Op_ConvI2L: return; - // AddNode::IdealIL can do this transform (and similar other ones): - // Convert "a*b+a*c into a*(b+c) - // The example had AddI(MulI(a, b), MulI(a, c)). Why did this not happen - // during IGVN? There was a mutation for one of the MulI, and only - // after that the pattern was as needed for the optimization. The MulI - // was added to the IGVN worklist, but not the AddI. This probably - // can be fixed by adding the correct pattern in add_users_of_use_to_worklist. - // - // Found with: - // test/hotspot/jtreg/compiler/loopopts/superword/ReductionPerf.java - // -XX:VerifyIterativeGVN=1110 - case Op_AddI: - return; - // ArrayCopyNode::Ideal // calls ArrayCopyNode::prepare_array_copy // calls Compile::conv_I2X_index -> is called with sizetype = intcon(0), I think that @@ -1743,67 +1610,6 @@ void PhaseIterGVN::verify_Ideal_for(Node* n, bool can_reshape, bool deep_revisit case Op_CastII: return; - // MaxLNode::Ideal - // calls AddNode::Ideal - // calls commute -> decides to swap edges - // - // Another notification issue, because we check inputs of inputs? - // MaxL -> Phi -> Loop - // MaxL -> Phi -> MaxL - // - // Found with: - // compiler/c2/irTests/TestIfMinMax.java - // -XX:VerifyIterativeGVN=1110 - case Op_MaxL: - case Op_MinL: - return; - - // OrINode::Ideal - // calls AddNode::Ideal - // calls commute -> left is Load, right not -> commute. - // - // Not sure why notification does not work here, seems like - // the depth is only 1, so it should work. Needs investigation. - // - // Found with: - // compiler/codegen/TestCharVect2.java#id0 - // -XX:VerifyIterativeGVN=1110 - case Op_OrI: - case Op_OrL: - return; - - // Bool -> constant folded to 1. - // Issue with notification? - // - // Found with: - // compiler/c2/irTests/TestVectorizationMismatchedAccess.java - // -XX:VerifyIterativeGVN=1110 - case Op_Bool: - return; - - // LShiftLNode::Ideal - // Looks at pattern: "(x + x) << c0", converts it to "x << (c0 + 1)" - // Probably a notification issue. - // - // Found with: - // compiler/conversions/TestMoveConvI2LOrCastIIThruAddIs.java - // -ea -esa -XX:CompileThreshold=100 -XX:+UnlockExperimentalVMOptions -server -XX:-TieredCompilation -XX:+IgnoreUnrecognizedVMOptions -XX:VerifyIterativeGVN=1110 - case Op_LShiftL: - return; - - // LShiftINode::Ideal - // pattern: ((x + con1) << con2) -> x << con2 + con1 << con2 - // Could be issue with notification of inputs of inputs - // - // Side-note: should cases like these not be shared between - // LShiftI and LShiftL? - // - // Found with: - // compiler/escapeAnalysis/Test6689060.java - // -XX:+IgnoreUnrecognizedVMOptions -XX:VerifyIterativeGVN=1110 -ea -esa -XX:CompileThreshold=100 -XX:+UnlockExperimentalVMOptions -server -XX:-TieredCompilation -XX:+IgnoreUnrecognizedVMOptions -XX:VerifyIterativeGVN=1110 - case Op_LShiftI: - return; - // AddPNode::Ideal seems to do set_req without removing lock first. // Found with various vector tests tier1-tier3. case Op_AddP: @@ -1853,13 +1659,6 @@ void PhaseIterGVN::verify_Ideal_for(Node* n, bool can_reshape, bool deep_revisit // java -XX:VerifyIterativeGVN=1110 -Xcomp --version case Op_CmpP: return; - - // MinINode::Ideal - // Did not investigate, but there are some patterns that might - // need more notification. - case Op_MinI: - case Op_MaxI: // preemptively removed it as well. - return; } if (n->is_Store()) { @@ -2065,24 +1864,6 @@ void PhaseIterGVN::verify_Identity_for(Node* n) { case Op_CheckCastPP: return; - // In SubNode::Identity, we do: - // Convert "(X+Y) - Y" into X and "(X+Y) - X" into Y - // In the example, the AddI had an input replaced, the AddI is - // added to the IGVN worklist, but the SubI is one link further - // down and is not added. I checked add_users_of_use_to_worklist - // where I would expect the SubI would be added, and I cannot - // find the pattern, only this one: - // If changed AddI/SubI inputs, check CmpU for range check optimization. - // - // Fix this "notification" issue and check if there are any other - // issues. - // - // Found with: - // java -XX:VerifyIterativeGVN=1000 -Xcomp --version - case Op_SubI: - case Op_SubL: - return; - // PhiNode::Identity checks for patterns like: // r = (x != con) ? x : con; // that can be constant folded to "x". @@ -2096,22 +1877,6 @@ void PhaseIterGVN::verify_Identity_for(Node* n) { // -XX:VerifyIterativeGVN=1110 case Op_Phi: return; - - // ConvI2LNode::Identity does - // convert I2L(L2I(x)) => x - // - // Investigate why this did not already happen during IGVN. - // - // Found with: - // compiler/loopopts/superword/TestDependencyOffsets.java#vanilla-A - // -XX:VerifyIterativeGVN=1110 - case Op_ConvI2L: - return; - - // AbsINode::Identity - // Not investigated yet. - case Op_AbsI: - return; } if (n->is_Load()) { @@ -2545,8 +2310,18 @@ void PhaseIterGVN::add_users_of_use_to_worklist(Node* n, Node* use, Unique_Node_ } } - int use_op = use->Opcode(); - if(use->is_Cmp()) { // Enable CMP/BOOL optimization + // AndLNode::Ideal folds GraphKit::mark_word_test patterns. Give it a chance to run. + if (n->is_Load() && use->is_Phi()) { + for (DUIterator_Fast imax, i = use->fast_outs(imax); i < imax; i++) { + Node* u = use->fast_out(i); + if (u->Opcode() == Op_AndL) { + worklist.push(u); + } + } + } + + const int use_op = use->Opcode(); + if (use->is_Cmp()) { // Enable CMP/BOOL optimization add_users_to_worklist0(use, worklist); // Put Bool on worklist for (DUIterator_Fast jmax, j = use->fast_outs(jmax); j < jmax; j++) { Node* bol = use->fast_out(j); @@ -2692,6 +2467,18 @@ void PhaseIterGVN::add_users_of_use_to_worklist(Node* n, Node* use, Unique_Node_ return u->Opcode() == Op_AndI || u->Opcode() == Op_AndL; }); } + // OrI/LNode::Ideal converts shift/or patterns into rotates, which + // needs to trigger when x and y common. + // e.g. (x << 14) | (y >>> 18) + if (use_op == Op_LShiftI || use_op == Op_URShiftI) { + add_users_to_worklist_if(worklist, use, [](Node* u) { + return u->Opcode() == Op_OrI; + }); + } else if (use_op == Op_LShiftL || use_op == Op_URShiftL) { + add_users_to_worklist_if(worklist, use, [](Node* u) { + return u->Opcode() == Op_OrL; + }); + } // If changed LShift inputs, check CompressBits and ExpandBits users for // compress(x, 1 << n), compress(x, -1 << n), // expand(x, 1 << n), expand(x, -1 << n) optimizations. @@ -2701,6 +2488,20 @@ void PhaseIterGVN::add_users_of_use_to_worklist(Node* n, Node* use, Unique_Node_ u->in(2) == use; }); } + // AddNode::Ideal_collapse_variable_times_con optimizes 3-hop pattern: + // e.g. ((x << c1) + (x << c2)) + x -> c * x + // pattern: n -> use=shift -> add1 -> add2 + if (use_op == Op_LShiftI || use_op == Op_LShiftL) { + const int add_op = (use_op == Op_LShiftI) ? Op_AddI : Op_AddL; + for (DUIterator_Fast imax, i = use->fast_outs(imax); i < imax; i++) { + Node* add1 = use->fast_out(i); + if (add1->Opcode() == add_op) { + add_users_to_worklist_if(worklist, add1, [&](Node* add2) { + return add2->Opcode() == add_op; + }); + } + } + } // If changed ExpandBits inputs, check CompressBits users for // compress(expand(x, m), m) optimization. if (use_op == Op_ExpandBits) { @@ -2721,6 +2522,33 @@ void PhaseIterGVN::add_users_of_use_to_worklist(Node* n, Node* use, Unique_Node_ return u->Opcode() == Op_CmpU; }); } + // LShiftNode::IdealIL: + // (x + c1) << c2 -> (x << c2) + c3 + // (c1 - x) << c2 -> c3 - (x << c2) + if (use_op == Op_AddI || use_op == Op_AddL || + use_op == Op_SubI || use_op == Op_SubL) { + add_users_to_worklist_if(worklist, use, [](Node* u) { + return u->Opcode() == Op_LShiftI || u->Opcode() == Op_LShiftL; + }); + } + // AddNode::Ideal reassociates constants to the outside, e.g.: + // (x + C) + y -> (x + y) + C + // (x | C) | y -> (x | y) | C + if (use->is_Add()) { + add_users_to_worklist_if(worklist, use, [&](Node* u) { + return u->Opcode() == use_op; + }); + } + // If changed AddI/AddL inputs, check for add users: + // e.g. x - (y + n) -> x - (y + C) -> (x - y) + (-C) + // before mutation after mutation optimized by + // AddNode::Ideal + if (use_op == Op_AddI || use_op == Op_AddL) { + int sub_op = (use_op == Op_AddI) ? Op_SubI : Op_SubL; + add_users_to_worklist_if(worklist, use, [&](Node* u) { + return u->Opcode() == sub_op; + }); + } // If changed AddI/AddL inputs, check URShift users for // "((X << z) + Y) >>> z" optimization in URShift{I,L}Node::Ideal. if (use_op == Op_AddI || use_op == Op_AddL) { @@ -2749,10 +2577,20 @@ void PhaseIterGVN::add_users_of_use_to_worklist(Node* n, Node* use, Unique_Node_ } } // If changed AndI/AndL inputs, check RShift/URShift users for "(x & mask) >> shift" optimization opportunity + // LShiftNode::IdealIL optimizes patterns like: LShift(And(RShift(x, c), Y), c) if (use_op == Op_AndI || use_op == Op_AndL) { add_users_to_worklist_if(worklist, use, [](Node* u) { return u->Opcode() == Op_RShiftI || u->Opcode() == Op_RShiftL || - u->Opcode() == Op_URShiftI || u->Opcode() == Op_URShiftL; + u->Opcode() == Op_URShiftI || u->Opcode() == Op_URShiftL || + u->Opcode() == Op_LShiftI || u->Opcode() == Op_LShiftL; + }); + } + // MulNode::AndIL_sum_and_mask can optimize: + // AndX(AddX(x, y), mask) -> AndX(x, mask) + if (use_op == Op_AddI || use_op == Op_AddL) { + const int and_op = (use_op == Op_AddI) ? Op_AndI : Op_AndL; + add_users_to_worklist_if(worklist, use, [&](Node* u) { + return u->Opcode() == and_op; }); } // If changed AndL inputs, check ConvL2I users for @@ -2769,15 +2607,20 @@ void PhaseIterGVN::add_users_of_use_to_worklist(Node* n, Node* use, Unique_Node_ // ConvI2F->ConvF2I->ConvI2F // Note: there may be other 3-nodes conversion chains that would require to be added here, but these // are the only ones that are known to trigger missed optimizations otherwise + // + // ConvI2LNode::Identity optimizes the following 2-hop optimization, once x is proven to be in int range: + // ConvI2L(ConvL2I(x)) -> x if (use_op == Op_ConvL2D || use_op == Op_ConvI2F || use_op == Op_ConvL2F || - use_op == Op_ConvF2I) { - add_users_to_worklist_if(worklist, use, [=](Node* u) { + use_op == Op_ConvF2I || + use_op == Op_ConvL2I) { + add_users_to_worklist_if(worklist, use, [&](Node* u) { return (use_op == Op_ConvL2D && u->Opcode() == Op_ConvD2L) || (use_op == Op_ConvI2F && u->Opcode() == Op_ConvF2I) || (use_op == Op_ConvL2F && u->Opcode() == Op_ConvF2L) || - (use_op == Op_ConvF2I && u->Opcode() == Op_ConvI2F); + (use_op == Op_ConvF2I && u->Opcode() == Op_ConvI2F) || + (use_op == Op_ConvL2I && u->Opcode() == Op_ConvI2L); }); } // ConvD2F::Ideal matches ConvD2F(SqrtD(ConvF2D(x))) => SqrtF(x). @@ -2796,7 +2639,7 @@ void PhaseIterGVN::add_users_of_use_to_worklist(Node* n, Node* use, Unique_Node_ // address expression flattening. if (use_op == Op_AddP) { bool offset_changed = n == use->in(AddPNode::Offset); - add_users_to_worklist_if(worklist, use, [=](Node* u) { + add_users_to_worklist_if(worklist, use, [&](Node* u) { return u->is_Mem() || (offset_changed && u->is_AddP() && u->in(AddPNode::Offset)->is_Con()); }); @@ -2809,13 +2652,22 @@ void PhaseIterGVN::add_users_of_use_to_worklist(Node* n, Node* use, Unique_Node_ }); } // Check for Max/Min(A, Max/Min(B, C)) where A == B or A == C - if (use->is_MinMax()) { - add_users_to_worklist_if(worklist, use, [](Node* u) { return u->is_MinMax(); }); + // MinMaxNode::IdealI also optimizes these cases (if no overflow): + // MinI(AddI(x, -1), x) -> AddI(x, -1) + if (use->is_MinMax() || use->Opcode() == Op_AddI) { + add_users_to_worklist_if(worklist, use, [](Node* u) { + return u->is_MinMax(); + }); } // Check for A | (B | C) and (B | C) | A where A == B or A == C. if (use_op == Op_OrI || use_op == Op_OrL) { add_users_to_worklist_if(worklist, use, [&](Node* u) { return u->Opcode() == use->Opcode(); }); } + // MulNode::Ideal idealizes (x | c1) & c2 -> (x & c2) | (c1 & c2) + if (use_op == Op_OrI || use_op == Op_OrL) { + const int and_op = use_op == Op_OrI ? Op_AndI : Op_AndL; + add_users_to_worklist_if(worklist, use, [&](Node* u) { return u->Opcode() == and_op; }); + } auto enqueue_init_mem_projs = [&](ProjNode* proj) { add_users_to_worklist0(proj, worklist); }; @@ -2976,9 +2828,92 @@ void PhaseIterGVN::add_users_of_use_to_worklist(Node* n, Node* use, Unique_Node_ // If changed Sub inputs, check Add for identity. // e.g., (x - y) + y -> x; x + (y - x) -> y. + // SubI/LNode::Ideal also optimize this: + // (x - y) - x -> -y if (use_op == Op_SubI || use_op == Op_SubL) { const int add_op = (use_op == Op_SubI) ? Op_AddI : Op_AddL; - add_users_to_worklist_if(worklist, use, [=](Node* u) { return u->Opcode() == add_op; }); + add_users_to_worklist_if(worklist, use, [&](Node* u) { + return u->Opcode() == add_op || u->Opcode() == use_op; + }); + } + + // If changed Mul inputs, check Add/Sub for common-factor reassociation. + // e.g., (a * b) + (a * c) -> a * (b + c). + // Additionally, check for nested Mul reassociation. + // e.g. (x * c1) * c2 -> x * (c1 * c2) + if (use_op == Op_MulI || use_op == Op_MulL) { + const int add_op = (use_op == Op_MulI) ? Op_AddI : Op_AddL; + const int sub_op = (use_op == Op_MulI) ? Op_SubI : Op_SubL; + add_users_to_worklist_if(worklist, use, [&](Node* u) { + return u->Opcode() == add_op || u->Opcode() == sub_op || u->Opcode() == use_op; + }); + } + + // MulNode::Ideal distributes constant multiplication: + // e.g., (x + c1) * c2 -> x * c2 + (c1 * c2) + if (use_op == Op_AddI || use_op == Op_AddL) { + const int mul_op = (use_op == Op_AddI) ? Op_MulI : Op_MulL; + add_users_to_worklist_if(worklist, use, [&](Node* u) { return u->Opcode() == mul_op; }); + } + + // We may have a loop-phi that is about to close the AddI recurrence, + // and we would have to ensure AddNode::commute is called for the AddI. + // e.g. use = Phi(x, n) -> use = Phi(x, u) -> use = Phi(x, u) + // u = AddI(y, use) u = AddI(y, use) -> u = AddI(use, y) + // current state, After mutation, After commute, + // before mutation before commute canonical loop incr + // + // Alternatively, we may have a region-phi, that is about to turn + // into a loop-phi, and where the AddI recurrence is already closed, + // and the AddI has its inputs sorted by idx. In this case, we also + // need AddNode::commute to canonicalize the AddI, so that the phi + // input is on the left now. + // e.g. use = Phi(region, u) -> use = Phi(loop, u) -> use = Phi(loop, u) + // u = AddI(y, use) u = AddI(y, use) -> u = AddI(use, y) + // current state, loop-phi, After commute, + // region-phi before commute canonical loop incr + if (use->is_Phi() && + use->req() == 3 && + (n == use->in(0) || // possibly: region-phi -> loop-phi + n == use->in(LoopNode::LoopBackControl))) { // possibly: closing AddI recurrence + add_users_to_worklist_if(worklist, use, [](Node* u) { + return u->is_Add(); + }); + } + + // AndLNode::Ideal optimizes GraphKit::mark_word_test patterns + // Pattern: AndL(LoadL(x=mark word), y) + // Pattern: AndL(Phi(LoadL(x=mark word), z), y) + if (use->is_Load()) { + for (DUIterator_Fast i2max, i2 = use->fast_outs(i2max); i2 < i2max; i2++) { + Node* u = use->fast_out(i2); + if (u->Opcode() == Op_AndL) { + // n -> Load -> AndL + worklist.push(u); + } else if (u->is_Phi()) { + // n -> Load -> Phi -> AndL + add_users_to_worklist_if(worklist, u, [](Node* u2) { + return u2->Opcode() == Op_AndL; + }); + } + } + } + + // PhiNode::Ideal can optimize: + // Pattern: Phi(condition, x + y, x) -> x + Phi(condition, y, 0) + if (use_op == Op_AddI) { + add_users_to_worklist_if(worklist, use, [](Node* u) { + return u->is_Phi(); + }); + } + + // AndVNode::Ideal and OrVNode::Ideal have 2-hop operations for + // repeated and/or with the same value: + // Pattern: AndV(AndV(a, b), a) -> AndV(a, b) + if (use_op == Op_AndV || use_op == Op_OrV) { + add_users_to_worklist_if(worklist, use, [&](Node* u) { + return u->Opcode() == use_op; + }); } // (Phi (ValueType ValueType)) is transformed into (ValueType (Phi ) (Phi )...) // and is applied if there are EncodeP/DecodeN or casts between a Phi and InlineType nodes diff --git a/src/hotspot/share/opto/superword.cpp b/src/hotspot/share/opto/superword.cpp index 8acdf2b80002..62fa32c7045b 100644 --- a/src/hotspot/share/opto/superword.cpp +++ b/src/hotspot/share/opto/superword.cpp @@ -1081,10 +1081,12 @@ SuperWord::PairOrderStatus SuperWord::order_inputs_of_uses_to_match_def_pair(Nod if (is_marked_reduction(use1) && is_marked_reduction(use2)) { Node* use1_in2 = use1->in(2); if (use1_in2->is_Phi() || is_marked_reduction(use1_in2)) { + igvn().rehash_node_delayed(use1); use1->swap_edges(1, 2); } Node* use2_in2 = use2->in(2); if (use2_in2->is_Phi() || is_marked_reduction(use2_in2)) { + igvn().rehash_node_delayed(use2); use2->swap_edges(1, 2); } return PairOrderStatus::Ordered; @@ -1100,14 +1102,17 @@ SuperWord::PairOrderStatus SuperWord::order_inputs_of_uses_to_match_def_pair(Nod if (i1 != i2) { if ((i1 == (3-i2)) && (use2->is_Add() || use2->is_Mul())) { // 2. Commutative: swap edges, and hope the other position matches too. + igvn().rehash_node_delayed(use2); use2->swap_edges(i1, i2); } else if (VectorNode::is_muladds2i(use2) && use1 != use2) { // 3.a/b: MulAddS2I. if (i1 == 5 - i2) { // ((i1 == 3 && i2 == 2) || (i1 == 2 && i2 == 3) || (i1 == 1 && i2 == 4) || (i1 == 4 && i2 == 1)) + igvn().rehash_node_delayed(use2); use2->swap_edges(1, 2); use2->swap_edges(3, 4); } if (i1 == 3 - i2 || i1 == 7 - i2) { // ((i1 == 1 && i2 == 2) || (i1 == 2 && i2 == 1) || (i1 == 3 && i2 == 4) || (i1 == 4 && i2 == 3)) + igvn().rehash_node_delayed(use2); use2->swap_edges(2, 3); use2->swap_edges(1, 4); } @@ -1118,6 +1123,7 @@ SuperWord::PairOrderStatus SuperWord::order_inputs_of_uses_to_match_def_pair(Nod } } else if (i1 == i2 && VectorNode::is_muladds2i(use2) && use1 != use2) { // 3.c: MulAddS2I. + igvn().rehash_node_delayed(use2); use2->swap_edges(1, 3); use2->swap_edges(2, 4); return PairOrderStatus::Unknown; diff --git a/test/hotspot/jtreg/compiler/c2/igvn/TestMissingAndOrIdealization.java b/test/hotspot/jtreg/compiler/c2/igvn/TestMissingAndOrIdealization.java new file mode 100644 index 000000000000..02c7805ac3ec --- /dev/null +++ b/test/hotspot/jtreg/compiler/c2/igvn/TestMissingAndOrIdealization.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.c2.igvn; + +/* + * @test + * @bug 8393645 8393641 8393586 + * @summary C2 IGVN should idealize: (x | c1) & c2 -> (x & c2) | (c1 & c2) + * @library /test/lib / + * @run main/othervm -XX:-TieredCompilation -Xcomp + * -XX:+IgnoreUnrecognizedVMOptions -XX:+UnlockDiagnosticVMOptions -XX:VerifyIterativeGVN=1110 + * -XX:CompileOnly=${test.main.class}::test* + * -XX:+StressIGVN + * ${test.main.class} + * @run main ${test.main.class} + */ + +public class TestMissingAndOrIdealization { + public static void main(String[] args) { + testI(0, 0.0, false); + testL(0, 0.0, false); + } + + // Failed to optimize AndI(OrI(x, c1), c2). + // Optimization from MulNode::Ideal + static int testI(int x, double i, boolean b) { + while (i < 4) { + x |= b ? -126 : -30; + x &= 32; + i++; + } + return x; + } + + // Failed to optimize AndL(OrL(x, c1), c2). + // Optimization from MulNode::Ideal + static long testL(long x, double i, boolean b) { + while (i < 4) { + x |= b ? -126 : -30; + x &= 32; + i++; + } + return x; + } +} diff --git a/test/hotspot/jtreg/compiler/c2/igvn/TestMissingNestedMulIdealization.java b/test/hotspot/jtreg/compiler/c2/igvn/TestMissingNestedMulIdealization.java new file mode 100644 index 000000000000..77aa48e4ed65 --- /dev/null +++ b/test/hotspot/jtreg/compiler/c2/igvn/TestMissingNestedMulIdealization.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.c2.igvn; + +/* + * @test + * @bug 8393645 8393608 8393644 + * @summary C2 IGVN should idealize: (x * c1) * c2 -> x * (c1 * c2) + * @library /test/lib / + * @run main/othervm -XX:-TieredCompilation -Xcomp + * -XX:+IgnoreUnrecognizedVMOptions -XX:+UnlockDiagnosticVMOptions -XX:VerifyIterativeGVN=1110 + * -XX:CompileOnly=${test.main.class}::test* + * -XX:+StressIGVN + * ${test.main.class} + * @run main ${test.main.class} + */ + +// The test is somewhat intermittent, more stable: -XX:RepeatCompilation=50 +public class TestMissingNestedMulIdealization { + + public static void main(String[] args) { + testI(0); + testL(0); + } + + // Failed to optimize MulI(MulI(x, c1), c2). + // Optimization from MulNode::Ideal + static int testI(int x) { + for (int i = 0; i < 17; i++) { + x *= ~i; + } + return x; + } + + // Failed to optimize MulL(MulL(x, c1), c2). + // Optimization from MulNode::Ideal + static long testL(long x) { + for (long i = 0; i < 17; i++) { + x *= ~i; + } + return x; + } +} diff --git a/test/hotspot/jtreg/compiler/c2/igvn/TestMissingVariableTimesConIdealization.java b/test/hotspot/jtreg/compiler/c2/igvn/TestMissingVariableTimesConIdealization.java new file mode 100644 index 000000000000..f0f0f6b41f88 --- /dev/null +++ b/test/hotspot/jtreg/compiler/c2/igvn/TestMissingVariableTimesConIdealization.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.c2.igvn; + +/* + * @test + * @bug 8393645 8393610 8393640 + * @summary Missing notification pattern for AddNode::Ideal_collapse_variable_times_con: + * ((x << c1) + (x << c2)) + x -> c * x + * @library /test/lib / + * @run main/othervm -XX:-TieredCompilation -Xbatch + * -XX:+IgnoreUnrecognizedVMOptions -XX:+UnlockDiagnosticVMOptions -XX:VerifyIterativeGVN=1110 + * -XX:CompileOnly=${test.main.class}::test* + * -XX:+StressIGVN + * ${test.main.class} + * @run main/othervm --enable-preview + * -XX:-TieredCompilation -Xcomp + * -XX:+IgnoreUnrecognizedVMOptions -XX:+UnlockDiagnosticVMOptions -XX:VerifyIterativeGVN=1110 + * -XX:CompileOnly=${test.main.class}::test* + * -XX:+StressIGVN + * ${test.main.class} + * @run main ${test.main.class} + */ + +// Test was intermittent, reproduces better with -XX:RepeatCompilation=50 +public class TestMissingVariableTimesConIdealization { + static short sFld; + static int iFld; + static long lFld; + + public static void main(String[] args) { + for (int i = 0; i < 10000; i++) { + test1I(); + test1L(); + } + test2I(); + } + + // ((x << 2) + (x << 1)) + x -> 7 * x + static void test1I() { + iFld = iFld - Byte.valueOf((byte) -6) * iFld; + } + + // ((x << 2) + (x << 1)) + x -> 7 * x + static void test1L() { + lFld = lFld - Byte.valueOf((byte) -6) * lFld; + } + + // ((x << 14) + (x << 2)) + x -> 16389 * x + // Only reproduced with --enable-preview. + static int test2I() { + return sFld + Short.valueOf((short) 16388) * sFld; + } +} diff --git a/test/hotspot/jtreg/compiler/loopopts/superword/TestReductions.java b/test/hotspot/jtreg/compiler/loopopts/superword/TestReductions.java index 599e95bd6aba..d7b4278ede90 100644 --- a/test/hotspot/jtreg/compiler/loopopts/superword/TestReductions.java +++ b/test/hotspot/jtreg/compiler/loopopts/superword/TestReductions.java @@ -118,8 +118,7 @@ public static void main(String[] args) { switch (args[0]) { case "P0" -> { framework.addFlags("-XX:+UnlockDiagnosticVMOptions", "-XX:AutoVectorizationOverrideProfitability=0"); } case "P1" -> { framework.addFlags("-XX:+UnlockDiagnosticVMOptions", "-XX:AutoVectorizationOverrideProfitability=1"); } - // Note: increasing the node count limit also helps in some cases. - case "P2" -> { framework.addFlags("-XX:+UnlockDiagnosticVMOptions", "-XX:AutoVectorizationOverrideProfitability=2", "-XX:LoopUnrollLimit=1000"); } + case "P2" -> { framework.addFlags("-XX:+UnlockDiagnosticVMOptions", "-XX:AutoVectorizationOverrideProfitability=2"); } default -> { throw new RuntimeException("Test argument not recognized: " + args[0]); } }; framework.start(); @@ -2116,15 +2115,7 @@ private static long longAddBig() { IRNode.MUL_REDUCTION_VL, "> 0", IRNode.MUL_VL, "> 0"}, applyIfCPUFeatureOr = {"avx512dq", "true", "sve", "true"}, - applyIfAnd = {"AutoVectorizationOverrideProfitability", "> 0", - "LoopUnrollLimit", ">= 1000"}) - @IR(failOn = IRNode.LOAD_VECTOR_L, - applyIfCPUFeature = {"avx512dq", "true"}, - applyIfAnd = {"AutoVectorizationOverrideProfitability", "> 0", - "LoopUnrollLimit", "< 1000"}) - // Increasing the body limit seems to help. Filed for investigation: JDK-8370685 - // If you can eliminate this exception for LoopUnrollLimit, please remove - // the flag completely from the test, also the "addFlags" at the top. + applyIf = {"AutoVectorizationOverrideProfitability", "> 0"}) @IR(failOn = IRNode.LOAD_VECTOR_L, applyIfCPUFeatureAnd = {"asimd", "true", "sve", "false"}) // MulVL is not implemented on NEON, so we also not have the reduction. From 019a9d04a8d17aa284fc4abfdd30dfd20426f037 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Lund=C3=A9n?= Date: Thu, 15 Oct 2026 08:06:51 +0000 Subject: [PATCH 053/169] 8337821: C2 hits compiler MemLimit in PhaseChaitin::Split Reviewed-by: kvn, rcastanedalo --- src/hotspot/share/opto/c2_globals.hpp | 5 ++ src/hotspot/share/opto/reg_split.cpp | 7 ++- test/hotspot/jtreg/ProblemList.txt | 2 - .../TestFloat16RearrangeInlining.java | 59 +++++++++++++++++++ .../VectorReplicateLongSpecialImmTest.java | 1 - .../jtreg/vmTestbase/jit/t/t105/t105.java | 4 +- 6 files changed, 72 insertions(+), 6 deletions(-) create mode 100644 test/hotspot/jtreg/compiler/vectorapi/TestFloat16RearrangeInlining.java diff --git a/src/hotspot/share/opto/c2_globals.hpp b/src/hotspot/share/opto/c2_globals.hpp index 083a3c94d480..3379da006347 100644 --- a/src/hotspot/share/opto/c2_globals.hpp +++ b/src/hotspot/share/opto/c2_globals.hpp @@ -288,6 +288,11 @@ "Maximum allowed edges in the interference graphs") \ range(0, max_juint) \ \ + product(uint, RegSplitLimit, 20*M, DIAGNOSTIC, \ + "Maximum product of spill and CFG block counts during Split " \ + "in the Register Allocator") \ + range(0, max_juint) \ + \ develop(bool, UseUniqueSubclasses, true, \ "Narrow an abstract reference to the unique concrete subclass") \ \ diff --git a/src/hotspot/share/opto/reg_split.cpp b/src/hotspot/share/opto/reg_split.cpp index 96f87fe6947d..7b2597d74146 100644 --- a/src/hotspot/share/opto/reg_split.cpp +++ b/src/hotspot/share/opto/reg_split.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -534,6 +534,11 @@ uint PhaseChaitin::Split(uint maxlrg, ResourceArea* split_arena) { } } + if (static_cast(spill_cnt) * _cfg.number_of_blocks() > RegSplitLimit) { + C->record_method_not_compilable("PhaseChaitin::Split requires too much memory"); + return 0; + } + // Create side arrays for propagating reaching defs info. // Each block needs a node pointer for each spilling live range for the // Def which is live into the block. Phi nodes handle multiple input diff --git a/test/hotspot/jtreg/ProblemList.txt b/test/hotspot/jtreg/ProblemList.txt index 9e4d24efc3bb..189d9b4aa0ff 100644 --- a/test/hotspot/jtreg/ProblemList.txt +++ b/test/hotspot/jtreg/ProblemList.txt @@ -69,8 +69,6 @@ compiler/c2/aarch64/TestStaticCallStub.java 8359963 generic-aarch64 compiler/escapeAnalysis/TestBCEscapeAnalyzerOverflow.java 8387392 windows-aarch64 -compiler/vectorapi/VectorExpressionFuzzer.java 8337281 linux-aarch64,macosx-aarch64 - compiler/igvn/TestIncorrectDeadPathTypeNode.java 8392354 generic-all ############################################################################# diff --git a/test/hotspot/jtreg/compiler/vectorapi/TestFloat16RearrangeInlining.java b/test/hotspot/jtreg/compiler/vectorapi/TestFloat16RearrangeInlining.java new file mode 100644 index 000000000000..18ed88dd4c7a --- /dev/null +++ b/test/hotspot/jtreg/compiler/vectorapi/TestFloat16RearrangeInlining.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @bug 8337821 8393397 + * @summary Test that C2's register allocator gracefully handles pathological + * input resulting from extreme inlining. See 8393397 for the + * inlining issue (observed on aarch64). + * @modules jdk.incubator.vector + * @library /test/lib / + * @run main/othervm -Xbatch + * -esa + * -XX:CompileCommand=CompileOnly,jdk.incubator.vector.Float16Vector::lambda$rearrangeTemplate$0 + * compiler.vectorapi.TestFloat16RearrangeInlining + * @run main compiler.vectorapi.TestFloat16RearrangeInlining + */ + +package compiler.vectorapi; + +import jdk.incubator.vector.*; + +public class TestFloat16RearrangeInlining { + + public static void main(String[] args) { + Float16Vector vector512 = Float16Vector.zero(Float16Vector.SPECIES_512); + VectorShuffle shuffle512 = + VectorShuffle.iota(Float16Vector.SPECIES_512, 0, 1, false); + for (int i = 0; i < 1_500; i++) { + vector512.rearrange(shuffle512); + } + Float16Vector vector256 = Float16Vector.zero(Float16Vector.SPECIES_256); + VectorShuffle shuffle256 = + VectorShuffle.iota(Float16Vector.SPECIES_256, 0, 1, false); + for (int i = 0; i < 20_000; i++) { + vector256.rearrange(shuffle256); + } + } +} diff --git a/test/hotspot/jtreg/compiler/vectorapi/VectorReplicateLongSpecialImmTest.java b/test/hotspot/jtreg/compiler/vectorapi/VectorReplicateLongSpecialImmTest.java index f9cb7d163d02..c288fc2a4d6d 100644 --- a/test/hotspot/jtreg/compiler/vectorapi/VectorReplicateLongSpecialImmTest.java +++ b/test/hotspot/jtreg/compiler/vectorapi/VectorReplicateLongSpecialImmTest.java @@ -39,7 +39,6 @@ * @run testng/othervm -XX:UseSVE=0 -XX:-TieredCompilation * -XX:CompileThreshold=100 * -XX:+IgnoreUnrecognizedVMOptions - * -XX:CompileCommand=MemLimit,*.*,0 * -XX:CompileTaskTimeout=0 * compiler.vectorapi.VectorReplicateLongSpecialImmTest */ diff --git a/test/hotspot/jtreg/vmTestbase/jit/t/t105/t105.java b/test/hotspot/jtreg/vmTestbase/jit/t/t105/t105.java index b27975260bde..ba54f72c8005 100644 --- a/test/hotspot/jtreg/vmTestbase/jit/t/t105/t105.java +++ b/test/hotspot/jtreg/vmTestbase/jit/t/t105/t105.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -31,7 +31,7 @@ * @library /vmTestbase * /test/lib * @run main/othervm -XX:-OmitStackTraceInFastThrow jit.t.t105.t105 - * @run main/othervm -XX:CompileCommand=MemLimit,*.*,0 -XX:-OmitStackTraceInFastThrow -Xbatch -XX:Tier0BackedgeNotifyFreqLog=0 -XX:Tier2BackedgeNotifyFreqLog=0 -XX:Tier3BackedgeNotifyFreqLog=0 -XX:Tier2BackEdgeThreshold=1 -XX:Tier3BackEdgeThreshold=1 -XX:Tier4BackEdgeThreshold=1 jit.t.t105.t105 + * @run main/othervm -XX:-OmitStackTraceInFastThrow -Xbatch -XX:Tier0BackedgeNotifyFreqLog=0 -XX:Tier2BackedgeNotifyFreqLog=0 -XX:Tier3BackedgeNotifyFreqLog=0 -XX:Tier2BackEdgeThreshold=1 -XX:Tier3BackEdgeThreshold=1 -XX:Tier4BackEdgeThreshold=1 jit.t.t105.t105 * * This test must be run with OmitStackTraceInFastThrow disabled to avoid preallocated * exceptions. They don't have the detailed message that this test relies on. From f7917fbd76af5d44ffd68ec75923654cc48bd599 Mon Sep 17 00:00:00 2001 From: Dingli Zhang Date: Thu, 15 Oct 2026 10:44:10 +0000 Subject: [PATCH 054/169] 8394622: [BACKOUT] RISC-V: Use movptr2 for C2 pointer constants Reviewed-by: gcao, fyang --- src/hotspot/cpu/riscv/macroAssembler_riscv.cpp | 8 ++++---- src/hotspot/cpu/riscv/macroAssembler_riscv.hpp | 4 ++-- src/hotspot/cpu/riscv/riscv.ad | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/hotspot/cpu/riscv/macroAssembler_riscv.cpp b/src/hotspot/cpu/riscv/macroAssembler_riscv.cpp index 733464b8b269..fbbfc87ae861 100644 --- a/src/hotspot/cpu/riscv/macroAssembler_riscv.cpp +++ b/src/hotspot/cpu/riscv/macroAssembler_riscv.cpp @@ -3792,7 +3792,7 @@ void MacroAssembler::cmp_klass_bne(Register obj, Register klass, } // Move an oop into a register. -void MacroAssembler::movoop(Register dst, jobject obj, Register tmp) { +void MacroAssembler::movoop(Register dst, jobject obj) { int oop_index; if (obj == nullptr) { oop_index = oop_recorder()->allocate_oop_index(obj); @@ -3808,7 +3808,7 @@ void MacroAssembler::movoop(Register dst, jobject obj, Register tmp) { RelocationHolder rspec = oop_Relocation::spec(oop_index); if (BarrierSet::barrier_set()->barrier_set_assembler()->supports_instruction_patching()) { - movptr(dst, Address((address)obj, rspec), tmp); + movptr(dst, Address((address)obj, rspec)); } else { address dummy = address(uintptr_t(pc()) & -wordSize); // A nearby aligned address ld(dst, Address(dummy, rspec)); @@ -3816,7 +3816,7 @@ void MacroAssembler::movoop(Register dst, jobject obj, Register tmp) { } // Move a metadata address into a register. -void MacroAssembler::mov_metadata(Register dst, Metadata* obj, Register tmp) { +void MacroAssembler::mov_metadata(Register dst, Metadata* obj) { assert((uintptr_t)obj < (1ull << 48), "48-bit overflow in metadata"); int oop_index; if (obj == nullptr) { @@ -3825,7 +3825,7 @@ void MacroAssembler::mov_metadata(Register dst, Metadata* obj, Register tmp) { oop_index = oop_recorder()->find_index(obj); } RelocationHolder rspec = metadata_Relocation::spec(oop_index); - movptr(dst, Address((address)obj, rspec), tmp); + movptr(dst, Address((address)obj, rspec)); } void MacroAssembler::payload_offset(Register value_klass, Register offset) { diff --git a/src/hotspot/cpu/riscv/macroAssembler_riscv.hpp b/src/hotspot/cpu/riscv/macroAssembler_riscv.hpp index 7a65ce5a27e5..838491511921 100644 --- a/src/hotspot/cpu/riscv/macroAssembler_riscv.hpp +++ b/src/hotspot/cpu/riscv/macroAssembler_riscv.hpp @@ -191,8 +191,8 @@ class MacroAssembler: public Assembler { void resolve_jobject(Register value, Register tmp1, Register tmp2); void resolve_global_jobject(Register value, Register tmp1, Register tmp2); - void movoop(Register dst, jobject obj, Register tmp = noreg); - void mov_metadata(Register dst, Metadata* obj, Register tmp = noreg); + void movoop(Register dst, jobject obj); + void mov_metadata(Register dst, Metadata* obj); void bang_stack_size(Register size, Register tmp); void set_narrow_oop(Register dst, jobject obj); void set_narrow_klass(Register dst, Klass* k); diff --git a/src/hotspot/cpu/riscv/riscv.ad b/src/hotspot/cpu/riscv/riscv.ad index 8dfc989846e5..615378b7f4a0 100644 --- a/src/hotspot/cpu/riscv/riscv.ad +++ b/src/hotspot/cpu/riscv/riscv.ad @@ -2372,9 +2372,9 @@ encode %{ } else { relocInfo::relocType rtype = $src->constant_reloc(); if (rtype == relocInfo::oop_type) { - __ movoop(dst_reg, (jobject)con, t0); + __ movoop(dst_reg, (jobject)con); } else if (rtype == relocInfo::metadata_type) { - __ mov_metadata(dst_reg, (Metadata*)con, t0); + __ mov_metadata(dst_reg, (Metadata*)con); } else { assert(rtype == relocInfo::none || rtype == relocInfo::external_word_type, "unexpected reloc type"); // Load fake address constants as a plain immediate. They are not real From 2990eca07a7889750baf5213147f62f8e01f91fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Jeli=C5=84ski?= Date: Thu, 15 Oct 2026 11:04:06 +0000 Subject: [PATCH 055/169] 8394574: Clean up net_util Reviewed-by: alanb --- src/java.base/share/native/libnet/net_util.h | 10 +-- .../unix/native/libnet/net_util_md.h | 11 --- .../windows/native/libnet/Inet4AddressImpl.c | 3 + .../windows/native/libnet/Inet6AddressImpl.c | 3 + .../windows/native/libnet/NetworkInterface.c | 5 +- .../native/libnet/ResolverConfigurationImpl.c | 2 + .../windows/native/libnet/net_util_md.c | 5 +- .../windows/native/libnet/net_util_md.h | 77 +------------------ .../native/libnio/ch/DatagramChannelImpl.c | 3 +- src/java.base/windows/native/libnio/ch/Net.c | 1 + 10 files changed, 19 insertions(+), 101 deletions(-) diff --git a/src/java.base/share/native/libnet/net_util.h b/src/java.base/share/native/libnet/net_util.h index 92b812b18686..a739cb3d2ccf 100644 --- a/src/java.base/share/native/libnet/net_util.h +++ b/src/java.base/share/native/libnet/net_util.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -82,14 +82,6 @@ extern jfieldID ni_addrsID; extern jfieldID ni_descID; extern jmethodID ni_ctrID; -/* DatagramPacket fields */ -extern jfieldID dp_addressID; -extern jfieldID dp_portID; -extern jfieldID dp_bufID; -extern jfieldID dp_offsetID; -extern jfieldID dp_lengthID; -extern jfieldID dp_bufLengthID; - /* Inet6Address fields */ extern jclass ia6_class; extern jfieldID ia6_holder6ID; diff --git a/src/java.base/unix/native/libnet/net_util_md.h b/src/java.base/unix/native/libnet/net_util_md.h index 639cf00515fa..18c563f98a4e 100644 --- a/src/java.base/unix/native/libnet/net_util_md.h +++ b/src/java.base/unix/native/libnet/net_util_md.h @@ -54,17 +54,6 @@ #endif #endif -/* - * On 64-bit JDKs we use a much larger stack and heap buffer. - */ -#ifdef _LP64 -#define MAX_BUFFER_LEN 65536 -#define MAX_HEAP_BUFFER_LEN 131072 -#else -#define MAX_BUFFER_LEN 8192 -#define MAX_HEAP_BUFFER_LEN 65536 -#endif - typedef union { struct sockaddr sa; struct sockaddr_in sa4; diff --git a/src/java.base/windows/native/libnet/Inet4AddressImpl.c b/src/java.base/windows/native/libnet/Inet4AddressImpl.c index 91b307375006..8ea34cfbd4b6 100644 --- a/src/java.base/windows/native/libnet/Inet4AddressImpl.c +++ b/src/java.base/windows/native/libnet/Inet4AddressImpl.c @@ -24,6 +24,9 @@ */ #include #include +#include +#include +#include #include "net_util.h" diff --git a/src/java.base/windows/native/libnet/Inet6AddressImpl.c b/src/java.base/windows/native/libnet/Inet6AddressImpl.c index 812693299ba2..029d2c588e90 100644 --- a/src/java.base/windows/native/libnet/Inet6AddressImpl.c +++ b/src/java.base/windows/native/libnet/Inet6AddressImpl.c @@ -24,6 +24,9 @@ */ #include #include +#include +#include +#include #include "net_util.h" diff --git a/src/java.base/windows/native/libnet/NetworkInterface.c b/src/java.base/windows/native/libnet/NetworkInterface.c index 838921567c3b..b166c1cc4cff 100644 --- a/src/java.base/windows/native/libnet/NetworkInterface.c +++ b/src/java.base/windows/native/libnet/NetworkInterface.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -22,6 +22,9 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ +#include +#include +#include #include "net_util.h" #include "NetworkInterface.h" diff --git a/src/java.base/windows/native/libnet/ResolverConfigurationImpl.c b/src/java.base/windows/native/libnet/ResolverConfigurationImpl.c index f3bc27f67ca3..f6c240a1c0b6 100644 --- a/src/java.base/windows/native/libnet/ResolverConfigurationImpl.c +++ b/src/java.base/windows/native/libnet/ResolverConfigurationImpl.c @@ -28,6 +28,8 @@ #include #include #include +#include +#include #include "net_util.h" #include "jni_util.h" diff --git a/src/java.base/windows/native/libnet/net_util_md.c b/src/java.base/windows/native/libnet/net_util_md.c index 5abdd8d4c2e3..6320c6b81121 100644 --- a/src/java.base/windows/native/libnet/net_util_md.c +++ b/src/java.base/windows/native/libnet/net_util_md.c @@ -22,6 +22,8 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ +#include +#include #include "net_util.h" #include "java_net_InetAddress.h" @@ -37,9 +39,6 @@ #define IPTOS_PREC_MASK 0xe0 #endif -/* true if SO_RCVTIMEO is supported */ -jboolean isRcvTimeoutSupported = JNI_TRUE; - /* * Table of Windows Sockets errors, the specific exception we * throw for the error, and the error text. diff --git a/src/java.base/windows/native/libnet/net_util_md.h b/src/java.base/windows/native/libnet/net_util_md.h index 255988c461e3..6a661e70bdda 100644 --- a/src/java.base/windows/native/libnet/net_util_md.h +++ b/src/java.base/windows/native/libnet/net_util_md.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -24,32 +24,6 @@ */ #include #include -#include -#include -#include - -/* used to disable connection reset messages on Windows XP */ -#ifndef SIO_UDP_CONNRESET -#define SIO_UDP_CONNRESET _WSAIOW(IOC_VENDOR,12) -#endif - -#ifndef IN6_IS_ADDR_ANY -#define IN6_IS_ADDR_ANY(a) \ - (((a)->s6_words[0] == 0) && ((a)->s6_words[1] == 0) && \ - ((a)->s6_words[2] == 0) && ((a)->s6_words[3] == 0) && \ - ((a)->s6_words[4] == 0) && ((a)->s6_words[5] == 0) && \ - ((a)->s6_words[6] == 0) && ((a)->s6_words[7] == 0)) -#endif - -#ifndef IPV6_V6ONLY -#define IPV6_V6ONLY 27 /* Treat wildcard bind as AF_INET6-only. */ -#endif - -#define MAX_BUFFER_LEN 2048 -#define MAX_HEAP_BUFFER_LEN 65536 - -/* true if SO_RCVTIMEO is supported by underlying provider */ -extern jboolean isRcvTimeoutSupported; typedef union { struct sockaddr sa; @@ -57,24 +31,6 @@ typedef union { struct sockaddr_in6 sa6; } SOCKETADDRESS; -#define SOCKETADDRESS_COPY(DST,SRC) { \ - if ((SRC)->sa_family == AF_INET6) { \ - memcpy ((DST), (SRC), sizeof (struct sockaddr_in6)); \ - } else { \ - memcpy ((DST), (SRC), sizeof (struct sockaddr_in)); \ - } \ -} - -#define SET_PORT(X,Y) { \ - if ((X)->sa.sa_family == AF_INET) { \ - (X)->sa4.sin_port = (Y); \ - } else { \ - (X)->sa6.sin6_port = (Y); \ - } \ -} - -#define GET_PORT(X) ((X)->sa.sa_family == AF_INET ? (X)->sa4.sin_port : (X)->sa6.sin6_port) - /** * With dual socket implementation the * IPv4 addresseses might be mapped as IPv6. @@ -116,34 +72,3 @@ JNIEXPORT int JNICALL NET_WinBind(int s, SOCKETADDRESS *sa, int len, JNIEXPORT jint JNICALL NET_EnableFastTcpLoopbackConnect(int fd); -/* XP versions of the native routines */ - -JNIEXPORT jobject JNICALL Java_java_net_NetworkInterface_getByName0_XP - (JNIEnv *env, jclass cls, jstring name); - -JNIEXPORT jobject JNICALL Java_java_net_NetworkInterface_getByIndex0_XP - (JNIEnv *env, jclass cls, jint index); - -JNIEXPORT jobject JNICALL Java_java_net_NetworkInterface_getByInetAddress0_XP - (JNIEnv *env, jclass cls, jobject iaObj); - -JNIEXPORT jobjectArray JNICALL Java_java_net_NetworkInterface_getAll_XP - (JNIEnv *env, jclass cls); - -JNIEXPORT jboolean JNICALL Java_java_net_NetworkInterface_supportsMulticast0_XP - (JNIEnv *env, jclass cls, jstring name, jint index); - -JNIEXPORT jboolean JNICALL Java_java_net_NetworkInterface_isUp0_XP - (JNIEnv *env, jclass cls, jstring name, jint index); - -JNIEXPORT jboolean JNICALL Java_java_net_NetworkInterface_isP2P0_XP - (JNIEnv *env, jclass cls, jstring name, jint index); - -JNIEXPORT jbyteArray JNICALL Java_java_net_NetworkInterface_getMacAddr0_XP - (JNIEnv *env, jclass cls, jstring name, jint index); - -JNIEXPORT jint JNICALL Java_java_net_NetworkInterface_getMTU0_XP - (JNIEnv *env, jclass class, jstring name, jint index); - -JNIEXPORT jboolean JNICALL Java_java_net_NetworkInterface_isLoopback0_XP - (JNIEnv *env, jclass cls, jstring name, jint index); diff --git a/src/java.base/windows/native/libnio/ch/DatagramChannelImpl.c b/src/java.base/windows/native/libnio/ch/DatagramChannelImpl.c index 54a4133a3b22..ce3144d5c045 100644 --- a/src/java.base/windows/native/libnio/ch/DatagramChannelImpl.c +++ b/src/java.base/windows/native/libnio/ch/DatagramChannelImpl.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -24,6 +24,7 @@ */ #include +#include #include "jni.h" #include "jni_util.h" diff --git a/src/java.base/windows/native/libnio/ch/Net.c b/src/java.base/windows/native/libnio/ch/Net.c index d9300ee2028d..36c0e4fa738f 100644 --- a/src/java.base/windows/native/libnio/ch/Net.c +++ b/src/java.base/windows/native/libnio/ch/Net.c @@ -25,6 +25,7 @@ #include #include +#include #include "jni.h" #include "jni_util.h" From 47b09aa1912d594d23134a51ed8893287f25d4a3 Mon Sep 17 00:00:00 2001 From: Christian Hagedorn Date: Thu, 15 Oct 2026 12:28:52 +0000 Subject: [PATCH 056/169] 8388433: [IR Framework] Introduce @Skip to skip failing tests instead of commenting them out Reviewed-by: mchevalier --- .../jtreg/compiler/lib/ir_framework/README.md | 6 + .../jtreg/compiler/lib/ir_framework/Skip.java | 50 +++ .../lib/ir_framework/test/DeclaredTest.java | 8 +- .../lib/ir_framework/test/TestVM.java | 58 +++- .../examples/SkipTestExample.java | 128 ++++++++ .../ir_framework/tests/TestBadFormat.java | 38 +++ .../tests/TestNoMainExecution.java | 2 +- .../ir_framework/tests/TestSkip.java | 303 ++++++++++++++++++ 8 files changed, 587 insertions(+), 6 deletions(-) create mode 100644 test/hotspot/jtreg/compiler/lib/ir_framework/Skip.java create mode 100644 test/hotspot/jtreg/testlibrary_tests/ir_framework/examples/SkipTestExample.java create mode 100644 test/hotspot/jtreg/testlibrary_tests/ir_framework/tests/TestSkip.java diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/README.md b/test/hotspot/jtreg/compiler/lib/ir_framework/README.md index 5560869d80f1..77b11277b3c1 100644 --- a/test/hotspot/jtreg/compiler/lib/ir_framework/README.md +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/README.md @@ -60,6 +60,11 @@ A custom run test gives full control over the invocation of the `@Test` annotate More information on checked tests with a precise definition can be found in the Javadocs of [Run](./Run.java). Concrete examples on how to specify a custom run test can be found in [CustomRunTestsExample](../../../testlibrary_tests/ir_framework/examples/CustomRunTestExample.java). +#### Skipping a Test Completely When Encountering Problems +When a test execution is failing due to a VM problem (crash, wrong execution etc.) without having an immediate fix, it is preferred to specify [@Skip](./Skip.java) at the `@Test`-method to disable the test over commenting the test out. + +Specifying `@Skip` allows the IR Framework to still execute the skipped test on command by using the property flag `-DIgnoreSkip=true`. This can be useful when trying to quickly verify if a disabled test is still failing or not. + ### 2.2 IR Verification The main feature of this framework is to perform a simple but yet powerful regex-based C2 IR matching on the output of `-XX:+PrintIdeal`, `-XX:+PrintOptoAssembly` and/or on specific compile phases emitted by the compile command `-XX:CompileCommand=PrintIdealPhase` which supports the same set of compile phases as the Ideal Graph Visualizer (IGV). @@ -187,6 +192,7 @@ The framework provides various stress and debug flags. They should mainly be use - `-DWaitForCompilationTimeout=20`: Change the default waiting time (default: 10s) for a compilation of a `@Test` annotated method with compilation level [WAIT\_FOR\_COMPILATION](./CompLevel.java). - `-DIgnoreCompilerControls=true`: Ignore all compiler controls applied in the framework. This includes any compiler control annotations (`@DontCompile`, `@DontInline`, `@ForceCompile`, `@ForceInline`, `@ForceCompileStaticInitializer`), the exclusion of `@Run` and `@Check` methods from compilation, and the directive to not inline `@Test` annotated methods. - `-DPreferCommandLineFlags=true`: Prefer flags set via the command line over flags specified by the tests. +- `-DIgnoreSkip=true`: Additionally run all skipped tests (i.e. tests with a `@Skip` annotation). ## 3. Test Framework Execution This section gives an overview of how the framework is executing a JTreg test that calls the framework from within its `main()` method. diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/Skip.java b/test/hotspot/jtreg/compiler/lib/ir_framework/Skip.java new file mode 100644 index 000000000000..fefd2ab2f214 --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/Skip.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.lib.ir_framework; + +import compiler.lib.ir_framework.shared.TestFormatException; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * This annotation skips the execution of a {@link Test @Test}-annotated method completely. This is useful when the + * {@link Test @Test}-method causes the VM to misbehave (crash, wrong execution etc.) with no immediately available fix. + * + *

    + * It is preferable to use {@link Skip @Skip} over commenting a test out because one can still execute the skipped + * test by passing the property flag {@code -DIgnoreSkip=true} (see {@link ir_framework.examples.SkipTestExample}). + * This can be useful when trying to quickly verify if a disabled test is still failing or not. + * + *

    + * The {@link Skip @Skip} annotation can only be used at {@link Test @Test}-annotated methods. If the + * {@link Test @Test}-method is part of a custom run test, then the {@link Run @Run}-method is also not executed. + * A special case is a {@link Run @Run}-annotated method with multiple associated {@link Test @Test}-methods. In this + * case, you must either skip all or none to limit the impact. Partially skipping tests results in a + * {@link TestFormatException}. When only needing to disable one of the multiple associated tests, consider moving it + * to a separate custom run test with a 1-to-1 {@link Run @Run}-{@link Test @Test}-relation for minimum impact. + */ +@Retention(RetentionPolicy.RUNTIME) +public @interface Skip { +} diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/test/DeclaredTest.java b/test/hotspot/jtreg/compiler/lib/ir_framework/test/DeclaredTest.java index 81143b6c2540..f72a3c3f9265 100644 --- a/test/hotspot/jtreg/compiler/lib/ir_framework/test/DeclaredTest.java +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/test/DeclaredTest.java @@ -37,16 +37,18 @@ public class DeclaredTest { private final CompLevel compLevel; private final int warmupIterations; private final boolean allowNotCompilable; + private final boolean hasSkipAnno; private Method attachedMethod; public DeclaredTest(Method testMethod, ArgumentsProvider argumentsProvider, CompLevel compLevel, int warmupIterations, - boolean allowNotCompilable) { + boolean hasSkipAnno, boolean allowNotCompilable) { // Make sure we can also call non-public or public methods in package private classes testMethod.setAccessible(true); this.testMethod = testMethod; this.argumentsProvider = argumentsProvider; this.compLevel = compLevel; this.warmupIterations = warmupIterations; + this.hasSkipAnno = hasSkipAnno; this.allowNotCompilable = allowNotCompilable; this.attachedMethod = null; } @@ -71,6 +73,10 @@ public Object[] getArguments(Object invocationTarget, int invocationCounter) { return argumentsProvider.getArguments(invocationTarget, invocationCounter); } + public boolean hasSkipAnno() { + return hasSkipAnno; + } + public void setAttachedMethod(Method m) { attachedMethod = m; } diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/test/TestVM.java b/test/hotspot/jtreg/compiler/lib/ir_framework/test/TestVM.java index be189cd6f245..32e6ae5faa32 100644 --- a/test/hotspot/jtreg/compiler/lib/ir_framework/test/TestVM.java +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/test/TestVM.java @@ -97,6 +97,8 @@ assertions from main() of your test! static final boolean EXCLUDE_RANDOM = Boolean.getBoolean("ExcludeRandom"); private static final String TEST_LIST = SystemProperty.getTestList(); private static final String EXCLUDE_LIST = SystemProperty.getExcludeList(); + // We ignore @Skip when running with -DIgnoreSkip=true + private static final boolean IGNORE_SKIP = Boolean.getBoolean("IgnoreSkip"); private static final boolean DUMP_REPLAY = Boolean.getBoolean("DumpReplay"); private static final boolean GC_AFTER = Boolean.getBoolean("GCAfter"); private static final boolean SHUFFLE_TESTS = Boolean.parseBoolean(System.getProperty("ShuffleTests", "true")); @@ -312,11 +314,13 @@ private void addBaseTests() { * A test is excluded from execution if: * - -DTest does not list the method * - -DExclude lists the method + * - The method specifies a (temporary) @Skip annotation. */ private boolean shouldExcludeTest(Method testMethod) { String testName = testMethod.getName(); return isNotOnTestList(testName) || - isOnExcludeList(testName); + isOnExcludeList(testName) || + shouldSkip(testMethod); } private boolean isNotOnTestList(String testName) { @@ -327,6 +331,14 @@ private boolean isOnExcludeList(String testName) { return excludeList.contains(testName); } + private boolean shouldSkip(Method method) { + return !IGNORE_SKIP && hasSkipAnnotation(method); + } + + private boolean hasSkipAnnotation(Method method) { + return getAnnotation(method, Skip.class) != null; + } + /** * Generate replay compilation files. */ @@ -560,6 +572,8 @@ private void setupDeclaredTests() { "Found @IR annotation on non-@Test method " + m); TestFormat.checkNoThrow(!m.isAnnotationPresent(Warmup.class) || getAnnotation(m, Run.class) != null, "Found @Warmup annotation on non-@Test or non-@Run method " + m); + TestFormat.checkNoThrow(!m.isAnnotationPresent(Skip.class) , + "Found @Skip annotation on non-@Test method " + m); } } catch (TestFormatException e) { // Failure logged. Continue and report later. @@ -582,16 +596,22 @@ private void addDeclaredTest(Method m) { // Don't inline test methods by default. Do not apply this when -DIgnoreCompilerControls=true is set. WHITE_BOX.testSetDontInlineMethod(m, true); } + CompLevel compLevel = restrictCompLevel(testAnno.compLevel()); if (FLIP_C1_C2) { compLevel = restrictCompLevel(compLevel.flipCompLevel()); } + if (EXCLUDE_RANDOM) { compLevel = compLevel.excludeCompilationRandomly(m); } + boolean allowNotCompilable = testAnno.allowNotCompilable() || ALLOW_METHOD_NOT_COMPILABLE; ArgumentsProvider argumentsProvider = ArgumentsProviderBuilder.build(m, setupMethodMap); - DeclaredTest test = new DeclaredTest(m, argumentsProvider, compLevel, warmupIterations, allowNotCompilable); + Skip skip = getAnnotation(m, Skip.class); + boolean hasSkip = skip != null; + DeclaredTest test = new DeclaredTest(m, argumentsProvider, compLevel, warmupIterations, + hasSkip, allowNotCompilable); declaredTests.put(m, test); testMethodMap.put(m.getName(), m); } @@ -762,9 +782,13 @@ private void addCustomRunTest(Method m, Run runAnno) { // Logged, continue. } } - if (tests.isEmpty()) { - return; // There was a format violation. Return. + + if (tests.size() != runAnno.test().length) { + // At least one test is invalid. Return and report the already recorded format violation(s) later. + return; } + + checkNoneOrAllAssociatedTestsSkipped(m, tests); dontCompileAndDontInlineMethod(m); CustomRunTest customRunTest = new CustomRunTest(m, getAnnotation(m, Warmup.class), runAnno, tests, shouldExcludeTest); allTests.add(customRunTest); @@ -809,6 +833,32 @@ private void checkRunMethod(Method m, Run runAnno) { "Cannot set @Warmup at @Run method " + m + " when used with RunMode.STANDALONE. The @Run method is only invoked once."); } + private void checkNoneOrAllAssociatedTestsSkipped(Method runMethod, List tests) { + List testMethodsWithoutSkip = testMethodsWithoutSkipAnnotation(tests); + if (testMethodsWithoutSkip.isEmpty() || + testMethodsWithoutSkip.size() == tests.size()) { + // All with @Skip or none. + return; + } + + // Skipping some tests but not all is a format violation. + reportPartiallySkippingTests(runMethod, testMethodsWithoutSkip); + } + + private List testMethodsWithoutSkipAnnotation(List tests) { + return tests.stream().filter(test -> !test.hasSkipAnno()).map(DeclaredTest::getTestMethod).toList(); + } + + private void reportPartiallySkippingTests(Method runMethod, List testMethodsWithoutSkip) { + StringBuilder builder = new StringBuilder(); + builder.append("@Run-methods with multiple tests can only either skip none or all methods with @Skip:") + .append(System.lineSeparator()) + .append(" - @Run-method: ").append(runMethod); + testMethodsWithoutSkip.forEach(method -> builder.append(System.lineSeparator()) + .append(" - @Test without @Skip: ").append(method)); + TestFormat.fail(builder.toString()); + } + private static T getAnnotation(AnnotatedElement element, Class c) { T[] annos = element.getAnnotationsByType(c); TestFormat.check(annos.length < 2, element + " has duplicated annotations"); diff --git a/test/hotspot/jtreg/testlibrary_tests/ir_framework/examples/SkipTestExample.java b/test/hotspot/jtreg/testlibrary_tests/ir_framework/examples/SkipTestExample.java new file mode 100644 index 000000000000..4c8cfb266967 --- /dev/null +++ b/test/hotspot/jtreg/testlibrary_tests/ir_framework/examples/SkipTestExample.java @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package ir_framework.examples; + +import compiler.lib.ir_framework.*; +import compiler.lib.ir_framework.driver.TestVMException; +import compiler.lib.ir_framework.shared.TestFormatException; + +import jdk.test.lib.Asserts; + +/* + * @test + * @summary Example to show how to skip individual IR tests over commenting them out or problemlisting. + * @library /test/lib / + * @run driver ${test.main.class} + */ + +/** + * When a general jtreg test is failing, there are several options to mitigate that problem including uncommenting the + * problematic part of the test out or just problemlisting. + * + *

    + * The IR Framework introduces another way: Use {@link Skip @Skip} at a {@link Test @Test}-annotated method to exclude + * this test from being executed. + * + *

    + * The IR Framework provides the following additional flag to run those skipped tests on the fly without requiring + * any additional test or IR Framework changes (also see README.md): + *

      + *
    • -DIgnoreSkip=true: The IR Framework executes all tests - including {@link Skip @Skip}-annotated + * tests.

    • + *
    + * + *

    + * Below you find some examples. + * + * @see Skip + */ +public class SkipTestExample { + public static void main(String[] args) { + header("SkipTestExample Tests:"); + TestFramework.run(); + try { + header("SkipTestExample Tests - -DIgnoreSkip=true:"); + TestFramework.runWithFlags("-DIgnoreSkip=true"); + Asserts.fail("Should report TestVMException"); + } catch (TestVMException e) { + // Expected, uncomment to see the output: + //throw e; + } + + try { + header("Violation Tests:"); + TestFramework.run(Violation.class); + Asserts.fail("Should report TestFormatException"); + } catch (TestFormatException e) { + // Expected, uncomment to see the output: + //throw e; + } + } + + private static void header(String header) { + System.out.println(header); + System.out.println("=".repeat(header.length())); + } + + // Skipped test that would otherwise fail. + // - -DIgnoreSkip=true: This test will fail and is reported as such. + @Test + @Skip + public void testWithSkip() { + throw new RuntimeException("skipped"); + } + + @Test + @Skip + public void testForRunner() { + } + + // testWithSkip2() is skipped and thus the IR Framework is not executing this @Run method. + // - -DIgnoreSkip=true: This test will fail and is reported as such. + @Run(test = "testForRunner") + public void runWithSkip() { + testForRunner(); + throw new RuntimeException("skipped"); + } +} + +class Violation { + @Test + @Skip + public void testSkip() {} + + @Test + public void testNotSkipped() {} + + // Even though not all tests that are called from this @Run method are skipped, there is no way to modify the + // code to call testSkip() but avoid calling testNotSkipped(). Therefore, the entire @Run method should be excluded + // which in turn could be undesirable. Therefore, the IR Framework rejects such partially skipped tests with a + // TestFormatException by requiring either all @Test methods to be skipped or none. + @Run(test = {"testSkip", + "testNotSkipped"}) + public void runMultipleTests() { + testSkip(); + testNotSkipped(); + } +} diff --git a/test/hotspot/jtreg/testlibrary_tests/ir_framework/tests/TestBadFormat.java b/test/hotspot/jtreg/testlibrary_tests/ir_framework/tests/TestBadFormat.java index effb18611962..3df645be17ee 100644 --- a/test/hotspot/jtreg/testlibrary_tests/ir_framework/tests/TestBadFormat.java +++ b/test/hotspot/jtreg/testlibrary_tests/ir_framework/tests/TestBadFormat.java @@ -58,6 +58,7 @@ public static void main(String[] args) { expectTestFormatException(BadRunTests.class); expectTestFormatException(BadSetupTest.class); expectTestFormatException(BadCheckTest.class); + expectTestFormatException(BadSkip.class); expectTestFormatException(BadIRAnnotationBeforeFlagVM.class); expectTestFormatException(BadIRAnnotations.class); expectTestFormatException(BadIRAnnotationsAfterTestVM.class); @@ -777,6 +778,43 @@ public void testInvalidRunWithArgAnnotation() {} public void invalidRunWithArgAnnotation(TestInfo info) {} } +class BadSkip { + @NoFail + @Test + public void test() {} + + @NoFail + @Test + public void test2() {} + + @Skip + public void noTest() {} + + @Run(test = "test") + @Skip + public void noTest2() {} + + @Check(test = "test2") + @Skip + public void noTest3() {} + + @Setup + @Skip + public void noTest4() {} + + @NoFail + @Test + @Skip + public static void testSkipOnlyOneTestWithRunMultiple1() {} + + @FailCount(0) // Combined with runTestSkipOnlyOneTestWithRunMultiple() below + @Test + public static void testSkipOnlyOneTestWithRunMultiple2() {} + + @Run(test = {"testSkipOnlyOneTestWithRunMultiple1", + "testSkipOnlyOneTestWithRunMultiple2"}) + public static void runTestSkipOnlyOneTestWithRunMultiple() {} +} class BadIRAnnotationBeforeFlagVM { diff --git a/test/hotspot/jtreg/testlibrary_tests/ir_framework/tests/TestNoMainExecution.java b/test/hotspot/jtreg/testlibrary_tests/ir_framework/tests/TestNoMainExecution.java index 8e9410cdb8d9..156d8d82184d 100644 --- a/test/hotspot/jtreg/testlibrary_tests/ir_framework/tests/TestNoMainExecution.java +++ b/test/hotspot/jtreg/testlibrary_tests/ir_framework/tests/TestNoMainExecution.java @@ -30,7 +30,7 @@ * @run driver ${test.main.class} */ -package testlibrary_tests.ir_framework.tests; +package ir_framework.tests; import compiler.lib.ir_framework.*; import jdk.test.lib.Asserts; diff --git a/test/hotspot/jtreg/testlibrary_tests/ir_framework/tests/TestSkip.java b/test/hotspot/jtreg/testlibrary_tests/ir_framework/tests/TestSkip.java new file mode 100644 index 000000000000..509f899bd694 --- /dev/null +++ b/test/hotspot/jtreg/testlibrary_tests/ir_framework/tests/TestSkip.java @@ -0,0 +1,303 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package ir_framework.tests; + +import compiler.lib.ir_framework.*; +import compiler.lib.ir_framework.driver.TestVMException; +import compiler.lib.ir_framework.driver.irmatching.IRViolationException; +import compiler.lib.ir_framework.shared.TestFormatException; +import jdk.internal.misc.Unsafe; +import jdk.test.lib.Asserts; + +/* + * @test + * @requires vm.debug == true & vm.compMode != "Xint" & vm.compiler1.enabled & vm.compiler2.enabled & vm.flagless + * @summary Test different custom run tests. + * @modules java.base/jdk.internal.misc:+open + * @library /test/lib /testlibrary_tests / + * @run driver ${test.main.class} + */ + +public class TestSkip { + public static void main(String[] args) { + runGood(); + runMix(); + runViolation(); + runVMCrash(); + runIRVerification(); + } + + private static void runGood() { + TestFramework.run(SkipBasics.class); + } + + private static void runMix() { + try { + TestFramework.run(Mix.class); + Asserts.fail("Should have thrown"); + } catch (TestVMException e) { + Asserts.assertTrue(e.getExceptionInfo().contains("testFailNoSkip() no @Skip")); + } + + TestFramework testFramework = new TestFramework(Mix.class); + testFramework.addFlags("-DIgnoreSkip=true"); + try { + testFramework.start(); + Asserts.fail("Should have thrown"); + } catch (TestVMException e) { + Asserts.assertTrue(e.getExceptionInfo().contains("testFailNoSkip() no @Skip")); + Asserts.assertTrue(e.getExceptionInfo().contains("testFailWithSkip() with @Skip")); + Asserts.assertTrue(e.getExceptionInfo().contains("testWithSingleTest() with @Skip")); + Asserts.assertTrue(e.getExceptionInfo().contains("testWithMultipleTestsFail() with @Skip")); + } + } + + private static void runViolation() { + runViolation(new TestFramework(Violation.class)); + runViolation(new TestFramework(Violation.class).addFlags("-DIgnoreSkip=true")); + } + + private static void runViolation(TestFramework testFramework) { + try { + testFramework.start(); + Asserts.fail("Should throw"); + } catch (TestFormatException e) { + String message = e.getMessage(); + Asserts.assertTrue(message.contains("Violations (2)")); + Asserts.assertTrue(message.contains("@Run-methods with multiple tests can only either skip none or all " + + "methods with @Skip")); + Asserts.assertTrue(message.contains("run1()")); + Asserts.assertTrue(message.contains("two()")); + Asserts.assertTrue(message.contains("run2()")); + Asserts.assertTrue(message.contains("missing")); + } + } + + private static void runVMCrash() { + new TestFramework(VMCrash.class) + .addFlags("--add-exports", "java.base/jdk.internal.misc=ALL-UNNAMED") + .start(); + + try { + new TestFramework(VMCrash.class) + .addFlags("--add-exports", "java.base/jdk.internal.misc=ALL-UNNAMED", "-DIgnoreSkip=true") + .start(); + Asserts.fail("Should have thrown"); + } catch (TestVMException e) { + // Expected. + Asserts.assertTrue(e.getExceptionInfo().contains("A fatal error has been detected")); + } + } + + private static void runIRVerification() { + TestFramework.run(IRVerification.class); + + try { + new TestFramework(IRVerification.class) + .addFlags("-DIgnoreSkip=true") + .start(); + Asserts.fail("Should have thrown"); + } catch (IRViolationException e) { + // Expected. + String message = e.getExceptionInfo(); + Asserts.assertTrue(message.contains("Failed IR Rules (3) of Methods (3)")); + Asserts.assertTrue(message.contains("Method \"ir_framework.tests." + + "TestSkip$IRVerification::testFailingIrCustomRunTest")); + Asserts.assertTrue(message.contains("Method \"ir_framework.tests." + + "TestSkip$IRVerification::testFailingIrCheckedTest")); + Asserts.assertTrue(message.contains("Method \"ir_framework.tests." + + "TestSkip$IRVerification::testFailingIrBaseTest")); + } + } + + static class SkipBasics { + @Skip + @Test + public static void testSkip() { + throw new RuntimeException("testSkip() with @Skip"); + } + + @Skip + @Test + public static void testWithSingleTest() { + throw new RuntimeException("testSkipWithRun() with @Skip"); + } + + @Run(test = "testWithSingleTest") + public static void runWithSingleTest() { + try { + testWithSingleTest(); + } catch (RuntimeException e) { + throw new RuntimeException("From @Run", e); + } + } + + @Skip + @Test + public static void testWithMultipleTestsAllSkipFail1() { + throw new RuntimeException("testWithMultipleTestsAllSkipFail1() with @Skip"); + } + + @Skip + @Test + public static void testWithMultipleTestsAllSkipFail2() { + throw new RuntimeException("testWithMultipleTestsAllSkipFail2() with @Skip"); + } + + @Run(test = {"testWithMultipleTestsAllSkipFail1", + "testWithMultipleTestsAllSkipFail2"}) + public static void runWithMultipleTestsAllSkipFail() { + testWithMultipleTestsAllSkipFail1(); + testWithMultipleTestsAllSkipFail2(); + } + } + + static class IRVerification { + static int iFld; + + @Skip + @Test + @IR(failOn = IRNode.STORE_I) + public static void testFailingIrBaseTest() { + iFld = 34; + } + + @Skip + @Test + @IR(failOn = IRNode.STORE_I) + public static void testFailingIrCheckedTest() { + iFld = 34; + } + + @Check(test = "testFailingIrCheckedTest") + public static void checkTest() {} + + @Skip + @Test + @IR(failOn = IRNode.STORE_I) + public static void testFailingIrCustomRunTest() { + iFld = 34; + } + + @Run(test = "testFailingIrCustomRunTest") + public static void runCustomRunTest() { + testFailingIrCustomRunTest(); + } + } + + static class Mix { + @Skip + @Test + public static void testFailWithSkip() { + throw new RuntimeException("testFailWithSkip() with @Skip"); + } + + @Test + public static void testFailNoSkip() { + throw new RuntimeException("testFailNoSkip() no @Skip"); + } + + @Test + public static void testSuccessNoSkip() { + } + + @Test + @Skip + public static void testSuccessWithSkip() { + } + + @Skip + @Test + public static void testWithSingleTest() { + throw new RuntimeException("testWithSingleTest() with @Skip"); + } + + @Run(test = "testWithSingleTest") + public static void runWithSingleTest() { + testWithSingleTest(); + } + + // If one is skipped, + @Skip + @Test + public static void testWithMultipleTestsFail() { + throw new RuntimeException("testWithMultipleTestsFail() with @Skip"); + } + + @Skip + @Test + public static void testWithMultipleTestsSuccess() { + } + + @Run(test = {"testWithMultipleTestsFail", + "testWithMultipleTestsSuccess"}) + public static void runWithMultipleTestsOneFail() { + testWithMultipleTestsFail(); + testWithMultipleTestsSuccess(); + } + } + + static class Violation { + Violation() { + throw new RuntimeException("should not be constructed"); + } + + @Test + @Skip + void one() {} + + // Forbidden, should also be reported with -DIgnoreSkip=true + @Test + void two() {} + + @Run(test = {"one", "two"}) + void run1() { + one(); + two(); + } + + @Test + @Skip + void skipped1() {} + + @Test + @Skip + void skipped2() {} + + @Run(test = {"skipped1", "skipped2", "missing"}) + void run2() { + one(); + two(); + } + } + + static class VMCrash { + @Test + @Skip + public void testCrashVM() { + Unsafe unsafe = Unsafe.getUnsafe(); + unsafe.putAddress(0, 0); // Crash + } + } +} From c110aa0a8f4a39684c28b48cc9a0a272ef72c28c Mon Sep 17 00:00:00 2001 From: Rudraroop Ray Date: Thu, 15 Oct 2026 13:10:46 +0000 Subject: [PATCH 057/169] 8393745: Shenandoah: Purge SHENANDOAH_CENSUS_NOISE support Reviewed-by: shade, wkemper --- .../shenandoahGenerationalHeuristics.cpp | 1 + .../heuristics/shenandoahGlobalHeuristics.cpp | 1 + .../gc/shenandoah/shenandoahAgeCensus.cpp | 86 +---------------- .../gc/shenandoah/shenandoahAgeCensus.hpp | 96 +------------------ .../shenandoah/shenandoahAgeCensus.inline.hpp | 26 +---- .../gc/shenandoah/shenandoahGeneration.cpp | 1 + .../shenandoahGenerationalFullGC.cpp | 1 + .../gc/shenandoah/shenandoahHeapRegion.cpp | 4 - .../gc/shenandoah/shenandoahHeapRegion.hpp | 8 +- .../gc/shenandoah/shenandoahMark.inline.hpp | 3 +- .../shenandoah/test_shenandoahAgeCensus.cpp | 3 +- 11 files changed, 12 insertions(+), 218 deletions(-) diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGenerationalHeuristics.cpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGenerationalHeuristics.cpp index 31f32836ceb3..c941d024e2ff 100644 --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGenerationalHeuristics.cpp +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGenerationalHeuristics.cpp @@ -24,6 +24,7 @@ */ #include "gc/shenandoah/heuristics/shenandoahGenerationalHeuristics.hpp" +#include "gc/shenandoah/shenandoahAgeCensus.hpp" #include "gc/shenandoah/shenandoahAllocRate.inline.hpp" #include "gc/shenandoah/shenandoahCollectionSet.hpp" #include "gc/shenandoah/shenandoahCollectorPolicy.hpp" diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGlobalHeuristics.cpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGlobalHeuristics.cpp index 5c07fcb6b59f..21ac29895538 100644 --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGlobalHeuristics.cpp +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGlobalHeuristics.cpp @@ -24,6 +24,7 @@ */ #include "gc/shenandoah/heuristics/shenandoahGlobalHeuristics.hpp" +#include "gc/shenandoah/shenandoahAgeCensus.hpp" #include "gc/shenandoah/shenandoahAsserts.hpp" #include "gc/shenandoah/shenandoahGenerationalHeap.inline.hpp" #include "gc/shenandoah/shenandoahGlobalGeneration.hpp" diff --git a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp index 3c23dd0bf233..d90100953920 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp @@ -44,24 +44,17 @@ ShenandoahAgeCensus::ShenandoahAgeCensus(uint max_workers) } _global_age_tables = NEW_C_HEAP_ARRAY(AgeTable*, MAX_SNAPSHOTS, mtGC); - CENSUS_NOISE(_global_noise = NEW_C_HEAP_ARRAY(ShenandoahNoiseStats, MAX_SNAPSHOTS, mtGC);) _tenuring_threshold = NEW_C_HEAP_ARRAY(uint, MAX_SNAPSHOTS, mtGC); - CENSUS_NOISE(_skipped = 0); - NOT_PRODUCT(_counted = 0); - NOT_PRODUCT(_total = 0); for (int i = 0; i < MAX_SNAPSHOTS; i++) { // Note that we don't now get perfdata from age_table _global_age_tables[i] = new AgeTable(false); - CENSUS_NOISE(_global_noise[i].clear();) // Sentinel value _tenuring_threshold[i] = MAX_COHORTS; } _local_age_tables = NEW_C_HEAP_ARRAY(AgeTable*, _max_workers, mtGC); - CENSUS_NOISE(_local_noise = NEW_C_HEAP_ARRAY(ShenandoahNoiseStats, max_workers, mtGC);) for (uint i = 0; i < _max_workers; i++) { _local_age_tables[i] = new AgeTable(false); - CENSUS_NOISE(_local_noise[i].clear();) } _epoch = MAX_SNAPSHOTS - 1; // see prepare_for_census_update() } @@ -72,34 +65,14 @@ ShenandoahAgeCensus::~ShenandoahAgeCensus() { } FREE_C_HEAP_ARRAY(_global_age_tables); FREE_C_HEAP_ARRAY(_tenuring_threshold); - CENSUS_NOISE(FREE_C_HEAP_ARRAY(_global_noise)); if (_local_age_tables) { for (uint i = 0; i < _max_workers; i++) { delete _local_age_tables[i]; } FREE_C_HEAP_ARRAY(_local_age_tables); - CENSUS_NOISE(FREE_C_HEAP_ARRAY(_local_noise)); } } -#ifdef SHENANDOAH_CENSUS_NOISE -void ShenandoahAgeCensus::add_skipped(size_t size, uint worker_id) { - _local_noise[worker_id].skipped += size; -} - -void ShenandoahAgeCensus::add_aged(size_t size, uint worker_id) { - _local_noise[worker_id].aged += size; -} - -void ShenandoahAgeCensus::add_clamped(size_t size, uint worker_id) { - _local_noise[worker_id].clamped += size; -} - -void ShenandoahAgeCensus::add_young(size_t size, uint worker_id) { - _local_noise[worker_id].young += size; -} -#endif // SHENANDOAH_CENSUS_NOISE - // Prepare for a new census update, by clearing appropriate global slots. void ShenandoahAgeCensus::prepare_for_census_update() { assert(_epoch < MAX_SNAPSHOTS, "Out of bounds"); @@ -107,7 +80,6 @@ void ShenandoahAgeCensus::prepare_for_census_update() { _epoch=0; } _global_age_tables[_epoch]->clear(); - CENSUS_NOISE(_global_noise[_epoch].clear();) } // Update the census data from appropriate sources, @@ -115,7 +87,6 @@ void ShenandoahAgeCensus::prepare_for_census_update() { void ShenandoahAgeCensus::update_census(size_t age0_pop) { prepare_for_census_update(); assert(_global_age_tables[_epoch]->is_clear(), "Dirty decks"); - CENSUS_NOISE(assert(_global_noise[_epoch].is_clear(), "Dirty decks");) // Seed cohort 0 with population that may have been missed during // regular census. @@ -124,19 +95,11 @@ void ShenandoahAgeCensus::update_census(size_t age0_pop) { // Merge data from local age tables into the global age table for the epoch, // clearing the local tables. for (uint i = 0; i < _max_workers; i++) { - // age stats _global_age_tables[_epoch]->merge(_local_age_tables[i]); _local_age_tables[i]->clear(); // clear for next census - // Merge noise stats - CENSUS_NOISE(_global_noise[_epoch].merge(_local_noise[i]);) - CENSUS_NOISE(_local_noise[i].clear();) } update_tenuring_threshold(); - - // used for checking reasonableness of census coverage, non-product - // only. - NOT_PRODUCT(update_total();) } size_t ShenandoahAgeCensus::get_tenurable_bytes(const uint tenuring_threshold) const { @@ -155,7 +118,6 @@ void ShenandoahAgeCensus::reset_global() { assert(_epoch < MAX_SNAPSHOTS, "Out of bounds"); for (uint i = 0; i < MAX_SNAPSHOTS; i++) { _global_age_tables[i]->clear(); - CENSUS_NOISE(_global_noise[i].clear();) } _epoch = MAX_SNAPSHOTS; assert(_epoch < MAX_SNAPSHOTS, "Error"); @@ -165,7 +127,6 @@ void ShenandoahAgeCensus::reset_global() { void ShenandoahAgeCensus::reset_local() { for (uint i = 0; i < _max_workers; i++) { _local_age_tables[i]->clear(); - CENSUS_NOISE(_local_noise[i].clear();) } } @@ -174,9 +135,7 @@ void ShenandoahAgeCensus::reset_local() { bool ShenandoahAgeCensus::is_clear_global() { assert(_epoch < MAX_SNAPSHOTS, "Out of bounds"); for (uint i = 0; i < MAX_SNAPSHOTS; i++) { - bool clear = _global_age_tables[i]->is_clear(); - CENSUS_NOISE(clear |= _global_noise[i].is_clear();) - if (!clear) { + if (!_global_age_tables[i]->is_clear()) { return false; } } @@ -186,35 +145,12 @@ bool ShenandoahAgeCensus::is_clear_global() { // Is local census information clear? bool ShenandoahAgeCensus::is_clear_local() { for (uint i = 0; i < _max_workers; i++) { - bool clear = _local_age_tables[i]->is_clear(); - CENSUS_NOISE(clear |= _local_noise[i].is_clear();) - if (!clear) { + if (!_local_age_tables[i]->is_clear()) { return false; } } return true; } - -size_t ShenandoahAgeCensus::get_all_ages(uint snap) { - assert(snap < MAX_SNAPSHOTS, "Out of bounds"); - size_t pop = 0; - const AgeTable* pv = _global_age_tables[snap]; - for (uint i = 0; i < MAX_COHORTS; i++) { - pop += pv->sizes[i]; - } - return pop; -} - -size_t ShenandoahAgeCensus::get_skipped(uint snap) { - assert(snap < MAX_SNAPSHOTS, "Out of bounds"); - return _global_noise[snap].skipped; -} - -void ShenandoahAgeCensus::update_total() { - _counted = get_all_ages(_epoch); - _skipped = get_skipped(_epoch); - _total = _counted + _skipped; -} #endif // !PRODUCT void ShenandoahAgeCensus::update_tenuring_threshold() { @@ -332,7 +268,6 @@ void ShenandoahAgeCensus::print() { const uint tt = tenuring_threshold(); - size_t total= 0; for (uint i = 1; i < MAX_COHORTS; i++) { const size_t prev_pop = prev_pv->sizes[i-1]; // (i-1) OK because i >= 1 const size_t cur_pop = cur_pv->sizes[i]; @@ -342,26 +277,9 @@ void ShenandoahAgeCensus::print() { ls.print_cr(" - age %3u: prev %10zu bytes, curr %10zu bytes, mortality %.2f ", i, prev_pop * oopSize, cur_pop * oopSize, mr); } - total += cur_pop; if (i == tt) { // Underline the cohort for tenuring threshold (if < MAX_COHORTS) ls.print_cr("----------------------------------------------------------------------------"); } } - CENSUS_NOISE(_global_noise[cur_epoch].print(ls, total);) -} - -#ifdef SHENANDOAH_CENSUS_NOISE -void ShenandoahNoiseStats::print(LogStream& ls, const size_t total) { - if (total > 0) { - const float f_skipped = (float)skipped/(float)total; - const float f_aged = (float)aged/(float)total; - const float f_clamped = (float)clamped/(float)total; - const float f_young = (float)young/(float)total; - ls.print_cr("Skipped: %10zu (%.2f), R-Aged: %10zu (%.2f), " - "Clamped: %10zu (%.2f), R-Young: %10zu (%.2f)", - skipped*oopSize, f_skipped, aged*oopSize, f_aged, - clamped*oopSize, f_clamped, young*oopSize, f_young); - } } -#endif // SHENANDOAH_CENSUS_NOISE diff --git a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.hpp b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.hpp index 2d79eb559609..6577715f44d3 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.hpp @@ -27,55 +27,6 @@ #include "gc/shared/ageTable.hpp" -#ifndef PRODUCT -// Enable noise instrumentation -#define SHENANDOAH_CENSUS_NOISE 1 -#endif // PRODUCT - -#ifdef SHENANDOAH_CENSUS_NOISE - -#define CENSUS_NOISE(x) x -#define NO_CENSUS_NOISE(x) - -class LogStream; - -struct ShenandoahNoiseStats { - size_t skipped; // Volume of objects skipped - size_t aged; // Volume of objects from aged regions - size_t clamped; // Volume of objects whose ages were clamped - size_t young; // Volume of (rejuvenated) objects of retrograde age - - ShenandoahNoiseStats() { - clear(); - } - - void clear() { - skipped = 0; - aged = 0; - clamped = 0; - young = 0; - } - -#ifndef PRODUCT - bool is_clear() { - return (skipped + aged + clamped + young) == 0; - } -#endif // !PRODUCT - - void merge(ShenandoahNoiseStats& other) { - skipped += other.skipped; - aged += other.aged; - clamped += other.clamped; - young += other.young; - } - - void print(LogStream& ls, size_t total); -}; -#else // SHENANDOAH_CENSUS_NOISE -#define CENSUS_NOISE(x) -#define NO_CENSUS_NOISE(x) x -#endif // SHENANDOAH_CENSUS_NOISE - // A class for tracking a sequence of cohort population vectors (or, // interchangeably, age tables) for up to C=MAX_COHORTS age cohorts, where a cohort // represents the set of objects allocated during a specific inter-GC epoch. @@ -102,19 +53,6 @@ class ShenandoahAgeCensus: public CHeapObj { AgeTable** _global_age_tables; // Global age tables used for adapting tenuring threshold, one per snapshot AgeTable** _local_age_tables; // Local scratch age tables to track object ages, one per worker -#ifdef SHENANDOAH_CENSUS_NOISE - ShenandoahNoiseStats* _global_noise; // Noise stats, one per snapshot - ShenandoahNoiseStats* _local_noise; // Local scratch table for noise stats, one per worker - - size_t _skipped; // net size of objects encountered, but skipped during census, - // because their age was indeterminate -#endif // SHENANDOAH_CENSUS_NOISE - -#ifndef PRODUCT - size_t _counted; // net size of objects counted in census - size_t _total; // net size of objects encountered (counted or skipped) in census -#endif - uint _epoch; // Current epoch (modulo max age) uint* _tenuring_threshold; // An array of the last N tenuring threshold values we // computed. @@ -156,20 +94,6 @@ class ShenandoahAgeCensus: public CHeapObj { // to force is_tenurable() to be true for every age during WB.fullGC tests. void set_always_tenure(bool always_tenure) { _always_tenure = always_tenure; } -#ifndef PRODUCT - // Return the sum of size of objects of all ages recorded in the - // census at snapshot indexed by snap. - size_t get_all_ages(uint snap); - - // Return the size of all objects that were encountered, but skipped, - // during the census, because their age was indeterminate. - size_t get_skipped(uint snap); - - // Update the total size of objects counted or skipped at the census for - // the most recent epoch. - void update_total(); -#endif // !PRODUCT - public: enum { MAX_COHORTS = AgeTable::table_size, // = markWord::max_age + 1 @@ -204,20 +128,8 @@ class ShenandoahAgeCensus: public CHeapObj { bool is_always_tenure() const { return _always_tenure; } // Update the local age table for worker_id by size for - // given obj_age, region_age, and region_youth - CENSUS_NOISE(inline void add(uint obj_age, uint region_age, uint region_youth, size_t size, uint worker_id);) - NO_CENSUS_NOISE(inline void add(uint obj_age, uint region_age, size_t size, uint worker_id);) - -#ifdef SHENANDOAH_CENSUS_NOISE - // Update the local skip table for worker_id by size - void add_skipped(size_t size, uint worker_id); - // Update the local aged region volume table for worker_id by size - void add_aged(size_t size, uint worker_id); - // Update the local clamped object volume table for worker_id by size - void add_clamped(size_t size, uint worker_id); - // Update the local (rejuvenated) object volume (retrograde age) for worker_id by size - void add_young(size_t size, uint worker_id); -#endif // SHENANDOAH_CENSUS_NOISE + // given obj_age and region_age + inline void add(uint obj_age, uint region_age, size_t size, uint worker_id); // Update the census data, and compute the new tenuring threshold. // This method should be called at the end of each marking cycle to update @@ -245,10 +157,6 @@ class ShenandoahAgeCensus: public CHeapObj { // Check whether census information is clear bool is_clear_global(); bool is_clear_local(); - - // Return the net size of objects encountered (counted or skipped) in census - // at most recent epoch. - size_t get_total() const { return _total; } #endif // !PRODUCT // Print the age census information diff --git a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.inline.hpp b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.inline.hpp index 299b9a3f8b7a..704915375fa3 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.inline.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.inline.hpp @@ -27,35 +27,11 @@ #include "gc/shenandoah/shenandoahAgeCensus.hpp" -CENSUS_NOISE(void ShenandoahAgeCensus::add(uint obj_age, uint region_age, uint region_youth, size_t size, uint worker_id) {) -NO_CENSUS_NOISE(void ShenandoahAgeCensus::add(uint obj_age, uint region_age, size_t size, uint worker_id) {) +void ShenandoahAgeCensus::add(uint obj_age, uint region_age, size_t size, uint worker_id) { if (obj_age <= markWord::max_age) { assert(obj_age < MAX_COHORTS && region_age < MAX_COHORTS, "Should have been tenured"); -#ifdef SHENANDOAH_CENSUS_NOISE - // Region ageing is stochastic and non-monotonic; this vitiates mortality - // demographics in ways that might defeat our algorithms. Marking may be a - // time when we might be able to correct this, but we currently do not do - // this. Like skipped statistics further below, we want to track the - // impact of this noise to see if this may be worthwhile. JDK-. - uint age = obj_age; - if (region_age > 0) { - add_aged(size, worker_id); // this tracking is coarse for now - age += region_age; - if (age >= MAX_COHORTS) { - age = (uint)(MAX_COHORTS - 1); // clamp - add_clamped(size, worker_id); - } - } - if (region_youth > 0) { // track object volume with retrograde age - add_young(size, worker_id); - } -#else // SHENANDOAH_CENSUS_NOISE uint age = MIN2(obj_age + region_age, (uint)(MAX_COHORTS - 1)); // clamp -#endif // SHENANDOAH_CENSUS_NOISE get_local_age_table(worker_id)->add(age, size); - } else { - // update skipped statistics - CENSUS_NOISE(add_skipped(size, worker_id);) } } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp index fb99825bef93..6b1d94d64348 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp @@ -25,6 +25,7 @@ #include "gc/shenandoah/heuristics/shenandoahHeuristics.hpp" #include "gc/shenandoah/shenandoahAffiliation.hpp" +#include "gc/shenandoah/shenandoahAgeCensus.hpp" #include "gc/shenandoah/shenandoahCollectorPolicy.hpp" #include "gc/shenandoah/shenandoahFreeSet.hpp" #include "gc/shenandoah/shenandoahGeneration.hpp" diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalFullGC.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalFullGC.cpp index 46230c9654f4..4d178f4b0494 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalFullGC.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalFullGC.cpp @@ -26,6 +26,7 @@ #include "gc/shared/fullGCForwarding.inline.hpp" #include "gc/shared/preservedMarks.inline.hpp" #include "gc/shenandoah/shenandoahAffiliation.hpp" +#include "gc/shenandoah/shenandoahAgeCensus.hpp" #include "gc/shenandoah/shenandoahForwarding.inline.hpp" #include "gc/shenandoah/shenandoahGeneration.hpp" #include "gc/shenandoah/shenandoahGenerationalFullGC.hpp" diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.cpp b/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.cpp index 3aac7f21efaf..289bf95ecf57 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.cpp @@ -74,9 +74,6 @@ ShenandoahHeapRegion::ShenandoahHeapRegion(HeapWord* start, size_t index, bool c _mixed_candidate_garbage_words(0), _update_watermark(start), _age(0), -#ifdef SHENANDOAH_CENSUS_NOISE - _youth(0), -#endif // SHENANDOAH_CENSUS_NOISE _needs_bitmap_reset(false) { @@ -320,7 +317,6 @@ void ShenandoahHeapRegion::make_trash_immediate() { void ShenandoahHeapRegion::make_empty() { reset_age(); - CENSUS_NOISE(clear_youth();) switch (state()) { case _trash: set_state(_empty_committed); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.hpp b/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.hpp index 86e3afae81e3..0a4c90575d09 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.hpp @@ -29,7 +29,6 @@ #include "gc/shared/gc_globals.hpp" #include "gc/shared/spaceDecorator.hpp" #include "gc/shenandoah/shenandoahAffiliation.hpp" -#include "gc/shenandoah/shenandoahAgeCensus.hpp" #include "gc/shenandoah/shenandoahAllocRequest.hpp" #include "gc/shenandoah/shenandoahAsserts.hpp" #include "gc/shenandoah/shenandoahHeap.hpp" @@ -271,7 +270,6 @@ class ShenandoahHeapRegion { uint _age; bool _promoted_in_place; - CENSUS_NOISE(uint _youth;) // tracks epochs of retrograde ageing (rejuvenation) ShenandoahSharedFlag _recycling; // Used to indicate that the region is being recycled; see try_recycle*(). @@ -505,9 +503,8 @@ class ShenandoahHeapRegion { void set_affiliation(ShenandoahAffiliation new_affiliation); - // Region ageing and rejuvenation + // Region ageing uint age() const { return _age; } - CENSUS_NOISE(uint youth() const { return _youth; }) void increment_age() { const uint max_age = markWord::max_age; @@ -519,12 +516,9 @@ class ShenandoahHeapRegion { void reset_age() { assert(get_top_before_promote() == nullptr, "Cannot reset age on region (%zu) scheduled for promotion", index()); - CENSUS_NOISE(_youth += _age;) _age = 0; } - CENSUS_NOISE(void clear_youth() { _youth = 0; }) - inline bool need_bitmap_reset() const { return _needs_bitmap_reset; } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahMark.inline.hpp b/src/hotspot/share/gc/shenandoah/shenandoahMark.inline.hpp index 7a3a5e89b11d..3df384dcb243 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahMark.inline.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahMark.inline.hpp @@ -163,8 +163,7 @@ void ShenandoahMark::count_liveness(ShenandoahLiveData* live_data, oop obj, Klas assert(region->is_young(), "Only for young objects"); const uint age = ShenandoahForwarding::age(obj); ShenandoahAgeCensus* const census = ShenandoahGenerationalHeap::heap()->age_census(); - CENSUS_NOISE(census->add(age, region->age(), region->youth(), size, worker_id);) - NO_CENSUS_NOISE(census->add(age, region->age(), size, worker_id);) + census->add(age, region->age(), size, worker_id); } if (!region->is_humongous_start()) { diff --git a/test/hotspot/gtest/gc/shenandoah/test_shenandoahAgeCensus.cpp b/test/hotspot/gtest/gc/shenandoah/test_shenandoahAgeCensus.cpp index 9189e5af456e..394e7037e9ad 100644 --- a/test/hotspot/gtest/gc/shenandoah/test_shenandoahAgeCensus.cpp +++ b/test/hotspot/gtest/gc/shenandoah/test_shenandoahAgeCensus.cpp @@ -41,8 +41,7 @@ class ShenandoahAgeCensusTest : public ::testing::Test { } static void add_population(ShenandoahAgeCensus& census, const uint age, const size_t population_words) { - CENSUS_NOISE(census.add(age, 0, 0, population_words, 0)); - NO_CENSUS_NOISE(census.add(age, 0, population_words, 0)); + census.add(age, 0, population_words, 0); } void update(ShenandoahAgeCensus& census, size_t cohorts) const { From 196fc94db84275ca0d02e5e6e8ac7ea445d74c4a Mon Sep 17 00:00:00 2001 From: Hannes Greule Date: Thu, 15 Oct 2026 15:27:19 +0000 Subject: [PATCH 058/169] 8364407: [REDO] Consolidate Identity of self-inverse operations Reviewed-by: mhaessig, chagedorn --- src/hotspot/share/opto/subnode.cpp | 32 ++- src/hotspot/share/opto/subnode.hpp | 2 + .../c2/gvn/InvolutionIdentityTests.java | 241 ++++++++++++++++++ .../compiler/lib/ir_framework/IRNode.java | 10 + 4 files changed, 277 insertions(+), 8 deletions(-) create mode 100644 test/hotspot/jtreg/compiler/c2/gvn/InvolutionIdentityTests.java diff --git a/src/hotspot/share/opto/subnode.cpp b/src/hotspot/share/opto/subnode.cpp index 69be1c2b8947..18e82113f1b1 100644 --- a/src/hotspot/share/opto/subnode.cpp +++ b/src/hotspot/share/opto/subnode.cpp @@ -2222,16 +2222,32 @@ const Type* ReverseLNode::Value(PhaseGVN* phase) const { return bottom_type(); } -Node* ReverseINode::Identity(PhaseGVN* phase) { - if (in(1)->Opcode() == Op_ReverseI) { - return in(1)->in(1); +static Node* simplify_involution(PhaseGVN* phase, Node* involution) { + if (involution->in(1)->Opcode() == involution->Opcode()) { + Node* original = involution->in(1)->in(1); + const TypeInt* type = phase->type(original)->isa_int(); + // Operations on sub-int types might not be "real" involutions for values outside their type range, + // for example, a ReverseBytesS node with an input larger than short. + // Make sure not to drop potential truncations. + if (type == nullptr || involution->bottom_type()->is_int()->contains(type)) { + return original; + } } - return this; + return involution; +} + +Node* NegNode::Identity(PhaseGVN* phase) { + return simplify_involution(phase, this); +} + +Node* ReverseBytesNode::Identity(PhaseGVN* phase) { + return simplify_involution(phase, this); +} + +Node* ReverseINode::Identity(PhaseGVN* phase) { + return simplify_involution(phase, this); } Node* ReverseLNode::Identity(PhaseGVN* phase) { - if (in(1)->Opcode() == Op_ReverseL) { - return in(1)->in(1); - } - return this; + return simplify_involution(phase, this); } diff --git a/src/hotspot/share/opto/subnode.hpp b/src/hotspot/share/opto/subnode.hpp index 3b3877f35daf..f3f1f9a265d1 100644 --- a/src/hotspot/share/opto/subnode.hpp +++ b/src/hotspot/share/opto/subnode.hpp @@ -473,6 +473,7 @@ class NegNode : public Node { NegNode(Node* in1) : Node(nullptr, in1) { init_class_id(Class_Neg); } + virtual Node* Identity(PhaseGVN* phase); }; //------------------------------NegINode--------------------------------------- @@ -603,6 +604,7 @@ class ReverseBytesNode : public Node { public: ReverseBytesNode(Node* in) : Node(nullptr, in) {} virtual const Type* Value(PhaseGVN* phase) const; + virtual Node* Identity(PhaseGVN* phase); }; //-------------------------------ReverseBytesINode-------------------------------- // reverse bytes of an integer diff --git a/test/hotspot/jtreg/compiler/c2/gvn/InvolutionIdentityTests.java b/test/hotspot/jtreg/compiler/c2/gvn/InvolutionIdentityTests.java new file mode 100644 index 000000000000..bc64df850c48 --- /dev/null +++ b/test/hotspot/jtreg/compiler/c2/gvn/InvolutionIdentityTests.java @@ -0,0 +1,241 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package compiler.c2.gvn; + +import compiler.lib.generators.Generator; +import compiler.lib.generators.Generators; +import compiler.lib.generators.RestrictableGenerator; +import compiler.lib.ir_framework.DontCompile; +import compiler.lib.ir_framework.IR; +import compiler.lib.ir_framework.IRNode; +import compiler.lib.ir_framework.Run; +import compiler.lib.ir_framework.Test; +import compiler.lib.ir_framework.TestFramework; +import jdk.test.lib.Asserts; + +/* + * @test + * @bug 8350988 8364407 + * @summary Test that Identity simplifications of Involution nodes are being performed as expected. + * @key randomness + * @library /test/lib / + * @compile ReverseBytesConstantsHelper.jasm + * @run driver ${test.main.class} + */ +public class InvolutionIdentityTests { + + public static final RestrictableGenerator GEN_CHAR = Generators.G.safeRestrict(Generators.G.ints(), Character.MIN_VALUE, Character.MAX_VALUE); + public static final RestrictableGenerator GEN_SHORT = Generators.G.safeRestrict(Generators.G.ints(), Short.MIN_VALUE, Short.MAX_VALUE); + public static final RestrictableGenerator GEN_LONG = Generators.G.longs(); + public static final RestrictableGenerator GEN_INT = Generators.G.ints(); + public static final Generator GEN_FLOAT = Generators.G.floats(); + public static final Generator GEN_DOUBLE = Generators.G.doubles(); + + public static void main(String[] args) { + TestFramework.runWithFlags("-XX:CompileCommand=inline,compiler.c2.gvn.ReverseBytesConstantsHelper::*"); + } + + @Run(test = { + "testI1", "testI2", + "testL1", "testL2", + "testS1", "testS2", + "testUS1", "testUS2", + "testF1", + "testD1" + }) + public void runMethod() { + int ai = GEN_INT.next(); + + int mini = Integer.MIN_VALUE; + int maxi = Integer.MAX_VALUE; + + assertResultI(0); + assertResultI(ai); + assertResultI(mini); + assertResultI(maxi); + + long al = GEN_LONG.next(); + + long minl = Long.MIN_VALUE; + long maxl = Long.MAX_VALUE; + + assertResultL(0); + assertResultL(al); + assertResultL(minl); + assertResultL(maxl); + + short as = GEN_SHORT.next().shortValue(); + + short mins = Short.MIN_VALUE; + short maxs = Short.MAX_VALUE; + + assertResultS((short) 0); + assertResultS(as); + assertResultS(mins); + assertResultS(maxs); + + char ac = (char) GEN_CHAR.next().intValue(); + + char minc = Character.MIN_VALUE; + char maxc = Character.MAX_VALUE; + + assertResultUS((char) 0); + assertResultUS(ac); + assertResultUS(minc); + assertResultUS(maxc); + + // short and char variants but passing an int + assertResultTruncating(0); + assertResultTruncating(ai); + assertResultTruncating(mini); + assertResultTruncating(maxi); + + float af = GEN_FLOAT.next(); + float inf = Float.POSITIVE_INFINITY; + float nanf = Float.NaN; + + assertResultF(0f); + assertResultF(-0f); + assertResultF(af); + assertResultF(inf); + assertResultF(nanf); + + double ad = GEN_DOUBLE.next(); + double ind = Double.POSITIVE_INFINITY; + double nand = Double.NaN; + + assertResultD(0d); + assertResultD(-0d); + assertResultD(ad); + assertResultD(ind); + assertResultD(nand); + } + + @DontCompile + public void assertResultI(int a) { + Asserts.assertEQ(Integer.reverseBytes(Integer.reverseBytes(a)), testI1(a)); + Asserts.assertEQ(Integer.reverse(Integer.reverse(a)) , testI2(a)); + } + + @DontCompile + public void assertResultL(long a) { + Asserts.assertEQ(Long.reverseBytes(Long.reverseBytes(a)), testL1(a)); + Asserts.assertEQ(Long.reverse(Long.reverse(a)) , testL2(a)); + } + + @DontCompile + public void assertResultS(short a) { + Asserts.assertEQ(Short.reverseBytes(Short.reverseBytes(a)), testS1(a)); + } + + @DontCompile + public void assertResultUS(char a) { + Asserts.assertEQ(Character.reverseBytes(Character.reverseBytes(a)), testUS1(a)); + } + + @DontCompile + public void assertResultF(float a) { + Asserts.assertEQ(Float.floatToRawIntBits(-(-a)), Float.floatToRawIntBits(testF1(a))); + } + + @DontCompile + public void assertResultD(double a) { + Asserts.assertEQ(Double.doubleToRawLongBits(-(-a)), Double.doubleToRawLongBits(testD1(a))); + } + + @DontCompile + public void assertResultTruncating(int a) { + Asserts.assertEQ((int) Short.reverseBytes( ReverseBytesConstantsHelper.reverseBytesShort(a)), testS2( a)); + Asserts.assertEQ((int) Character.reverseBytes(ReverseBytesConstantsHelper.reverseBytesChar( a)), testUS2(a)); + } + + @Test + @IR(failOn = {IRNode.REVERSE_BYTES_I}) + public int testI1(int x) { + return Integer.reverseBytes(Integer.reverseBytes(x)); + } + + @Test + @IR(failOn = {IRNode.REVERSE_I}) + public int testI2(int x) { + return Integer.reverse(Integer.reverse(x)); + } + + @Test + @IR(failOn = {IRNode.REVERSE_BYTES_L}) + public long testL1(long x) { + return Long.reverseBytes(Long.reverseBytes(x)); + } + + @Test + @IR(failOn = {IRNode.REVERSE_L}) + public long testL2(long x) { + return Long.reverse(Long.reverse(x)); + } + + @Test + @IR(failOn = {IRNode.REVERSE_BYTES_S}) + public short testS1(short x) { + // explicit cast to restrict the input to TypeInt::SHORT + return Short.reverseBytes(Short.reverseBytes((short) (int) x)); + } + + @Test + @IR(failOn = {IRNode.REVERSE_BYTES_US}) + public char testUS1(char x) { + // explicit cast to restrict the input to TypeInt::CHAR + return Character.reverseBytes(Character.reverseBytes((char) (int) x)); + } + + @Test + @IR(counts = {IRNode.REVERSE_BYTES_S, "2"}, + applyIfPlatform = {"riscv64", "true"}, + applyIfCPUFeature = { "zbb", "true" }) + @IR(counts = {IRNode.REVERSE_BYTES_S, "2"}, + applyIfPlatform = {"riscv64", "false"}) + public int testS2(int x) { + return Short.reverseBytes(ReverseBytesConstantsHelper.reverseBytesShort(x)); + } + + @Test + @IR(counts = {IRNode.REVERSE_BYTES_US, "2"}, + applyIfPlatform = {"riscv64", "true"}, + applyIfCPUFeature = { "zbb", "true" }) + @IR(counts = {IRNode.REVERSE_BYTES_US, "2"}, + applyIfPlatform = {"riscv64", "false"}) + public int testUS2(int x) { + return Character.reverseBytes(ReverseBytesConstantsHelper.reverseBytesChar(x)); + } + + @Test + @IR(failOn = {IRNode.NEG_F}) + public float testF1(float x) { + return -(-x); + } + + @Test + @IR(failOn = {IRNode.NEG_D}) + public double testD1(double x) { + return -(-x); + } +} diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java b/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java index 87ad9f029f7c..147300d0416b 100644 --- a/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java @@ -1705,6 +1705,16 @@ public static void anyLoadOfNodes(String irNodePlaceholder, String fieldHolder) superWordNodes(UMAX_REDUCTION_V, "UMaxReductionV"); } + public static final String NEG_F = PREFIX + "NEG_F" + POSTFIX; + static { + beforeMatchingNameRegex(NEG_F, "NegF"); + } + + public static final String NEG_D = PREFIX + "NEG_D" + POSTFIX; + static { + beforeMatchingNameRegex(NEG_D, "NegD"); + } + public static final String NEG_VF = VECTOR_PREFIX + "NEG_VF" + POSTFIX; static { vectorNode(NEG_VF, "NegVF", TYPE_FLOAT); From b7653ecd5e98cad27e357c79f2ac3fe0026a91e3 Mon Sep 17 00:00:00 2001 From: Frederic Parain Date: Thu, 15 Oct 2026 17:45:50 +0000 Subject: [PATCH 059/169] 8394654: [Valhalla] Missing return after uncommon trap in LibraryCallKit::inline_unsafe_flat_access() Reviewed-by: shade, qamai, kvn --- src/hotspot/share/opto/library_call.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/hotspot/share/opto/library_call.cpp b/src/hotspot/share/opto/library_call.cpp index 6b3f61342a9b..be0c21e823ec 100644 --- a/src/hotspot/share/opto/library_call.cpp +++ b/src/hotspot/share/opto/library_call.cpp @@ -2816,6 +2816,7 @@ bool LibraryCallKit::inline_unsafe_flat_access(bool is_store, AccessKind kind) { if (new_base->is_top()) { uncommon_trap(Deoptimization::Reason_intrinsic, Deoptimization::Action_none); + return true; } replace_in_map(base, new_base); base = new_base; From decb49e1f3d05eeae7ef48e43623e3795b21d266 Mon Sep 17 00:00:00 2001 From: Dan Smith Date: Thu, 15 Oct 2026 18:22:48 +0000 Subject: [PATCH 060/169] 8388161: value-objects-jls: 12.6: address finalization of value objects Reviewed-by: vromero, liach --- .../share/classes/java/lang/Object.java | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/java.base/share/classes/java/lang/Object.java b/src/java.base/share/classes/java/lang/Object.java index 5fee4809a700..6cfe3b5b3ba7 100644 --- a/src/java.base/share/classes/java/lang/Object.java +++ b/src/java.base/share/classes/java/lang/Object.java @@ -47,11 +47,11 @@ * It is not possible to synchronize on a value object. An attempt to {@code * synchronize} on a value object causes {@link IdentityException} to be thrown. *

    - * The {@link #finalize()} method of a value class will never be invoked by - * the garbage collector. + * Value objects are never finalized, and it is an error to declare a + * {@code finalize} method in a value class. *

    * A {@linkplain java.lang.ref.Reference Reference object} can only refer to an - * object with identity. Creating a reference object with a value object as + * object with identity. Creating a Reference object with a value object as * the referent throws {@code IdentityException}. * * @@ -598,12 +598,9 @@ public final void wait(long timeoutMillis, int nanos) throws InterruptedExceptio * system resources or to perform other cleanup. *

    *
    - * The {@code finalize} method of a value class is never directly - * invoked by the garbage collector. This includes the case where an - * abstract value class declares a {@code finalize} method and the - * class is extended by an identity class; the garbage collector never - * directly invokes the {@code finalize} method declared by the - * abstract value class. + * A value class cannot override the {@code finalize} method, and + * the garbage collector never invokes {@code finalize} on a value + * object. *
    *
    *

    @@ -616,7 +613,7 @@ public final void wait(long timeoutMillis, int nanos) throws InterruptedExceptio * The general contract of {@code finalize} is that it is invoked * if and when the Java virtual * machine has determined that there is no longer any - * means by which this object can be accessed by any thread that has + * means by which this identity object can be accessed by any thread that has * not yet died, except as a result of an action taken by the * finalization of some other object or class which is ready to be * finalized. The {@code finalize} method may take any action, including From c9661bb64bdf788bcc67d888980691948e406a1e Mon Sep 17 00:00:00 2001 From: Yasumasa Suenaga Date: Thu, 15 Oct 2026 22:58:02 +0000 Subject: [PATCH 061/169] 8381336: SA FlatArray support is incomplete Reviewed-by: sspitsyn, cjplummer --- src/hotspot/share/runtime/vmStructs.cpp | 15 +++ .../classes/sun/jvm/hotspot/oops/Array.java | 4 + .../sun/jvm/hotspot/oops/FlatArray.java | 33 +++++-- .../sun/jvm/hotspot/oops/FlatArrayKlass.java | 10 +- .../classes/sun/jvm/hotspot/oops/Klass.java | 7 ++ .../sun/jvm/hotspot/oops/LayoutKind.java | 93 +++++++++++++++++++ .../jvm/hotspot/oops/LayoutKindHelper.java | 35 +++++++ .../sun/jvm/hotspot/oops/ObjArray.java | 12 ++- .../sun/jvm/hotspot/oops/ObjectHeap.java | 22 ++++- .../sun/jvm/hotspot/oops/OopField.java | 16 ++-- .../sun/jvm/hotspot/oops/ValueFieldInfo.java | 7 ++ .../sun/jvm/hotspot/runtime/BasicType.java | 3 +- .../sa/ClhsdbInspectWithValueObject.java | 30 +++++- .../sa/LingeredAppWithValueObject.java | 17 ++++ 14 files changed, 275 insertions(+), 29 deletions(-) create mode 100644 src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/LayoutKind.java create mode 100644 src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/LayoutKindHelper.java diff --git a/src/hotspot/share/runtime/vmStructs.cpp b/src/hotspot/share/runtime/vmStructs.cpp index d6b26f209f62..17cd61ea55f9 100644 --- a/src/hotspot/share/runtime/vmStructs.cpp +++ b/src/hotspot/share/runtime/vmStructs.cpp @@ -193,6 +193,7 @@ nonstatic_field(ConstantPoolCache, _resolved_indy_entries, Array*) \ nonstatic_field(ResolvedIndyEntry, _cpool_index, u2) \ nonstatic_field(ValueFieldInfo, _klass, ValueKlass*) \ + nonstatic_field(ValueFieldInfo, _kind, LayoutKind) \ volatile_nonstatic_field(InstanceKlass, _array_klasses, ObjArrayKlass*) \ nonstatic_field(InstanceKlass, _methods, Array*) \ nonstatic_field(InstanceKlass, _default_methods, Array*) \ @@ -273,6 +274,7 @@ nonstatic_field(ConstMethod, _num_stack_arg_slots, u2) \ nonstatic_field(ObjArrayKlass, _element_klass, Klass*) \ nonstatic_field(ObjArrayKlass, _bottom_klass, Klass*) \ + nonstatic_field(FlatArrayKlass, _layout_kind, LayoutKind) \ volatile_nonstatic_field(Symbol, _hash_and_refcount, unsigned int) \ nonstatic_field(Symbol, _length, u2) \ unchecked_nonstatic_field(Symbol, _body, sizeof(u1)) /* NOTE: no type */ \ @@ -1203,6 +1205,7 @@ \ declare_integer_type(AOTCompressedPointers::narrowPtr) \ declare_integer_type(Bytecodes::Code) \ + declare_integer_type(LayoutKind) \ declare_integer_type(InstanceKlass::ClassState) \ declare_integer_type(Klass::KlassKind) \ declare_integer_type(JavaThreadState) \ @@ -1486,6 +1489,18 @@ \ declare_constant(InstanceKlass::enclosing_method_attribute_size) \ \ + /*******************/ \ + /* LayoutKind enum */ \ + /*******************/ \ + \ + declare_constant(LayoutKind::REFERENCE) \ + declare_constant(LayoutKind::BUFFERED) \ + declare_constant(LayoutKind::NULL_FREE_NON_ATOMIC_FLAT) \ + declare_constant(LayoutKind::NULL_FREE_ATOMIC_FLAT) \ + declare_constant(LayoutKind::NULLABLE_ATOMIC_FLAT) \ + declare_constant(LayoutKind::NULLABLE_NON_ATOMIC_FLAT) \ + declare_constant(LayoutKind::UNKNOWN) \ + \ /*********************************/ \ /* InstanceKlass ClassState enum */ \ /*********************************/ \ diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/Array.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/Array.java index add2f8b31184..f9d4016ac356 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/Array.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/Array.java @@ -61,6 +61,9 @@ private static void initialize(TypeDataBase db) throws WrongTypeException { // aligned 0 mod 8. The arrayOop itself must be aligned at least this // strongly. private static boolean elementTypeShouldBeAligned(BasicType type) { + if (type == BasicType.T_FLAT_ELEMENT) { + return true; + } if (VM.getVM().isLP64()) { if (type == BasicType.T_OBJECT || type == BasicType.T_ARRAY) { return !VM.getVM().isCompressedOopsEnabled(); @@ -117,6 +120,7 @@ public static long baseOffsetInBytes(BasicType type) { } public boolean isArray() { return true; } + public boolean isFlatArray() { return false; } public void iterateFields(OopVisitor visitor, boolean doVMFields) { super.iterateFields(visitor, doVMFields); diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/FlatArray.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/FlatArray.java index 523378db5bff..d8614d040021 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/FlatArray.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/FlatArray.java @@ -34,7 +34,7 @@ // A FlatArray is an array containing flattened value objects. -public class FlatArray extends Array { +public class FlatArray extends ObjArray { static { VM.registerVMInitializedObserver(new Observer() { public void update(Observable o, Object data) { @@ -50,6 +50,7 @@ private static synchronized void initialize(TypeDataBase db) throws WrongTypeExc super(handle, heap); } + @Override public boolean isFlatArray() { return true; } public void printValueOn(PrintStream tty) { @@ -57,14 +58,30 @@ public void printValueOn(PrintStream tty) { klass.printValueOn(tty); } - public void iterateFields(OopVisitor visitor, boolean doVMFields) { - super.iterateFields(visitor, doVMFields); - FlatArrayKlass klass = (FlatArrayKlass) getKlass(); - int length = (int) getLength(); - int type = klass.getElementType(); - //System.out.println("FlatArray.iterateFields: length:" + length + " type:" + type); + @Override + public OopHandle getOopHandleAt(long index) { + // FlatArray contains value object, it means array contents are payload. + // Thus we need to create oop handle from oop of value object. + return getObjAt(index).getHandle(); + } + + private OopField getOopField(int index) { + int shift = Klass.layoutHelperLog2ElementSize(getKlass().getLayoutHelper()); + long baseOffset = baseOffsetInBytes(BasicType.T_FLAT_ELEMENT); + long elementSize = 1 << shift; // from FlatArrayKlass::oop_oop_iterate_elements_specialized_bounded() (addr_incr) + long offset = baseOffset + (elementSize * (long)index); + return new OopField(new IndexableFieldIdentifier(index), offset, false); + } + + @Override + public Oop getObjAt(long index) { + return getHeap().newFlattenedOop(this, getOopField((int)index)); + } + + @Override + protected void iterateFieldsInternal(OopVisitor visitor, int length) { for (int index = 0; index < length; index++) { - // FIXME - call visitor.doXXX() for each component of each value object + visitor.doOop(getOopField(index), false); } } } diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/FlatArrayKlass.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/FlatArrayKlass.java index 082777d27b5d..929154dbcce2 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/FlatArrayKlass.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/FlatArrayKlass.java @@ -36,6 +36,9 @@ // FlatArrayKlass is a proxy for FlatArrayKlass in the JVM public class FlatArrayKlass extends ObjArrayKlass { + + private static CIntegerField layoutKindField; + static { VM.registerVMInitializedObserver(new Observer() { public void update(Observable o, Object data) { @@ -46,13 +49,18 @@ public void update(Observable o, Object data) { private static synchronized void initialize(TypeDataBase db) throws WrongTypeException { Type t = db.lookupType("FlatArrayKlass"); - // TODO: implement similar features as in ObjArrayKlass + + layoutKindField = t.getCIntegerField("_layout_kind"); } public FlatArrayKlass(Address addr) { super(addr); } + public LayoutKind getLayoutKind() { + return LayoutKind.valueOf((int)layoutKindField.getValue(addr)); + } + public void printValueOn(PrintStream tty) { tty.print("FlatArrayKlass for "); getElementKlass().printValueOn(tty); diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/Klass.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/Klass.java index d4f59185b3ef..72db865ebdad 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/Klass.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/Klass.java @@ -123,6 +123,7 @@ public void update(Observable o, Object data) { // anon-enum constants for _layout_helper. public static int LH_INSTANCE_SLOW_PATH_BIT; public static int LH_LOG2_ELEMENT_SIZE_SHIFT; + public static int LH_LOG2_ELEMENT_SIZE_MASK; public static int LH_ELEMENT_TYPE_SHIFT; public static int LH_HEADER_SIZE_SHIFT; public static int LH_ARRAY_TAG_SHIFT; @@ -148,6 +149,7 @@ private static synchronized void initialize(TypeDataBase db) throws WrongTypeExc LH_INSTANCE_SLOW_PATH_BIT = db.lookupIntConstant("Klass::_lh_instance_slow_path_bit").intValue(); LH_LOG2_ELEMENT_SIZE_SHIFT = db.lookupIntConstant("Klass::_lh_log2_element_size_shift").intValue(); + LH_LOG2_ELEMENT_SIZE_MASK = db.lookupIntConstant("Klass::_lh_log2_element_size_mask").intValue(); LH_ELEMENT_TYPE_SHIFT = db.lookupIntConstant("Klass::_lh_element_type_shift").intValue(); LH_HEADER_SIZE_SHIFT = db.lookupIntConstant("Klass::_lh_header_size_shift").intValue(); LH_ARRAY_TAG_SHIFT = db.lookupIntConstant("Klass::_lh_array_tag_shift").intValue(); @@ -279,4 +281,9 @@ public Klass arrayKlassImpl(boolean orNull) { // The subclasses override this to produce the correct form, eg // Ljava/lang/String; For ArrayKlasses getName itself is the signature. public String signature() { return getName().asString(); } + + public static int layoutHelperLog2ElementSize(int lh) { + int l2esz = (lh >>> LH_LOG2_ELEMENT_SIZE_SHIFT) & LH_LOG2_ELEMENT_SIZE_MASK; + return l2esz; + } } diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/LayoutKind.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/LayoutKind.java new file mode 100644 index 000000000000..f4b61aba7d41 --- /dev/null +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/LayoutKind.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2026, NTT DATA + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ +package sun.jvm.hotspot.oops; + +import sun.jvm.hotspot.runtime.VM; +import sun.jvm.hotspot.runtime.VMObject; +import sun.jvm.hotspot.types.Type; +import sun.jvm.hotspot.types.TypeDataBase; +import sun.jvm.hotspot.types.WrongTypeException; + + +public final class LayoutKind { + + public static LayoutKind REFERENCE; + public static LayoutKind BUFFERED; + public static LayoutKind NULL_FREE_NON_ATOMIC_FLAT; + public static LayoutKind NULL_FREE_ATOMIC_FLAT; + public static LayoutKind NULLABLE_ATOMIC_FLAT; + public static LayoutKind NULLABLE_NON_ATOMIC_FLAT; + public static LayoutKind UNKNOWN; + + private final int value; + + static { + VM.registerVMInitializedObserver((_, _) -> initialize(VM.getVM().getTypeDataBase())); + } + + private static synchronized void initialize(TypeDataBase db) throws WrongTypeException { + Type type = db.lookupType("LayoutKind"); + + REFERENCE = new LayoutKind(db.lookupIntConstant("LayoutKind::REFERENCE").intValue()); + BUFFERED = new LayoutKind(db.lookupIntConstant("LayoutKind::BUFFERED").intValue()); + NULL_FREE_NON_ATOMIC_FLAT = new LayoutKind(db.lookupIntConstant("LayoutKind::NULL_FREE_NON_ATOMIC_FLAT").intValue()); + NULL_FREE_ATOMIC_FLAT = new LayoutKind(db.lookupIntConstant("LayoutKind::NULL_FREE_ATOMIC_FLAT").intValue()); + NULLABLE_ATOMIC_FLAT = new LayoutKind(db.lookupIntConstant("LayoutKind::NULLABLE_ATOMIC_FLAT").intValue()); + NULLABLE_NON_ATOMIC_FLAT = new LayoutKind(db.lookupIntConstant("LayoutKind::NULLABLE_NON_ATOMIC_FLAT").intValue()); + UNKNOWN = new LayoutKind(db.lookupIntConstant("LayoutKind::UNKNOWN").intValue()); + } + + private LayoutKind(int value) { + this.value = value; + } + + public static LayoutKind valueOf(int rawValue) { + if (rawValue == REFERENCE.value) { + return REFERENCE; + } else if (rawValue == BUFFERED.value) { + return BUFFERED; + } else if (rawValue == NULL_FREE_NON_ATOMIC_FLAT.value) { + return NULL_FREE_NON_ATOMIC_FLAT; + } else if (rawValue == NULL_FREE_ATOMIC_FLAT.value) { + return NULL_FREE_ATOMIC_FLAT; + } else if (rawValue == NULLABLE_ATOMIC_FLAT.value) { + return NULLABLE_ATOMIC_FLAT; + } else if (rawValue == NULLABLE_NON_ATOMIC_FLAT.value) { + return NULLABLE_NON_ATOMIC_FLAT; + } else if (rawValue == UNKNOWN.value) { + return UNKNOWN; + } else { + throw new IllegalArgumentException("Out of range"); + } + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof LayoutKind k) { + return k.value == this.value; + } + return false; + } +} diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/LayoutKindHelper.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/LayoutKindHelper.java new file mode 100644 index 000000000000..76b7be40cb55 --- /dev/null +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/LayoutKindHelper.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2026, NTT DATA + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ +package sun.jvm.hotspot.oops; + + +public class LayoutKindHelper { + + public static boolean isNullableFlat(LayoutKind layoutKind) { + return layoutKind.equals(LayoutKind.NULLABLE_ATOMIC_FLAT) || + layoutKind.equals(LayoutKind.NULLABLE_NON_ATOMIC_FLAT); + } + +} diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/ObjArray.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/ObjArray.java index bacf6011a266..a3da0b86135d 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/ObjArray.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/ObjArray.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -73,9 +73,7 @@ public void printValueOn(PrintStream tty) { tty.print("ObjArray"); } - public void iterateFields(OopVisitor visitor, boolean doVMFields) { - super.iterateFields(visitor, doVMFields); - int length = (int) getLength(); + protected void iterateFieldsInternal(OopVisitor visitor, int length) { long baseOffset = baseOffsetInBytes(BasicType.T_OBJECT); for (int index = 0; index < length; index++) { long offset = baseOffset + (index * elementSize); @@ -88,4 +86,10 @@ public void iterateFields(OopVisitor visitor, boolean doVMFields) { visitor.doOop(field, false); } } + + public void iterateFields(OopVisitor visitor, boolean doVMFields) { + super.iterateFields(visitor, doVMFields); + int length = (int) getLength(); + iterateFieldsInternal(visitor, length); + } } diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/ObjectHeap.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/ObjectHeap.java index 2f1b662aaeb9..b503f39cf0c3 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/ObjectHeap.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/ObjectHeap.java @@ -199,13 +199,25 @@ public Oop newOop(OopHandle handle) { // This method is used to instantiate a holder object that contains // the flattened field payload. - public Oop newOop(Address payload, ValueKlass klass, OopField field) { - if (Assert.ASSERTS_ENABLED) { - Assert.that(payload != null, "payload should not be null"); + public Oop newFlattenedOop(Oop obj, OopField field) { + ValueKlass vk; + boolean needNullCheck = false; + if (obj.isArray() && ((Array)obj).isFlatArray()) { + FlatArrayKlass k = (FlatArrayKlass)obj.getKlass(); + vk = (ValueKlass)k.getElementKlass(); + needNullCheck = LayoutKindHelper.isNullableFlat(k.getLayoutKind()); + } else { + var layout = ((InstanceKlass)obj.getKlass()).getValueFieldInfoArray().at(field.getFieldIndex()); + vk = layout.getKlass(); + needNullCheck = field.hasNullMarker(); } - return field.hasNullMarker() && klass.isPayloadMarkedAsNull(payload) + + // OopHandle does not allow to call addOffsetTo() due to prevent interior + // object pointers. So addOffsetToAsOopHandle() is required here. + Address payload = obj.getHandle().addOffsetToAsOopHandle(field.getOffset()); + return needNullCheck && vk.isPayloadMarkedAsNull(payload) ? null - : new FlattenedValue(payload, this, klass); + : new FlattenedValue(payload, this, vk); } // Print all objects in the object heap diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/OopField.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/OopField.java index 9eb1a62f7532..82f461276871 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/OopField.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/OopField.java @@ -48,14 +48,14 @@ public Oop getValue(Oop obj) { throw new InternalError(); } var heap = obj.getHeap(); - if (isFlat()) { - var layout = ((InstanceKlass)obj.getKlass()).getValueFieldInfoArray().at(getFieldIndex()); - ValueKlass vk = layout.getKlass(); - - // OopHandle does not allow to call addOffsetTo() due to prevent interior - // object pointers. So addOffsetToAsOopHandle() is required here. - Address payload = obj.getHandle().addOffsetToAsOopHandle(getOffset()); - return heap.newOop(payload, vk, this); + if (obj.isArray()) { + if (((Array)obj).isFlatArray()) { + return heap.newFlattenedOop(obj, this); + } else { + return heap.newOop(getValueAsOopHandle(obj)); + } + } else if (isFlat()) { + return heap.newFlattenedOop(obj, this); } else { return heap.newOop(getValueAsOopHandle(obj)); } diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/ValueFieldInfo.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/ValueFieldInfo.java index 7e1a713fb281..433ce0c1695c 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/ValueFieldInfo.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/ValueFieldInfo.java @@ -35,6 +35,7 @@ public class ValueFieldInfo extends VMObject { private static MetadataField klassField; + private static CIntField kindField; static { VM.registerVMInitializedObserver((_, _) -> initialize(VM.getVM().getTypeDataBase())); @@ -44,6 +45,7 @@ private static synchronized void initialize(TypeDataBase db) throws WrongTypeExc Type type = db.lookupType("ValueFieldInfo"); klassField = new MetadataField(type.getAddressField("_klass"), 0); + kindField = new CIntField(type.getCIntegerField("_kind"), 0); } public ValueFieldInfo(Address addr) { @@ -54,4 +56,9 @@ public ValueKlass getKlass() { return (ValueKlass)klassField.getValue(this); } + public LayoutKind getKind() { + int rawVal = (int)kindField.getValue(this); + return LayoutKind.valueOf(rawVal); + } + } diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/BasicType.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/BasicType.java index 36f71407cc92..6f6e091da93d 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/BasicType.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/BasicType.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -42,6 +42,7 @@ public class BasicType { public static final BasicType T_OBJECT = new BasicType(); public static final BasicType T_ARRAY = new BasicType(); public static final BasicType T_VOID = new BasicType(); + public static final BasicType T_FLAT_ELEMENT = new BasicType(); public static final BasicType T_ADDRESS = new BasicType(); public static final BasicType T_NARROWOOP = new BasicType(); public static final BasicType T_METADATA = new BasicType(); diff --git a/test/hotspot/jtreg/serviceability/sa/ClhsdbInspectWithValueObject.java b/test/hotspot/jtreg/serviceability/sa/ClhsdbInspectWithValueObject.java index 9f2e1da67e03..9331f36d6fc5 100644 --- a/test/hotspot/jtreg/serviceability/sa/ClhsdbInspectWithValueObject.java +++ b/test/hotspot/jtreg/serviceability/sa/ClhsdbInspectWithValueObject.java @@ -34,7 +34,7 @@ /** * @test - * @bug 8390106 + * @bug 8390106 8381336 * @requires vm.hasSA * @requires vm.gc != "Z" * @requires (os.arch != "riscv64" | !(vm.cpu.features ~= ".*qemu.*")) @@ -77,7 +77,9 @@ public static void main(String[] args) throws Exception { var cmds = List.of( "inspect " + addresses.getProperty("valObj"), "inspect " + addresses.getProperty("nonNullValObj"), - "inspect " + addresses.getProperty("nonFlattenedValObj") + "inspect " + addresses.getProperty("nonFlattenedValObj"), + "inspect " + addresses.getProperty("valObjArray"), + "inspect " + addresses.getProperty("nonFlattenedValObjArray") ); var expStrMap = Map.of( @@ -97,6 +99,30 @@ public static void main(String[] args) throws Exception { cmds.get(2) /* nonFlattenedValObj */ , List.of( "a: 100", "obj: Oop for java/lang/Object" + ), + cmds.get(3) /* valObjArray */ , List.of( + "0:", + "a: 1", + "rec:", + "recA: 10", + "recB: 20", + "nullField: null", + "1: null", + "2:", + "a: 2", + "rec:", + "recA: 30", + "recB: 40", + "nullField: null" + ), + cmds.get(4) /* nonFlattenedValObjArray */ , List.of( + "0:", + "a: 100", + "obj: Oop for java/lang/Object", + "1: null", + "2:", + "a: 200", + "obj: Oop for java/lang/Object" ) ); test.run(theApp.getPid(), cmds, expStrMap, null); diff --git a/test/hotspot/jtreg/serviceability/sa/LingeredAppWithValueObject.java b/test/hotspot/jtreg/serviceability/sa/LingeredAppWithValueObject.java index 3015ad60d552..fc3478e34dfe 100644 --- a/test/hotspot/jtreg/serviceability/sa/LingeredAppWithValueObject.java +++ b/test/hotspot/jtreg/serviceability/sa/LingeredAppWithValueObject.java @@ -83,10 +83,25 @@ public NonFlattenedValueObj(int a, Object obj) { private static NonFlattenedValueObj nonFlattenedValObj; + private static ValueObj[] valObjArray; + + private static NonFlattenedValueObj[] nonFlattenedValObjArray; + static { valObj = new ValueObj((byte)1, (byte)10, (byte)20); nonNullValObj = new NonNullValueObj((byte)2, (byte)30, (byte)40); nonFlattenedValObj = new NonFlattenedValueObj(100, new Object()); + + valObjArray = new ValueObj[] { + new ValueObj((byte)1, (byte)10, (byte)20), + null, + new ValueObj((byte)2, (byte)30, (byte)40) + }; + nonFlattenedValObjArray = new NonFlattenedValueObj[] { + new NonFlattenedValueObj(100, new Object()), + null, + new NonFlattenedValueObj(200, new Object()) + }; } public static void main(String[] args) { @@ -95,6 +110,8 @@ public static void main(String[] args) { addresses.setProperty("valObj", String.format("0x%x", wb.getObjectAddress(valObj))); addresses.setProperty("nonNullValObj", String.format("0x%x", wb.getObjectAddress(nonNullValObj))); addresses.setProperty("nonFlattenedValObj", String.format("0x%x", wb.getObjectAddress(nonFlattenedValObj))); + addresses.setProperty("valObjArray", String.format("0x%x", wb.getObjectAddress(valObjArray))); + addresses.setProperty("nonFlattenedValObjArray", String.format("0x%x", wb.getObjectAddress(nonFlattenedValObjArray))); try (var out = Files.newOutputStream(ADDR_FILE_PATH)) { addresses.store(out, null); From 81d817dcc384b8acf607951790eb1c9589e343c6 Mon Sep 17 00:00:00 2001 From: Naoto Sato Date: Thu, 15 Oct 2026 23:33:07 +0000 Subject: [PATCH 062/169] 8392962: Incorporate JSONTestSuite into test Reviewed-by: jlu --- .../incubator/json/JSONTestSuite/LICENSE.txt | 21 +++ .../i_number_double_huge_neg_exp.json | 1 + .../test_parsing/i_number_huge_exp.json | 1 + .../i_number_neg_int_huge_exp.json | 1 + .../i_number_pos_double_huge_exp.json | 1 + .../i_number_real_neg_overflow.json | 1 + .../i_number_real_pos_overflow.json | 1 + .../test_parsing/i_number_real_underflow.json | 1 + .../i_number_too_big_neg_int.json | 1 + .../i_number_too_big_pos_int.json | 1 + .../i_number_very_big_negative_int.json | 1 + .../i_object_key_lone_2nd_surrogate.json | 1 + ..._string_1st_surrogate_but_2nd_missing.json | 1 + ...tring_1st_valid_surrogate_2nd_invalid.json | 1 + .../i_string_UTF-16LE_with_BOM.json | Bin 0 -> 12 bytes .../i_string_UTF-8_invalid_sequence.json | 1 + .../i_string_UTF8_surrogate_U+D800.json | 1 + ...incomplete_surrogate_and_escape_valid.json | 1 + .../i_string_incomplete_surrogate_pair.json | 1 + ...ng_incomplete_surrogates_escape_valid.json | 1 + .../i_string_invalid_lonely_surrogate.json | 1 + .../i_string_invalid_surrogate.json | 1 + .../test_parsing/i_string_invalid_utf-8.json | 1 + .../i_string_inverted_surrogates_U+1D11E.json | 1 + .../test_parsing/i_string_iso_latin_1.json | 1 + .../i_string_lone_second_surrogate.json | 1 + .../i_string_lone_utf8_continuation_byte.json | 1 + .../i_string_not_in_unicode_range.json | 1 + .../i_string_overlong_sequence_2_bytes.json | 1 + .../i_string_overlong_sequence_6_bytes.json | 1 + ...string_overlong_sequence_6_bytes_null.json | 1 + .../i_string_truncated-utf-8.json | 1 + .../test_parsing/i_string_utf16BE_no_BOM.json | Bin 0 -> 10 bytes .../test_parsing/i_string_utf16LE_no_BOM.json | Bin 0 -> 10 bytes .../i_structure_500_nested_arrays.json | 1 + .../i_structure_UTF-8_BOM_empty_object.json | 1 + .../n_array_1_true_without_comma.json | 1 + .../test_parsing/n_array_a_invalid_utf8.json | 1 + .../n_array_colon_instead_of_comma.json | 1 + .../n_array_comma_after_close.json | 1 + .../n_array_comma_and_number.json | 1 + .../test_parsing/n_array_double_comma.json | 1 + .../n_array_double_extra_comma.json | 1 + .../test_parsing/n_array_extra_close.json | 1 + .../test_parsing/n_array_extra_comma.json | 1 + .../test_parsing/n_array_incomplete.json | 1 + .../n_array_incomplete_invalid_value.json | 1 + .../n_array_inner_array_no_comma.json | 1 + .../test_parsing/n_array_invalid_utf8.json | 1 + .../n_array_items_separated_by_semicolon.json | 1 + .../test_parsing/n_array_just_comma.json | 1 + .../test_parsing/n_array_just_minus.json | 1 + .../test_parsing/n_array_missing_value.json | 1 + .../n_array_newlines_unclosed.json | 3 + .../n_array_number_and_comma.json | 1 + .../n_array_number_and_several_commas.json | 1 + .../n_array_spaces_vertical_tab_formfeed.json | 1 + .../test_parsing/n_array_star_inside.json | 1 + .../test_parsing/n_array_unclosed.json | 1 + .../n_array_unclosed_trailing_comma.json | 1 + .../n_array_unclosed_with_new_lines.json | 3 + .../n_array_unclosed_with_object_inside.json | 1 + .../test_parsing/n_incomplete_false.json | 1 + .../test_parsing/n_incomplete_null.json | 1 + .../test_parsing/n_incomplete_true.json | 1 + .../n_multidigit_number_then_00.json | Bin 0 -> 4 bytes .../test_parsing/n_number_++.json | 1 + .../test_parsing/n_number_+1.json | 1 + .../test_parsing/n_number_+Inf.json | 1 + .../test_parsing/n_number_-01.json | 1 + .../test_parsing/n_number_-1.0..json | 1 + .../test_parsing/n_number_-2..json | 1 + .../test_parsing/n_number_-NaN.json | 1 + .../test_parsing/n_number_.-1.json | 1 + .../test_parsing/n_number_.2e-3.json | 1 + .../test_parsing/n_number_0.1.2.json | 1 + .../test_parsing/n_number_0.3e+.json | 1 + .../test_parsing/n_number_0.3e.json | 1 + .../test_parsing/n_number_0.e1.json | 1 + .../test_parsing/n_number_0_capital_E+.json | 1 + .../test_parsing/n_number_0_capital_E.json | 1 + .../test_parsing/n_number_0e+.json | 1 + .../test_parsing/n_number_0e.json | 1 + .../test_parsing/n_number_1.0e+.json | 1 + .../test_parsing/n_number_1.0e-.json | 1 + .../test_parsing/n_number_1.0e.json | 1 + .../test_parsing/n_number_1_000.json | 1 + .../test_parsing/n_number_1eE2.json | 1 + .../test_parsing/n_number_2.e+3.json | 1 + .../test_parsing/n_number_2.e-3.json | 1 + .../test_parsing/n_number_2.e3.json | 1 + .../test_parsing/n_number_9.e+.json | 1 + .../test_parsing/n_number_Inf.json | 1 + .../test_parsing/n_number_NaN.json | 1 + .../n_number_U+FF11_fullwidth_digit_one.json | 1 + .../test_parsing/n_number_expression.json | 1 + .../test_parsing/n_number_hex_1_digit.json | 1 + .../test_parsing/n_number_hex_2_digits.json | 1 + .../test_parsing/n_number_infinity.json | 1 + .../test_parsing/n_number_invalid+-.json | 1 + .../n_number_invalid-negative-real.json | 1 + .../n_number_invalid-utf-8-in-bigger-int.json | 1 + .../n_number_invalid-utf-8-in-exponent.json | 1 + .../n_number_invalid-utf-8-in-int.json | 1 + .../test_parsing/n_number_minus_infinity.json | 1 + ...mber_minus_sign_with_trailing_garbage.json | 1 + .../test_parsing/n_number_minus_space_1.json | 1 + .../n_number_neg_int_starting_with_zero.json | 1 + .../n_number_neg_real_without_int_part.json | 1 + .../n_number_neg_with_garbage_at_end.json | 1 + .../n_number_real_garbage_after_e.json | 1 + ...number_real_with_invalid_utf8_after_e.json | 1 + ...n_number_real_without_fractional_part.json | 1 + .../n_number_starting_with_dot.json | 1 + .../test_parsing/n_number_with_alpha.json | 1 + .../n_number_with_alpha_char.json | 1 + .../n_number_with_leading_zero.json | 1 + .../test_parsing/n_object_bad_value.json | 1 + .../test_parsing/n_object_bracket_key.json | 1 + .../n_object_comma_instead_of_colon.json | 1 + .../test_parsing/n_object_double_colon.json | 1 + .../test_parsing/n_object_emoji.json | 1 + .../test_parsing/n_object_garbage_at_end.json | 1 + .../n_object_key_with_single_quotes.json | 1 + ...uation_byte_in_key_and_trailing_comma.json | 1 + .../test_parsing/n_object_missing_colon.json | 1 + .../test_parsing/n_object_missing_key.json | 1 + .../n_object_missing_semicolon.json | 1 + .../test_parsing/n_object_missing_value.json | 1 + .../test_parsing/n_object_no-colon.json | 1 + .../test_parsing/n_object_non_string_key.json | 1 + ...on_string_key_but_huge_number_instead.json | 1 + .../n_object_repeated_null_null.json | 1 + .../n_object_several_trailing_commas.json | 1 + .../test_parsing/n_object_single_quote.json | 1 + .../test_parsing/n_object_trailing_comma.json | 1 + .../n_object_trailing_comment.json | 1 + .../n_object_trailing_comment_open.json | 1 + .../n_object_trailing_comment_slash_open.json | 1 + ...railing_comment_slash_open_incomplete.json | 1 + .../n_object_two_commas_in_a_row.json | 1 + .../test_parsing/n_object_unquoted_key.json | 1 + .../n_object_unterminated-value.json | 1 + .../n_object_with_single_string.json | 1 + .../n_object_with_trailing_garbage.json | 1 + .../test_parsing/n_single_space.json | 1 + .../n_string_1_surrogate_then_escape.json | 1 + .../n_string_1_surrogate_then_escape_u.json | 1 + .../n_string_1_surrogate_then_escape_u1.json | 1 + .../n_string_1_surrogate_then_escape_u1x.json | 1 + .../n_string_accentuated_char_no_quotes.json | 1 + .../test_parsing/n_string_backslash_00.json | Bin 0 -> 6 bytes .../test_parsing/n_string_escape_x.json | 1 + .../n_string_escaped_backslash_bad.json | 1 + .../n_string_escaped_ctrl_char_tab.json | 1 + .../test_parsing/n_string_escaped_emoji.json | 1 + .../n_string_incomplete_escape.json | 1 + ...n_string_incomplete_escaped_character.json | 1 + .../n_string_incomplete_surrogate.json | 1 + ...g_incomplete_surrogate_escape_invalid.json | 1 + .../n_string_invalid-utf-8-in-escape.json | 1 + .../n_string_invalid_backslash_esc.json | 1 + .../n_string_invalid_unicode_escape.json | 1 + .../n_string_invalid_utf8_after_escape.json | 1 + .../n_string_leading_uescaped_thinspace.json | 1 + .../n_string_no_quotes_with_bad_escape.json | 1 + .../n_string_single_doublequote.json | 1 + .../test_parsing/n_string_single_quote.json | 1 + ...string_single_string_no_double_quotes.json | 1 + .../n_string_start_escape_unclosed.json | 1 + .../n_string_unescaped_ctrl_char.json | Bin 0 -> 7 bytes .../n_string_unescaped_newline.json | 2 + .../test_parsing/n_string_unescaped_tab.json | 1 + .../n_string_unicode_CapitalU.json | 1 + .../n_string_with_trailing_garbage.json | 1 + .../n_structure_100000_opening_arrays.json | 1 + .../n_structure_U+2060_word_joined.json | 1 + .../n_structure_UTF8_BOM_no_data.json | 1 + .../n_structure_angle_bracket_..json | 1 + .../n_structure_angle_bracket_null.json | 1 + .../n_structure_array_trailing_garbage.json | 1 + ...tructure_array_with_extra_array_close.json | 1 + ..._structure_array_with_unclosed_string.json | 1 + .../n_structure_ascii-unicode-identifier.json | 1 + .../n_structure_capitalized_True.json | 1 + .../n_structure_close_unopened_array.json | 1 + ...ucture_comma_instead_of_closing_brace.json | 1 + .../n_structure_double_array.json | 1 + .../test_parsing/n_structure_end_array.json | 1 + .../n_structure_incomplete_UTF8_BOM.json | 1 + .../n_structure_lone-invalid-utf-8.json | 1 + .../n_structure_lone-open-bracket.json | 1 + .../test_parsing/n_structure_no_data.json | 0 .../n_structure_null-byte-outside-string.json | Bin 0 -> 3 bytes ...tructure_number_with_trailing_garbage.json | 1 + ...ure_object_followed_by_closing_object.json | 1 + .../n_structure_object_unclosed_no_value.json | 1 + .../n_structure_object_with_comment.json | 1 + ...tructure_object_with_trailing_garbage.json | 1 + .../n_structure_open_array_apostrophe.json | 1 + .../n_structure_open_array_comma.json | 1 + .../n_structure_open_array_object.json | 1 + .../n_structure_open_array_open_object.json | 1 + .../n_structure_open_array_open_string.json | 1 + .../n_structure_open_array_string.json | 1 + .../test_parsing/n_structure_open_object.json | 1 + .../n_structure_open_object_close_array.json | 1 + .../n_structure_open_object_comma.json | 1 + .../n_structure_open_object_open_array.json | 1 + .../n_structure_open_object_open_string.json | 1 + ...e_open_object_string_with_apostrophes.json | 1 + .../test_parsing/n_structure_open_open.json | 1 + .../n_structure_single_eacute.json | 1 + .../test_parsing/n_structure_single_star.json | 1 + .../test_parsing/n_structure_trailing_#.json | 1 + ...n_structure_uescaped_LF_before_string.json | 1 + .../n_structure_unclosed_array.json | 1 + ...structure_unclosed_array_partial_null.json | 1 + ...cture_unclosed_array_unfinished_false.json | 1 + ...ucture_unclosed_array_unfinished_true.json | 1 + .../n_structure_unclosed_object.json | 1 + .../n_structure_unicode-identifier.json | 1 + ...ructure_whitespace_U+2060_word_joiner.json | 1 + .../n_structure_whitespace_formfeed.json | 1 + .../y_array_arraysWithSpaces.json | 1 + .../test_parsing/y_array_empty-string.json | 1 + .../test_parsing/y_array_empty.json | 1 + .../y_array_ending_with_newline.json | 1 + .../test_parsing/y_array_false.json | 1 + .../test_parsing/y_array_heterogeneous.json | 1 + .../test_parsing/y_array_null.json | 1 + .../y_array_with_1_and_newline.json | 2 + .../y_array_with_leading_space.json | 1 + .../y_array_with_several_null.json | 1 + .../y_array_with_trailing_space.json | 1 + .../JSONTestSuite/test_parsing/y_number.json | 1 + .../test_parsing/y_number_0e+1.json | 1 + .../test_parsing/y_number_0e1.json | 1 + .../test_parsing/y_number_after_space.json | 1 + .../y_number_double_close_to_zero.json | 1 + .../test_parsing/y_number_int_with_exp.json | 1 + .../test_parsing/y_number_minus_zero.json | 1 + .../test_parsing/y_number_negative_int.json | 1 + .../test_parsing/y_number_negative_one.json | 1 + .../test_parsing/y_number_negative_zero.json | 1 + .../test_parsing/y_number_real_capital_e.json | 1 + .../y_number_real_capital_e_neg_exp.json | 1 + .../y_number_real_capital_e_pos_exp.json | 1 + .../test_parsing/y_number_real_exponent.json | 1 + .../y_number_real_fraction_exponent.json | 1 + .../test_parsing/y_number_real_neg_exp.json | 1 + .../y_number_real_pos_exponent.json | 1 + .../test_parsing/y_number_simple_int.json | 1 + .../test_parsing/y_number_simple_real.json | 1 + .../JSONTestSuite/test_parsing/y_object.json | 1 + .../test_parsing/y_object_basic.json | 1 + .../test_parsing/y_object_duplicated_key.json | 1 + .../y_object_duplicated_key_and_value.json | 1 + .../test_parsing/y_object_empty.json | 1 + .../test_parsing/y_object_empty_key.json | 1 + .../y_object_escaped_null_in_key.json | 1 + .../y_object_extreme_numbers.json | 1 + .../test_parsing/y_object_long_strings.json | 1 + .../test_parsing/y_object_simple.json | 1 + .../test_parsing/y_object_string_unicode.json | 1 + .../test_parsing/y_object_with_newlines.json | 3 + .../y_string_1_2_3_bytes_UTF-8_sequences.json | 1 + .../y_string_accepted_surrogate_pair.json | 1 + .../y_string_accepted_surrogate_pairs.json | 1 + .../y_string_allowed_escapes.json | 1 + ...y_string_backslash_and_u_escaped_zero.json | 1 + .../y_string_backslash_doublequotes.json | 1 + .../test_parsing/y_string_comments.json | 1 + .../y_string_double_escape_a.json | 1 + .../y_string_double_escape_n.json | 1 + .../y_string_escaped_control_character.json | 1 + .../y_string_escaped_noncharacter.json | 1 + .../test_parsing/y_string_in_array.json | 1 + .../y_string_in_array_with_leading_space.json | 1 + .../y_string_last_surrogates_1_and_2.json | 1 + .../test_parsing/y_string_nbsp_uescaped.json | 1 + ...y_string_nonCharacterInUTF-8_U+10FFFF.json | 1 + .../y_string_nonCharacterInUTF-8_U+FFFF.json | 1 + .../test_parsing/y_string_null_escape.json | 1 + .../test_parsing/y_string_one-byte-utf-8.json | 1 + .../test_parsing/y_string_pi.json | 1 + ...ring_reservedCharacterInUTF-8_U+1BFFF.json | 1 + .../test_parsing/y_string_simple_ascii.json | 1 + .../test_parsing/y_string_space.json | 1 + ...rogates_U+1D11E_MUSICAL_SYMBOL_G_CLEF.json | 1 + .../y_string_three-byte-utf-8.json | 1 + .../test_parsing/y_string_two-byte-utf-8.json | 1 + .../y_string_u+2028_line_sep.json | 1 + .../test_parsing/y_string_u+2029_par_sep.json | 1 + .../test_parsing/y_string_uEscape.json | 1 + .../y_string_uescaped_newline.json | 1 + .../y_string_unescaped_char_delete.json | 1 + .../test_parsing/y_string_unicode.json | 1 + .../y_string_unicodeEscapedBackslash.json | 1 + .../test_parsing/y_string_unicode_2.json | 1 + .../y_string_unicode_U+10FFFE_nonchar.json | 1 + .../y_string_unicode_U+1FFFE_nonchar.json | 1 + ...tring_unicode_U+200B_ZERO_WIDTH_SPACE.json | 1 + ..._string_unicode_U+2064_invisible_plus.json | 1 + .../y_string_unicode_U+FDD0_nonchar.json | 1 + .../y_string_unicode_U+FFFE_nonchar.json | 1 + ...y_string_unicode_escaped_double_quote.json | 1 + .../test_parsing/y_string_utf8.json | 1 + .../y_string_with_del_character.json | 1 + .../y_structure_lonely_false.json | 1 + .../test_parsing/y_structure_lonely_int.json | 1 + .../y_structure_lonely_negative_real.json | 1 + .../test_parsing/y_structure_lonely_null.json | 1 + .../y_structure_lonely_string.json | 1 + .../test_parsing/y_structure_lonely_true.json | 1 + .../y_structure_string_empty.json | 1 + .../y_structure_trailing_newline.json | 1 + .../y_structure_true_in_array.json | 1 + .../y_structure_whitespace_array.json | 1 + .../number_-9223372036854775808.json | 1 + .../number_-9223372036854775809.json | 1 + .../test_transform/number_1.0.json | 1 + .../number_1.000000000000000005.json | 1 + .../number_1000000000000000.json | 1 + .../number_10000000000000000999.json | 1 + .../test_transform/number_1e-999.json | 1 + .../test_transform/number_1e6.json | 1 + .../number_9223372036854775807.json | 1 + .../number_9223372036854775808.json | 1 + .../test_transform/object_key_nfc_nfd.json | 1 + .../test_transform/object_key_nfd_nfc.json | 1 + .../object_same_key_different_values.json | 1 + .../object_same_key_same_value.json | 1 + .../object_same_key_unclear_values.json | 1 + .../string_1_escaped_invalid_codepoint.json | 1 + .../string_1_invalid_codepoint.json | 1 + .../string_2_escaped_invalid_codepoints.json | 1 + .../string_2_invalid_codepoints.json | 1 + .../string_3_escaped_invalid_codepoints.json | 1 + .../string_3_invalid_codepoints.json | 1 + .../string_with_escaped_NULL.json | 1 + .../jdk/incubator/json/TestJSONParsing.java | 175 ++++++++++++++++++ 342 files changed, 536 insertions(+) create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/LICENSE.txt create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_number_double_huge_neg_exp.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_number_huge_exp.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_number_neg_int_huge_exp.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_number_pos_double_huge_exp.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_number_real_neg_overflow.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_number_real_pos_overflow.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_number_real_underflow.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_number_too_big_neg_int.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_number_too_big_pos_int.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_number_very_big_negative_int.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_object_key_lone_2nd_surrogate.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_1st_surrogate_but_2nd_missing.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_1st_valid_surrogate_2nd_invalid.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_UTF-16LE_with_BOM.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_UTF-8_invalid_sequence.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_UTF8_surrogate_U+D800.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_incomplete_surrogate_and_escape_valid.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_incomplete_surrogate_pair.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_incomplete_surrogates_escape_valid.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_invalid_lonely_surrogate.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_invalid_surrogate.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_invalid_utf-8.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_inverted_surrogates_U+1D11E.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_iso_latin_1.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_lone_second_surrogate.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_lone_utf8_continuation_byte.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_not_in_unicode_range.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_overlong_sequence_2_bytes.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_overlong_sequence_6_bytes.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_overlong_sequence_6_bytes_null.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_truncated-utf-8.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_utf16BE_no_BOM.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_utf16LE_no_BOM.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_structure_500_nested_arrays.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_structure_UTF-8_BOM_empty_object.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_1_true_without_comma.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_a_invalid_utf8.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_colon_instead_of_comma.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_comma_after_close.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_comma_and_number.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_double_comma.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_double_extra_comma.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_extra_close.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_extra_comma.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_incomplete.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_incomplete_invalid_value.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_inner_array_no_comma.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_invalid_utf8.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_items_separated_by_semicolon.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_just_comma.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_just_minus.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_missing_value.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_newlines_unclosed.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_number_and_comma.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_number_and_several_commas.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_spaces_vertical_tab_formfeed.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_star_inside.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_unclosed.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_unclosed_trailing_comma.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_unclosed_with_new_lines.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_unclosed_with_object_inside.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_incomplete_false.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_incomplete_null.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_incomplete_true.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_multidigit_number_then_00.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_++.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_+1.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_+Inf.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_-01.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_-1.0..json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_-2..json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_-NaN.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_.-1.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_.2e-3.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_0.1.2.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_0.3e+.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_0.3e.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_0.e1.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_0_capital_E+.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_0_capital_E.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_0e+.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_0e.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_1.0e+.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_1.0e-.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_1.0e.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_1_000.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_1eE2.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_2.e+3.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_2.e-3.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_2.e3.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_9.e+.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_Inf.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_NaN.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_U+FF11_fullwidth_digit_one.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_expression.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_hex_1_digit.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_hex_2_digits.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_infinity.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_invalid+-.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_invalid-negative-real.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_invalid-utf-8-in-bigger-int.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_invalid-utf-8-in-exponent.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_invalid-utf-8-in-int.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_minus_infinity.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_minus_sign_with_trailing_garbage.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_minus_space_1.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_neg_int_starting_with_zero.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_neg_real_without_int_part.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_neg_with_garbage_at_end.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_real_garbage_after_e.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_real_with_invalid_utf8_after_e.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_real_without_fractional_part.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_starting_with_dot.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_with_alpha.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_with_alpha_char.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_with_leading_zero.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_bad_value.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_bracket_key.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_comma_instead_of_colon.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_double_colon.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_emoji.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_garbage_at_end.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_key_with_single_quotes.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_lone_continuation_byte_in_key_and_trailing_comma.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_missing_colon.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_missing_key.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_missing_semicolon.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_missing_value.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_no-colon.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_non_string_key.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_non_string_key_but_huge_number_instead.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_repeated_null_null.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_several_trailing_commas.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_single_quote.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_trailing_comma.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_trailing_comment.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_trailing_comment_open.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_trailing_comment_slash_open.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_trailing_comment_slash_open_incomplete.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_two_commas_in_a_row.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_unquoted_key.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_unterminated-value.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_with_single_string.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_with_trailing_garbage.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_single_space.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_1_surrogate_then_escape.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_1_surrogate_then_escape_u.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_1_surrogate_then_escape_u1.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_1_surrogate_then_escape_u1x.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_accentuated_char_no_quotes.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_backslash_00.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_escape_x.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_escaped_backslash_bad.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_escaped_ctrl_char_tab.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_escaped_emoji.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_incomplete_escape.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_incomplete_escaped_character.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_incomplete_surrogate.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_incomplete_surrogate_escape_invalid.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_invalid-utf-8-in-escape.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_invalid_backslash_esc.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_invalid_unicode_escape.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_invalid_utf8_after_escape.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_leading_uescaped_thinspace.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_no_quotes_with_bad_escape.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_single_doublequote.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_single_quote.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_single_string_no_double_quotes.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_start_escape_unclosed.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_unescaped_ctrl_char.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_unescaped_newline.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_unescaped_tab.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_unicode_CapitalU.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_with_trailing_garbage.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_100000_opening_arrays.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_U+2060_word_joined.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_UTF8_BOM_no_data.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_angle_bracket_..json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_angle_bracket_null.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_array_trailing_garbage.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_array_with_extra_array_close.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_array_with_unclosed_string.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_ascii-unicode-identifier.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_capitalized_True.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_close_unopened_array.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_comma_instead_of_closing_brace.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_double_array.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_end_array.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_incomplete_UTF8_BOM.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_lone-invalid-utf-8.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_lone-open-bracket.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_no_data.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_null-byte-outside-string.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_number_with_trailing_garbage.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_object_followed_by_closing_object.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_object_unclosed_no_value.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_object_with_comment.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_object_with_trailing_garbage.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_array_apostrophe.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_array_comma.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_array_object.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_array_open_object.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_array_open_string.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_array_string.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_object.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_object_close_array.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_object_comma.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_object_open_array.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_object_open_string.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_object_string_with_apostrophes.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_open.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_single_eacute.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_single_star.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_trailing_#.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_uescaped_LF_before_string.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_unclosed_array.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_unclosed_array_partial_null.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_unclosed_array_unfinished_false.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_unclosed_array_unfinished_true.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_unclosed_object.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_unicode-identifier.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_whitespace_U+2060_word_joiner.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_whitespace_formfeed.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_array_arraysWithSpaces.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_array_empty-string.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_array_empty.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_array_ending_with_newline.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_array_false.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_array_heterogeneous.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_array_null.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_array_with_1_and_newline.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_array_with_leading_space.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_array_with_several_null.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_array_with_trailing_space.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_0e+1.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_0e1.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_after_space.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_double_close_to_zero.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_int_with_exp.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_minus_zero.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_negative_int.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_negative_one.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_negative_zero.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_real_capital_e.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_real_capital_e_neg_exp.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_real_capital_e_pos_exp.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_real_exponent.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_real_fraction_exponent.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_real_neg_exp.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_real_pos_exponent.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_simple_int.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_simple_real.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object_basic.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object_duplicated_key.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object_duplicated_key_and_value.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object_empty.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object_empty_key.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object_escaped_null_in_key.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object_extreme_numbers.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object_long_strings.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object_simple.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object_string_unicode.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object_with_newlines.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_1_2_3_bytes_UTF-8_sequences.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_accepted_surrogate_pair.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_accepted_surrogate_pairs.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_allowed_escapes.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_backslash_and_u_escaped_zero.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_backslash_doublequotes.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_comments.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_double_escape_a.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_double_escape_n.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_escaped_control_character.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_escaped_noncharacter.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_in_array.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_in_array_with_leading_space.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_last_surrogates_1_and_2.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_nbsp_uescaped.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_nonCharacterInUTF-8_U+10FFFF.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_nonCharacterInUTF-8_U+FFFF.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_null_escape.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_one-byte-utf-8.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_pi.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_reservedCharacterInUTF-8_U+1BFFF.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_simple_ascii.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_space.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_surrogates_U+1D11E_MUSICAL_SYMBOL_G_CLEF.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_three-byte-utf-8.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_two-byte-utf-8.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_u+2028_line_sep.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_u+2029_par_sep.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_uEscape.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_uescaped_newline.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_unescaped_char_delete.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_unicode.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_unicodeEscapedBackslash.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_unicode_2.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_unicode_U+10FFFE_nonchar.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_unicode_U+1FFFE_nonchar.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_unicode_U+200B_ZERO_WIDTH_SPACE.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_unicode_U+2064_invisible_plus.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_unicode_U+FDD0_nonchar.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_unicode_U+FFFE_nonchar.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_unicode_escaped_double_quote.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_utf8.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_with_del_character.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_structure_lonely_false.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_structure_lonely_int.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_structure_lonely_negative_real.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_structure_lonely_null.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_structure_lonely_string.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_structure_lonely_true.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_structure_string_empty.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_structure_trailing_newline.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_structure_true_in_array.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_structure_whitespace_array.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/number_-9223372036854775808.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/number_-9223372036854775809.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/number_1.0.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/number_1.000000000000000005.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/number_1000000000000000.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/number_10000000000000000999.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/number_1e-999.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/number_1e6.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/number_9223372036854775807.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/number_9223372036854775808.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/object_key_nfc_nfd.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/object_key_nfd_nfc.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/object_same_key_different_values.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/object_same_key_same_value.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/object_same_key_unclear_values.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/string_1_escaped_invalid_codepoint.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/string_1_invalid_codepoint.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/string_2_escaped_invalid_codepoints.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/string_2_invalid_codepoints.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/string_3_escaped_invalid_codepoints.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/string_3_invalid_codepoints.json create mode 100644 test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/string_with_escaped_NULL.json create mode 100644 test/jdk/jdk/incubator/json/TestJSONParsing.java diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/LICENSE.txt b/test/jdk/jdk/incubator/json/JSONTestSuite/LICENSE.txt new file mode 100644 index 000000000000..c4b3621d5d59 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016 Nicolas Seriot + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_number_double_huge_neg_exp.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_number_double_huge_neg_exp.json new file mode 100644 index 000000000000..ae4c7b71f620 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_number_double_huge_neg_exp.json @@ -0,0 +1 @@ +[123.456e-789] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_number_huge_exp.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_number_huge_exp.json new file mode 100644 index 000000000000..9b5efa236823 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_number_huge_exp.json @@ -0,0 +1 @@ +[0.4e00669999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999969999999006] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_number_neg_int_huge_exp.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_number_neg_int_huge_exp.json new file mode 100644 index 000000000000..3abd58a5c41a --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_number_neg_int_huge_exp.json @@ -0,0 +1 @@ +[-1e+9999] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_number_pos_double_huge_exp.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_number_pos_double_huge_exp.json new file mode 100644 index 000000000000..e10a7eb62a55 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_number_pos_double_huge_exp.json @@ -0,0 +1 @@ +[1.5e+9999] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_number_real_neg_overflow.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_number_real_neg_overflow.json new file mode 100644 index 000000000000..3d628a9943f9 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_number_real_neg_overflow.json @@ -0,0 +1 @@ +[-123123e100000] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_number_real_pos_overflow.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_number_real_pos_overflow.json new file mode 100644 index 000000000000..54d7d3dcdb19 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_number_real_pos_overflow.json @@ -0,0 +1 @@ +[123123e100000] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_number_real_underflow.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_number_real_underflow.json new file mode 100644 index 000000000000..c5236eb26bfd --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_number_real_underflow.json @@ -0,0 +1 @@ +[123e-10000000] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_number_too_big_neg_int.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_number_too_big_neg_int.json new file mode 100644 index 000000000000..dfa384619749 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_number_too_big_neg_int.json @@ -0,0 +1 @@ +[-123123123123123123123123123123] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_number_too_big_pos_int.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_number_too_big_pos_int.json new file mode 100644 index 000000000000..338a8c3c0be7 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_number_too_big_pos_int.json @@ -0,0 +1 @@ +[100000000000000000000] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_number_very_big_negative_int.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_number_very_big_negative_int.json new file mode 100644 index 000000000000..e2d9738c2390 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_number_very_big_negative_int.json @@ -0,0 +1 @@ +[-237462374673276894279832749832423479823246327846] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_object_key_lone_2nd_surrogate.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_object_key_lone_2nd_surrogate.json new file mode 100644 index 000000000000..5be7ebaf98d4 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_object_key_lone_2nd_surrogate.json @@ -0,0 +1 @@ +{"\uDFAA":0} \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_1st_surrogate_but_2nd_missing.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_1st_surrogate_but_2nd_missing.json new file mode 100644 index 000000000000..3b9e37c67aa0 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_1st_surrogate_but_2nd_missing.json @@ -0,0 +1 @@ +["\uDADA"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_1st_valid_surrogate_2nd_invalid.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_1st_valid_surrogate_2nd_invalid.json new file mode 100644 index 000000000000..487592832ed2 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_1st_valid_surrogate_2nd_invalid.json @@ -0,0 +1 @@ +["\uD888\u1234"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_UTF-16LE_with_BOM.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_UTF-16LE_with_BOM.json new file mode 100644 index 0000000000000000000000000000000000000000..2a79c0629a49133d8f715bddbef19cfb3ef025bd GIT binary patch literal 12 ScmezWFPcG#;Uy5qG5`P~Km+3d literal 0 HcmV?d00001 diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_UTF-8_invalid_sequence.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_UTF-8_invalid_sequence.json new file mode 100644 index 000000000000..e2a968a15942 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_UTF-8_invalid_sequence.json @@ -0,0 +1 @@ +["日шú"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_UTF8_surrogate_U+D800.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_UTF8_surrogate_U+D800.json new file mode 100644 index 000000000000..916bff920fa1 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_UTF8_surrogate_U+D800.json @@ -0,0 +1 @@ +["í €"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_incomplete_surrogate_and_escape_valid.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_incomplete_surrogate_and_escape_valid.json new file mode 100644 index 000000000000..3cb11d2294b0 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_incomplete_surrogate_and_escape_valid.json @@ -0,0 +1 @@ +["\uD800\n"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_incomplete_surrogate_pair.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_incomplete_surrogate_pair.json new file mode 100644 index 000000000000..38ec23bb0a95 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_incomplete_surrogate_pair.json @@ -0,0 +1 @@ +["\uDd1ea"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_incomplete_surrogates_escape_valid.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_incomplete_surrogates_escape_valid.json new file mode 100644 index 000000000000..c9cd6f6c31da --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_incomplete_surrogates_escape_valid.json @@ -0,0 +1 @@ +["\uD800\uD800\n"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_invalid_lonely_surrogate.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_invalid_lonely_surrogate.json new file mode 100644 index 000000000000..3abbd8d8d789 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_invalid_lonely_surrogate.json @@ -0,0 +1 @@ +["\ud800"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_invalid_surrogate.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_invalid_surrogate.json new file mode 100644 index 000000000000..ffddc04f52fb --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_invalid_surrogate.json @@ -0,0 +1 @@ +["\ud800abc"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_invalid_utf-8.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_invalid_utf-8.json new file mode 100644 index 000000000000..8e45a7ecae23 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_invalid_utf-8.json @@ -0,0 +1 @@ +["ÿ"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_inverted_surrogates_U+1D11E.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_inverted_surrogates_U+1D11E.json new file mode 100644 index 000000000000..0d5456cc38f4 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_inverted_surrogates_U+1D11E.json @@ -0,0 +1 @@ +["\uDd1e\uD834"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_iso_latin_1.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_iso_latin_1.json new file mode 100644 index 000000000000..9389c982311c --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_iso_latin_1.json @@ -0,0 +1 @@ +["é"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_lone_second_surrogate.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_lone_second_surrogate.json new file mode 100644 index 000000000000..1dbd397f3924 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_lone_second_surrogate.json @@ -0,0 +1 @@ +["\uDFAA"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_lone_utf8_continuation_byte.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_lone_utf8_continuation_byte.json new file mode 100644 index 000000000000..729337c0a354 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_lone_utf8_continuation_byte.json @@ -0,0 +1 @@ +["�"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_not_in_unicode_range.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_not_in_unicode_range.json new file mode 100644 index 000000000000..df90a2916c67 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_not_in_unicode_range.json @@ -0,0 +1 @@ +["ô¿¿¿"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_overlong_sequence_2_bytes.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_overlong_sequence_2_bytes.json new file mode 100644 index 000000000000..c8cee5e0a120 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_overlong_sequence_2_bytes.json @@ -0,0 +1 @@ +["À¯"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_overlong_sequence_6_bytes.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_overlong_sequence_6_bytes.json new file mode 100644 index 000000000000..9a91da791a9e --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_overlong_sequence_6_bytes.json @@ -0,0 +1 @@ +["üƒ¿¿¿¿"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_overlong_sequence_6_bytes_null.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_overlong_sequence_6_bytes_null.json new file mode 100644 index 000000000000..d24fffdd9821 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_overlong_sequence_6_bytes_null.json @@ -0,0 +1 @@ +["ü€€€€€"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_truncated-utf-8.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_truncated-utf-8.json new file mode 100644 index 000000000000..63c7777fb183 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_truncated-utf-8.json @@ -0,0 +1 @@ +["àÿ"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_utf16BE_no_BOM.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_utf16BE_no_BOM.json new file mode 100644 index 0000000000000000000000000000000000000000..57e5392ff6309134268c7e3ec2a9289b99ff7148 GIT binary patch literal 10 PcmZRGW>8{y3B<7g33~zN literal 0 HcmV?d00001 diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_utf16LE_no_BOM.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_utf16LE_no_BOM.json new file mode 100644 index 0000000000000000000000000000000000000000..c49c1b25d8e5819591993d4d45e4649a46c1b3e0 GIT binary patch literal 10 Pcma!MP-1uq#IXzj3t$1} literal 0 HcmV?d00001 diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_structure_500_nested_arrays.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_structure_500_nested_arrays.json new file mode 100644 index 000000000000..7118405898f5 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_structure_500_nested_arrays.json @@ -0,0 +1 @@ +[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_structure_UTF-8_BOM_empty_object.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_structure_UTF-8_BOM_empty_object.json new file mode 100644 index 000000000000..22fdca1b2686 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_structure_UTF-8_BOM_empty_object.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_1_true_without_comma.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_1_true_without_comma.json new file mode 100644 index 000000000000..c14e3f6b1e9d --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_1_true_without_comma.json @@ -0,0 +1 @@ +[1 true] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_a_invalid_utf8.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_a_invalid_utf8.json new file mode 100644 index 000000000000..38a86e2e6512 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_a_invalid_utf8.json @@ -0,0 +1 @@ +[aå] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_colon_instead_of_comma.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_colon_instead_of_comma.json new file mode 100644 index 000000000000..0d02ad448332 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_colon_instead_of_comma.json @@ -0,0 +1 @@ +["": 1] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_comma_after_close.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_comma_after_close.json new file mode 100644 index 000000000000..2ccba8d95033 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_comma_after_close.json @@ -0,0 +1 @@ +[""], \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_comma_and_number.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_comma_and_number.json new file mode 100644 index 000000000000..d2c84e374a2a --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_comma_and_number.json @@ -0,0 +1 @@ +[,1] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_double_comma.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_double_comma.json new file mode 100644 index 000000000000..0431712bc1db --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_double_comma.json @@ -0,0 +1 @@ +[1,,2] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_double_extra_comma.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_double_extra_comma.json new file mode 100644 index 000000000000..3f01d3129238 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_double_extra_comma.json @@ -0,0 +1 @@ +["x",,] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_extra_close.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_extra_close.json new file mode 100644 index 000000000000..c12f9fae1cbe --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_extra_close.json @@ -0,0 +1 @@ +["x"]] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_extra_comma.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_extra_comma.json new file mode 100644 index 000000000000..5f8ce18e4b2a --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_extra_comma.json @@ -0,0 +1 @@ +["",] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_incomplete.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_incomplete.json new file mode 100644 index 000000000000..cc65b0b512a0 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_incomplete.json @@ -0,0 +1 @@ +["x" \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_incomplete_invalid_value.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_incomplete_invalid_value.json new file mode 100644 index 000000000000..c21a8f6cff9b --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_incomplete_invalid_value.json @@ -0,0 +1 @@ +[x \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_inner_array_no_comma.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_inner_array_no_comma.json new file mode 100644 index 000000000000..c70b716471b7 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_inner_array_no_comma.json @@ -0,0 +1 @@ +[3[4]] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_invalid_utf8.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_invalid_utf8.json new file mode 100644 index 000000000000..6099d3441a41 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_invalid_utf8.json @@ -0,0 +1 @@ +[ÿ] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_items_separated_by_semicolon.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_items_separated_by_semicolon.json new file mode 100644 index 000000000000..d4bd7314ca87 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_items_separated_by_semicolon.json @@ -0,0 +1 @@ +[1:2] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_just_comma.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_just_comma.json new file mode 100644 index 000000000000..9d7077c6804a --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_just_comma.json @@ -0,0 +1 @@ +[,] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_just_minus.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_just_minus.json new file mode 100644 index 000000000000..29501c6ca23d --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_just_minus.json @@ -0,0 +1 @@ +[-] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_missing_value.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_missing_value.json new file mode 100644 index 000000000000..3a6ba86f3ab5 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_missing_value.json @@ -0,0 +1 @@ +[ , ""] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_newlines_unclosed.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_newlines_unclosed.json new file mode 100644 index 000000000000..646680065295 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_newlines_unclosed.json @@ -0,0 +1,3 @@ +["a", +4 +,1, \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_number_and_comma.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_number_and_comma.json new file mode 100644 index 000000000000..13f6f1d18a44 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_number_and_comma.json @@ -0,0 +1 @@ +[1,] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_number_and_several_commas.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_number_and_several_commas.json new file mode 100644 index 000000000000..0ac408cb8aeb --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_number_and_several_commas.json @@ -0,0 +1 @@ +[1,,] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_spaces_vertical_tab_formfeed.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_spaces_vertical_tab_formfeed.json new file mode 100644 index 000000000000..6cd7cf5855d7 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_spaces_vertical_tab_formfeed.json @@ -0,0 +1 @@ +[" a"\f] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_star_inside.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_star_inside.json new file mode 100644 index 000000000000..5a5194647ade --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_star_inside.json @@ -0,0 +1 @@ +[*] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_unclosed.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_unclosed.json new file mode 100644 index 000000000000..060733059011 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_unclosed.json @@ -0,0 +1 @@ +["" \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_unclosed_trailing_comma.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_unclosed_trailing_comma.json new file mode 100644 index 000000000000..6604698ffcd4 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_unclosed_trailing_comma.json @@ -0,0 +1 @@ +[1, \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_unclosed_with_new_lines.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_unclosed_with_new_lines.json new file mode 100644 index 000000000000..4f61de3fb1d0 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_unclosed_with_new_lines.json @@ -0,0 +1,3 @@ +[1, +1 +,1 \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_unclosed_with_object_inside.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_unclosed_with_object_inside.json new file mode 100644 index 000000000000..043a87e2db92 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_unclosed_with_object_inside.json @@ -0,0 +1 @@ +[{} \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_incomplete_false.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_incomplete_false.json new file mode 100644 index 000000000000..eb18c6a1437e --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_incomplete_false.json @@ -0,0 +1 @@ +[fals] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_incomplete_null.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_incomplete_null.json new file mode 100644 index 000000000000..c18ef5385103 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_incomplete_null.json @@ -0,0 +1 @@ +[nul] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_incomplete_true.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_incomplete_true.json new file mode 100644 index 000000000000..f451ac6d2e7a --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_incomplete_true.json @@ -0,0 +1 @@ +[tru] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_multidigit_number_then_00.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_multidigit_number_then_00.json new file mode 100644 index 0000000000000000000000000000000000000000..c22507b864f7f089c91c1eb85b1b15cc63b943a6 GIT binary patch literal 4 LcmXpsGG+h(0mJ~8 literal 0 HcmV?d00001 diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_++.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_++.json new file mode 100644 index 000000000000..bdb62aaf4d90 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_++.json @@ -0,0 +1 @@ +[++1234] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_+1.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_+1.json new file mode 100644 index 000000000000..3cbe58c92b1d --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_+1.json @@ -0,0 +1 @@ +[+1] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_+Inf.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_+Inf.json new file mode 100644 index 000000000000..871ae14d535f --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_+Inf.json @@ -0,0 +1 @@ +[+Inf] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_-01.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_-01.json new file mode 100644 index 000000000000..0df32bac8076 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_-01.json @@ -0,0 +1 @@ +[-01] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_-1.0..json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_-1.0..json new file mode 100644 index 000000000000..7cf55a85a739 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_-1.0..json @@ -0,0 +1 @@ +[-1.0.] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_-2..json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_-2..json new file mode 100644 index 000000000000..9be84365d096 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_-2..json @@ -0,0 +1 @@ +[-2.] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_-NaN.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_-NaN.json new file mode 100644 index 000000000000..f61615d404ea --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_-NaN.json @@ -0,0 +1 @@ +[-NaN] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_.-1.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_.-1.json new file mode 100644 index 000000000000..1c9f2dd1b71d --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_.-1.json @@ -0,0 +1 @@ +[.-1] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_.2e-3.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_.2e-3.json new file mode 100644 index 000000000000..c6c976f25776 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_.2e-3.json @@ -0,0 +1 @@ +[.2e-3] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_0.1.2.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_0.1.2.json new file mode 100644 index 000000000000..c83a25621e31 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_0.1.2.json @@ -0,0 +1 @@ +[0.1.2] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_0.3e+.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_0.3e+.json new file mode 100644 index 000000000000..a55a1bfefa33 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_0.3e+.json @@ -0,0 +1 @@ +[0.3e+] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_0.3e.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_0.3e.json new file mode 100644 index 000000000000..3dd5df4b3a85 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_0.3e.json @@ -0,0 +1 @@ +[0.3e] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_0.e1.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_0.e1.json new file mode 100644 index 000000000000..c92c71ccb23f --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_0.e1.json @@ -0,0 +1 @@ +[0.e1] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_0_capital_E+.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_0_capital_E+.json new file mode 100644 index 000000000000..3ba2c7d6d093 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_0_capital_E+.json @@ -0,0 +1 @@ +[0E+] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_0_capital_E.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_0_capital_E.json new file mode 100644 index 000000000000..5301840d1c31 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_0_capital_E.json @@ -0,0 +1 @@ +[0E] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_0e+.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_0e+.json new file mode 100644 index 000000000000..8ab0bc4b8b2c --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_0e+.json @@ -0,0 +1 @@ +[0e+] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_0e.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_0e.json new file mode 100644 index 000000000000..47ec421bb624 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_0e.json @@ -0,0 +1 @@ +[0e] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_1.0e+.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_1.0e+.json new file mode 100644 index 000000000000..cd84b9f69e3b --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_1.0e+.json @@ -0,0 +1 @@ +[1.0e+] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_1.0e-.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_1.0e-.json new file mode 100644 index 000000000000..4eb7afa0f99c --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_1.0e-.json @@ -0,0 +1 @@ +[1.0e-] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_1.0e.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_1.0e.json new file mode 100644 index 000000000000..21753f4c745f --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_1.0e.json @@ -0,0 +1 @@ +[1.0e] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_1_000.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_1_000.json new file mode 100644 index 000000000000..7b18b66b38ef --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_1_000.json @@ -0,0 +1 @@ +[1 000.0] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_1eE2.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_1eE2.json new file mode 100644 index 000000000000..4318a341d710 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_1eE2.json @@ -0,0 +1 @@ +[1eE2] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_2.e+3.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_2.e+3.json new file mode 100644 index 000000000000..4442f394dd73 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_2.e+3.json @@ -0,0 +1 @@ +[2.e+3] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_2.e-3.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_2.e-3.json new file mode 100644 index 000000000000..a65060edfcfd --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_2.e-3.json @@ -0,0 +1 @@ +[2.e-3] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_2.e3.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_2.e3.json new file mode 100644 index 000000000000..66f7cf701bc2 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_2.e3.json @@ -0,0 +1 @@ +[2.e3] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_9.e+.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_9.e+.json new file mode 100644 index 000000000000..732a7b11ce60 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_9.e+.json @@ -0,0 +1 @@ +[9.e+] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_Inf.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_Inf.json new file mode 100644 index 000000000000..c40c734c3cde --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_Inf.json @@ -0,0 +1 @@ +[Inf] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_NaN.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_NaN.json new file mode 100644 index 000000000000..4992317909e5 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_NaN.json @@ -0,0 +1 @@ +[NaN] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_U+FF11_fullwidth_digit_one.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_U+FF11_fullwidth_digit_one.json new file mode 100644 index 000000000000..b14587e5ebbb --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_U+FF11_fullwidth_digit_one.json @@ -0,0 +1 @@ +[1] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_expression.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_expression.json new file mode 100644 index 000000000000..76fdbc8a4999 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_expression.json @@ -0,0 +1 @@ +[1+2] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_hex_1_digit.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_hex_1_digit.json new file mode 100644 index 000000000000..3b214880c630 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_hex_1_digit.json @@ -0,0 +1 @@ +[0x1] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_hex_2_digits.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_hex_2_digits.json new file mode 100644 index 000000000000..83e516ab0e34 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_hex_2_digits.json @@ -0,0 +1 @@ +[0x42] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_infinity.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_infinity.json new file mode 100644 index 000000000000..8c2baf783a8e --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_infinity.json @@ -0,0 +1 @@ +[Infinity] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_invalid+-.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_invalid+-.json new file mode 100644 index 000000000000..1cce602b518f --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_invalid+-.json @@ -0,0 +1 @@ +[0e+-1] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_invalid-negative-real.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_invalid-negative-real.json new file mode 100644 index 000000000000..5fc3c1efb60c --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_invalid-negative-real.json @@ -0,0 +1 @@ +[-123.123foo] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_invalid-utf-8-in-bigger-int.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_invalid-utf-8-in-bigger-int.json new file mode 100644 index 000000000000..3b97e580e859 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_invalid-utf-8-in-bigger-int.json @@ -0,0 +1 @@ +[123å] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_invalid-utf-8-in-exponent.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_invalid-utf-8-in-exponent.json new file mode 100644 index 000000000000..ea35d723cdee --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_invalid-utf-8-in-exponent.json @@ -0,0 +1 @@ +[1e1å] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_invalid-utf-8-in-int.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_invalid-utf-8-in-int.json new file mode 100644 index 000000000000..371226e4cd93 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_invalid-utf-8-in-int.json @@ -0,0 +1 @@ +[0å] diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_minus_infinity.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_minus_infinity.json new file mode 100644 index 000000000000..cf4133d22d68 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_minus_infinity.json @@ -0,0 +1 @@ +[-Infinity] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_minus_sign_with_trailing_garbage.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_minus_sign_with_trailing_garbage.json new file mode 100644 index 000000000000..a6d8e78e7ce3 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_minus_sign_with_trailing_garbage.json @@ -0,0 +1 @@ +[-foo] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_minus_space_1.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_minus_space_1.json new file mode 100644 index 000000000000..9a5ebedf6797 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_minus_space_1.json @@ -0,0 +1 @@ +[- 1] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_neg_int_starting_with_zero.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_neg_int_starting_with_zero.json new file mode 100644 index 000000000000..67af0960af79 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_neg_int_starting_with_zero.json @@ -0,0 +1 @@ +[-012] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_neg_real_without_int_part.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_neg_real_without_int_part.json new file mode 100644 index 000000000000..1f2a43496ed9 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_neg_real_without_int_part.json @@ -0,0 +1 @@ +[-.123] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_neg_with_garbage_at_end.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_neg_with_garbage_at_end.json new file mode 100644 index 000000000000..2aa73119fbbc --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_neg_with_garbage_at_end.json @@ -0,0 +1 @@ +[-1x] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_real_garbage_after_e.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_real_garbage_after_e.json new file mode 100644 index 000000000000..9213dfca8de9 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_real_garbage_after_e.json @@ -0,0 +1 @@ +[1ea] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_real_with_invalid_utf8_after_e.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_real_with_invalid_utf8_after_e.json new file mode 100644 index 000000000000..1e52ef964cf0 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_real_with_invalid_utf8_after_e.json @@ -0,0 +1 @@ +[1eå] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_real_without_fractional_part.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_real_without_fractional_part.json new file mode 100644 index 000000000000..1de287cf892d --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_real_without_fractional_part.json @@ -0,0 +1 @@ +[1.] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_starting_with_dot.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_starting_with_dot.json new file mode 100644 index 000000000000..f682dbdce039 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_starting_with_dot.json @@ -0,0 +1 @@ +[.123] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_with_alpha.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_with_alpha.json new file mode 100644 index 000000000000..1e42d81822b2 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_with_alpha.json @@ -0,0 +1 @@ +[1.2a-3] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_with_alpha_char.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_with_alpha_char.json new file mode 100644 index 000000000000..b79daccb8a6c --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_with_alpha_char.json @@ -0,0 +1 @@ +[1.8011670033376514H-308] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_with_leading_zero.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_with_leading_zero.json new file mode 100644 index 000000000000..7106da1f3b85 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_with_leading_zero.json @@ -0,0 +1 @@ +[012] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_bad_value.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_bad_value.json new file mode 100644 index 000000000000..a03a8c03b72a --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_bad_value.json @@ -0,0 +1 @@ +["x", truth] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_bracket_key.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_bracket_key.json new file mode 100644 index 000000000000..cc443b483214 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_bracket_key.json @@ -0,0 +1 @@ +{[: "x"} diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_comma_instead_of_colon.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_comma_instead_of_colon.json new file mode 100644 index 000000000000..8d56377087d2 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_comma_instead_of_colon.json @@ -0,0 +1 @@ +{"x", null} \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_double_colon.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_double_colon.json new file mode 100644 index 000000000000..80e8c7b89a90 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_double_colon.json @@ -0,0 +1 @@ +{"x"::"b"} \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_emoji.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_emoji.json new file mode 100644 index 000000000000..cb4078eaa101 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_emoji.json @@ -0,0 +1 @@ +{🇨🇭} \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_garbage_at_end.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_garbage_at_end.json new file mode 100644 index 000000000000..80c42cbadcfc --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_garbage_at_end.json @@ -0,0 +1 @@ +{"a":"a" 123} \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_key_with_single_quotes.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_key_with_single_quotes.json new file mode 100644 index 000000000000..77c32759962b --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_key_with_single_quotes.json @@ -0,0 +1 @@ +{key: 'value'} \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_lone_continuation_byte_in_key_and_trailing_comma.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_lone_continuation_byte_in_key_and_trailing_comma.json new file mode 100644 index 000000000000..aa2cb637cd36 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_lone_continuation_byte_in_key_and_trailing_comma.json @@ -0,0 +1 @@ +{"¹":"0",} \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_missing_colon.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_missing_colon.json new file mode 100644 index 000000000000..b98eff62da4f --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_missing_colon.json @@ -0,0 +1 @@ +{"a" b} \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_missing_key.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_missing_key.json new file mode 100644 index 000000000000..b4fb0f528edb --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_missing_key.json @@ -0,0 +1 @@ +{:"b"} \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_missing_semicolon.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_missing_semicolon.json new file mode 100644 index 000000000000..e3451384f81a --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_missing_semicolon.json @@ -0,0 +1 @@ +{"a" "b"} \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_missing_value.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_missing_value.json new file mode 100644 index 000000000000..3ef538a60e69 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_missing_value.json @@ -0,0 +1 @@ +{"a": \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_no-colon.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_no-colon.json new file mode 100644 index 000000000000..f3797b357646 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_no-colon.json @@ -0,0 +1 @@ +{"a" \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_non_string_key.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_non_string_key.json new file mode 100644 index 000000000000..b9945b34b449 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_non_string_key.json @@ -0,0 +1 @@ +{1:1} \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_non_string_key_but_huge_number_instead.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_non_string_key_but_huge_number_instead.json new file mode 100644 index 000000000000..b37fa86c0e02 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_non_string_key_but_huge_number_instead.json @@ -0,0 +1 @@ +{9999E9999:1} \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_repeated_null_null.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_repeated_null_null.json new file mode 100644 index 000000000000..f7d2959d0d48 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_repeated_null_null.json @@ -0,0 +1 @@ +{null:null,null:null} \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_several_trailing_commas.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_several_trailing_commas.json new file mode 100644 index 000000000000..3c9afe8dc90d --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_several_trailing_commas.json @@ -0,0 +1 @@ +{"id":0,,,,,} \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_single_quote.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_single_quote.json new file mode 100644 index 000000000000..e5cdf976ad63 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_single_quote.json @@ -0,0 +1 @@ +{'a':0} \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_trailing_comma.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_trailing_comma.json new file mode 100644 index 000000000000..a4b02509459f --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_trailing_comma.json @@ -0,0 +1 @@ +{"id":0,} \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_trailing_comment.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_trailing_comment.json new file mode 100644 index 000000000000..a372c6553d2e --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_trailing_comment.json @@ -0,0 +1 @@ +{"a":"b"}/**/ \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_trailing_comment_open.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_trailing_comment_open.json new file mode 100644 index 000000000000..d557f41ca452 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_trailing_comment_open.json @@ -0,0 +1 @@ +{"a":"b"}/**// \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_trailing_comment_slash_open.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_trailing_comment_slash_open.json new file mode 100644 index 000000000000..e335136c0796 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_trailing_comment_slash_open.json @@ -0,0 +1 @@ +{"a":"b"}// \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_trailing_comment_slash_open_incomplete.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_trailing_comment_slash_open_incomplete.json new file mode 100644 index 000000000000..d892e49f1767 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_trailing_comment_slash_open_incomplete.json @@ -0,0 +1 @@ +{"a":"b"}/ \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_two_commas_in_a_row.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_two_commas_in_a_row.json new file mode 100644 index 000000000000..7c639ae64969 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_two_commas_in_a_row.json @@ -0,0 +1 @@ +{"a":"b",,"c":"d"} \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_unquoted_key.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_unquoted_key.json new file mode 100644 index 000000000000..8ba137293c3a --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_unquoted_key.json @@ -0,0 +1 @@ +{a: "b"} \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_unterminated-value.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_unterminated-value.json new file mode 100644 index 000000000000..7fe699a6a3fe --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_unterminated-value.json @@ -0,0 +1 @@ +{"a":"a \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_with_single_string.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_with_single_string.json new file mode 100644 index 000000000000..d63f7fbb7e3c --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_with_single_string.json @@ -0,0 +1 @@ +{ "foo" : "bar", "a" } \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_with_trailing_garbage.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_with_trailing_garbage.json new file mode 100644 index 000000000000..787c8f0a8c03 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_with_trailing_garbage.json @@ -0,0 +1 @@ +{"a":"b"}# \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_single_space.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_single_space.json new file mode 100644 index 000000000000..0519ecba6ea9 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_single_space.json @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_1_surrogate_then_escape.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_1_surrogate_then_escape.json new file mode 100644 index 000000000000..acec66d8f4bf --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_1_surrogate_then_escape.json @@ -0,0 +1 @@ +["\uD800\"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_1_surrogate_then_escape_u.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_1_surrogate_then_escape_u.json new file mode 100644 index 000000000000..e834b05e9637 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_1_surrogate_then_escape_u.json @@ -0,0 +1 @@ +["\uD800\u"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_1_surrogate_then_escape_u1.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_1_surrogate_then_escape_u1.json new file mode 100644 index 000000000000..a04cd348928d --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_1_surrogate_then_escape_u1.json @@ -0,0 +1 @@ +["\uD800\u1"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_1_surrogate_then_escape_u1x.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_1_surrogate_then_escape_u1x.json new file mode 100644 index 000000000000..bfbd234098fe --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_1_surrogate_then_escape_u1x.json @@ -0,0 +1 @@ +["\uD800\u1x"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_accentuated_char_no_quotes.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_accentuated_char_no_quotes.json new file mode 100644 index 000000000000..fd6895693f52 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_accentuated_char_no_quotes.json @@ -0,0 +1 @@ +[é] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_backslash_00.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_backslash_00.json new file mode 100644 index 0000000000000000000000000000000000000000..b5bf267b5d4ee922d20cec1543b66d1530ff76cf GIT binary patch literal 6 Ncma!6ieXTS1pox&0a*Y5 literal 0 HcmV?d00001 diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_escape_x.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_escape_x.json new file mode 100644 index 000000000000..fae291938d9d --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_escape_x.json @@ -0,0 +1 @@ +["\x00"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_escaped_backslash_bad.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_escaped_backslash_bad.json new file mode 100644 index 000000000000..016fcb47ef52 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_escaped_backslash_bad.json @@ -0,0 +1 @@ +["\\\"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_escaped_ctrl_char_tab.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_escaped_ctrl_char_tab.json new file mode 100644 index 000000000000..f35ea382bb03 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_escaped_ctrl_char_tab.json @@ -0,0 +1 @@ +["\ "] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_escaped_emoji.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_escaped_emoji.json new file mode 100644 index 000000000000..a27775421363 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_escaped_emoji.json @@ -0,0 +1 @@ +["\🌀"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_incomplete_escape.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_incomplete_escape.json new file mode 100644 index 000000000000..3415c33ca8ab --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_incomplete_escape.json @@ -0,0 +1 @@ +["\"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_incomplete_escaped_character.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_incomplete_escaped_character.json new file mode 100644 index 000000000000..0f2197ea2901 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_incomplete_escaped_character.json @@ -0,0 +1 @@ +["\u00A"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_incomplete_surrogate.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_incomplete_surrogate.json new file mode 100644 index 000000000000..75504a656188 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_incomplete_surrogate.json @@ -0,0 +1 @@ +["\uD834\uDd"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_incomplete_surrogate_escape_invalid.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_incomplete_surrogate_escape_invalid.json new file mode 100644 index 000000000000..bd9656060df4 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_incomplete_surrogate_escape_invalid.json @@ -0,0 +1 @@ +["\uD800\uD800\x"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_invalid-utf-8-in-escape.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_invalid-utf-8-in-escape.json new file mode 100644 index 000000000000..0c43006430d3 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_invalid-utf-8-in-escape.json @@ -0,0 +1 @@ +["\uå"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_invalid_backslash_esc.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_invalid_backslash_esc.json new file mode 100644 index 000000000000..d1eb60921a08 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_invalid_backslash_esc.json @@ -0,0 +1 @@ +["\a"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_invalid_unicode_escape.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_invalid_unicode_escape.json new file mode 100644 index 000000000000..7608cb6ba896 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_invalid_unicode_escape.json @@ -0,0 +1 @@ +["\uqqqq"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_invalid_utf8_after_escape.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_invalid_utf8_after_escape.json new file mode 100644 index 000000000000..2f757a25b545 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_invalid_utf8_after_escape.json @@ -0,0 +1 @@ +["\å"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_leading_uescaped_thinspace.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_leading_uescaped_thinspace.json new file mode 100644 index 000000000000..7b297c6365af --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_leading_uescaped_thinspace.json @@ -0,0 +1 @@ +[\u0020"asd"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_no_quotes_with_bad_escape.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_no_quotes_with_bad_escape.json new file mode 100644 index 000000000000..01bc70abae62 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_no_quotes_with_bad_escape.json @@ -0,0 +1 @@ +[\n] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_single_doublequote.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_single_doublequote.json new file mode 100644 index 000000000000..9d68933c44f1 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_single_doublequote.json @@ -0,0 +1 @@ +" \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_single_quote.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_single_quote.json new file mode 100644 index 000000000000..caff239bfc36 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_single_quote.json @@ -0,0 +1 @@ +['single quote'] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_single_string_no_double_quotes.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_single_string_no_double_quotes.json new file mode 100644 index 000000000000..f2ba8f84ab5c --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_single_string_no_double_quotes.json @@ -0,0 +1 @@ +abc \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_start_escape_unclosed.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_start_escape_unclosed.json new file mode 100644 index 000000000000..db62a46fcbc6 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_start_escape_unclosed.json @@ -0,0 +1 @@ +["\ \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_unescaped_ctrl_char.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_unescaped_ctrl_char.json new file mode 100644 index 0000000000000000000000000000000000000000..9f21348071d3d736bdd469f159cea674c09237af GIT binary patch literal 7 Ocma!6N@Pe>iUj}$`2oKG literal 0 HcmV?d00001 diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_unescaped_newline.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_unescaped_newline.json new file mode 100644 index 000000000000..700d36086914 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_unescaped_newline.json @@ -0,0 +1,2 @@ +["new +line"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_unescaped_tab.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_unescaped_tab.json new file mode 100644 index 000000000000..160264a2d995 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_unescaped_tab.json @@ -0,0 +1 @@ +[" "] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_unicode_CapitalU.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_unicode_CapitalU.json new file mode 100644 index 000000000000..17332bb174cb --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_unicode_CapitalU.json @@ -0,0 +1 @@ +"\UA66D" \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_with_trailing_garbage.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_with_trailing_garbage.json new file mode 100644 index 000000000000..efe3bd272c4e --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_with_trailing_garbage.json @@ -0,0 +1 @@ +""x \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_100000_opening_arrays.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_100000_opening_arrays.json new file mode 100644 index 000000000000..a4823eeccebc --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_100000_opening_arrays.json @@ -0,0 +1 @@ +[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[ \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_U+2060_word_joined.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_U+2060_word_joined.json new file mode 100644 index 000000000000..81156a6996ed --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_U+2060_word_joined.json @@ -0,0 +1 @@ +[â� ] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_UTF8_BOM_no_data.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_UTF8_BOM_no_data.json new file mode 100644 index 000000000000..5f282702bb03 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_UTF8_BOM_no_data.json @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_angle_bracket_..json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_angle_bracket_..json new file mode 100644 index 000000000000..a56fef0b021f --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_angle_bracket_..json @@ -0,0 +1 @@ +<.> \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_angle_bracket_null.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_angle_bracket_null.json new file mode 100644 index 000000000000..617f26254934 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_angle_bracket_null.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_array_trailing_garbage.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_array_trailing_garbage.json new file mode 100644 index 000000000000..5a745e6f3c36 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_array_trailing_garbage.json @@ -0,0 +1 @@ +[1]x \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_array_with_extra_array_close.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_array_with_extra_array_close.json new file mode 100644 index 000000000000..6cfb1398d224 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_array_with_extra_array_close.json @@ -0,0 +1 @@ +[1]] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_array_with_unclosed_string.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_array_with_unclosed_string.json new file mode 100644 index 000000000000..ba6b1788b672 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_array_with_unclosed_string.json @@ -0,0 +1 @@ +["asd] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_ascii-unicode-identifier.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_ascii-unicode-identifier.json new file mode 100644 index 000000000000..ef2ab62fe763 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_ascii-unicode-identifier.json @@ -0,0 +1 @@ +aÃ¥ \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_capitalized_True.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_capitalized_True.json new file mode 100644 index 000000000000..7cd88469ab3d --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_capitalized_True.json @@ -0,0 +1 @@ +[True] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_close_unopened_array.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_close_unopened_array.json new file mode 100644 index 000000000000..d2af0c646a8f --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_close_unopened_array.json @@ -0,0 +1 @@ +1] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_comma_instead_of_closing_brace.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_comma_instead_of_closing_brace.json new file mode 100644 index 000000000000..ac61b820068b --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_comma_instead_of_closing_brace.json @@ -0,0 +1 @@ +{"x": true, \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_double_array.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_double_array.json new file mode 100644 index 000000000000..058d1626e5af --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_double_array.json @@ -0,0 +1 @@ +[][] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_end_array.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_end_array.json new file mode 100644 index 000000000000..54caf60b1367 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_end_array.json @@ -0,0 +1 @@ +] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_incomplete_UTF8_BOM.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_incomplete_UTF8_BOM.json new file mode 100644 index 000000000000..bfcdd514f330 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_incomplete_UTF8_BOM.json @@ -0,0 +1 @@ +ï»{} \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_lone-invalid-utf-8.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_lone-invalid-utf-8.json new file mode 100644 index 000000000000..8b1296cad20a --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_lone-invalid-utf-8.json @@ -0,0 +1 @@ +å \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_lone-open-bracket.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_lone-open-bracket.json new file mode 100644 index 000000000000..8e2f0bef135b --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_lone-open-bracket.json @@ -0,0 +1 @@ +[ \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_no_data.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_no_data.json new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_null-byte-outside-string.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_null-byte-outside-string.json new file mode 100644 index 0000000000000000000000000000000000000000..326db14422a756e9bd39221edf37b844cb8471c7 GIT binary patch literal 3 Kcma!Mhy?%vaR9jh literal 0 HcmV?d00001 diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_number_with_trailing_garbage.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_number_with_trailing_garbage.json new file mode 100644 index 000000000000..0746539d246c --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_number_with_trailing_garbage.json @@ -0,0 +1 @@ +2@ \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_object_followed_by_closing_object.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_object_followed_by_closing_object.json new file mode 100644 index 000000000000..aa9ebaec5709 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_object_followed_by_closing_object.json @@ -0,0 +1 @@ +{}} \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_object_unclosed_no_value.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_object_unclosed_no_value.json new file mode 100644 index 000000000000..17d045147fe5 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_object_unclosed_no_value.json @@ -0,0 +1 @@ +{"": \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_object_with_comment.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_object_with_comment.json new file mode 100644 index 000000000000..ed1b569b7049 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_object_with_comment.json @@ -0,0 +1 @@ +{"a":/*comment*/"b"} \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_object_with_trailing_garbage.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_object_with_trailing_garbage.json new file mode 100644 index 000000000000..9ca2336d7451 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_object_with_trailing_garbage.json @@ -0,0 +1 @@ +{"a": true} "x" \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_array_apostrophe.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_array_apostrophe.json new file mode 100644 index 000000000000..8bebe3af09a7 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_array_apostrophe.json @@ -0,0 +1 @@ +[' \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_array_comma.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_array_comma.json new file mode 100644 index 000000000000..6295fdc36db0 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_array_comma.json @@ -0,0 +1 @@ +[, \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_array_object.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_array_object.json new file mode 100644 index 000000000000..e870445b2e25 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_array_object.json @@ -0,0 +1 @@ +[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"": diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_array_open_object.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_array_open_object.json new file mode 100644 index 000000000000..7a63c8c57c8a --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_array_open_object.json @@ -0,0 +1 @@ +[{ \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_array_open_string.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_array_open_string.json new file mode 100644 index 000000000000..9822a6baf7e5 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_array_open_string.json @@ -0,0 +1 @@ +["a \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_array_string.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_array_string.json new file mode 100644 index 000000000000..42a619362021 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_array_string.json @@ -0,0 +1 @@ +["a" \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_object.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_object.json new file mode 100644 index 000000000000..81750b96f9d8 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_object.json @@ -0,0 +1 @@ +{ \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_object_close_array.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_object_close_array.json new file mode 100644 index 000000000000..eebc700a1013 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_object_close_array.json @@ -0,0 +1 @@ +{] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_object_comma.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_object_comma.json new file mode 100644 index 000000000000..47bc9106f86a --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_object_comma.json @@ -0,0 +1 @@ +{, \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_object_open_array.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_object_open_array.json new file mode 100644 index 000000000000..381ede5dea5f --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_object_open_array.json @@ -0,0 +1 @@ +{[ \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_object_open_string.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_object_open_string.json new file mode 100644 index 000000000000..328c30cd782a --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_object_open_string.json @@ -0,0 +1 @@ +{"a \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_object_string_with_apostrophes.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_object_string_with_apostrophes.json new file mode 100644 index 000000000000..9dba17090cf6 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_object_string_with_apostrophes.json @@ -0,0 +1 @@ +{'a' \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_open.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_open.json new file mode 100644 index 000000000000..841fd5f8640e --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_open.json @@ -0,0 +1 @@ +["\{["\{["\{["\{ \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_single_eacute.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_single_eacute.json new file mode 100644 index 000000000000..92a39f398b80 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_single_eacute.json @@ -0,0 +1 @@ +é \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_single_star.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_single_star.json new file mode 100644 index 000000000000..f59ec20aabf5 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_single_star.json @@ -0,0 +1 @@ +* \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_trailing_#.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_trailing_#.json new file mode 100644 index 000000000000..8986110875af --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_trailing_#.json @@ -0,0 +1 @@ +{"a":"b"}#{} \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_uescaped_LF_before_string.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_uescaped_LF_before_string.json new file mode 100644 index 000000000000..df2f0f242244 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_uescaped_LF_before_string.json @@ -0,0 +1 @@ +[\u000A""] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_unclosed_array.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_unclosed_array.json new file mode 100644 index 000000000000..11209515c8dd --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_unclosed_array.json @@ -0,0 +1 @@ +[1 \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_unclosed_array_partial_null.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_unclosed_array_partial_null.json new file mode 100644 index 000000000000..0d591762c127 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_unclosed_array_partial_null.json @@ -0,0 +1 @@ +[ false, nul \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_unclosed_array_unfinished_false.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_unclosed_array_unfinished_false.json new file mode 100644 index 000000000000..a2ff8504a9c6 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_unclosed_array_unfinished_false.json @@ -0,0 +1 @@ +[ true, fals \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_unclosed_array_unfinished_true.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_unclosed_array_unfinished_true.json new file mode 100644 index 000000000000..3149e8f5a7a5 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_unclosed_array_unfinished_true.json @@ -0,0 +1 @@ +[ false, tru \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_unclosed_object.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_unclosed_object.json new file mode 100644 index 000000000000..694d69dbd00c --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_unclosed_object.json @@ -0,0 +1 @@ +{"asd":"asd" \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_unicode-identifier.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_unicode-identifier.json new file mode 100644 index 000000000000..7284aea33d3f --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_unicode-identifier.json @@ -0,0 +1 @@ +Ã¥ \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_whitespace_U+2060_word_joiner.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_whitespace_U+2060_word_joiner.json new file mode 100644 index 000000000000..81156a6996ed --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_whitespace_U+2060_word_joiner.json @@ -0,0 +1 @@ +[â� ] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_whitespace_formfeed.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_whitespace_formfeed.json new file mode 100644 index 000000000000..a9ea535d1bbe --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_whitespace_formfeed.json @@ -0,0 +1 @@ +[ ] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_array_arraysWithSpaces.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_array_arraysWithSpaces.json new file mode 100644 index 000000000000..582290798f43 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_array_arraysWithSpaces.json @@ -0,0 +1 @@ +[[] ] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_array_empty-string.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_array_empty-string.json new file mode 100644 index 000000000000..93b6be2bccad --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_array_empty-string.json @@ -0,0 +1 @@ +[""] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_array_empty.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_array_empty.json new file mode 100644 index 000000000000..0637a088a01e --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_array_empty.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_array_ending_with_newline.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_array_ending_with_newline.json new file mode 100644 index 000000000000..eac5f7b46e04 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_array_ending_with_newline.json @@ -0,0 +1 @@ +["a"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_array_false.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_array_false.json new file mode 100644 index 000000000000..67b2f07601e4 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_array_false.json @@ -0,0 +1 @@ +[false] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_array_heterogeneous.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_array_heterogeneous.json new file mode 100644 index 000000000000..d3c1e264845e --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_array_heterogeneous.json @@ -0,0 +1 @@ +[null, 1, "1", {}] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_array_null.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_array_null.json new file mode 100644 index 000000000000..500db4a86aa3 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_array_null.json @@ -0,0 +1 @@ +[null] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_array_with_1_and_newline.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_array_with_1_and_newline.json new file mode 100644 index 000000000000..994825500ae3 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_array_with_1_and_newline.json @@ -0,0 +1,2 @@ +[1 +] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_array_with_leading_space.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_array_with_leading_space.json new file mode 100644 index 000000000000..18bfe6422c78 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_array_with_leading_space.json @@ -0,0 +1 @@ + [1] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_array_with_several_null.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_array_with_several_null.json new file mode 100644 index 000000000000..99f6c5d1d883 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_array_with_several_null.json @@ -0,0 +1 @@ +[1,null,null,null,2] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_array_with_trailing_space.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_array_with_trailing_space.json new file mode 100644 index 000000000000..de9e7a94492d --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_array_with_trailing_space.json @@ -0,0 +1 @@ +[2] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number.json new file mode 100644 index 000000000000..e5f5cc3340d2 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number.json @@ -0,0 +1 @@ +[123e65] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_0e+1.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_0e+1.json new file mode 100644 index 000000000000..d1d3967065cf --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_0e+1.json @@ -0,0 +1 @@ +[0e+1] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_0e1.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_0e1.json new file mode 100644 index 000000000000..3283a7936514 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_0e1.json @@ -0,0 +1 @@ +[0e1] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_after_space.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_after_space.json new file mode 100644 index 000000000000..623570d96040 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_after_space.json @@ -0,0 +1 @@ +[ 4] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_double_close_to_zero.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_double_close_to_zero.json new file mode 100644 index 000000000000..96555ff78230 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_double_close_to_zero.json @@ -0,0 +1 @@ +[-0.000000000000000000000000000000000000000000000000000000000000000000000000000001] diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_int_with_exp.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_int_with_exp.json new file mode 100644 index 000000000000..a4ca9e754fb0 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_int_with_exp.json @@ -0,0 +1 @@ +[20e1] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_minus_zero.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_minus_zero.json new file mode 100644 index 000000000000..37af1312ab5d --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_minus_zero.json @@ -0,0 +1 @@ +[-0] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_negative_int.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_negative_int.json new file mode 100644 index 000000000000..8e30f8bd9660 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_negative_int.json @@ -0,0 +1 @@ +[-123] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_negative_one.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_negative_one.json new file mode 100644 index 000000000000..99d21a2a0f09 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_negative_one.json @@ -0,0 +1 @@ +[-1] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_negative_zero.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_negative_zero.json new file mode 100644 index 000000000000..37af1312ab5d --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_negative_zero.json @@ -0,0 +1 @@ +[-0] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_real_capital_e.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_real_capital_e.json new file mode 100644 index 000000000000..6edbdfcb180d --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_real_capital_e.json @@ -0,0 +1 @@ +[1E22] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_real_capital_e_neg_exp.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_real_capital_e_neg_exp.json new file mode 100644 index 000000000000..0a01bd3ef4ca --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_real_capital_e_neg_exp.json @@ -0,0 +1 @@ +[1E-2] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_real_capital_e_pos_exp.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_real_capital_e_pos_exp.json new file mode 100644 index 000000000000..5a8fc0972425 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_real_capital_e_pos_exp.json @@ -0,0 +1 @@ +[1E+2] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_real_exponent.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_real_exponent.json new file mode 100644 index 000000000000..da2522d61a1e --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_real_exponent.json @@ -0,0 +1 @@ +[123e45] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_real_fraction_exponent.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_real_fraction_exponent.json new file mode 100644 index 000000000000..3944a7a454b3 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_real_fraction_exponent.json @@ -0,0 +1 @@ +[123.456e78] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_real_neg_exp.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_real_neg_exp.json new file mode 100644 index 000000000000..ca40d3c25538 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_real_neg_exp.json @@ -0,0 +1 @@ +[1e-2] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_real_pos_exponent.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_real_pos_exponent.json new file mode 100644 index 000000000000..343601d51fdd --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_real_pos_exponent.json @@ -0,0 +1 @@ +[1e+2] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_simple_int.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_simple_int.json new file mode 100644 index 000000000000..e47f69afcf45 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_simple_int.json @@ -0,0 +1 @@ +[123] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_simple_real.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_simple_real.json new file mode 100644 index 000000000000..b02878e5fc19 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_simple_real.json @@ -0,0 +1 @@ +[123.456789] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object.json new file mode 100644 index 000000000000..78262eda3fa9 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object.json @@ -0,0 +1 @@ +{"asd":"sdf", "dfg":"fgh"} \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object_basic.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object_basic.json new file mode 100644 index 000000000000..646bbe7bb1f8 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object_basic.json @@ -0,0 +1 @@ +{"asd":"sdf"} \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object_duplicated_key.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object_duplicated_key.json new file mode 100644 index 000000000000..bbc2e1ce433c --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object_duplicated_key.json @@ -0,0 +1 @@ +{"a":"b","a":"c"} \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object_duplicated_key_and_value.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object_duplicated_key_and_value.json new file mode 100644 index 000000000000..211581c20717 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object_duplicated_key_and_value.json @@ -0,0 +1 @@ +{"a":"b","a":"b"} \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object_empty.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object_empty.json new file mode 100644 index 000000000000..9e26dfeeb6e6 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object_empty.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object_empty_key.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object_empty_key.json new file mode 100644 index 000000000000..c0013d3b8bd2 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object_empty_key.json @@ -0,0 +1 @@ +{"":0} \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object_escaped_null_in_key.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object_escaped_null_in_key.json new file mode 100644 index 000000000000..593f0f67f9cb --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object_escaped_null_in_key.json @@ -0,0 +1 @@ +{"foo\u0000bar": 42} \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object_extreme_numbers.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object_extreme_numbers.json new file mode 100644 index 000000000000..a0d3531c32f9 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object_extreme_numbers.json @@ -0,0 +1 @@ +{ "min": -1.0e+28, "max": 1.0e+28 } \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object_long_strings.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object_long_strings.json new file mode 100644 index 000000000000..bdc4a08719ef --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object_long_strings.json @@ -0,0 +1 @@ +{"x":[{"id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"}], "id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"} \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object_simple.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object_simple.json new file mode 100644 index 000000000000..dacac917fb7b --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object_simple.json @@ -0,0 +1 @@ +{"a":[]} \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object_string_unicode.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object_string_unicode.json new file mode 100644 index 000000000000..8effdb297c78 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object_string_unicode.json @@ -0,0 +1 @@ +{"title":"\u041f\u043e\u043b\u0442\u043e\u0440\u0430 \u0417\u0435\u043c\u043b\u0435\u043a\u043e\u043f\u0430" } \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object_with_newlines.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object_with_newlines.json new file mode 100644 index 000000000000..246ec6b34d5e --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object_with_newlines.json @@ -0,0 +1,3 @@ +{ +"a": "b" +} \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_1_2_3_bytes_UTF-8_sequences.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_1_2_3_bytes_UTF-8_sequences.json new file mode 100644 index 000000000000..9967ddeb8b11 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_1_2_3_bytes_UTF-8_sequences.json @@ -0,0 +1 @@ +["\u0060\u012a\u12AB"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_accepted_surrogate_pair.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_accepted_surrogate_pair.json new file mode 100644 index 000000000000..996875cc8c96 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_accepted_surrogate_pair.json @@ -0,0 +1 @@ +["\uD801\udc37"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_accepted_surrogate_pairs.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_accepted_surrogate_pairs.json new file mode 100644 index 000000000000..3401021ecef5 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_accepted_surrogate_pairs.json @@ -0,0 +1 @@ +["\ud83d\ude39\ud83d\udc8d"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_allowed_escapes.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_allowed_escapes.json new file mode 100644 index 000000000000..7f495532fb37 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_allowed_escapes.json @@ -0,0 +1 @@ +["\"\\\/\b\f\n\r\t"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_backslash_and_u_escaped_zero.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_backslash_and_u_escaped_zero.json new file mode 100644 index 000000000000..d4439eda73ac --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_backslash_and_u_escaped_zero.json @@ -0,0 +1 @@ +["\\u0000"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_backslash_doublequotes.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_backslash_doublequotes.json new file mode 100644 index 000000000000..ae03243b6742 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_backslash_doublequotes.json @@ -0,0 +1 @@ +["\""] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_comments.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_comments.json new file mode 100644 index 000000000000..2260c20c2f86 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_comments.json @@ -0,0 +1 @@ +["a/*b*/c/*d//e"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_double_escape_a.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_double_escape_a.json new file mode 100644 index 000000000000..6715d6f4049f --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_double_escape_a.json @@ -0,0 +1 @@ +["\\a"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_double_escape_n.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_double_escape_n.json new file mode 100644 index 000000000000..44ca56c4d942 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_double_escape_n.json @@ -0,0 +1 @@ +["\\n"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_escaped_control_character.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_escaped_control_character.json new file mode 100644 index 000000000000..5b014a9c25b5 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_escaped_control_character.json @@ -0,0 +1 @@ +["\u0012"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_escaped_noncharacter.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_escaped_noncharacter.json new file mode 100644 index 000000000000..2ff52e2c50bc --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_escaped_noncharacter.json @@ -0,0 +1 @@ +["\uFFFF"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_in_array.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_in_array.json new file mode 100644 index 000000000000..21d7ae4cd80b --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_in_array.json @@ -0,0 +1 @@ +["asd"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_in_array_with_leading_space.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_in_array_with_leading_space.json new file mode 100644 index 000000000000..9e1887c1e4d2 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_in_array_with_leading_space.json @@ -0,0 +1 @@ +[ "asd"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_last_surrogates_1_and_2.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_last_surrogates_1_and_2.json new file mode 100644 index 000000000000..3919cef76579 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_last_surrogates_1_and_2.json @@ -0,0 +1 @@ +["\uDBFF\uDFFF"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_nbsp_uescaped.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_nbsp_uescaped.json new file mode 100644 index 000000000000..2085ab1a1c77 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_nbsp_uescaped.json @@ -0,0 +1 @@ +["new\u00A0line"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_nonCharacterInUTF-8_U+10FFFF.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_nonCharacterInUTF-8_U+10FFFF.json new file mode 100644 index 000000000000..059e4d9dd0f4 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_nonCharacterInUTF-8_U+10FFFF.json @@ -0,0 +1 @@ +["ô�¿¿"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_nonCharacterInUTF-8_U+FFFF.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_nonCharacterInUTF-8_U+FFFF.json new file mode 100644 index 000000000000..4c913bd41a08 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_nonCharacterInUTF-8_U+FFFF.json @@ -0,0 +1 @@ +["ï¿¿"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_null_escape.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_null_escape.json new file mode 100644 index 000000000000..c1ad844043e6 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_null_escape.json @@ -0,0 +1 @@ +["\u0000"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_one-byte-utf-8.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_one-byte-utf-8.json new file mode 100644 index 000000000000..157185923ac7 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_one-byte-utf-8.json @@ -0,0 +1 @@ +["\u002c"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_pi.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_pi.json new file mode 100644 index 000000000000..9df11ae88bde --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_pi.json @@ -0,0 +1 @@ +["Ï€"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_reservedCharacterInUTF-8_U+1BFFF.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_reservedCharacterInUTF-8_U+1BFFF.json new file mode 100644 index 000000000000..10a33a1717ec --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_reservedCharacterInUTF-8_U+1BFFF.json @@ -0,0 +1 @@ +["𛿿"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_simple_ascii.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_simple_ascii.json new file mode 100644 index 000000000000..8cadf7d051df --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_simple_ascii.json @@ -0,0 +1 @@ +["asd "] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_space.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_space.json new file mode 100644 index 000000000000..efd782cc3250 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_space.json @@ -0,0 +1 @@ +" " \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_surrogates_U+1D11E_MUSICAL_SYMBOL_G_CLEF.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_surrogates_U+1D11E_MUSICAL_SYMBOL_G_CLEF.json new file mode 100644 index 000000000000..7620b66559f3 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_surrogates_U+1D11E_MUSICAL_SYMBOL_G_CLEF.json @@ -0,0 +1 @@ +["\uD834\uDd1e"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_three-byte-utf-8.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_three-byte-utf-8.json new file mode 100644 index 000000000000..108f1d67dffc --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_three-byte-utf-8.json @@ -0,0 +1 @@ +["\u0821"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_two-byte-utf-8.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_two-byte-utf-8.json new file mode 100644 index 000000000000..461503c31001 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_two-byte-utf-8.json @@ -0,0 +1 @@ +["\u0123"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_u+2028_line_sep.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_u+2028_line_sep.json new file mode 100644 index 000000000000..897b6021af72 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_u+2028_line_sep.json @@ -0,0 +1 @@ +["
"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_u+2029_par_sep.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_u+2029_par_sep.json new file mode 100644 index 000000000000..8cd998c89ea3 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_u+2029_par_sep.json @@ -0,0 +1 @@ +["
"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_uEscape.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_uEscape.json new file mode 100644 index 000000000000..f7b41a02fa5e --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_uEscape.json @@ -0,0 +1 @@ +["\u0061\u30af\u30EA\u30b9"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_uescaped_newline.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_uescaped_newline.json new file mode 100644 index 000000000000..3a5a220b69cc --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_uescaped_newline.json @@ -0,0 +1 @@ +["new\u000Aline"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_unescaped_char_delete.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_unescaped_char_delete.json new file mode 100644 index 000000000000..7d064f498715 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_unescaped_char_delete.json @@ -0,0 +1 @@ +[""] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_unicode.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_unicode.json new file mode 100644 index 000000000000..3598095b79be --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_unicode.json @@ -0,0 +1 @@ +["\uA66D"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_unicodeEscapedBackslash.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_unicodeEscapedBackslash.json new file mode 100644 index 000000000000..0bb3b51e7eae --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_unicodeEscapedBackslash.json @@ -0,0 +1 @@ +["\u005C"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_unicode_2.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_unicode_2.json new file mode 100644 index 000000000000..a7dcb97683f7 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_unicode_2.json @@ -0,0 +1 @@ +["â�‚㈴â�‚"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_unicode_U+10FFFE_nonchar.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_unicode_U+10FFFE_nonchar.json new file mode 100644 index 000000000000..9a8370b96a1c --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_unicode_U+10FFFE_nonchar.json @@ -0,0 +1 @@ +["\uDBFF\uDFFE"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_unicode_U+1FFFE_nonchar.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_unicode_U+1FFFE_nonchar.json new file mode 100644 index 000000000000..c51f8ae45c3c --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_unicode_U+1FFFE_nonchar.json @@ -0,0 +1 @@ +["\uD83F\uDFFE"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_unicode_U+200B_ZERO_WIDTH_SPACE.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_unicode_U+200B_ZERO_WIDTH_SPACE.json new file mode 100644 index 000000000000..626d5f81572d --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_unicode_U+200B_ZERO_WIDTH_SPACE.json @@ -0,0 +1 @@ +["\u200B"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_unicode_U+2064_invisible_plus.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_unicode_U+2064_invisible_plus.json new file mode 100644 index 000000000000..1e23972c65e3 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_unicode_U+2064_invisible_plus.json @@ -0,0 +1 @@ +["\u2064"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_unicode_U+FDD0_nonchar.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_unicode_U+FDD0_nonchar.json new file mode 100644 index 000000000000..18ef151b4f21 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_unicode_U+FDD0_nonchar.json @@ -0,0 +1 @@ +["\uFDD0"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_unicode_U+FFFE_nonchar.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_unicode_U+FFFE_nonchar.json new file mode 100644 index 000000000000..13d261fdad59 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_unicode_U+FFFE_nonchar.json @@ -0,0 +1 @@ +["\uFFFE"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_unicode_escaped_double_quote.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_unicode_escaped_double_quote.json new file mode 100644 index 000000000000..4e6257856dd2 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_unicode_escaped_double_quote.json @@ -0,0 +1 @@ +["\u0022"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_utf8.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_utf8.json new file mode 100644 index 000000000000..40878435f978 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_utf8.json @@ -0,0 +1 @@ +["€ð�„ž"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_with_del_character.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_with_del_character.json new file mode 100644 index 000000000000..8bd24907d9ea --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_with_del_character.json @@ -0,0 +1 @@ +["aa"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_structure_lonely_false.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_structure_lonely_false.json new file mode 100644 index 000000000000..02e4a84d62c4 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_structure_lonely_false.json @@ -0,0 +1 @@ +false \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_structure_lonely_int.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_structure_lonely_int.json new file mode 100644 index 000000000000..f70d7bba4ae1 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_structure_lonely_int.json @@ -0,0 +1 @@ +42 \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_structure_lonely_negative_real.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_structure_lonely_negative_real.json new file mode 100644 index 000000000000..b5135a207dee --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_structure_lonely_negative_real.json @@ -0,0 +1 @@ +-0.1 \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_structure_lonely_null.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_structure_lonely_null.json new file mode 100644 index 000000000000..ec747fa47ddb --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_structure_lonely_null.json @@ -0,0 +1 @@ +null \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_structure_lonely_string.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_structure_lonely_string.json new file mode 100644 index 000000000000..b6e982ca96aa --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_structure_lonely_string.json @@ -0,0 +1 @@ +"asd" \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_structure_lonely_true.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_structure_lonely_true.json new file mode 100644 index 000000000000..f32a5804e292 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_structure_lonely_true.json @@ -0,0 +1 @@ +true \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_structure_string_empty.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_structure_string_empty.json new file mode 100644 index 000000000000..3cc762b5501e --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_structure_string_empty.json @@ -0,0 +1 @@ +"" \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_structure_trailing_newline.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_structure_trailing_newline.json new file mode 100644 index 000000000000..0c3426d4c287 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_structure_trailing_newline.json @@ -0,0 +1 @@ +["a"] diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_structure_true_in_array.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_structure_true_in_array.json new file mode 100644 index 000000000000..de601e305f4f --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_structure_true_in_array.json @@ -0,0 +1 @@ +[true] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_structure_whitespace_array.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_structure_whitespace_array.json new file mode 100644 index 000000000000..2bedf7f2de5c --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_structure_whitespace_array.json @@ -0,0 +1 @@ + [] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/number_-9223372036854775808.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/number_-9223372036854775808.json new file mode 100644 index 000000000000..f8853ff83d5e --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/number_-9223372036854775808.json @@ -0,0 +1 @@ +[-9223372036854775808] diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/number_-9223372036854775809.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/number_-9223372036854775809.json new file mode 100644 index 000000000000..5dca91fde6d6 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/number_-9223372036854775809.json @@ -0,0 +1 @@ +[-9223372036854775809] diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/number_1.0.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/number_1.0.json new file mode 100644 index 000000000000..0ea64a68f2d1 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/number_1.0.json @@ -0,0 +1 @@ +[1.0] diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/number_1.000000000000000005.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/number_1.000000000000000005.json new file mode 100644 index 000000000000..4c3849de2c62 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/number_1.000000000000000005.json @@ -0,0 +1 @@ +[1.000000000000000005] diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/number_1000000000000000.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/number_1000000000000000.json new file mode 100644 index 000000000000..cd38afa77680 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/number_1000000000000000.json @@ -0,0 +1 @@ +[1000000000000000] diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/number_10000000000000000999.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/number_10000000000000000999.json new file mode 100644 index 000000000000..fa37dfa7c6ca --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/number_10000000000000000999.json @@ -0,0 +1 @@ +[10000000000000000999] diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/number_1e-999.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/number_1e-999.json new file mode 100644 index 000000000000..61ce7529afad --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/number_1e-999.json @@ -0,0 +1 @@ +[1E-999] diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/number_1e6.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/number_1e6.json new file mode 100644 index 000000000000..b55cfc4b2fa7 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/number_1e6.json @@ -0,0 +1 @@ +[1E6] diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/number_9223372036854775807.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/number_9223372036854775807.json new file mode 100644 index 000000000000..f899e4c407ba --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/number_9223372036854775807.json @@ -0,0 +1 @@ +[9223372036854775807] diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/number_9223372036854775808.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/number_9223372036854775808.json new file mode 100644 index 000000000000..15757d3e5d29 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/number_9223372036854775808.json @@ -0,0 +1 @@ +[9223372036854775808] diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/object_key_nfc_nfd.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/object_key_nfc_nfd.json new file mode 100644 index 000000000000..e4cbc1dc114d --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/object_key_nfc_nfd.json @@ -0,0 +1 @@ +{"é":"NFC","eÌ�":"NFD"} \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/object_key_nfd_nfc.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/object_key_nfd_nfc.json new file mode 100644 index 000000000000..b04ece18df70 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/object_key_nfd_nfc.json @@ -0,0 +1 @@ +{"eÌ�":"NFD","é":"NFC"} \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/object_same_key_different_values.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/object_same_key_different_values.json new file mode 100644 index 000000000000..0c4547df378b --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/object_same_key_different_values.json @@ -0,0 +1 @@ +{"a":1,"a":2} \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/object_same_key_same_value.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/object_same_key_same_value.json new file mode 100644 index 000000000000..e1070184de79 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/object_same_key_same_value.json @@ -0,0 +1 @@ +{"a":1,"a":1} \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/object_same_key_unclear_values.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/object_same_key_unclear_values.json new file mode 100644 index 000000000000..8a76bd4ffeee --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/object_same_key_unclear_values.json @@ -0,0 +1 @@ +{"a":0, "a":-0} diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/string_1_escaped_invalid_codepoint.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/string_1_escaped_invalid_codepoint.json new file mode 100644 index 000000000000..8e6247319f54 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/string_1_escaped_invalid_codepoint.json @@ -0,0 +1 @@ +["\uD800"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/string_1_invalid_codepoint.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/string_1_invalid_codepoint.json new file mode 100644 index 000000000000..916bff920fa1 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/string_1_invalid_codepoint.json @@ -0,0 +1 @@ +["í €"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/string_2_escaped_invalid_codepoints.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/string_2_escaped_invalid_codepoints.json new file mode 100644 index 000000000000..93568e2c776c --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/string_2_escaped_invalid_codepoints.json @@ -0,0 +1 @@ +["\uD800\uD800"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/string_2_invalid_codepoints.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/string_2_invalid_codepoints.json new file mode 100644 index 000000000000..043a72e813cf --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/string_2_invalid_codepoints.json @@ -0,0 +1 @@ +["í €í €"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/string_3_escaped_invalid_codepoints.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/string_3_escaped_invalid_codepoints.json new file mode 100644 index 000000000000..407dc6575080 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/string_3_escaped_invalid_codepoints.json @@ -0,0 +1 @@ +["\uD800\uD800\uD800"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/string_3_invalid_codepoints.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/string_3_invalid_codepoints.json new file mode 100644 index 000000000000..2fcb0927d103 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/string_3_invalid_codepoints.json @@ -0,0 +1 @@ +["í €í €í €"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/string_with_escaped_NULL.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/string_with_escaped_NULL.json new file mode 100644 index 000000000000..8ca2be59e602 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/string_with_escaped_NULL.json @@ -0,0 +1 @@ +["A\u0000B"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/TestJSONParsing.java b/test/jdk/jdk/incubator/json/TestJSONParsing.java new file mode 100644 index 000000000000..603cdfc5c2e7 --- /dev/null +++ b/test/jdk/jdk/incubator/json/TestJSONParsing.java @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @modules jdk.incubator.json + * @run junit TestJSONParsing + */ + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Stream; + +import jdk.incubator.json.*; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class TestJSONParsing { + + private static final String SRC_DIR = System.getProperty("test.src", "."); + private static final String TEST_DIR_PARSING = "JSONTestSuite/test_parsing"; + private static final String TEST_DIR_TRANSFORM = "JSONTestSuite/test_transform"; + + // Excluding these test cases, as our implementation does not parse in bytes + private static final List PARSING_I_FAIL = Arrays.asList( + "i_string_UTF-16LE_with_BOM.json", + "i_string_utf16BE_no_BOM.json", + "i_string_utf16LE_no_BOM.json", + "i_structure_UTF-8_BOM_empty_object.json"); + + // See RFC 8259.4 - "The names within an object SHOULD be unique." + // These should be recognized as n (pass to method reference) + private static final List PARSING_Y_FAIL = Arrays.asList( + "y_object_duplicated_key_and_value.json", + "y_object_duplicated_key.json"); + + // See unique key note for PARSING_Y_FAIL + private static final List TRANSFORM_FAIL = Arrays.asList( + "object_same_key_different_values.json", + "object_same_key_unclear_values.json", + "object_same_key_same_value.json"); + + // All files in parsing prefixed with y_ or i_ + // that are not found in the designated parsing failure lists + private static Stream parsingPass() throws IOException { + return Files.walk(Paths.get(SRC_DIR, TEST_DIR_PARSING)) + .filter(a -> (a.getFileName().toString().startsWith("y_") + || a.getFileName().toString().startsWith("i_"))) + .filter(a -> !PARSING_I_FAIL.contains(a.getFileName().toString())) + .filter(a -> !PARSING_Y_FAIL.contains(a.getFileName().toString())); + } + + @ParameterizedTest + @MethodSource + public void parsingPass(Path p) { + testJson(p, true); + } + + // All files in parsing prefixed with n_ + // or files found in the designated parsing failure lists + private static Stream parsingFail() throws IOException { + return Stream.concat( + Stream.concat(PARSING_I_FAIL.stream().map( + a -> Path.of(SRC_DIR, "JSONTestSuite/test_parsing/", a)), + PARSING_Y_FAIL.stream().map( + a -> Path.of(SRC_DIR, "JSONTestSuite/test_parsing/", a))), + Files.walk(Paths.get(SRC_DIR, TEST_DIR_PARSING)). + filter(a -> (a.getFileName().toString()).startsWith("n_"))); + } + + @ParameterizedTest + @MethodSource + public void parsingFail(Path p) { + testJson(p, false); + } + + // Transform files that are not found in the designated transform failure list + private static Stream transformPass() throws IOException { + return Files.walk(Paths.get(SRC_DIR, TEST_DIR_TRANSFORM)) + .filter(a -> !TRANSFORM_FAIL.contains(a.getFileName().toString())); + } + + @ParameterizedTest + @MethodSource + public void transformPass(Path p) { + testJson(p, true); + } + + // Transform files found in the designated transform failure list + private static Stream transformFail() { + return TRANSFORM_FAIL.stream().map(a -> Path.of(SRC_DIR, "JSONTestSuite/test_transform/", a)); + } + + @ParameterizedTest + @MethodSource + public void transformFail(Path p) { + testJson(p, false); + } + + private void testJson(Path p, boolean pass) { + if (!p.toString().endsWith(".json")) { + return; + } + var fileName = p.getFileName().toString(); + String json; + try { + json = new String(Files.readAllBytes(p)); + } catch (IOException e) { + throw new RuntimeException(String.format("File: %s not found", fileName)); + } + testJsonString(json, fileName, pass); + } + + // Test public Json.parse(String) + private static void testJsonString(String json, String file, boolean pass) { + if (pass) { + // Check the JSON String can be parsed + var jsonValue = assertDoesNotThrow(() -> Json.parse(json), + "Parsing failed for : " + json); + // Check the toString() representation of the JsonValue + var jsonString = assertDoesNotThrow(jsonValue::toString, + "toString failed for : " + json); + // Check the value representation of the JsonValue as well as the + // round trip for supplying the value to the factory + switch (jsonValue) { + case JsonObject jo -> JsonObject.of(jo.asMap()); + case JsonArray ja -> JsonArray.of(ja.asList()); + case JsonString js -> JsonString.of(js.asString()); + case JsonNumber jn -> { + try { + JsonNumber.of(jn.asLong()); + } catch (JsonValueException _) { + JsonNumber.of(jn.asDouble()); + } + } + case JsonBoolean jb -> JsonBoolean.of(jb.asBoolean()); + case JsonNull _ -> {} + } + // Check round trip for parsing the toString() representation + assertDoesNotThrow(() -> Json.parse(jsonString), + "Parse round trip for toString() failed : " + json); + } else { + assertThrows(JsonParseException.class, () -> Json.parse(json), + "Parsing did not throw JsonParseException for : " + json); + } + } +} From d51030a6582570d92ed179e01f01dc688697a0d0 Mon Sep 17 00:00:00 2001 From: Ioi Lam Date: Thu, 15 Oct 2026 23:34:20 +0000 Subject: [PATCH 063/169] 8393434: Automatically grow ResizeableHashTable Reviewed-by: matsaave, fparain --- .../cpu/aarch64/codeBuffer_aarch64.cpp | 3 - src/hotspot/share/cds/aotMappedHeapWriter.cpp | 1 - src/hotspot/share/cds/aotMetaspace.cpp | 3 - .../share/cds/aotStreamedHeapWriter.cpp | 3 - src/hotspot/share/cds/archiveBuilder.cpp | 8 -- src/hotspot/share/cds/classListParser.cpp | 3 - src/hotspot/share/cds/heapShared.cpp | 2 - .../methodtracer/jfrClassFilterClosure.cpp | 1 - .../methodtracer/jfrClassFilterClosure.hpp | 6 +- .../support/methodtracer/jfrMethodTracer.cpp | 1 - src/hotspot/share/memory/metaspaceClosure.cpp | 3 - src/hotspot/share/prims/jvmtiTagMapTable.cpp | 14 ---- src/hotspot/share/prims/jvmtiTagMapTable.hpp | 4 +- src/hotspot/share/runtime/hotCodeSampler.cpp | 3 - .../share/utilities/objectBitSet.inline.hpp | 3 +- .../share/utilities/resizableHashTable.hpp | 74 +++++++++++-------- 16 files changed, 48 insertions(+), 84 deletions(-) diff --git a/src/hotspot/cpu/aarch64/codeBuffer_aarch64.cpp b/src/hotspot/cpu/aarch64/codeBuffer_aarch64.cpp index 9c8c7393354d..e3140a7ba6ed 100644 --- a/src/hotspot/cpu/aarch64/codeBuffer_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/codeBuffer_aarch64.cpp @@ -35,9 +35,6 @@ void CodeBuffer::share_trampoline_for(address dest, int caller_offset) { bool created; Offsets* offsets = _shared_trampoline_requests->put_if_absent(dest, &created); - if (created) { - _shared_trampoline_requests->maybe_grow(); - } offsets->push(caller_offset); _finalize_stubs = true; } diff --git a/src/hotspot/share/cds/aotMappedHeapWriter.cpp b/src/hotspot/share/cds/aotMappedHeapWriter.cpp index ed35c8d09d18..1d73fb0bf9a4 100644 --- a/src/hotspot/share/cds/aotMappedHeapWriter.cpp +++ b/src/hotspot/share/cds/aotMappedHeapWriter.cpp @@ -417,7 +417,6 @@ void AOTMappedHeapWriter::copy_source_objs_to_buffer(GrowableArrayCHeapput_when_absent(buffer_offset, handle); - _buffer_offset_to_source_obj_table->maybe_grow(); if (java_lang_Module::is_instance(src_obj)) { AOTModules::check_archived_module_oop(src_obj); diff --git a/src/hotspot/share/cds/aotMetaspace.cpp b/src/hotspot/share/cds/aotMetaspace.cpp index cd102e4eee73..d22dccce6809 100644 --- a/src/hotspot/share/cds/aotMetaspace.cpp +++ b/src/hotspot/share/cds/aotMetaspace.cpp @@ -195,9 +195,6 @@ class DumpClassListCLDClosure : public CLDClosure { if (!created) { return; } - if (_dumped_classes.maybe_grow()) { - log_info(aot, hashtables)("Expanded _dumped_classes table to %d", _dumped_classes.table_size()); - } if (ik->super()) { dump(ik->super()); } diff --git a/src/hotspot/share/cds/aotStreamedHeapWriter.cpp b/src/hotspot/share/cds/aotStreamedHeapWriter.cpp index 007a0ae4c6dc..d2ea2d02b342 100644 --- a/src/hotspot/share/cds/aotStreamedHeapWriter.cpp +++ b/src/hotspot/share/cds/aotStreamedHeapWriter.cpp @@ -127,7 +127,6 @@ void AOTStreamedHeapWriter::order_source_objs(GrowableArrayCHeaplength(); ++i) { oop obj = _source_objs->at(i); _dfs_order_table->put(cast_from_oop(obj), -1); - _dfs_order_table->maybe_grow(); } int dfs_order = 0; @@ -152,7 +151,6 @@ void AOTStreamedHeapWriter::order_source_objs(GrowableArrayCHeapput(cast_from_oop(obj), ++dfs_order); - _dfs_order_table->maybe_grow(); FollowOopIterateClosure cl(&dfs_stack, obj, AOTReferenceObjSupport::check_if_ref_obj(obj)); obj->oop_iterate(&cl); @@ -262,7 +260,6 @@ void AOTStreamedHeapWriter::copy_source_objs_to_buffer(GrowableArrayCHeapput_when_absent(buffer_offset, handle); - _buffer_offset_to_source_obj_table->maybe_grow(); int dfs_order = i + 1; _dfs_to_archive_object_table[dfs_order] = buffer_offset; diff --git a/src/hotspot/share/cds/archiveBuilder.cpp b/src/hotspot/share/cds/archiveBuilder.cpp index 57ab0b316639..211d4a3cc871 100644 --- a/src/hotspot/share/cds/archiveBuilder.cpp +++ b/src/hotspot/share/cds/archiveBuilder.cpp @@ -437,11 +437,6 @@ bool ArchiveBuilder::gather_one_source_obj(MetaspaceClosure::Ref* ref, bool read SourceObjInfo src_info(ref, read_only, follow_mode); bool created; SourceObjInfo* p = _src_obj_table.put_if_absent(src_obj, src_info, &created); - if (created) { - if (_src_obj_table.maybe_grow()) { - log_info(aot, hashtables)("Expanded _src_obj_table table to %d", _src_obj_table.table_size()); - } - } #ifdef ASSERT if (ref->type() == MetaspaceClosureType::MethodType) { @@ -659,9 +654,6 @@ void ArchiveBuilder::make_shallow_copy(DumpRegion *dump_region, SourceObjInfo* s bool created; _buffered_to_src_table.put_if_absent((address)dest, src, &created); assert(created, "must be"); - if (_buffered_to_src_table.maybe_grow()) { - log_info(aot, hashtables)("Expanded _buffered_to_src_table table to %d", _buffered_to_src_table.table_size()); - } } intptr_t* archived_vtable = CppVtables::get_archived_vtable(src_info->type(), (address)dest); diff --git a/src/hotspot/share/cds/classListParser.cpp b/src/hotspot/share/cds/classListParser.cpp index 836d018a714c..24cd9454e088 100644 --- a/src/hotspot/share/cds/classListParser.cpp +++ b/src/hotspot/share/cds/classListParser.cpp @@ -774,9 +774,6 @@ Klass* ClassListParser::load_current_class(Symbol* class_name_symbol, TRAPS) { if (!created) { error("Duplicated ID %d for class %s", id, _class_name); } - if (id2klass_table()->maybe_grow()) { - log_info(aot, hashtables)("Expanded id2klass_table() to %d", id2klass_table()->table_size()); - } } return klass; diff --git a/src/hotspot/share/cds/heapShared.cpp b/src/hotspot/share/cds/heapShared.cpp index b486b2f17cb2..588c2b6f4d0e 100644 --- a/src/hotspot/share/cds/heapShared.cpp +++ b/src/hotspot/share/cds/heapShared.cpp @@ -635,7 +635,6 @@ bool HeapShared::archive_object(oop obj, oop referrer, KlassSubGraphInfo* subgra OopHandle oh(Universe::vm_global(), obj); CachedOopInfo info = make_cached_oop_info(obj, referrer); archived_object_cache()->put_when_absent(oh, info); - archived_object_cache()->maybe_grow(); Klass* k = obj->klass(); if (k->is_instance_klass()) { @@ -2211,7 +2210,6 @@ bool HeapShared::has_been_seen_during_subgraph_recording(oop obj) { void HeapShared::set_has_been_seen_during_subgraph_recording(oop obj) { assert(!has_been_seen_during_subgraph_recording(obj), "sanity"); _seen_objects_table->put_when_absent(obj, true); - _seen_objects_table->maybe_grow(); ++ _num_new_walked_objs; } diff --git a/src/hotspot/share/jfr/support/methodtracer/jfrClassFilterClosure.cpp b/src/hotspot/share/jfr/support/methodtracer/jfrClassFilterClosure.cpp index 238763863c30..803493f4a067 100644 --- a/src/hotspot/share/jfr/support/methodtracer/jfrClassFilterClosure.cpp +++ b/src/hotspot/share/jfr/support/methodtracer/jfrClassFilterClosure.cpp @@ -66,7 +66,6 @@ void JfrFilterClassClosure::do_klass(Klass* k) { if (!_classes_to_modify->contains(klass_id)) { jclass mirror = mirror_as_local_jni_handle(ik, _thread); _classes_to_modify->put(klass_id, mirror); - _classes_to_modify->maybe_grow(); } } } diff --git a/src/hotspot/share/jfr/support/methodtracer/jfrClassFilterClosure.hpp b/src/hotspot/share/jfr/support/methodtracer/jfrClassFilterClosure.hpp index e9712e616b6e..f13c74a8fa06 100644 --- a/src/hotspot/share/jfr/support/methodtracer/jfrClassFilterClosure.hpp +++ b/src/hotspot/share/jfr/support/methodtracer/jfrClassFilterClosure.hpp @@ -30,6 +30,7 @@ #include "jfr/utilities/jfrTypes.hpp" #include "jni.h" #include "memory/iterator.hpp" +#include "utilities/resizableHashTable.hpp" class InstanceKlass; class JavaThread; @@ -38,11 +39,6 @@ class Klass; template class GrowableArray; -template class ResizeableHashTable; - // Knuth multiplicative hashing. inline uint32_t knuth_hash(const traceid& id) { const uint32_t v = static_cast(id); diff --git a/src/hotspot/share/jfr/support/methodtracer/jfrMethodTracer.cpp b/src/hotspot/share/jfr/support/methodtracer/jfrMethodTracer.cpp index 1d704f05a154..1916481a1f9f 100644 --- a/src/hotspot/share/jfr/support/methodtracer/jfrMethodTracer.cpp +++ b/src/hotspot/share/jfr/support/methodtracer/jfrMethodTracer.cpp @@ -104,7 +104,6 @@ static void register_placeholder(const InstanceKlass* ik, const JfrMethodProcess JfrTraceTagging::tag_sticky(ik, mp); assert(!placeholder_table()->contains(JfrTraceId::load_raw(ik)), "invariant"); placeholder_table()->put(JfrTraceId::load_raw(ik), ik); - placeholder_table()->maybe_grow(); } // Quick and unlocked check to see if the Method Tracer has been activated. diff --git a/src/hotspot/share/memory/metaspaceClosure.cpp b/src/hotspot/share/memory/metaspaceClosure.cpp index f0b1e00506fe..28466a0963f4 100644 --- a/src/hotspot/share/memory/metaspaceClosure.cpp +++ b/src/hotspot/share/memory/metaspaceClosure.cpp @@ -102,9 +102,6 @@ bool UniqueMetaspaceClosure::do_ref(MetaspaceClosure::Ref* ref, bool read_only) if (!created) { return false; // Already visited: no need to iterate embedded pointers. } else { - if (_has_been_visited.maybe_grow()) { - log_info(aot, hashtables)("Expanded _has_been_visited table to %d", _has_been_visited.table_size()); - } return do_unique_ref(ref, read_only); } } diff --git a/src/hotspot/share/prims/jvmtiTagMapTable.cpp b/src/hotspot/share/prims/jvmtiTagMapTable.cpp index 31cb9890d74b..f5d238511285 100644 --- a/src/hotspot/share/prims/jvmtiTagMapTable.cpp +++ b/src/hotspot/share/prims/jvmtiTagMapTable.cpp @@ -270,13 +270,6 @@ void JvmtiTagMapTable::add(const JvmtiHeapwalkObject& obj, jlong tag) { jlong* value = _table.put_if_absent(new_entry, tag, &is_added); *value = tag; // assign the new tag } - if (is_added) { - if (_table.maybe_grow(5, true /* use_large_table_sizes */)) { - int max_bucket_size = DEBUG_ONLY(_table.verify()) NOT_DEBUG(0); - log_info(jvmti, table) ("JvmtiTagMap table resized to %d for %d entries max bucket %d", - _table.table_size(), _table.number_of_entries(), max_bucket_size); - } - } } bool JvmtiTagMapTable::update(const JvmtiHeapwalkObject& obj, jlong tag) { @@ -393,13 +386,6 @@ void JvmtiFlatTagMapTable::add(const JvmtiHeapwalkObject& obj, jlong tag) { bool is_added; jlong* value = _table.put_if_absent(entry, tag, &is_added); *value = tag; // assign the new tag - if (is_added) { - if (_table.maybe_grow(5, true /* use_large_table_sizes */)) { - int max_bucket_size = DEBUG_ONLY(_table.verify()) NOT_DEBUG(0); - log_info(jvmti, table) ("JvmtiFlatTagMapTable table resized to %d for %d entries max bucket %d", - _table.table_size(), _table.number_of_entries(), max_bucket_size); - } - } } jlong JvmtiFlatTagMapTable::remove(const JvmtiHeapwalkObject& obj) { diff --git a/src/hotspot/share/prims/jvmtiTagMapTable.hpp b/src/hotspot/share/prims/jvmtiTagMapTable.hpp index 07ed4cfc4430..ee1237b5b8d9 100644 --- a/src/hotspot/share/prims/jvmtiTagMapTable.hpp +++ b/src/hotspot/share/prims/jvmtiTagMapTable.hpp @@ -118,7 +118,9 @@ typedef ResizeableHashTable ResizableHT; + JvmtiTagMapKey::equals, + 5, /*load-factor*/ + true /*large table */> ResizableHT; // A supporting class for iterating over all entries in Hashmap class JvmtiTagMapKeyClosure { diff --git a/src/hotspot/share/runtime/hotCodeSampler.cpp b/src/hotspot/share/runtime/hotCodeSampler.cpp index 33579d1b2685..7a73786a6aa8 100644 --- a/src/hotspot/share/runtime/hotCodeSampler.cpp +++ b/src/hotspot/share/runtime/hotCodeSampler.cpp @@ -89,9 +89,6 @@ bool ThreadSampler::sample_all_java_threads() { bool created = false; Pair* sampled_nm = _samples.put_if_absent(compile_id, Pair(nm, 0), &created); sampled_nm->second++; - if (created) { - _samples.maybe_grow(); - } } return true; } diff --git a/src/hotspot/share/utilities/objectBitSet.inline.hpp b/src/hotspot/share/utilities/objectBitSet.inline.hpp index e204841e6e20..6f1167d0022b 100644 --- a/src/hotspot/share/utilities/objectBitSet.inline.hpp +++ b/src/hotspot/share/utilities/objectBitSet.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -77,7 +77,6 @@ inline CHeapBitMap* ObjectBitSet::get_fragment_bits(uintptr_t addr) { bits = fragment->bits(); _fragment_list = fragment; _bitmap_fragments.put(granule, bits); - _bitmap_fragments.maybe_grow(); } _last_fragment_bits = bits; diff --git a/src/hotspot/share/utilities/resizableHashTable.hpp b/src/hotspot/share/utilities/resizableHashTable.hpp index ad51caff5e1d..62594a82947d 100644 --- a/src/hotspot/share/utilities/resizableHashTable.hpp +++ b/src/hotspot/share/utilities/resizableHashTable.hpp @@ -74,7 +74,9 @@ template< AnyObj::allocation_type ALLOC_TYPE = AnyObj::RESOURCE_AREA, MemTag MEM_TAG = mtInternal, unsigned (*HASH) (K const&) = primitive_hash, - bool (*EQUALS)(K const&, K const&) = primitive_equals + bool (*EQUALS)(K const&, K const&) = primitive_equals, + int LOAD_FACTOR = 8, + bool USE_LARGE_TABLE_SIZE = false > class ResizeableHashTable : public HashTableBase< ResizeableHashTableStorage, @@ -87,7 +89,7 @@ class ResizeableHashTable : public HashTableBase< NONCOPYABLE(ResizeableHashTable); // Calculate next "good" hashtable size based on requested count - int calculate_resize(bool use_large_table_sizes) const { + int calculate_resize() const { const int resize_factor = 2; // by how much we will resize using current number of entries // possible hashmap sizes - odd primes that roughly double in size. @@ -99,7 +101,7 @@ class ResizeableHashTable : public HashTableBase< const int large_array_size = sizeof(large_table_sizes)/sizeof(int); int requested = resize_factor * BASE::number_of_entries(); - int start_at = use_large_table_sizes ? 8 : 0; + int start_at = USE_LARGE_TABLE_SIZE ? 8 : 0; int newsize; for (int i = start_at; i < large_array_size; i++) { newsize = large_table_sizes[i]; @@ -110,19 +112,13 @@ class ResizeableHashTable : public HashTableBase< return requested; // greater than a size in the table } -public: - ResizeableHashTable(unsigned size, unsigned max_size) - : BASE(size), _max_size(max_size) { - assert(size <= 0x3fffffff && max_size <= 0x3fffffff, "avoid overflow in resize"); - } - - bool maybe_grow(int load_factor = 8, bool use_large_table_sizes = false) { + bool maybe_grow() { unsigned old_size = BASE::_table_size; if (old_size >= _max_size) { return false; } - if (BASE::number_of_entries() / int(old_size) > load_factor) { - unsigned new_size = MIN2(calculate_resize(use_large_table_sizes), _max_size); + if (BASE::number_of_entries() / int(old_size) > LOAD_FACTOR) { + unsigned new_size = MIN2(calculate_resize(), _max_size); resize(new_size); return true; } else { @@ -130,6 +126,12 @@ class ResizeableHashTable : public HashTableBase< } } +public: + ResizeableHashTable(unsigned size, unsigned max_size) + : BASE(size), _max_size(max_size) { + assert(size <= 0x3fffffff && max_size <= 0x3fffffff, "avoid overflow in resize"); + } + void resize(unsigned new_size) { Node** old_table = BASE::_table; Node** new_table = BASE::alloc_table(new_size); @@ -157,27 +159,37 @@ class ResizeableHashTable : public HashTableBase< BASE::_table_size = new_size; } -#ifdef ASSERT - int verify() { - Node** table = BASE::_table; - // Return max bucket size. If hashcode is broken, this will be - // too high. - int max_bucket_size = 0; - int index = 0; - Node* const* bucket = table; - while (bucket < &table[BASE::_table_size]) { - int count = 0; - Node* node = *bucket; - while (node != nullptr) { - count++; - node = node->_next; - } - max_bucket_size = MAX2(count, max_bucket_size); - ++bucket; + bool put(K const& key, V const& value) { + bool created = BASE::put(key, value); + if (created) { + maybe_grow(); + } + return created; + } + + bool put_when_absent(K const& key, V const& value) { + bool created = BASE::put_when_absent(key, value); + if (created) { + maybe_grow(); + } + return created; + } + + V* put_if_absent(K const& key, bool* p_created) { + V* node = BASE::put_if_absent(key, p_created); + if (*p_created) { + maybe_grow(); + } + return node; + } + + V* put_if_absent(K const& key, V const& value, bool* p_created) { + V* node = BASE::put_if_absent(key, value, p_created); + if (*p_created) { + maybe_grow(); } - return max_bucket_size; + return node; } -#endif // ASSERT }; #endif // SHARE_UTILITIES_RESIZABLEHASHTABLE_HPP From cefadef6c23b4b081bf4942511f7e59dfd077d22 Mon Sep 17 00:00:00 2001 From: Quan Anh Mai Date: Fri, 16 Oct 2026 00:55:06 +0000 Subject: [PATCH 064/169] 8386157: [Valhalla] assert memnode.cpp:1383 added by 8382936 caused issues with Valhalla arrays Reviewed-by: thartmann, mchevalier --- src/hotspot/share/opto/castnode.cpp | 10 ++++--- src/hotspot/share/opto/library_call.cpp | 9 +++++++ src/hotspot/share/opto/macro.cpp | 6 ++++- src/hotspot/share/opto/memnode.cpp | 18 ++++++++++++- src/hotspot/share/opto/parse2.cpp | 34 ++++++++++-------------- src/hotspot/share/opto/subnode.cpp | 13 ++++++--- src/hotspot/share/opto/valuetypenode.cpp | 33 ++++++----------------- 7 files changed, 69 insertions(+), 54 deletions(-) diff --git a/src/hotspot/share/opto/castnode.cpp b/src/hotspot/share/opto/castnode.cpp index f2d22f8a2f6b..587216f84af0 100644 --- a/src/hotspot/share/opto/castnode.cpp +++ b/src/hotspot/share/opto/castnode.cpp @@ -233,14 +233,16 @@ Node* ConstraintCastNode::ideal_cast_of_value_type_node(PhaseGVN* phase) { return nullptr; } - const Type* join = vt->type()->filter(type()); + const Type* vt_type = vt->type(); + const Type* join = vt_type->filter(type()); if (join == Type::TOP) { // Do not push a dead Cast since its type can be unrelated return nullptr; } - if (join == vt->type()->remove_speculative()) { - // Redundant cast, let Identity handle + if (join == vt_type->remove_speculative() && type()->maybe_null()) { + // Redundant cast, let Identity handle. Except when the cast is not-null, in which case the + // users will also expect the null-free fields to be not-null. return nullptr; } @@ -250,7 +252,7 @@ Node* ConstraintCastNode::ideal_cast_of_value_type_node(PhaseGVN* phase) { } // The only possible case left is that the cast is a cast to not-null - assert(join == vt->type()->filter(TypePtr::NOTNULL), "must be"); + assert(join == vt_type->filter(TypePtr::NOTNULL), "must be"); ValueTypeNode* res = vt->clone()->as_ValueType(); res->set_null_marker(*phase); diff --git a/src/hotspot/share/opto/library_call.cpp b/src/hotspot/share/opto/library_call.cpp index be0c21e823ec..209df4f85f4e 100644 --- a/src/hotspot/share/opto/library_call.cpp +++ b/src/hotspot/share/opto/library_call.cpp @@ -7168,7 +7168,16 @@ bool LibraryCallKit::inline_arraycopy() { // TODO 8251971 This is too strong generate_fair_guard(flat_array_test(src), slow_region); + if (const TypeAryPtr* src_ary_type = _gvn.type(src)->isa_aryptr(); src_ary_type != nullptr && !src_ary_type->is_flat()) { + const TypeAryPtr* not_flat_src = src_ary_type->cast_to_not_flat(); + src = _gvn.transform(new CheckCastPPNode(control(), src, not_flat_src, ConstraintCastNode::DependencyType::NonFloatingNarrowing)); + } + generate_fair_guard(flat_array_test(dest), slow_region); + if (const TypeAryPtr* dst_ary_type = _gvn.type(dest)->isa_aryptr(); dst_ary_type != nullptr && !dst_ary_type->is_flat()) { + const TypeAryPtr* not_flat_dst = dst_ary_type->cast_to_not_flat(); + dest = _gvn.transform(new CheckCastPPNode(control(), dest, not_flat_dst, ConstraintCastNode::DependencyType::NonFloatingNarrowing)); + } } { diff --git a/src/hotspot/share/opto/macro.cpp b/src/hotspot/share/opto/macro.cpp index 6285f4810a4e..dabba2c5f418 100644 --- a/src/hotspot/share/opto/macro.cpp +++ b/src/hotspot/share/opto/macro.cpp @@ -879,8 +879,12 @@ bool PhaseMacroExpand::can_eliminate_allocation(PhaseIterGVN* igvn, AllocateNode for (DUIterator_Fast kmax, k = use->fast_outs(kmax); k < kmax; k++) { Node* u = use->fast_out(k); if (u->is_ValueType()) { - // Use in flat field can be eliminated ValueTypeNode* vt = u->as_ValueType(); + if (vt->get_oop() == use) { + worklist.push(use); + } + + // Use in flat field can be eliminated for (uint i = 0; i < vt->field_count(); ++i) { if (vt->field_value(i) == use && !vt->field(i)->is_flat()) { can_eliminate = false; // Use in non-flat field diff --git a/src/hotspot/share/opto/memnode.cpp b/src/hotspot/share/opto/memnode.cpp index a522ea77064e..bd39603eaff1 100644 --- a/src/hotspot/share/opto/memnode.cpp +++ b/src/hotspot/share/opto/memnode.cpp @@ -1308,12 +1308,28 @@ static Node* see_through_value_type(PhaseValues* phase, const LoadNode* load, No // of aliasing. // This method may find an unencoded node instead of the corresponding encoded one. Node* LoadNode::can_see_stored_value_through_membars(Node* st, PhaseValues* phase) const { + auto verify_type = [&](Node* res) { + if (res == nullptr || res->bottom_type() == Type::TOP) { + return true; + } + + // We can return an uncompressed oop when this is a LoadN + if (const TypePtr* this_ptr_type = type()->make_ptr(); this_ptr_type != nullptr) { + const TypePtr* res_ptr_type = res->bottom_type()->make_ptr(); + return res_ptr_type != nullptr && res_ptr_type->higher_equal(this_ptr_type); + } + + return is_java_primitive(value_basic_type()) || res->bottom_type()->higher_equal(type()); + }; + Node* ld_adr = in(MemNode::Address); intptr_t ld_off = 0; Node* ld_base = AddPNode::Ideal_base_and_offset(ld_adr, phase, ld_off); // Try to see through an ValueTypeNode Node* value = see_through_value_type(phase, this, ld_base, ld_off); if (value != nullptr) { + // TODO 8390080 + // assert(verify_type(value), "the fold is unsafe"); return value; } @@ -1369,7 +1385,7 @@ Node* LoadNode::can_see_stored_value_through_membars(Node* st, PhaseValues* phas Node* res = can_see_stored_value(st, phase); // TODO: reimplement assert, see: JDK-8386157 - //assert(res == nullptr || is_java_primitive(value_basic_type()) || res->bottom_type()->higher_equal(type()), "the fold is unsafe"); + assert(verify_type(res), "the fold is unsafe"); return res; } diff --git a/src/hotspot/share/opto/parse2.cpp b/src/hotspot/share/opto/parse2.cpp index 0aae6e6bc441..09ffb87ebdc4 100644 --- a/src/hotspot/share/opto/parse2.cpp +++ b/src/hotspot/share/opto/parse2.cpp @@ -3738,26 +3738,20 @@ void Parse::do_one_bytecode() { maybe_add_safepoint(iter().get_dest()); a = null(); b = pop(); - if (b->is_ValueType()) { - // Null checking a scalarized but nullable value type. Check the null marker - // input instead of the oop input to avoid keeping buffer allocations alive - c = _gvn.transform(new CmpINode(b->as_ValueType()->get_null_marker(), zerocon(T_INT))); - } else { - if (!_gvn.type(b)->speculative_maybe_null() && - !too_many_traps(Deoptimization::Reason_speculate_null_check)) { - inc_sp(1); - Node* null_ctl = top(); - b = null_check_oop(b, &null_ctl, true, true, true); - assert(null_ctl->is_top(), "no null control here"); - dec_sp(1); - } else if (_gvn.type(b)->speculative_always_null() && - !too_many_traps(Deoptimization::Reason_speculate_null_assert)) { - inc_sp(1); - b = null_assert(b); - dec_sp(1); - } - c = _gvn.transform( new CmpPNode(b, a) ); - } + if (!_gvn.type(b)->speculative_maybe_null() && + !too_many_traps(Deoptimization::Reason_speculate_null_check)) { + inc_sp(1); + Node* null_ctl = top(); + b = null_check_oop(b, &null_ctl, true, true, true); + assert(null_ctl->is_top(), "no null control here"); + dec_sp(1); + } else if (_gvn.type(b)->speculative_always_null() && + !too_many_traps(Deoptimization::Reason_speculate_null_assert)) { + inc_sp(1); + b = null_assert(b); + dec_sp(1); + } + c = _gvn.transform(new CmpPNode(b, a)); do_ifnull(btest, c); break; diff --git a/src/hotspot/share/opto/subnode.cpp b/src/hotspot/share/opto/subnode.cpp index 18e82113f1b1..c8565ff43aa5 100644 --- a/src/hotspot/share/opto/subnode.cpp +++ b/src/hotspot/share/opto/subnode.cpp @@ -1144,9 +1144,16 @@ Node* CmpPNode::Ideal(PhaseGVN *phase, bool can_reshape) { Node* uncast_in1 = in(1)->uncast(); Node* uncast_in2 = in(2)->uncast(); if (uncast_in1->is_ValueType() && phase->type(uncast_in2)->is_zero_type()) { - // Null checking a scalarized but nullable value type. Check the null marker - // input instead of the oop input to avoid keeping buffer allocations alive. - return new CmpINode(uncast_in1->as_ValueType()->get_null_marker(), phase->intcon(0)); + if (can_reshape) { + // Null checking a scalarized but nullable inline type. Check the null marker input instead + // of the oop input to avoid keeping buffer allocations alive. + return new CmpINode(uncast_in1->as_ValueType()->get_null_marker(), phase->intcon(0)); + } else { + // Keep the CmpP during parsing so that the parser can recognize the compare-against-null + // pattern + phase->record_for_igvn(this); + return nullptr; + } } if (uncast_in1->is_ValueType() || uncast_in2->is_ValueType()) { // In C2 IR, CmpP on value objects is a pointer comparison, not a value comparison. diff --git a/src/hotspot/share/opto/valuetypenode.cpp b/src/hotspot/share/opto/valuetypenode.cpp index 15494a1cca01..b55621c4a418 100644 --- a/src/hotspot/share/opto/valuetypenode.cpp +++ b/src/hotspot/share/opto/valuetypenode.cpp @@ -94,9 +94,13 @@ ValueTypeNode* ValueTypeNode::clone_with_phis(PhaseGVN* gvn, Node* region, SafeP Node* value = vt->field_value(i); if (field->is_flat()) { // Handle flat fields recursively - value = value->as_ValueType()->clone_with_phis(gvn, region, map); + value = value->as_ValueType()->clone_with_phis(gvn, region, map, is_non_null && field->is_null_free()); } else { t = Type::get_const_type(type); + if (is_non_null && field->is_null_free()) { + t = t->join(TypePtr::NOTNULL); + } + value = PhiNode::make(region, init_with_top ? top : value, t); gvn->set_type(value, t); gvn->record_for_igvn(value); @@ -2009,34 +2013,13 @@ ValueTypeNode* ValueTypeNode::clone_if_required(PhaseGVN* gvn, SafePointNode* ma } const Type* ValueTypeNode::Value(PhaseGVN* phase) const { - Node* oop = get_oop(); - const Type* toop = phase->type(oop); -#ifdef ASSERT - if (oop->is_Con() && toop->is_zero_type() && _type->isa_oopptr()->is_known_instance()) { - // We are not allocated (anymore) and should therefore not have an instance id - dump(1); - assert(false, "Unbuffered value type should not have known instance id"); - } -#endif - if (toop == Type::TOP) { - return Type::TOP; - } - const Type* t = toop->filter_speculative(_type); - // Because of contradicting type profiling, we can end up with top as speculative type, - // which would then get removed by cleanup_speculative. In this case we have to run filter_speculative - // again, otherwise we would break the idempotence of Value - if (t->speculative() == nullptr && toop->speculative() != nullptr) { - t = toop->filter_speculative(t); - } - if (t->singleton()) { - // Don't replace ValueType by a constant - t = _type; - } const Type* tinit = phase->type(in(NullMarker)); if (tinit == Type::TOP) { return Type::TOP; } - if (tinit->isa_int() && tinit->is_int()->is_con(1)) { + + const Type* t = type(); + if (tinit == TypeInt::ONE) { t = t->join_speculative(TypePtr::NOTNULL); } return t; From 75809ed10b54f5dacafee1f874bf85e08f66c62e Mon Sep 17 00:00:00 2001 From: Vicente Romero Date: Fri, 16 Oct 2026 01:37:29 +0000 Subject: [PATCH 065/169] 8380732: Final variables cannot be assigned separately from declaration in record compact constructor Reviewed-by: jlahoda --- .../classes/com/sun/tools/javac/comp/Attr.java | 4 +++- .../tools/javac/records/RecordCompilationTests.java | 13 +++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java index 19354a956c75..e4dfeb9fbde8 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java @@ -290,7 +290,9 @@ boolean isAssignableAsBlankFinal(VarSymbol v, Env env) { && ((v.flags() & STATIC) != 0) == Resolve.isStatic(env)); boolean insideCompactConstructor = env.enclMethod != null && TreeInfo.isCompactConstructor(env.enclMethod); - return isAssignable & !insideCompactConstructor; + boolean assigningRecordField = (v.flags() & RECORD) != 0; + // Only record component fields are non-assignable in a compact constructor. + return isAssignable && (!insideCompactConstructor || !assigningRecordField); } /** Check that variable can be assigned to. diff --git a/test/langtools/tools/javac/records/RecordCompilationTests.java b/test/langtools/tools/javac/records/RecordCompilationTests.java index 48b5cdd85881..760945c48971 100644 --- a/test/langtools/tools/javac/records/RecordCompilationTests.java +++ b/test/langtools/tools/javac/records/RecordCompilationTests.java @@ -1075,6 +1075,19 @@ record R(int x) { """); } + @Test + void testLocalFinalCanBeAssignedInCompactConstructor() { + assertOK( + """ + record R() { + R { + final int c; + c = 0; + } + } + """); + } + @Test void testNoNativeMethods() { assertFail("compiler.err.mod.not.allowed.here", "record R(int x) { # }", From 7606da1bbf4ad150f9887904ce9d3f5e3e6c0356 Mon Sep 17 00:00:00 2001 From: Alexander Zvegintsev Date: Fri, 16 Oct 2026 05:28:39 +0000 Subject: [PATCH 066/169] 8393416: [Ubuntu 26.10] screen capture test does not work with wayland Ubuntu 26.10 and hangs indefinitely Reviewed-by: prr, kizune --- .../classes/sun/awt/X11GraphicsDevice.java | 10 +- .../sun/awt/screencast/ScreencastHelper.java | 20 +-- .../libawt_xawt/awt/screencast_pipewire.c | 147 ++++++++++++------ 3 files changed, 111 insertions(+), 66 deletions(-) diff --git a/src/java.desktop/unix/classes/sun/awt/X11GraphicsDevice.java b/src/java.desktop/unix/classes/sun/awt/X11GraphicsDevice.java index ff25120b6593..4a3a1b4bdba2 100644 --- a/src/java.desktop/unix/classes/sun/awt/X11GraphicsDevice.java +++ b/src/java.desktop/unix/classes/sun/awt/X11GraphicsDevice.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -155,6 +155,14 @@ public Rectangle getBounds() { return bounds.getBounds(); } + /** + * Returns the screen bounds in native pixels, + * ignoring UI scaling and Xwayland display mode emulation. + */ + public Rectangle getNativeBounds() { + return pGetBounds(getScreen()); + } + public Insets getInsets() { return insets; } diff --git a/src/java.desktop/unix/classes/sun/awt/screencast/ScreencastHelper.java b/src/java.desktop/unix/classes/sun/awt/screencast/ScreencastHelper.java index a8f7cd41a0e6..7f136552f58c 100644 --- a/src/java.desktop/unix/classes/sun/awt/screencast/ScreencastHelper.java +++ b/src/java.desktop/unix/classes/sun/awt/screencast/ScreencastHelper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,13 +26,11 @@ package sun.awt.screencast; import sun.awt.UNIXToolkit; -import sun.java2d.pipe.Region; +import sun.awt.X11GraphicsDevice; -import java.awt.GraphicsConfiguration; import java.awt.GraphicsEnvironment; import java.awt.Rectangle; import java.awt.Toolkit; -import java.awt.geom.AffineTransform; import java.util.Arrays; import java.util.List; import java.util.Set; @@ -115,19 +113,7 @@ private static List getSystemScreensBounds() { .stream(GraphicsEnvironment .getLocalGraphicsEnvironment() .getScreenDevices()) - .map(graphicsDevice -> { - GraphicsConfiguration gc = - graphicsDevice.getDefaultConfiguration(); - Rectangle screen = gc.getBounds(); - AffineTransform tx = gc.getDefaultTransform(); - - return new Rectangle( - Region.clipRound(screen.x * tx.getScaleX()), - Region.clipRound(screen.y * tx.getScaleY()), - Region.clipRound(screen.width * tx.getScaleX()), - Region.clipRound(screen.height * tx.getScaleY()) - ); - }) + .map(d -> ((X11GraphicsDevice) d).getNativeBounds()) .toList(); } diff --git a/src/java.desktop/unix/native/libawt_xawt/awt/screencast_pipewire.c b/src/java.desktop/unix/native/libawt_xawt/awt/screencast_pipewire.c index 13cf21994f91..e42a5f0124ab 100644 --- a/src/java.desktop/unix/native/libawt_xawt/awt/screencast_pipewire.c +++ b/src/java.desktop/unix/native/libawt_xawt/awt/screencast_pipewire.c @@ -28,6 +28,7 @@ #endif #include +#include #include "jni_util.h" #include "awt.h" @@ -75,14 +76,17 @@ int (*fp_pw_core_disconnect)(struct pw_core *core); struct pw_context * (*fp_pw_context_new)(struct pw_loop *main_loop, struct pw_properties *props, size_t user_data_size); +void (*fp_pw_context_destroy)(struct pw_context *context); struct pw_thread_loop * (*fp_pw_thread_loop_new)(const char *name, const struct spa_dict *props); struct pw_loop * (*fp_pw_thread_loop_get_loop)(struct pw_thread_loop *loop); void (*fp_pw_thread_loop_signal)(struct pw_thread_loop *loop, bool wait_for_accept); -void (*fp_pw_thread_loop_wait)(struct pw_thread_loop *loop); -void (*fp_pw_thread_loop_accept)(struct pw_thread_loop *loop); +int (*fp_pw_thread_loop_timed_wait_full)(struct pw_thread_loop *loop, + const struct timespec *abstime); +int (*fp_pw_thread_loop_get_time)(struct pw_thread_loop *loop, + struct timespec *abstime, int64_t timeout); int (*fp_pw_thread_loop_start)(struct pw_thread_loop *loop); void (*fp_pw_thread_loop_stop)(struct pw_thread_loop *loop); void (*fp_pw_thread_loop_destroy)(struct pw_thread_loop *loop); @@ -103,6 +107,9 @@ int DEBUG_SCREENCAST_ENABLED = FALSE; (*env)->ExceptionDescribe(env); \ } +// 10 s timeout for format negotiation and frames from all requested screens. +static const int64_t PIPEWIRE_TIMEOUT_NS = 10LL * 1000 * 1000 * 1000; + static gboolean hasPipewireFailed = FALSE; static gboolean sessionClosed = TRUE; static GString *activeSessionToken; @@ -158,6 +165,10 @@ static void doCleanup() { for (int i = 0; i < screenSpace.screenCount; ++i) { struct ScreenProps *screenProps = &screenSpace.screens[i]; + if (screenProps->captureDataPixbuf) { + gtk->g_object_unref(screenProps->captureDataPixbuf); + screenProps->captureDataPixbuf = NULL; + } if (screenProps->data) { if (screenProps->data->stream) { fp_pw_thread_loop_lock(pw.loop); @@ -183,6 +194,11 @@ static void doCleanup() { pw.core = NULL; } + if (pw.context) { + fp_pw_context_destroy(pw.context); + pw.context = NULL; + } + if (pw.loop) { fp_pw_thread_loop_destroy(pw.loop); pw.loop = NULL; @@ -281,7 +297,7 @@ static void onStreamParamChanged( data->rawFormat.size.height); data->hasFormat = TRUE; - fp_pw_thread_loop_signal(pw.loop, TRUE); + fp_pw_thread_loop_signal(pw.loop, FALSE); } static void onStreamProcess(void *userdata) { @@ -299,7 +315,6 @@ static void onStreamProcess(void *userdata) { if ( !data->hasFormat || !screen->shouldCapture - || screen->captureDataReady ) { return; } @@ -313,6 +328,11 @@ static void onStreamProcess(void *userdata) { return; } + if (screen->captureDataReady) { + fp_pw_stream_queue_buffer(data->stream, pwBuffer); + return; + } + spaBuffer = pwBuffer->buffer; if (!spaBuffer || spaBuffer->n_datas < 1 @@ -344,7 +364,6 @@ static void onStreamProcess(void *userdata) { ); GdkRectangle captureArea = screen->captureArea; - GdkRectangle screenBounds = screen->bounds; GdkPixbuf *pixbuf = gtk->gdk_pixbuf_new_from_data(spaData.data, GDK_COLORSPACE_RGB, @@ -355,9 +374,14 @@ static void onStreamProcess(void *userdata) { spaData.chunk->stride, NULL, NULL); + if (!pixbuf) { + ERR("Cannot create a pixbuf from stream data.\n"); + goto fail; + } - if (screen->bounds.width != streamWidth - || screen->bounds.height != streamHeight) { + gboolean needsScaling = screen->bounds.width != streamWidth + || screen->bounds.height != streamHeight; + if (needsScaling) { DEBUG_SCREEN_PREFIX(screen, "scaling stream data %dx%d -> %dx%d\n", streamWidth, streamHeight, @@ -369,37 +393,39 @@ static void onStreamProcess(void *userdata) { screen->bounds.height, GDK_INTERP_BILINEAR); + if (!scaled) { + ERR("Cannot scale the stream pixbuf.\n"); + goto fail; + } gtk->g_object_unref(pixbuf); pixbuf = scaled; } - GdkPixbuf *cropped = NULL; - if (captureArea.width != screenBounds.width - || captureArea.height != screenBounds.height) { - - cropped = gtk->gdk_pixbuf_new(GDK_COLORSPACE_RGB, + if (needsScaling + && captureArea.x == 0 && captureArea.y == 0 + && captureArea.width == screen->bounds.width + && captureArea.height == screen->bounds.height) { + screen->captureDataPixbuf = pixbuf; + } else { + GdkPixbuf *cropped = gtk->gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, captureArea.width, captureArea.height); - if (cropped) { - gtk->gdk_pixbuf_copy_area(pixbuf, + if (!cropped) { + ERR("Cannot create a new pixbuf.\n"); + goto fail; + } + gtk->gdk_pixbuf_copy_area(pixbuf, captureArea.x, captureArea.y, captureArea.width, captureArea.height, cropped, 0, 0); - } else { - ERR("Cannot create a new pixbuf.\n"); - } gtk->g_object_unref(pixbuf); - pixbuf = NULL; - - data->screenProps->captureDataPixbuf = cropped; - } else { - data->screenProps->captureDataPixbuf = pixbuf; + screen->captureDataPixbuf = cropped; } screen->captureDataReady = TRUE; @@ -408,6 +434,15 @@ static void onStreamProcess(void *userdata) { fp_pw_stream_queue_buffer(data->stream, pwBuffer); fp_pw_thread_loop_signal(pw.loop, FALSE); + return; + + fail: + if (pixbuf) { + gtk->g_object_unref(pixbuf); + } + fp_pw_stream_queue_buffer(data->stream, pwBuffer); + hasPipewireFailed = TRUE; + fp_pw_thread_loop_signal(pw.loop, FALSE); } static void onStreamStateChanged( @@ -485,10 +520,13 @@ static bool startStream( } /** + * The caller must hold the PipeWire thread loop lock. + * * @param index of a screen + * @param captureDeadline shared deadline for format negotiation and screen capture * @return TRUE on success */ -static gboolean connectStream(int index) { +static gboolean connectStream(int index, const struct timespec *captureDeadline) { DEBUG_SCREENCAST("@@@ using screen %i\n", index); if (index >= screenSpace.screenCount) { DEBUG_SCREENCAST("!!! Wrong index for screen\n", NULL); @@ -500,9 +538,7 @@ static gboolean connectStream(int index) { data->screenProps = &screenSpace.screens[index]; if (!sessionClosed && data->stream) { - fp_pw_thread_loop_lock(pw.loop); int result = fp_pw_stream_set_active(data->stream, TRUE); - fp_pw_thread_loop_unlock(pw.loop); DEBUG_SCREEN_PREFIX(data->screenProps, "stream %p: activate result |%i|\n", @@ -527,7 +563,6 @@ static gboolean connectStream(int index) { if (!data->stream) { DEBUG_SCREEN_PREFIX(data->screenProps, "!!! Could not create a pipewire stream\n", NULL); - fp_pw_thread_loop_unlock(pw.loop); return FALSE; } @@ -543,18 +578,20 @@ static gboolean connectStream(int index) { if (!startStream(data->stream, screenSpace.screens[index].id)){ DEBUG_SCREEN_PREFIX(data->screenProps, "!!! Could not start a pipewire stream\n", NULL); - fp_pw_thread_loop_unlock(pw.loop); return FALSE; } - while (!data->hasFormat) { - fp_pw_thread_loop_wait(pw.loop); - fp_pw_thread_loop_accept(pw.loop); - if (hasPipewireFailed) { - fp_pw_thread_loop_unlock(pw.loop); + while (!hasPipewireFailed && !data->hasFormat) { + int result = fp_pw_thread_loop_timed_wait_full(pw.loop, captureDeadline); + if (result < 0) { + DEBUG_SCREEN_PREFIX(data->screenProps, + "Format wait failed: %d\n", result); return FALSE; } } + if (hasPipewireFailed) { + return FALSE; + } DEBUG_SCREEN_PREFIX(data->screenProps, "frame size: %dx%d\n", @@ -636,7 +673,7 @@ static const struct pw_core_events coreEvents = { * @param requestedArea requested screenshot area * @return TRUE on success */ -static gboolean doLoop(GdkRectangle requestedArea) { +static gboolean doLoop(GdkRectangle requestedArea, struct timespec *captureDeadline) { gboolean isLoopLockTaken = FALSE; if (!pw.loop && !sessionClosed) { pw.loop = fp_pw_thread_loop_new("AWT Pipewire Thread", NULL); @@ -688,6 +725,13 @@ static gboolean doLoop(GdkRectangle requestedArea) { } pw_core_add_listener(pw.core, &pw.coreListener, &coreEvents, NULL); + } else if (pw.loop) { + fp_pw_thread_loop_lock(pw.loop); + isLoopLockTaken = TRUE; + } + + if (fp_pw_thread_loop_get_time(pw.loop, captureDeadline, PIPEWIRE_TIMEOUT_NS) < 0) { + goto fail; } for (int i = 0; i < screenSpace.screenCount; ++i) { @@ -707,7 +751,7 @@ static gboolean doLoop(GdkRectangle requestedArea) { DEBUG_SCREEN_PREFIX(screen, "@@@ adding screen %i\n", i); if (checkScreen(i, requestedArea)) { - if (!connectStream(i)){ + if (!connectStream(i, captureDeadline)){ goto fail; } } @@ -781,11 +825,12 @@ static gboolean loadSymbols() { LOAD_SYMBOL(fp_pw_context_connect_fd, "pw_context_connect_fd"); LOAD_SYMBOL(fp_pw_core_disconnect, "pw_core_disconnect"); LOAD_SYMBOL(fp_pw_context_new, "pw_context_new"); + LOAD_SYMBOL(fp_pw_context_destroy, "pw_context_destroy"); LOAD_SYMBOL(fp_pw_thread_loop_new, "pw_thread_loop_new"); LOAD_SYMBOL(fp_pw_thread_loop_get_loop, "pw_thread_loop_get_loop"); LOAD_SYMBOL(fp_pw_thread_loop_signal, "pw_thread_loop_signal"); - LOAD_SYMBOL(fp_pw_thread_loop_wait, "pw_thread_loop_wait"); - LOAD_SYMBOL(fp_pw_thread_loop_accept, "pw_thread_loop_accept"); + LOAD_SYMBOL(fp_pw_thread_loop_timed_wait_full, "pw_thread_loop_timed_wait_full"); + LOAD_SYMBOL(fp_pw_thread_loop_get_time, "pw_thread_loop_get_time"); LOAD_SYMBOL(fp_pw_thread_loop_start, "pw_thread_loop_start"); LOAD_SYMBOL(fp_pw_thread_loop_stop, "pw_thread_loop_stop"); LOAD_SYMBOL(fp_pw_thread_loop_destroy, "pw_thread_loop_destroy"); @@ -952,19 +997,27 @@ static int makeScreencast( return pw.pwFd; } - if (!doLoop(*requestedArea)) { + struct timespec captureDeadline; + if (!doLoop(*requestedArea, &captureDeadline)) { return RESULT_ERROR; } - while (!isAllDataReady()) { - fp_pw_thread_loop_lock(pw.loop); - fp_pw_thread_loop_wait(pw.loop); - fp_pw_thread_loop_unlock(pw.loop); - if (hasPipewireFailed) { - doCleanup(); - return RESULT_ERROR; + fp_pw_thread_loop_lock(pw.loop); + int waitResult = 0; + while (!hasPipewireFailed && !isAllDataReady()) { + waitResult = fp_pw_thread_loop_timed_wait_full(pw.loop, &captureDeadline); + if (waitResult < 0) { + DEBUG_SCREENCAST("Frame wait failed: %d\n", waitResult); + break; } } + gboolean failed = hasPipewireFailed || waitResult < 0; + fp_pw_thread_loop_unlock(pw.loop); + + if (failed) { + doCleanup(); + return RESULT_ERROR; + } return RESULT_OK; } @@ -1095,17 +1148,15 @@ JNIEXPORT jint JNICALL Java_sun_awt_screencast_ScreencastHelper_getRGBPixelsImpl } } + fp_pw_thread_loop_lock(pw.loop); if (screenProps->captureDataPixbuf) { gtk->g_object_unref(screenProps->captureDataPixbuf); screenProps->captureDataPixbuf = NULL; } screenProps->shouldCapture = FALSE; - - fp_pw_thread_loop_lock(pw.loop); fp_pw_stream_set_active(screenProps->data->stream, FALSE); - fp_pw_thread_loop_unlock(pw.loop); - screenProps->captureDataReady = FALSE; + fp_pw_thread_loop_unlock(pw.loop); } } From 11fe52380c5f20f2aada3d69924a7760d6d9c0e3 Mon Sep 17 00:00:00 2001 From: Matthias Baesken Date: Fri, 16 Oct 2026 06:30:51 +0000 Subject: [PATCH 067/169] 8394134: ResolverConfigurationImpl.c has unused macros and potential typo in returned value of loadConfig (win) Reviewed-by: djelinski --- .../native/libnet/ResolverConfigurationImpl.c | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/src/java.base/windows/native/libnet/ResolverConfigurationImpl.c b/src/java.base/windows/native/libnet/ResolverConfigurationImpl.c index f6c240a1c0b6..c195b4151714 100644 --- a/src/java.base/windows/native/libnet/ResolverConfigurationImpl.c +++ b/src/java.base/windows/native/libnet/ResolverConfigurationImpl.c @@ -38,14 +38,6 @@ #define BUFF_SIZE 15360 #define MAX_TRIES 3 -#define STS_NO_CONFIG 0x0 /* no configuration found */ -#define STS_SL_FOUND 0x1 /* search list found */ -#define STS_NS_FOUND 0x2 /* name servers found */ -#define STS_ERROR -1 /* error return lodConfig failed memory allccation failure*/ - -#define IS_SL_FOUND(sts) (sts & STS_SL_FOUND) -#define IS_NS_FOUND(sts) (sts & STS_NS_FOUND) - /* JNI ids */ static jfieldID searchlistID; static jfieldID nameserversID; @@ -150,7 +142,7 @@ void strappend(char *s1, char *s2) { * Use DNS server addresses returned by GetAdaptersAddresses for currently * active interfaces. */ -static int loadConfig(JNIEnv *env, char *sl, char *ns) { +static BOOL loadConfig(JNIEnv *env, char *sl, char *ns) { IP_ADAPTER_ADDRESSES *adapters, *adapter; IP_ADAPTER_DNS_SERVER_ADDRESS *dnsServer; WCHAR *suffix; @@ -181,7 +173,7 @@ static int loadConfig(JNIEnv *env, char *sl, char *ns) { ret = getAdapters(env, flags, &adapters); if (ret != ERROR_SUCCESS) { - return STS_ERROR; + return FALSE; } adapter = adapters; @@ -229,7 +221,7 @@ static int loadConfig(JNIEnv *env, char *sl, char *ns) { free(adapters); - return STS_SL_FOUND & STS_NS_FOUND; + return TRUE; } @@ -244,6 +236,7 @@ Java_sun_net_dns_ResolverConfigurationImpl_init0(JNIEnv *env, jclass cls) CHECK_NULL(searchlistID); nameserversID = (*env)->GetStaticFieldID(env, cls, "os_nameservers", "Ljava/lang/String;"); + CHECK_NULL(nameserversID); } /* @@ -261,7 +254,7 @@ Java_sun_net_dns_ResolverConfigurationImpl_loadDNSconfig0(JNIEnv *env, jclass cl searchlist[0] = '\0'; nameservers[0] = '\0'; - if (loadConfig(env, searchlist, nameservers) != STS_ERROR) { + if (loadConfig(env, searchlist, nameservers) == TRUE) { /* * Populate static fields in sun.net.DefaultResolverConfiguration From 3f4d498e737d53c72242646c0cd2b8558f7c0270 Mon Sep 17 00:00:00 2001 From: Fredrik Bredberg Date: Fri, 16 Oct 2026 07:05:09 +0000 Subject: [PATCH 068/169] 8393648: Reconsider ParkEvent alignment Reviewed-by: shade, eosterlund, pchilanomate --- src/hotspot/share/runtime/park.cpp | 19 ++++--------------- src/hotspot/share/runtime/park.hpp | 7 +------ 2 files changed, 5 insertions(+), 21 deletions(-) diff --git a/src/hotspot/share/runtime/park.cpp b/src/hotspot/share/runtime/park.cpp index 9cf29f96e556..82d59d573e9a 100644 --- a/src/hotspot/share/runtime/park.cpp +++ b/src/hotspot/share/runtime/park.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -22,8 +22,6 @@ * */ -#include "memory/allocation.inline.hpp" -#include "nmt/memTracker.hpp" #include "runtime/javaThread.hpp" #include "utilities/spinCriticalSection.hpp" @@ -74,7 +72,6 @@ ParkEvent * ParkEvent::Allocate (Thread * t) { } else { // Do this the hard way -- materialize a new ParkEvent. ev = new ParkEvent () ; - guarantee ((intptr_t(ev) & 0xFF) == 0, "invariant") ; } ev->reset() ; // courtesy to caller ev->AssociatedWith = t ; // Associate ev with t @@ -95,18 +92,10 @@ void ParkEvent::Release (ParkEvent * ev) { } } -// Override operator new and delete so we can ensure that the -// least significant byte of ParkEvent addresses is 0. -// Beware that excessive address alignment is undesirable -// as it can result in D$ index usage imbalance as -// well as bank access imbalance on Niagara-like platforms, -// although Niagara's hash function should help. - -void * ParkEvent::operator new (size_t sz) throw() { - return (void *) ((intptr_t (AllocateHeap(sz + 256, mtSynchronizer, CALLER_PC)) + 256) & -256); -} +// We currently allow a reference to a stale ParkEvent, even after the +// thread has died. Therefore we override operator delete to ensure that +// the ParkEvent is type-stable and immortal. void ParkEvent::operator delete (void * a) { - // ParkEvents are type-stable and immortal ... ShouldNotReachHere(); } diff --git a/src/hotspot/share/runtime/park.hpp b/src/hotspot/share/runtime/park.hpp index 4406a66da0d0..7369dc8b8b07 100644 --- a/src/hotspot/share/runtime/park.hpp +++ b/src/hotspot/share/runtime/park.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -135,11 +135,6 @@ class ParkEvent : public PlatformEvent { FreeNext = nullptr ; } - // We use placement-new to force ParkEvent instances to be - // aligned on 256-byte address boundaries. This ensures that the least - // significant byte of a ParkEvent address is always 0. - - void * operator new (size_t sz) throw(); void operator delete (void * a) ; public: From d3580139d4ee22596fba0e3b8bdeb740d00f7eef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20H=C3=A4ssig?= Date: Fri, 16 Oct 2026 07:18:44 +0000 Subject: [PATCH 069/169] 8394744: [BACKOUT][Valhalla] assert memnode.cpp:1383 added by 8382936 caused issues with Valhalla arrays Reviewed-by: qamai, mchevalier --- src/hotspot/share/opto/castnode.cpp | 10 +++---- src/hotspot/share/opto/library_call.cpp | 9 ------- src/hotspot/share/opto/macro.cpp | 6 +---- src/hotspot/share/opto/memnode.cpp | 18 +------------ src/hotspot/share/opto/parse2.cpp | 34 ++++++++++++++---------- src/hotspot/share/opto/subnode.cpp | 13 +++------ src/hotspot/share/opto/valuetypenode.cpp | 33 +++++++++++++++++------ 7 files changed, 54 insertions(+), 69 deletions(-) diff --git a/src/hotspot/share/opto/castnode.cpp b/src/hotspot/share/opto/castnode.cpp index 587216f84af0..f2d22f8a2f6b 100644 --- a/src/hotspot/share/opto/castnode.cpp +++ b/src/hotspot/share/opto/castnode.cpp @@ -233,16 +233,14 @@ Node* ConstraintCastNode::ideal_cast_of_value_type_node(PhaseGVN* phase) { return nullptr; } - const Type* vt_type = vt->type(); - const Type* join = vt_type->filter(type()); + const Type* join = vt->type()->filter(type()); if (join == Type::TOP) { // Do not push a dead Cast since its type can be unrelated return nullptr; } - if (join == vt_type->remove_speculative() && type()->maybe_null()) { - // Redundant cast, let Identity handle. Except when the cast is not-null, in which case the - // users will also expect the null-free fields to be not-null. + if (join == vt->type()->remove_speculative()) { + // Redundant cast, let Identity handle return nullptr; } @@ -252,7 +250,7 @@ Node* ConstraintCastNode::ideal_cast_of_value_type_node(PhaseGVN* phase) { } // The only possible case left is that the cast is a cast to not-null - assert(join == vt_type->filter(TypePtr::NOTNULL), "must be"); + assert(join == vt->type()->filter(TypePtr::NOTNULL), "must be"); ValueTypeNode* res = vt->clone()->as_ValueType(); res->set_null_marker(*phase); diff --git a/src/hotspot/share/opto/library_call.cpp b/src/hotspot/share/opto/library_call.cpp index 209df4f85f4e..be0c21e823ec 100644 --- a/src/hotspot/share/opto/library_call.cpp +++ b/src/hotspot/share/opto/library_call.cpp @@ -7168,16 +7168,7 @@ bool LibraryCallKit::inline_arraycopy() { // TODO 8251971 This is too strong generate_fair_guard(flat_array_test(src), slow_region); - if (const TypeAryPtr* src_ary_type = _gvn.type(src)->isa_aryptr(); src_ary_type != nullptr && !src_ary_type->is_flat()) { - const TypeAryPtr* not_flat_src = src_ary_type->cast_to_not_flat(); - src = _gvn.transform(new CheckCastPPNode(control(), src, not_flat_src, ConstraintCastNode::DependencyType::NonFloatingNarrowing)); - } - generate_fair_guard(flat_array_test(dest), slow_region); - if (const TypeAryPtr* dst_ary_type = _gvn.type(dest)->isa_aryptr(); dst_ary_type != nullptr && !dst_ary_type->is_flat()) { - const TypeAryPtr* not_flat_dst = dst_ary_type->cast_to_not_flat(); - dest = _gvn.transform(new CheckCastPPNode(control(), dest, not_flat_dst, ConstraintCastNode::DependencyType::NonFloatingNarrowing)); - } } { diff --git a/src/hotspot/share/opto/macro.cpp b/src/hotspot/share/opto/macro.cpp index dabba2c5f418..6285f4810a4e 100644 --- a/src/hotspot/share/opto/macro.cpp +++ b/src/hotspot/share/opto/macro.cpp @@ -879,12 +879,8 @@ bool PhaseMacroExpand::can_eliminate_allocation(PhaseIterGVN* igvn, AllocateNode for (DUIterator_Fast kmax, k = use->fast_outs(kmax); k < kmax; k++) { Node* u = use->fast_out(k); if (u->is_ValueType()) { - ValueTypeNode* vt = u->as_ValueType(); - if (vt->get_oop() == use) { - worklist.push(use); - } - // Use in flat field can be eliminated + ValueTypeNode* vt = u->as_ValueType(); for (uint i = 0; i < vt->field_count(); ++i) { if (vt->field_value(i) == use && !vt->field(i)->is_flat()) { can_eliminate = false; // Use in non-flat field diff --git a/src/hotspot/share/opto/memnode.cpp b/src/hotspot/share/opto/memnode.cpp index bd39603eaff1..a522ea77064e 100644 --- a/src/hotspot/share/opto/memnode.cpp +++ b/src/hotspot/share/opto/memnode.cpp @@ -1308,28 +1308,12 @@ static Node* see_through_value_type(PhaseValues* phase, const LoadNode* load, No // of aliasing. // This method may find an unencoded node instead of the corresponding encoded one. Node* LoadNode::can_see_stored_value_through_membars(Node* st, PhaseValues* phase) const { - auto verify_type = [&](Node* res) { - if (res == nullptr || res->bottom_type() == Type::TOP) { - return true; - } - - // We can return an uncompressed oop when this is a LoadN - if (const TypePtr* this_ptr_type = type()->make_ptr(); this_ptr_type != nullptr) { - const TypePtr* res_ptr_type = res->bottom_type()->make_ptr(); - return res_ptr_type != nullptr && res_ptr_type->higher_equal(this_ptr_type); - } - - return is_java_primitive(value_basic_type()) || res->bottom_type()->higher_equal(type()); - }; - Node* ld_adr = in(MemNode::Address); intptr_t ld_off = 0; Node* ld_base = AddPNode::Ideal_base_and_offset(ld_adr, phase, ld_off); // Try to see through an ValueTypeNode Node* value = see_through_value_type(phase, this, ld_base, ld_off); if (value != nullptr) { - // TODO 8390080 - // assert(verify_type(value), "the fold is unsafe"); return value; } @@ -1385,7 +1369,7 @@ Node* LoadNode::can_see_stored_value_through_membars(Node* st, PhaseValues* phas Node* res = can_see_stored_value(st, phase); // TODO: reimplement assert, see: JDK-8386157 - assert(verify_type(res), "the fold is unsafe"); + //assert(res == nullptr || is_java_primitive(value_basic_type()) || res->bottom_type()->higher_equal(type()), "the fold is unsafe"); return res; } diff --git a/src/hotspot/share/opto/parse2.cpp b/src/hotspot/share/opto/parse2.cpp index 09ffb87ebdc4..0aae6e6bc441 100644 --- a/src/hotspot/share/opto/parse2.cpp +++ b/src/hotspot/share/opto/parse2.cpp @@ -3738,20 +3738,26 @@ void Parse::do_one_bytecode() { maybe_add_safepoint(iter().get_dest()); a = null(); b = pop(); - if (!_gvn.type(b)->speculative_maybe_null() && - !too_many_traps(Deoptimization::Reason_speculate_null_check)) { - inc_sp(1); - Node* null_ctl = top(); - b = null_check_oop(b, &null_ctl, true, true, true); - assert(null_ctl->is_top(), "no null control here"); - dec_sp(1); - } else if (_gvn.type(b)->speculative_always_null() && - !too_many_traps(Deoptimization::Reason_speculate_null_assert)) { - inc_sp(1); - b = null_assert(b); - dec_sp(1); - } - c = _gvn.transform(new CmpPNode(b, a)); + if (b->is_ValueType()) { + // Null checking a scalarized but nullable value type. Check the null marker + // input instead of the oop input to avoid keeping buffer allocations alive + c = _gvn.transform(new CmpINode(b->as_ValueType()->get_null_marker(), zerocon(T_INT))); + } else { + if (!_gvn.type(b)->speculative_maybe_null() && + !too_many_traps(Deoptimization::Reason_speculate_null_check)) { + inc_sp(1); + Node* null_ctl = top(); + b = null_check_oop(b, &null_ctl, true, true, true); + assert(null_ctl->is_top(), "no null control here"); + dec_sp(1); + } else if (_gvn.type(b)->speculative_always_null() && + !too_many_traps(Deoptimization::Reason_speculate_null_assert)) { + inc_sp(1); + b = null_assert(b); + dec_sp(1); + } + c = _gvn.transform( new CmpPNode(b, a) ); + } do_ifnull(btest, c); break; diff --git a/src/hotspot/share/opto/subnode.cpp b/src/hotspot/share/opto/subnode.cpp index c8565ff43aa5..18e82113f1b1 100644 --- a/src/hotspot/share/opto/subnode.cpp +++ b/src/hotspot/share/opto/subnode.cpp @@ -1144,16 +1144,9 @@ Node* CmpPNode::Ideal(PhaseGVN *phase, bool can_reshape) { Node* uncast_in1 = in(1)->uncast(); Node* uncast_in2 = in(2)->uncast(); if (uncast_in1->is_ValueType() && phase->type(uncast_in2)->is_zero_type()) { - if (can_reshape) { - // Null checking a scalarized but nullable inline type. Check the null marker input instead - // of the oop input to avoid keeping buffer allocations alive. - return new CmpINode(uncast_in1->as_ValueType()->get_null_marker(), phase->intcon(0)); - } else { - // Keep the CmpP during parsing so that the parser can recognize the compare-against-null - // pattern - phase->record_for_igvn(this); - return nullptr; - } + // Null checking a scalarized but nullable value type. Check the null marker + // input instead of the oop input to avoid keeping buffer allocations alive. + return new CmpINode(uncast_in1->as_ValueType()->get_null_marker(), phase->intcon(0)); } if (uncast_in1->is_ValueType() || uncast_in2->is_ValueType()) { // In C2 IR, CmpP on value objects is a pointer comparison, not a value comparison. diff --git a/src/hotspot/share/opto/valuetypenode.cpp b/src/hotspot/share/opto/valuetypenode.cpp index b55621c4a418..15494a1cca01 100644 --- a/src/hotspot/share/opto/valuetypenode.cpp +++ b/src/hotspot/share/opto/valuetypenode.cpp @@ -94,13 +94,9 @@ ValueTypeNode* ValueTypeNode::clone_with_phis(PhaseGVN* gvn, Node* region, SafeP Node* value = vt->field_value(i); if (field->is_flat()) { // Handle flat fields recursively - value = value->as_ValueType()->clone_with_phis(gvn, region, map, is_non_null && field->is_null_free()); + value = value->as_ValueType()->clone_with_phis(gvn, region, map); } else { t = Type::get_const_type(type); - if (is_non_null && field->is_null_free()) { - t = t->join(TypePtr::NOTNULL); - } - value = PhiNode::make(region, init_with_top ? top : value, t); gvn->set_type(value, t); gvn->record_for_igvn(value); @@ -2013,13 +2009,34 @@ ValueTypeNode* ValueTypeNode::clone_if_required(PhaseGVN* gvn, SafePointNode* ma } const Type* ValueTypeNode::Value(PhaseGVN* phase) const { + Node* oop = get_oop(); + const Type* toop = phase->type(oop); +#ifdef ASSERT + if (oop->is_Con() && toop->is_zero_type() && _type->isa_oopptr()->is_known_instance()) { + // We are not allocated (anymore) and should therefore not have an instance id + dump(1); + assert(false, "Unbuffered value type should not have known instance id"); + } +#endif + if (toop == Type::TOP) { + return Type::TOP; + } + const Type* t = toop->filter_speculative(_type); + // Because of contradicting type profiling, we can end up with top as speculative type, + // which would then get removed by cleanup_speculative. In this case we have to run filter_speculative + // again, otherwise we would break the idempotence of Value + if (t->speculative() == nullptr && toop->speculative() != nullptr) { + t = toop->filter_speculative(t); + } + if (t->singleton()) { + // Don't replace ValueType by a constant + t = _type; + } const Type* tinit = phase->type(in(NullMarker)); if (tinit == Type::TOP) { return Type::TOP; } - - const Type* t = type(); - if (tinit == TypeInt::ONE) { + if (tinit->isa_int() && tinit->is_int()->is_con(1)) { t = t->join_speculative(TypePtr::NOTNULL); } return t; From d12c19c814af637d62aa35b598f4efab6455c9c5 Mon Sep 17 00:00:00 2001 From: Quan Anh Mai Date: Fri, 16 Oct 2026 07:45:56 +0000 Subject: [PATCH 070/169] 8394225: [Valhalla] C2 assert in TypeInstPtr::TypeInstPtr during scalar replacement Reviewed-by: kvn, thartmann --- src/hotspot/share/opto/type.cpp | 2 +- .../types/TestTryExactAbstractKlass.java | 82 +++++++++++++++++++ 2 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 test/hotspot/jtreg/compiler/types/TestTryExactAbstractKlass.java diff --git a/src/hotspot/share/opto/type.cpp b/src/hotspot/share/opto/type.cpp index 1ec37b9683d1..dcc34701d6cd 100644 --- a/src/hotspot/share/opto/type.cpp +++ b/src/hotspot/share/opto/type.cpp @@ -4367,7 +4367,7 @@ TypeInstPtr::TypeInstPtr(PTR ptr, ciKlass* k, const TypeInterfaces* interfaces, assert(k != nullptr && (k->is_loaded() || o == nullptr), "cannot have constants with non-loaded klass"); - assert(!xk || (k->is_loaded() && !k->is_abstract()), "pointer to an oop of an exact type must be concrete"); + assert(!xk || k->is_loaded(), "pointer to an oop of an exact type must be loaded"); assert(!xk || interfaces->eq(k->as_instance_klass()), "inconsistency between k and interfaces"); }; diff --git a/test/hotspot/jtreg/compiler/types/TestTryExactAbstractKlass.java b/test/hotspot/jtreg/compiler/types/TestTryExactAbstractKlass.java new file mode 100644 index 000000000000..a9b7bbbe94f1 --- /dev/null +++ b/test/hotspot/jtreg/compiler/types/TestTryExactAbstractKlass.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package compiler.types; + +import compiler.lib.ir_framework.*; +import jdk.test.lib.Asserts; + +/* + * @test + * @bug 8394225 + * @summary Multiple cases where a TypeInstPtr is constructed with an abstract instance_klass and + * klass_is_exact. + * @library /test/lib / + * @enablePreview + * @run driver ${test.main.class} + */ +public class TestTryExactAbstractKlass { + private static abstract value class A {} + + public static void main(String[] args) { + var framework = new TestFramework(); + framework.setDefaultWarmup(1); + framework.addFlags("--enable-preview"); + framework.start(); + } + + @Test + @IR(counts = {IRNode.IF, "2"}) + private static A testMethodSignature(A[] array) { + // Should know that A[] must not be flat + return array[0]; + } + + @Run(test = "testMethodSignature") + public void runMethodSignature() { + Asserts.assertNull(testMethodSignature(new A[1])); + } + + @Test + private static A testFromKlass(Object o) { + return (A) o; + } + + @Run(test = "testFromKlass") + public void runFromKlass() { + Asserts.assertNull(testFromKlass(null)); + } + + @Test + private static A testNewInstance(A obj) throws ReflectiveOperationException { + if (obj == null) { + return null; + } + + return obj.getClass().getConstructor().newInstance(); + } + + @Run(test = "testNewInstance") + public void runNewInstance() throws ReflectiveOperationException { + Asserts.assertNull(testNewInstance(null)); + } +} From 5e7839369ab4526d27fc03475de54f17fbf12c70 Mon Sep 17 00:00:00 2001 From: Jan Lahoda Date: Fri, 16 Oct 2026 07:46:59 +0000 Subject: [PATCH 071/169] 8389478: Crash with missing symbol in serializable lambda: AssertionError: isSubtype PACKAGE Reviewed-by: vromero --- .../com/sun/tools/javac/comp/Check.java | 2 +- .../tools/javac/recovery/AttrRecovery.java | 38 ++++++++++++++++++- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java index c39868223dcf..6d73f823d1b1 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java @@ -2995,7 +2995,7 @@ void checkAccessFromSerializableElement(final JCTree tree, boolean isLambda) { private void checkAccessFromSerializableElementInner(final JCTree tree, boolean isLambda) { Symbol sym = TreeInfo.symbol(tree); - if (!sym.kind.matches(KindSelector.VAL_MTH)) { + if (!sym.kind.matches(KindSelector.VAL_MTH) || sym.kind == ERR) { return; } diff --git a/test/langtools/tools/javac/recovery/AttrRecovery.java b/test/langtools/tools/javac/recovery/AttrRecovery.java index fb852b5b3f26..37e6bd69f1e9 100644 --- a/test/langtools/tools/javac/recovery/AttrRecovery.java +++ b/test/langtools/tools/javac/recovery/AttrRecovery.java @@ -23,7 +23,8 @@ /* * @test - * @bug 8301580 8322159 8333107 8332230 8338678 8351260 8366196 8372336 8373094 8384229 8387865 + * @bug 8301580 8322159 8333107 8332230 8338678 8351260 8366196 8372336 8373094 8384229 + * 8387865 8389478 * @summary Verify error recovery w.r.t. Attr * @library /tools/lib * @modules jdk.compiler/com.sun.tools.javac.api @@ -887,6 +888,41 @@ public C() { assertEquals(expected, actual); } + @Test //JDK-8389478 + public void testCheckAccessFromSerializableElement() throws Exception { + String code = """ + import java.io.Serializable; + + public class Test { + interface SerializableFunction extends Serializable { + R apply(T t); + } + + public static void main(String[] args) { + SerializableFunction f = (x) -> { + // Map is not imported + return Map.Entry.FIELD; + }; + } + } + """; + List actual = new JavacTask(tb) + .options("-XDrawDiagnostics", "-XDdev", + "-XDshould-stop.at=WARN", "-Xlint:all") + .sources(code) + .outdir(base) + .run(Expect.FAIL) + .writeAll() + .getOutputLines(OutputKind.DIRECT); + + List expected = List.of( + "Test.java:11:23: compiler.err.doesnt.exist: Map", + "1 error" + ); + + assertEquals(expected, actual); + } + @BeforeEach public void setUp(TestInfo info) throws IOException { base = Path.of(info.getTestMethod().orElseThrow().getName()); From c419514377933380ab7bc7bf5f6c3c723970ccb1 Mon Sep 17 00:00:00 2001 From: Gui Cao Date: Fri, 16 Oct 2026 08:25:06 +0000 Subject: [PATCH 072/169] 8388559: RISC-V: use branchless instructions for CMoveI with constant 0/1 Co-authored-by: gns Reviewed-by: gli, fyang, ayang --- .../cpu/riscv/c2_MacroAssembler_riscv.cpp | 69 +++++++++++++++++++ .../cpu/riscv/c2_MacroAssembler_riscv.hpp | 7 ++ src/hotspot/cpu/riscv/riscv.ad | 66 ++++++++++++++++++ 3 files changed, 142 insertions(+) diff --git a/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.cpp b/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.cpp index 72a20ca50012..7d412087753e 100644 --- a/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.cpp +++ b/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.cpp @@ -2108,6 +2108,40 @@ void C2_MacroAssembler::enc_cmove(int cmpFlag, Register op1, Register op2, Regis } } +// Set dst to 0 if the condition specified by cmpFlag is true, or 1 otherwise. +// cmpFlag may include unsigned_branch_mask for an unsigned comparison. +void C2_MacroAssembler::enc_cmove_zero_one(int cmpFlag, Register op1, Register op2, Register dst) { + bool is_unsigned = (cmpFlag & unsigned_branch_mask) == unsigned_branch_mask; + int op_select = cmpFlag & (~unsigned_branch_mask); + + switch (op_select) { + case BoolTest::eq: + xorr(dst, op1, op2); + snez(dst, dst); + break; + case BoolTest::ne: + xorr(dst, op1, op2); + seqz(dst, dst); + break; + case BoolTest::lt: + is_unsigned ? sltu(dst, op1, op2) : slt(dst, op1, op2); + seqz(dst, dst); + break; + case BoolTest::gt: + is_unsigned ? sltu(dst, op2, op1) : slt(dst, op2, op1); + seqz(dst, dst); + break; + case BoolTest::le: + is_unsigned ? sltu(dst, op2, op1) : slt(dst, op2, op1); + break; + case BoolTest::ge: + is_unsigned ? sltu(dst, op1, op2) : slt(dst, op1, op2); + break; + default: + ShouldNotReachHere(); + } +} + void C2_MacroAssembler::enc_cmove_cmp_fp(int cmpFlag, FloatRegister op1, FloatRegister op2, Register dst, Register src, bool is_single) { int op_select = cmpFlag & (~unsigned_branch_mask); @@ -2213,6 +2247,41 @@ void C2_MacroAssembler::enc_cmove_fp_cmp_fp(int cmpFlag, } } +// Set dst to 0 if the floating-point condition is true, or 1 otherwise. +// cmpFlag may include double_branch_mask for a double comparison. +// CmpF/CmpD treat unordered as less: eq/gt/ge are false for NaN, while +// ne/lt/le are true. +void C2_MacroAssembler::enc_cmove_cmp_fp_zero_one(int cmpFlag, FloatRegister op1, FloatRegister op2, Register dst) { + bool is_single = (cmpFlag & double_branch_mask) == 0; + int op_select = cmpFlag & (~double_branch_mask); + + switch (op_select) { + case BoolTest::eq: + is_single ? feq_s(dst, op1, op2) : feq_d(dst, op1, op2); + seqz(dst, dst); + break; + case BoolTest::ne: + is_single ? feq_s(dst, op1, op2) : feq_d(dst, op1, op2); + break; + case BoolTest::lt: + is_single ? fle_s(dst, op2, op1) : fle_d(dst, op2, op1); + break; + case BoolTest::le: + is_single ? flt_s(dst, op2, op1) : flt_d(dst, op2, op1); + break; + case BoolTest::gt: + is_single ? flt_s(dst, op2, op1) : flt_d(dst, op2, op1); + seqz(dst, dst); + break; + case BoolTest::ge: + is_single ? fle_s(dst, op2, op1) : fle_d(dst, op2, op1); + seqz(dst, dst); + break; + default: + ShouldNotReachHere(); + } +} + static void vshift_left(C2_MacroAssembler* masm, VectorRegister dst, VectorRegister src, int shift) { assert(shift >= 0 && shift <= BitsPerInt, "unexpected shift amount"); if (shift < BitsPerInt) { diff --git a/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.hpp b/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.hpp index 093f367b7903..1713b60ccc6a 100644 --- a/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.hpp +++ b/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.hpp @@ -131,6 +131,9 @@ Register op1, Register op2, Register dst, Register src); + void enc_cmove_zero_one(int cmpFlag, + Register op1, Register op2, Register dst); + void enc_cmove_cmp_fp(int cmpFlag, FloatRegister op1, FloatRegister op2, Register dst, Register src, bool is_single); @@ -142,6 +145,10 @@ FloatRegister dst, FloatRegister src, bool cmp_single, bool cmov_single); + void enc_cmove_cmp_fp_zero_one(int cmpFlag, + FloatRegister op1, FloatRegister op2, + Register dst); + void compress_bits_v(VectorRegister dst, VectorRegister src, VectorRegister bit_mask, VectorRegister src_tmp, VectorRegister mask_tmp, VectorRegister tmp1, VectorRegister tmp2, diff --git a/src/hotspot/cpu/riscv/riscv.ad b/src/hotspot/cpu/riscv/riscv.ad index 615378b7f4a0..5513eafa7762 100644 --- a/src/hotspot/cpu/riscv/riscv.ad +++ b/src/hotspot/cpu/riscv/riscv.ad @@ -10870,6 +10870,23 @@ instruct far_cmpP_narrowOop_imm0_branch(cmpOpEqNe cmp, iRegN op1, immP0 zero, la // --------- CMoveI --------- +instruct cmovI_cmpI_cmpL_zero_one(iRegINoSp dst, iRegIorL op1, iRegIorL op2, + cmpOp cop, immI_1 one, immI0 zero) %{ + match(Set dst (CMoveI (Binary cop (CmpI op1 op2)) (Binary one zero))); + match(Set dst (CMoveI (Binary cop (CmpL op1 op2)) (Binary one zero))); + + ins_cost(ALU_COST * 2); + format %{ "CMoveI $dst, ($op1 $cop $op2), 1, 0\t#@cmovI_cmpI_cmpL_zero_one" %} + + ins_encode %{ + __ enc_cmove_zero_one($cop$$cmpcode, + as_Register($op1$$reg), as_Register($op2$$reg), + as_Register($dst$$reg)); + %} + + ins_pipe(pipe_class_compare); +%} + instruct cmovI_cmpI(iRegINoSp dst, iRegI src, iRegI op1, iRegI op2, cmpOp cop) %{ match(Set dst (CMoveI (Binary cop (CmpI op1 op2)) (Binary dst src))); ins_cost(ALU_COST + BRANCH_COST); @@ -10887,6 +10904,23 @@ instruct cmovI_cmpI(iRegINoSp dst, iRegI src, iRegI op1, iRegI op2, cmpOp cop) % ins_pipe(pipe_class_compare); %} +instruct cmovI_cmpU_cmpUL_zero_one(iRegINoSp dst, iRegIorL op1, iRegIorL op2, + cmpOpU cop, immI_1 one, immI0 zero) %{ + match(Set dst (CMoveI (Binary cop (CmpU op1 op2)) (Binary one zero))); + match(Set dst (CMoveI (Binary cop (CmpUL op1 op2)) (Binary one zero))); + + ins_cost(ALU_COST * 2); + format %{ "CMoveI $dst, ($op1 $cop $op2), 1, 0\t#@cmovI_cmpU_cmpUL_zero_one" %} + + ins_encode %{ + __ enc_cmove_zero_one($cop$$cmpcode | C2_MacroAssembler::unsigned_branch_mask, + as_Register($op1$$reg), as_Register($op2$$reg), + as_Register($dst$$reg)); + %} + + ins_pipe(pipe_class_compare); +%} + instruct cmovI_cmpU(iRegINoSp dst, iRegI src, iRegI op1, iRegI op2, cmpOpU cop) %{ match(Set dst (CMoveI (Binary cop (CmpU op1 op2)) (Binary dst src))); ins_cost(ALU_COST + BRANCH_COST); @@ -10938,6 +10972,22 @@ instruct cmovI_cmpUL(iRegINoSp dst, iRegI src, iRegL op1, iRegL op2, cmpOpU cop) ins_pipe(pipe_class_compare); %} +instruct cmovI_cmpF_zero_one(iRegINoSp dst, fRegF op1, fRegF op2, + cmpOp cop, immI_1 one, immI0 zero) %{ + match(Set dst (CMoveI (Binary cop (CmpF op1 op2)) (Binary one zero))); + + ins_cost(ALU_COST * 2); + format %{ "CMoveI $dst, ($op1 $cop $op2), 1, 0\t#@cmovI_cmpF_zero_one" %} + + ins_encode %{ + __ enc_cmove_cmp_fp_zero_one($cop$$cmpcode, + as_FloatRegister($op1$$reg), as_FloatRegister($op2$$reg), + as_Register($dst$$reg)); + %} + + ins_pipe(pipe_class_compare); +%} + instruct cmovI_cmpF(iRegINoSp dst, iRegI src, fRegF op1, fRegF op2, cmpOp cop) %{ match(Set dst (CMoveI (Binary cop (CmpF op1 op2)) (Binary dst src))); ins_cost(ALU_COST + BRANCH_COST); @@ -10955,6 +11005,22 @@ instruct cmovI_cmpF(iRegINoSp dst, iRegI src, fRegF op1, fRegF op2, cmpOp cop) % ins_pipe(pipe_class_compare); %} +instruct cmovI_cmpD_zero_one(iRegINoSp dst, fRegD op1, fRegD op2, + cmpOp cop, immI_1 one, immI0 zero) %{ + match(Set dst (CMoveI (Binary cop (CmpD op1 op2)) (Binary one zero))); + + ins_cost(ALU_COST * 2); + format %{ "CMoveI $dst, ($op1 $cop $op2), 1, 0\t#@cmovI_cmpD_zero_one" %} + + ins_encode %{ + __ enc_cmove_cmp_fp_zero_one($cop$$cmpcode | C2_MacroAssembler::double_branch_mask, + as_FloatRegister($op1$$reg), as_FloatRegister($op2$$reg), + as_Register($dst$$reg)); + %} + + ins_pipe(pipe_class_compare); +%} + instruct cmovI_cmpD(iRegINoSp dst, iRegI src, fRegD op1, fRegD op2, cmpOp cop) %{ match(Set dst (CMoveI (Binary cop (CmpD op1 op2)) (Binary dst src))); ins_cost(ALU_COST + BRANCH_COST); From 8e8e30d71cc8f77d81170205a4627191e5201568 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Jeli=C5=84ski?= Date: Fri, 16 Oct 2026 08:51:11 +0000 Subject: [PATCH 073/169] 8394580: Binding a socket to 127.x.x.255 fails on Linux Reviewed-by: jpai, alanb --- src/java.base/unix/native/libnet/net_util_md.c | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/src/java.base/unix/native/libnet/net_util_md.c b/src/java.base/unix/native/libnet/net_util_md.c index d629c697ffe2..c8e872a3c8b7 100644 --- a/src/java.base/unix/native/libnet/net_util_md.c +++ b/src/java.base/unix/native/libnet/net_util_md.c @@ -594,29 +594,12 @@ NET_SetSockOpt(int fd, int level, int opt, const void *arg, /* * Wrapper for bind system call - performs any necessary pre/post * processing to deal with OS specific issues :- - * - * Linux allows a socket to bind to 127.0.0.255 which must be - * caught. */ int NET_Bind(int fd, SOCKETADDRESS *sa, int len) { int rv; -#ifdef __linux__ - /* - * ## get bugId for this issue - goes back to 1.2.2 port ## - * ## When IPv6 is enabled this will be an IPv4-mapped - * ## with family set to AF_INET6 - */ - if (sa->sa.sa_family == AF_INET) { - if ((ntohl(sa->sa4.sin_addr.s_addr) & 0x7f0000ff) == 0x7f0000ff) { - errno = EADDRNOTAVAIL; - return -1; - } - } -#endif - rv = bind(fd, &sa->sa, len); return rv; From 394a822c2cff9094dddaea80366da0575d862b1c Mon Sep 17 00:00:00 2001 From: Marc Chevalier Date: Fri, 16 Oct 2026 10:08:03 +0000 Subject: [PATCH 074/169] 8394416: C2: cleanup dead/useless fields Reviewed-by: chagedorn, qamai, thartmann --- src/hotspot/cpu/arm/vm_version_arm.hpp | 3 -- src/hotspot/cpu/x86/foreignGlobals_x86.hpp | 1 - src/hotspot/cpu/x86/foreignGlobals_x86_64.cpp | 2 - src/hotspot/share/opto/chaitin.cpp | 29 ++---------- src/hotspot/share/opto/chaitin.hpp | 9 +--- src/hotspot/share/opto/graphKit.cpp | 2 - src/hotspot/share/opto/graphKit.hpp | 3 -- src/hotspot/share/opto/machnode.hpp | 1 - src/hotspot/share/opto/matcher.cpp | 3 -- src/hotspot/share/opto/matcher.hpp | 5 --- src/hotspot/share/opto/type.cpp | 32 ++++--------- src/hotspot/share/opto/type.hpp | 45 ++++++++----------- 12 files changed, 32 insertions(+), 103 deletions(-) diff --git a/src/hotspot/cpu/arm/vm_version_arm.hpp b/src/hotspot/cpu/arm/vm_version_arm.hpp index c400cad24b45..4ade00a5d1fa 100644 --- a/src/hotspot/cpu/arm/vm_version_arm.hpp +++ b/src/hotspot/cpu/arm/vm_version_arm.hpp @@ -29,9 +29,6 @@ #include "runtime/globals_extension.hpp" class VM_Version: public Abstract_VM_Version { - static bool _has_simd; - static bool _has_mp_ext; - protected: // Are we done with vm version initialization static bool _is_initialized; diff --git a/src/hotspot/cpu/x86/foreignGlobals_x86.hpp b/src/hotspot/cpu/x86/foreignGlobals_x86.hpp index 7b3859ce6613..bc671ce4249a 100644 --- a/src/hotspot/cpu/x86/foreignGlobals_x86.hpp +++ b/src/hotspot/cpu/x86/foreignGlobals_x86.hpp @@ -32,7 +32,6 @@ struct ABIDescriptor { GrowableArray _integer_return_registers; GrowableArray _vector_argument_registers; GrowableArray _vector_return_registers; - size_t _X87_return_registers_noof; GrowableArray _integer_additional_volatile_registers; GrowableArray _vector_additional_volatile_registers; diff --git a/src/hotspot/cpu/x86/foreignGlobals_x86_64.cpp b/src/hotspot/cpu/x86/foreignGlobals_x86_64.cpp index 55952bba063a..67462ba3cbee 100644 --- a/src/hotspot/cpu/x86/foreignGlobals_x86_64.cpp +++ b/src/hotspot/cpu/x86/foreignGlobals_x86_64.cpp @@ -54,8 +54,6 @@ const ABIDescriptor ForeignGlobals::parse_abi_descriptor(jobject jabi) { refArrayOop outputStorage = jdk_internal_foreign_abi_ABIDescriptor::outputStorage(abi_oop); parse_register_array(outputStorage, StorageType::INTEGER, abi._integer_return_registers, as_Register); parse_register_array(outputStorage, StorageType::VECTOR, abi._vector_return_registers, as_XMMRegister); - refArrayOop subarray = oop_cast(outputStorage->obj_at((int) StorageType::X87)); - abi._X87_return_registers_noof = subarray->length(); refArrayOop volatileStorage = jdk_internal_foreign_abi_ABIDescriptor::volatileStorage(abi_oop); parse_register_array(volatileStorage, StorageType::INTEGER, abi._integer_additional_volatile_registers, as_Register); diff --git a/src/hotspot/share/opto/chaitin.cpp b/src/hotspot/share/opto/chaitin.cpp index 82c2e374d672..c0cb85010f85 100644 --- a/src/hotspot/share/opto/chaitin.cpp +++ b/src/hotspot/share/opto/chaitin.cpp @@ -206,7 +206,6 @@ PhaseChaitin::PhaseChaitin(uint unique, PhaseCFG &cfg, Matcher &matcher, bool sc ) , _live(nullptr) , _lo_degree(0), _lo_stk_degree(0), _hi_degree(0), _simplified(0) - , _oldphi(unique) #ifndef PRODUCT , _trace_spilling(C->directive()->TraceSpillingOption) #endif @@ -2699,20 +2698,8 @@ void PhaseChaitin::verify(ResourceArea* a, bool verify_ifg) const { } #endif // ASSERT -int PhaseChaitin::_final_loads = 0; -int PhaseChaitin::_final_stores = 0; -int PhaseChaitin::_final_memoves= 0; -int PhaseChaitin::_final_copies = 0; -double PhaseChaitin::_final_load_cost = 0; -double PhaseChaitin::_final_store_cost = 0; -double PhaseChaitin::_final_memove_cost= 0; -double PhaseChaitin::_final_copy_cost = 0; int PhaseChaitin::_conserv_coalesce = 0; -int PhaseChaitin::_conserv_coalesce_pair = 0; -int PhaseChaitin::_conserv_coalesce_trie = 0; -int PhaseChaitin::_conserv_coalesce_quad = 0; int PhaseChaitin::_post_alloc = 0; -int PhaseChaitin::_lost_opp_pp_coalesce = 0; int PhaseChaitin::_lost_opp_cflow_coalesce = 0; int PhaseChaitin::_used_cisc_instructions = 0; int PhaseChaitin::_unused_cisc_instructions = 0; @@ -2724,19 +2711,11 @@ uint PhaseChaitin::_high_pressure = 0; uint PhaseChaitin::_low_pressure = 0; void PhaseChaitin::print_chaitin_statistics() { - tty->print_cr("Inserted %d spill loads, %d spill stores, %d mem-mem moves and %d copies.", _final_loads, _final_stores, _final_memoves, _final_copies); - tty->print_cr("Total load cost= %6.0f, store cost = %6.0f, mem-mem cost = %5.2f, copy cost = %5.0f.", _final_load_cost, _final_store_cost, _final_memove_cost, _final_copy_cost); - tty->print_cr("Adjusted spill cost = %7.0f.", - _final_load_cost*4.0 + _final_store_cost * 2.0 + - _final_copy_cost*1.0 + _final_memove_cost*12.0); - tty->print("Conservatively coalesced %d copies, %d pairs", - _conserv_coalesce, _conserv_coalesce_pair); - if( _conserv_coalesce_trie || _conserv_coalesce_quad ) - tty->print(", %d tries, %d quads", _conserv_coalesce_trie, _conserv_coalesce_quad); + tty->print("Conservatively coalesced %d copies", + _conserv_coalesce); tty->print_cr(", %d post alloc.", _post_alloc); - if( _lost_opp_pp_coalesce || _lost_opp_cflow_coalesce ) - tty->print_cr("Lost coalesce opportunity, %d private-private, and %d cflow interfered.", - _lost_opp_pp_coalesce, _lost_opp_cflow_coalesce ); + if (_lost_opp_cflow_coalesce) + tty->print_cr("Lost coalesce opportunity, %d cflow interfered.", _lost_opp_cflow_coalesce); if( _used_cisc_instructions || _unused_cisc_instructions ) tty->print_cr("Used cisc instruction %d, remained in register %d", _used_cisc_instructions, _unused_cisc_instructions); diff --git a/src/hotspot/share/opto/chaitin.hpp b/src/hotspot/share/opto/chaitin.hpp index fd834c1002bb..296b4699a8fb 100644 --- a/src/hotspot/share/opto/chaitin.hpp +++ b/src/hotspot/share/opto/chaitin.hpp @@ -479,8 +479,6 @@ class PhaseChaitin : public PhaseRegAlloc { bool is_high_pressure( Block *b, LRG *lrg, uint insidx ); - uint _oldphi; // Node index which separates pre-allocation nodes - Block **_blks; // Array of blocks sorted by frequency for coalescing double _high_frequency_lrg; // Frequency at which LRG will be spilled for debug info @@ -777,12 +775,9 @@ class PhaseChaitin : public PhaseRegAlloc { private: - static int _final_loads, _final_stores, _final_copies, _final_memoves; - static double _final_load_cost, _final_store_cost, _final_copy_cost, _final_memove_cost; - static int _conserv_coalesce, _conserv_coalesce_pair; - static int _conserv_coalesce_trie, _conserv_coalesce_quad; + static int _conserv_coalesce; static int _post_alloc; - static int _lost_opp_pp_coalesce, _lost_opp_cflow_coalesce; + static int _lost_opp_cflow_coalesce; static int _used_cisc_instructions, _unused_cisc_instructions; static int _allocator_attempts, _allocator_successes; diff --git a/src/hotspot/share/opto/graphKit.cpp b/src/hotspot/share/opto/graphKit.cpp index b24801b42e28..ce93ede92267 100644 --- a/src/hotspot/share/opto/graphKit.cpp +++ b/src/hotspot/share/opto/graphKit.cpp @@ -81,8 +81,6 @@ GraphKit::GraphKit(JVMState* jvms, PhaseGVN* gvn) #ifdef ASSERT if (_gvn.is_IterGVN() != nullptr) { assert(_gvn.is_IterGVN()->delay_transform(), "Transformation must be delayed if IterGVN is used"); - // Save the initial size of _for_igvn worklist for verification (see ~GraphKit) - _worklist_size = _gvn.C->igvn_worklist()->size(); } #endif } diff --git a/src/hotspot/share/opto/graphKit.hpp b/src/hotspot/share/opto/graphKit.hpp index a1796ea81259..28932b145ebf 100644 --- a/src/hotspot/share/opto/graphKit.hpp +++ b/src/hotspot/share/opto/graphKit.hpp @@ -65,9 +65,6 @@ class GraphKit : public Phase { int _bci; // JVM Bytecode Pointer ciMethod* _method; // JVM Current Method BarrierSetC2* _barrier_set; -#ifdef ASSERT - uint _worklist_size; -#endif private: int _sp; // JVM Expression Stack Pointer; don't modify directly! diff --git a/src/hotspot/share/opto/machnode.hpp b/src/hotspot/share/opto/machnode.hpp index fda3f41db704..254578519258 100644 --- a/src/hotspot/share/opto/machnode.hpp +++ b/src/hotspot/share/opto/machnode.hpp @@ -1057,7 +1057,6 @@ class MachCallRuntimeNode : public MachCallNode { virtual uint size_of() const; // Size is bigger public: const char *_name; // Printable name, if _method is null - bool _leaf_no_fp; // Is this CallLeafNoFP? MachCallRuntimeNode() : MachCallNode() { init_class_id(Class_MachCallRuntime); } diff --git a/src/hotspot/share/opto/matcher.cpp b/src/hotspot/share/opto/matcher.cpp index 46640b2d8618..2cc75b3db8ba 100644 --- a/src/hotspot/share/opto/matcher.cpp +++ b/src/hotspot/share/opto/matcher.cpp @@ -65,8 +65,6 @@ Matcher::Matcher() _dontcare(&_states_arena), _reduceOp(reduceOp), _leftOp(leftOp), _rightOp(rightOp), _swallowed(swallowed), - _begin_inst_chain_rule(_BEGIN_INST_CHAIN_RULE), - _end_inst_chain_rule(_END_INST_CHAIN_RULE), _must_clone(must_clone), _shared_nodes(C->comp_arena()), #ifndef PRODUCT @@ -1261,7 +1259,6 @@ MachNode *Matcher::match_sfpt( SafePointNode *sfpt ) { else if( mcall->is_MachCallRuntime() ) { MachCallRuntimeNode* mach_call_rt = mcall->as_MachCallRuntime(); mach_call_rt->_name = call->as_CallRuntime()->_name; - mach_call_rt->_leaf_no_fp = call->is_CallLeafNoFP(); } msfpt = mcall; } diff --git a/src/hotspot/share/opto/matcher.hpp b/src/hotspot/share/opto/matcher.hpp index 07b8d1c6cd9d..721b977c48f0 100644 --- a/src/hotspot/share/opto/matcher.hpp +++ b/src/hotspot/share/opto/matcher.hpp @@ -108,10 +108,6 @@ class Matcher : public PhaseTransform { // Map dense opcode number to info on when rule is swallowed constant. const bool *_swallowed; - // Map dense rule number to determine if this is an instruction chain rule - const uint _begin_inst_chain_rule; - const uint _end_inst_chain_rule; - // We want to clone constants and possible CmpI-variants. // If we do not clone CmpI, then we can have many instances of // condition codes alive at once. This is OK on some chips and @@ -178,7 +174,6 @@ class Matcher : public PhaseTransform { RegMask *idealreg2debugmask [_last_machine_leaf]; void init_spill_mask( Node *ret ); // Convert machine register number to register mask - static uint mreg2regmask_max; static RegMask mreg2regmask[]; static RegMask STACK_ONLY_mask; static RegMask caller_save_regmask; diff --git a/src/hotspot/share/opto/type.cpp b/src/hotspot/share/opto/type.cpp index dcc34701d6cd..384e126bf67e 100644 --- a/src/hotspot/share/opto/type.cpp +++ b/src/hotspot/share/opto/type.cpp @@ -557,22 +557,16 @@ void Type::Initialize_shared(Compile* current) { BOTTOM = make(Bottom); // Everything HALF = make(Half); // Placeholder half of doublewide type - TypeF::MAX = TypeF::make(max_jfloat); // Float MAX - TypeF::MIN = TypeF::make(min_jfloat); // Float MIN TypeF::ZERO = TypeF::make(0.0); // Float 0 (positive zero) TypeF::ONE = TypeF::make(1.0); // Float 1 TypeF::POS_INF = TypeF::make(jfloat_cast(POSITIVE_INFINITE_F)); TypeF::NEG_INF = TypeF::make(-jfloat_cast(POSITIVE_INFINITE_F)); - TypeH::MAX = TypeH::make(max_jfloat16); // HalfFloat MAX - TypeH::MIN = TypeH::make(min_jfloat16); // HalfFloat MIN TypeH::ZERO = TypeH::make((jshort)0); // HalfFloat 0 (positive zero) TypeH::ONE = TypeH::make(one_jfloat16); // HalfFloat 1 TypeH::POS_INF = TypeH::make(pos_inf_jfloat16); TypeH::NEG_INF = TypeH::make(neg_inf_jfloat16); - TypeD::MAX = TypeD::make(max_jdouble); // Double MAX - TypeD::MIN = TypeD::make(min_jdouble); // Double MIN TypeD::ZERO = TypeD::make(0.0); // Double 0 (positive zero) TypeD::ONE = TypeD::make(1.0); // Double 1 TypeD::POS_INF = TypeD::make(jdouble_cast(POSITIVE_INFINITE_D)); @@ -616,10 +610,8 @@ void Type::Initialize_shared(Compile* current) { TypeLong::ONE = TypeLong::make( 1); // 1 TypeLong::NON_ZERO = TypeLong::make_or_top(TypeIntPrototype{{min_jlong, max_jlong}, {1, max_julong}, {0, 0}}, WidenMin)->is_long(); TypeLong::POS = TypeLong::make(0, max_jlong, WidenMin); // Non-neg values - TypeLong::NEG = TypeLong::make(min_jlong, -1, WidenMin); TypeLong::LONG = TypeLong::make(min_jlong, max_jlong, WidenMax); // 64-bit integers TypeLong::INT = TypeLong::make((jlong)min_jint, (jlong)max_jint,WidenMin); - TypeLong::UINT = TypeLong::make(0, (jlong)max_juint, WidenMin); TypeLong::TYPE_DOMAIN = TypeLong::LONG; const Type **fboth =(const Type**)shared_type_arena->AmallocWords(2*sizeof(Type*)); @@ -1615,12 +1607,10 @@ void Type::verify_meet_join() { //============================================================================= // Convenience common pre-built types. -const TypeF *TypeF::MAX; // Floating point max -const TypeF *TypeF::MIN; // Floating point min -const TypeF *TypeF::ZERO; // Floating point zero -const TypeF *TypeF::ONE; // Floating point one -const TypeF *TypeF::POS_INF; // Floating point positive infinity -const TypeF *TypeF::NEG_INF; // Floating point negative infinity +const TypeF* TypeF::ZERO; // Floating point zero +const TypeF* TypeF::ONE; // Floating point one +const TypeF* TypeF::POS_INF; // Floating point positive infinity +const TypeF* TypeF::NEG_INF; // Floating point negative infinity //------------------------------make------------------------------------------- // Create a float constant @@ -1707,8 +1697,6 @@ bool TypeF::empty(void) const { //============================================================================= // Convenience common pre-built types. -const TypeH* TypeH::MAX; // Half float max -const TypeH* TypeH::MIN; // Half float min const TypeH* TypeH::ZERO; // Half float zero const TypeH* TypeH::ONE; // Half float one const TypeH* TypeH::POS_INF; // Half float positive infinity @@ -1814,12 +1802,10 @@ bool TypeH::empty(void) const { //============================================================================= // Convenience common pre-built types. -const TypeD *TypeD::MAX; // Floating point max -const TypeD *TypeD::MIN; // Floating point min -const TypeD *TypeD::ZERO; // Floating point zero -const TypeD *TypeD::ONE; // Floating point one -const TypeD *TypeD::POS_INF; // Floating point positive infinity -const TypeD *TypeD::NEG_INF; // Floating point negative infinity +const TypeD* TypeD::ZERO; // Floating point zero +const TypeD* TypeD::ONE; // Floating point one +const TypeD* TypeD::POS_INF; // Floating point positive infinity +const TypeD* TypeD::NEG_INF; // Floating point negative infinity //------------------------------make------------------------------------------- const TypeD *TypeD::make(double d) { @@ -2110,10 +2096,8 @@ const TypeLong* TypeLong::ZERO; // 0 const TypeLong* TypeLong::ONE; // 1 const TypeLong* TypeLong::NON_ZERO; const TypeLong* TypeLong::POS; // >=0 -const TypeLong* TypeLong::NEG; const TypeLong* TypeLong::LONG; // 64-bit integers const TypeLong* TypeLong::INT; // 32-bit subrange -const TypeLong* TypeLong::UINT; // 32-bit unsigned subrange const TypeLong* TypeLong::TYPE_DOMAIN; // alias for TypeLong::LONG TypeLong::TypeLong(const TypeIntPrototype& t, int widen) diff --git a/src/hotspot/share/opto/type.hpp b/src/hotspot/share/opto/type.hpp index 8807bbd973d5..9c347492e192 100644 --- a/src/hotspot/share/opto/type.hpp +++ b/src/hotspot/share/opto/type.hpp @@ -461,17 +461,16 @@ class Type { } // Convenience common pre-built types. - static const Type *ABIO; - static const Type *BOTTOM; - static const Type *CONTROL; - static const Type *DOUBLE; - static const Type *FLOAT; - static const Type *HALF_FLOAT; - static const Type *HALF; - static const Type *MEMORY; - static const Type *MULTI; - static const Type *RETURN_ADDRESS; - static const Type *TOP; + static const Type* ABIO; + static const Type* BOTTOM; + static const Type* CONTROL; + static const Type* DOUBLE; + static const Type* FLOAT; + static const Type* HALF_FLOAT; + static const Type* HALF; + static const Type* MEMORY; + static const Type* RETURN_ADDRESS; + static const Type* TOP; // Mapping from compiler type to VM BasicType BasicType basic_type() const { return _type_info[_base].basic_type; } @@ -549,12 +548,10 @@ class TypeF : public Type { virtual const Type* xmeet(const Type* t) const; virtual const Type* xjoin(const Type* t) const; // Convenience common pre-built types. - static const TypeF *MAX; - static const TypeF *MIN; - static const TypeF *ZERO; // positive zero only - static const TypeF *ONE; - static const TypeF *POS_INF; - static const TypeF *NEG_INF; + static const TypeF* ZERO; // positive zero only + static const TypeF* ONE; + static const TypeF* POS_INF; + static const TypeF* NEG_INF; #ifndef PRODUCT virtual void dump2( Dict &d, uint depth, outputStream *st ) const; #endif @@ -581,8 +578,6 @@ class TypeH : public Type { virtual const Type* xmeet(const Type* t) const; virtual const Type* xjoin(const Type* t) const; // Convenience common pre-built types. - static const TypeH* MAX; - static const TypeH* MIN; static const TypeH* ZERO; // positive zero only static const TypeH* ONE; static const TypeH* POS_INF; @@ -612,12 +607,10 @@ class TypeD : public Type { virtual const Type* xmeet(const Type* t) const; virtual const Type* xjoin(const Type* t) const; // Convenience common pre-built types. - static const TypeD *MAX; - static const TypeD *MIN; - static const TypeD *ZERO; // positive zero only - static const TypeD *ONE; - static const TypeD *POS_INF; - static const TypeD *NEG_INF; + static const TypeD* ZERO; // positive zero only + static const TypeD* ONE; + static const TypeD* POS_INF; + static const TypeD* NEG_INF; #ifndef PRODUCT virtual void dump2( Dict &d, uint depth, outputStream *st ) const; #endif @@ -946,10 +939,8 @@ class TypeLong : public TypeInteger { static const TypeLong* ONE; static const TypeLong* NON_ZERO; static const TypeLong* POS; - static const TypeLong* NEG; static const TypeLong* LONG; static const TypeLong* INT; // 32-bit subrange [min_jint..max_jint] - static const TypeLong* UINT; // 32-bit unsigned [0..max_juint] static const TypeLong* TYPE_DOMAIN; // alias for TypeLong::LONG // static convenience methods. From 4c31fa84adb2b966e83599ddbe21dc87c77f64c5 Mon Sep 17 00:00:00 2001 From: Matthias Baesken Date: Fri, 16 Oct 2026 10:31:00 +0000 Subject: [PATCH 075/169] 8394564: Some sys/utsname.h inclusions are not needed any more Reviewed-by: prr, azeller, mdoerr --- src/java.base/unix/native/libnet/net_util_md.c | 1 - src/java.base/unix/native/libnio/ch/Net.c | 5 ----- .../unix/native/common/java2d/x11/X11FontScaler_md.c | 1 - src/java.desktop/unix/native/libawt/awt/awt_Mlib.c | 1 - src/java.desktop/unix/native/libfontmanager/X11FontScaler.c | 1 - 5 files changed, 9 deletions(-) diff --git a/src/java.base/unix/native/libnet/net_util_md.c b/src/java.base/unix/native/libnet/net_util_md.c index c8e872a3c8b7..054209342b15 100644 --- a/src/java.base/unix/native/libnet/net_util_md.c +++ b/src/java.base/unix/native/libnet/net_util_md.c @@ -33,7 +33,6 @@ #if defined(__linux__) #include #include -#include #endif #if defined(MACOSX) diff --git a/src/java.base/unix/native/libnio/ch/Net.c b/src/java.base/unix/native/libnio/ch/Net.c index fd93f7c28967..ad3f132f48ea 100644 --- a/src/java.base/unix/native/libnio/ch/Net.c +++ b/src/java.base/unix/native/libnio/ch/Net.c @@ -42,11 +42,6 @@ #include "nio_util.h" #include "nio.h" -#ifdef _AIX -#include -#include -#endif - /** * IP_MULTICAST_ALL supported since 2.6.31 but may not be available at * build time. diff --git a/src/java.desktop/unix/native/common/java2d/x11/X11FontScaler_md.c b/src/java.desktop/unix/native/common/java2d/x11/X11FontScaler_md.c index 0d352c36bbeb..d0b60f82be03 100644 --- a/src/java.desktop/unix/native/common/java2d/x11/X11FontScaler_md.c +++ b/src/java.desktop/unix/native/common/java2d/x11/X11FontScaler_md.c @@ -28,7 +28,6 @@ #include #include -#include #include #include diff --git a/src/java.desktop/unix/native/libawt/awt/awt_Mlib.c b/src/java.desktop/unix/native/libawt/awt/awt_Mlib.c index 5862ab1f47bd..dadd04b6bfad 100644 --- a/src/java.desktop/unix/native/libawt/awt/awt_Mlib.c +++ b/src/java.desktop/unix/native/libawt/awt/awt_Mlib.c @@ -26,7 +26,6 @@ #include #include #include -#include #include #include #include diff --git a/src/java.desktop/unix/native/libfontmanager/X11FontScaler.c b/src/java.desktop/unix/native/libfontmanager/X11FontScaler.c index 6e836fa111c3..e4cc3a69d844 100644 --- a/src/java.desktop/unix/native/libfontmanager/X11FontScaler.c +++ b/src/java.desktop/unix/native/libfontmanager/X11FontScaler.c @@ -34,7 +34,6 @@ #include #include #include -#include #include #include From 1592dc2f8af0957a63581bae03e9e52b2468a382 Mon Sep 17 00:00:00 2001 From: Aleksey Shipilev Date: Fri, 16 Oct 2026 11:11:24 +0000 Subject: [PATCH 076/169] 8394633: Shenandoah: C1 keep-alive barriers load without implicit null-check Reviewed-by: wkemper, rkennke --- .../shenandoahBarrierSetAssembler_aarch64.cpp | 2 +- .../shenandoahBarrierSetAssembler_ppc.cpp | 2 +- .../shenandoahBarrierSetAssembler_riscv.cpp | 2 +- .../shenandoahBarrierSetAssembler_x86.cpp | 2 +- .../gc/shenandoah/c1/shenandoahBarrierSetC1.cpp | 17 +++++++++++------ .../gc/shenandoah/c1/shenandoahBarrierSetC1.hpp | 16 +++++++++++----- 6 files changed, 26 insertions(+), 15 deletions(-) diff --git a/src/hotspot/cpu/aarch64/gc/shenandoah/shenandoahBarrierSetAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/gc/shenandoah/shenandoahBarrierSetAssembler_aarch64.cpp index e3af4f150f76..51d3959e4753 100644 --- a/src/hotspot/cpu/aarch64/gc/shenandoah/shenandoahBarrierSetAssembler_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/gc/shenandoah/shenandoahBarrierSetAssembler_aarch64.cpp @@ -549,7 +549,7 @@ void ShenandoahBarrierSetAssembler::keepalive_barrier_c1_stub(LIR_Assembler* ce, Register obj = stub->obj()->as_register(); if (stub->do_load()) { - ce->mem2reg(stub->addr(), stub->obj(), T_OBJECT, lir_patch_none, nullptr, /* wide = */ false); + ce->mem2reg(stub->addr(), stub->obj(), T_OBJECT, lir_patch_none, stub->info(), /* wide = */ false); } __ cbz(obj, *stub->continuation()); ce->store_parameter(obj, 0); diff --git a/src/hotspot/cpu/ppc/gc/shenandoah/shenandoahBarrierSetAssembler_ppc.cpp b/src/hotspot/cpu/ppc/gc/shenandoah/shenandoahBarrierSetAssembler_ppc.cpp index a393f6fbb118..cc45d60f3295 100644 --- a/src/hotspot/cpu/ppc/gc/shenandoah/shenandoahBarrierSetAssembler_ppc.cpp +++ b/src/hotspot/cpu/ppc/gc/shenandoah/shenandoahBarrierSetAssembler_ppc.cpp @@ -763,7 +763,7 @@ void ShenandoahBarrierSetAssembler::keepalive_barrier_c1_stub(LIR_Assembler* ce, // If 'do_load()' returns false, the to-be-stored value is already available in 'obj' if (stub->do_load()) { - ce->mem2reg(stub->addr(), stub->obj(), T_OBJECT, lir_patch_none, nullptr, false); + ce->mem2reg(stub->addr(), stub->obj(), T_OBJECT, lir_patch_none, stub->info(), false); } // Fast path: reference is null. diff --git a/src/hotspot/cpu/riscv/gc/shenandoah/shenandoahBarrierSetAssembler_riscv.cpp b/src/hotspot/cpu/riscv/gc/shenandoah/shenandoahBarrierSetAssembler_riscv.cpp index 0d1f603b9cc8..40489bd32cb8 100644 --- a/src/hotspot/cpu/riscv/gc/shenandoah/shenandoahBarrierSetAssembler_riscv.cpp +++ b/src/hotspot/cpu/riscv/gc/shenandoah/shenandoahBarrierSetAssembler_riscv.cpp @@ -568,7 +568,7 @@ void ShenandoahBarrierSetAssembler::keepalive_barrier_c1_stub(LIR_Assembler* ce, Register obj = stub->obj()->as_register(); if (stub->do_load()) { - ce->mem2reg(stub->addr(), stub->obj(), T_OBJECT, lir_patch_none, nullptr, false /* wide */); + ce->mem2reg(stub->addr(), stub->obj(), T_OBJECT, lir_patch_none, stub->info(), false /* wide */); } __ beqz(obj, *stub->continuation(), /* is_far */ true); diff --git a/src/hotspot/cpu/x86/gc/shenandoah/shenandoahBarrierSetAssembler_x86.cpp b/src/hotspot/cpu/x86/gc/shenandoah/shenandoahBarrierSetAssembler_x86.cpp index 0ef885fbffde..53bcd1300fcc 100644 --- a/src/hotspot/cpu/x86/gc/shenandoah/shenandoahBarrierSetAssembler_x86.cpp +++ b/src/hotspot/cpu/x86/gc/shenandoah/shenandoahBarrierSetAssembler_x86.cpp @@ -652,7 +652,7 @@ void ShenandoahBarrierSetAssembler::keepalive_barrier_c1_stub(LIR_Assembler* ce, Register obj = stub->obj()->as_register(); if (stub->do_load()) { - ce->mem2reg(stub->addr(), stub->obj(), T_OBJECT, lir_patch_none, nullptr, /* wide = */ false); + ce->mem2reg(stub->addr(), stub->obj(), T_OBJECT, lir_patch_none, stub->info(), /* wide = */ false); } __ cmpptr(obj, NULL_WORD); __ jcc(Assembler::equal, *stub->continuation()); diff --git a/src/hotspot/share/gc/shenandoah/c1/shenandoahBarrierSetC1.cpp b/src/hotspot/share/gc/shenandoah/c1/shenandoahBarrierSetC1.cpp index de0b838fe45d..26c4ab99c19e 100644 --- a/src/hotspot/share/gc/shenandoah/c1/shenandoahBarrierSetC1.cpp +++ b/src/hotspot/share/gc/shenandoah/c1/shenandoahBarrierSetC1.cpp @@ -106,7 +106,7 @@ void ShenandoahBarrierSetC1::enter_if_gc_state(LIRGenerator* gen, int flags, Cod __ branch_destination(slow_stub->continuation()); } -void ShenandoahBarrierSetC1::keepalive_barrier(LIRGenerator* gen, LIR_Opr obj, LIR_Opr addr, DecoratorSet decorators) { +void ShenandoahBarrierSetC1::keepalive_barrier(LIRGenerator* gen, LIR_Opr obj, LIR_Opr addr, CodeEmitInfo* info, DecoratorSet decorators) { CodeStub* slow_stub; if (obj == LIR_OprFact::illegalOpr) { // Caller wants us to do the load. @@ -118,10 +118,11 @@ void ShenandoahBarrierSetC1::keepalive_barrier(LIRGenerator* gen, LIR_Opr obj, L addr = LIR_OprFact::address(new LIR_Address(addr, T_OBJECT)); } - slow_stub = new ShenandoahKeepaliveBarrierStub(obj, addr); + slow_stub = new ShenandoahKeepaliveBarrierStub(obj, addr, info); } else { // Caller gave us the obj to work with. assert(addr == LIR_OprFact::illegalOpr, "sanity"); + assert(info == nullptr, "sanity"); assert(obj->is_register(), "must be"); assert(obj->type() == T_OBJECT, "must be an object"); @@ -172,7 +173,11 @@ void ShenandoahBarrierSetC1::store_at_resolved(LIRAccess& access, LIR_Opr value) LIRGenerator* gen = access.gen(); if (ShenandoahSATBBarrier && access.is_oop()) { - keepalive_barrier(gen, /* obj = */ LIR_OprFact::illegalOpr, /* addr = */ access.resolved_addr(), decorators); + CodeEmitInfo* info = access.access_emit_info(); + if (info != nullptr) { + info = new CodeEmitInfo(info); + } + keepalive_barrier(gen, /* obj = */ LIR_OprFact::illegalOpr, /* addr = */ access.resolved_addr(), info, decorators); } BarrierSetC1::store_at_resolved(access, value); @@ -225,7 +230,7 @@ void ShenandoahBarrierSetC1::load_at_resolved(LIRAccess& access, LIR_Opr result) Lcont_anonymous = new LabelObj(); generate_referent_check(access, Lcont_anonymous); } - keepalive_barrier(gen, /* obj = */ result, /* addr = */ LIR_OprFact::illegalOpr, decorators); + keepalive_barrier(gen, /* obj = */ result, /* addr = */ LIR_OprFact::illegalOpr, nullptr, decorators); if (is_anonymous) { __ branch_destination(Lcont_anonymous->label()); } @@ -365,7 +370,7 @@ LIR_Opr ShenandoahBarrierSetC1::atomic_cmpxchg_at_resolved(LIRAccess& access, LI // Handle the previous value through SATB, as we are about to perform the store. __ load(addr->as_address_ptr(), tmp); if (ShenandoahSATBBarrier) { - keepalive_barrier(gen, /* obj = */ tmp, /* addr = */ LIR_OprFact::illegalOpr, decorators); + keepalive_barrier(gen, /* obj = */ tmp, /* addr = */ LIR_OprFact::illegalOpr, nullptr, decorators); } // Perform LRB on location to fix it up for this and all following accesses. @@ -398,7 +403,7 @@ LIR_Opr ShenandoahBarrierSetC1::atomic_xchg_at_resolved(LIRAccess& access, LIRIt // Handle the previous value through SATB, as we are about to perform the store. __ load(addr->as_address_ptr(), tmp); if (ShenandoahSATBBarrier) { - keepalive_barrier(gen, /* obj = */ tmp, /* addr = */ LIR_OprFact::illegalOpr, decorators); + keepalive_barrier(gen, /* obj = */ tmp, /* addr = */ LIR_OprFact::illegalOpr, nullptr, decorators); } // Perform LRB on location to fix it up for this and all following accesses. diff --git a/src/hotspot/share/gc/shenandoah/c1/shenandoahBarrierSetC1.hpp b/src/hotspot/share/gc/shenandoah/c1/shenandoahBarrierSetC1.hpp index ade3d9a79520..62d167aff67c 100644 --- a/src/hotspot/share/gc/shenandoah/c1/shenandoahBarrierSetC1.hpp +++ b/src/hotspot/share/gc/shenandoah/c1/shenandoahBarrierSetC1.hpp @@ -35,10 +35,11 @@ class ShenandoahKeepaliveBarrierStub: public CodeStub { LIR_Opr _obj; LIR_Opr _addr; bool _do_load; + CodeEmitInfo* _info; public: - ShenandoahKeepaliveBarrierStub(LIR_Opr obj, LIR_Opr addr) : - _obj(obj), _addr(addr), _do_load(true) + ShenandoahKeepaliveBarrierStub(LIR_Opr obj, LIR_Opr addr, CodeEmitInfo* info) : + _obj(obj), _addr(addr), _do_load(true), _info(info) { assert(_obj->is_register(), "should be temporary register"); assert(_addr->is_address(), "should be the address of the field"); @@ -47,7 +48,7 @@ class ShenandoahKeepaliveBarrierStub: public CodeStub { } ShenandoahKeepaliveBarrierStub(LIR_Opr obj) : - _obj(obj), _addr(LIR_OprFact::illegalOpr), _do_load(false) + _obj(obj), _addr(LIR_OprFact::illegalOpr), _do_load(false), _info(nullptr) { assert(_obj->is_register(), "should be a register"); FrameMap* f = Compilation::current()->frame_map(); @@ -57,10 +58,15 @@ class ShenandoahKeepaliveBarrierStub: public CodeStub { LIR_Opr addr() const { return _addr; } LIR_Opr obj() const { return _obj; } bool do_load() const { return _do_load; } + CodeEmitInfo* info() const { return _info; } virtual void emit_code(LIR_Assembler* e); virtual void visit(LIR_OpVisitState* visitor) { - visitor->do_slow_case(); + if (_info != nullptr) { + visitor->do_slow_case(_info); + } else { + visitor->do_slow_case(); + } if (_do_load) { visitor->do_input(_addr); visitor->do_temp(_addr); @@ -121,7 +127,7 @@ class ShenandoahBarrierSetC1 : public BarrierSetC1 { void enter_if_gc_state(LIRGenerator* gen, int flags, CodeStub* slow_stub); - void keepalive_barrier(LIRGenerator* gen, LIR_Opr obj, LIR_Opr addr, DecoratorSet decorators); + void keepalive_barrier(LIRGenerator* gen, LIR_Opr obj, LIR_Opr addr, CodeEmitInfo* info, DecoratorSet decorators); void load_reference_barrier(LIRGenerator* gen, LIR_Opr obj, LIR_Opr addr, DecoratorSet decorators); void card_barrier(LIRGenerator* gen, LIR_Opr addr, DecoratorSet decorators); From 3dc8346e79a251138ad587118a443492acf560c8 Mon Sep 17 00:00:00 2001 From: Jaikiran Pai Date: Fri, 16 Oct 2026 13:11:23 +0000 Subject: [PATCH 077/169] 8394751: Improve test/jdk/java/net/SocketOption/OptionsTest.java for debuggability Reviewed-by: vyazici --- test/jdk/java/net/SocketOption/OptionsTest.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/jdk/java/net/SocketOption/OptionsTest.java b/test/jdk/java/net/SocketOption/OptionsTest.java index 004b94d5c63b..d2f64d48dcb5 100644 --- a/test/jdk/java/net/SocketOption/OptionsTest.java +++ b/test/jdk/java/net/SocketOption/OptionsTest.java @@ -50,6 +50,10 @@ static Test create(SocketOption option, T value) { return new Test(option, value); } + @Override + public String toString() { + return "Test SocketOption: " + option + " with value: " + value; + } } // The tests set the option using the new API, read back the set value @@ -131,6 +135,7 @@ static void testEqual(SocketOption option, T value1, T value2) { static void test(Socket s, Test test) throws Exception { SocketOption option = test.option; + System.out.println(s.getClass().getName() + " - " + test); s.setOption(option, test.value); T value1 = s.getOption(test.option); T value2 = (T) legacyGetOption(Socket.class, s, test.option); @@ -139,6 +144,7 @@ static void test(Socket s, Test test) throws Exception { static void test(ServerSocket ss, Test test) throws Exception { SocketOption option = test.option; + System.out.println(ss.getClass().getName() + " - " + test); ss.setOption(option, test.value); T value1 = ss.getOption(test.option); T value2 = (T) legacyGetOption(ServerSocket.class, ss, test.option); @@ -147,6 +153,7 @@ static void test(ServerSocket ss, Test test) throws Exception { static void test(DatagramSocket ds, Test test) throws Exception { SocketOption option = test.option; + System.out.println(ds.getClass().getName() + " - " + test); ds.setOption(option, test.value); T value1 = ds.getOption(test.option); T value2 = (T) legacyGetOption(ds.getClass(), ds, test.option); From a7199cecda712e207c4a44229fe74ea923aa9836 Mon Sep 17 00:00:00 2001 From: Kevin Walls Date: Fri, 16 Oct 2026 13:21:22 +0000 Subject: [PATCH 078/169] 8393535: Remove implementations of deprecated MBeanServer method, and update ArrayClassTest Reviewed-by: dfuchs --- .../sun/jmx/mbeanserver/JmxMBeanServer.java | 107 +----------------- .../jmx/mbeanserver/MBeanInstantiator.java | 77 +------------ .../management/loading/ArrayClassTest.java | 41 +++++-- 3 files changed, 35 insertions(+), 190 deletions(-) diff --git a/src/java.management/share/classes/com/sun/jmx/mbeanserver/JmxMBeanServer.java b/src/java.management/share/classes/com/sun/jmx/mbeanserver/JmxMBeanServer.java index fd7bf8fbcaed..541757813ca9 100644 --- a/src/java.management/share/classes/com/sun/jmx/mbeanserver/JmxMBeanServer.java +++ b/src/java.management/share/classes/com/sun/jmx/mbeanserver/JmxMBeanServer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -1068,111 +1068,6 @@ public boolean isInstanceOf(ObjectName name, String className) return mbsInterceptor.isInstanceOf(cloneObjectName(name), className); } - /** - * De-serializes a byte array in the context of the class loader - * of an MBean. - * - * @param name The name of the MBean whose class loader should - * be used for the de-serialization. - * @param data The byte array to be de-sererialized. - * - * @return The de-serialized object stream. - * - * @exception InstanceNotFoundException The MBean specified is not - * found. - * @exception OperationsException Any of the usual Input/Output - * related exceptions. - * - */ - @Deprecated - public ObjectInputStream deserialize(ObjectName name, byte[] data) - throws InstanceNotFoundException, OperationsException { - - final ClassLoader loader = getClassLoaderFor(name); - - return instantiator.deserialize(loader, data); - } - - /** - * De-serializes a byte array in the context of a given MBean class loader. - * The class loader is the one that loaded the class with name "className". - * - * @param className The name of the class whose class loader should be - * used for the de-serialization. - * @param data The byte array to be de-sererialized. - * - * @return The de-serialized object stream. - * - * @exception OperationsException Any of the usual Input/Output - * related exceptions. - * @exception ReflectionException The specified class could not be - * loaded by the default loader repository - * - */ - @Deprecated - public ObjectInputStream deserialize(String className, byte[] data) - throws OperationsException, ReflectionException { - - if (className == null) { - throw new RuntimeOperationsException( - new IllegalArgumentException(), - "Null className passed in parameter"); - } - - final ClassLoaderRepository clr = getClassLoaderRepository(); - - Class theClass; - try { - if (clr == null) throw new ClassNotFoundException(className); - theClass = clr.loadClass(className); - } catch (ClassNotFoundException e) { - throw new ReflectionException(e, - "The given class could not be " + - "loaded by the default loader " + - "repository"); - } - - return instantiator.deserialize(theClass.getClassLoader(), data); - } - - /** - * De-serializes a byte array in the context of a given MBean class loader. - * The class loader is the one that loaded the class with name "className". - * The name of the class loader to be used for loading the specified - * class is specified. - * If null, the MBean Server's class loader will be used. - * - * @param className The name of the class whose class loader should be - * used for the de-serialization. - * @param data The byte array to be de-sererialized. - * @param loaderName The name of the class loader to be used for - * loading the specified class. - * If null, the MBean Server's class loader will be used. - * - * @return The de-serialized object stream. - * - * @exception InstanceNotFoundException The specified class loader - * MBean is not found. - * @exception OperationsException Any of the usual Input/Output - * related exceptions. - * @exception ReflectionException The specified class could not - * be loaded by the specified class loader. - * - */ - @Deprecated - public ObjectInputStream deserialize(String className, - ObjectName loaderName, - byte[] data) throws - InstanceNotFoundException, OperationsException, ReflectionException { - - // Clone ObjectName - // - loaderName = cloneObjectName(loaderName); - - ClassLoader myLoader = outerShell.getClass().getClassLoader(); - return instantiator.deserialize(className, loaderName, data, myLoader); - } - /** * Initializes this MBeanServer, registering the MBeanServerDelegate. *

    This method must be called once, before using the MBeanServer. diff --git a/src/java.management/share/classes/com/sun/jmx/mbeanserver/MBeanInstantiator.java b/src/java.management/share/classes/com/sun/jmx/mbeanserver/MBeanInstantiator.java index b1fcf7999c62..269648268619 100644 --- a/src/java.management/share/classes/com/sun/jmx/mbeanserver/MBeanInstantiator.java +++ b/src/java.management/share/classes/com/sun/jmx/mbeanserver/MBeanInstantiator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -321,29 +321,7 @@ public Object instantiate(Class theClass, Object params[], public ObjectInputStream deserialize(ClassLoader loader, byte[] data) throws OperationsException { - // Check parameter validity - if (data == null) { - throw new RuntimeOperationsException(new - IllegalArgumentException(), "Null data passed in parameter"); - } - if (data.length == 0) { - throw new RuntimeOperationsException(new - IllegalArgumentException(), "Empty data passed in parameter"); - } - - // Object deserialization - ByteArrayInputStream bIn; - ObjectInputStream objIn; - - bIn = new ByteArrayInputStream(data); - try { - objIn = new ObjectInputStreamWithLoader(bIn,loader); - } catch (IOException e) { - throw new OperationsException( - "An IOException occurred trying to de-serialize the data"); - } - - return objIn; + throw new UnsupportedOperationException("Not supported"); } /** @@ -378,56 +356,7 @@ public ObjectInputStream deserialize(String className, OperationsException, ReflectionException { - // Check parameter validity - if (data == null) { - throw new RuntimeOperationsException(new - IllegalArgumentException(), "Null data passed in parameter"); - } - if (data.length == 0) { - throw new RuntimeOperationsException(new - IllegalArgumentException(), "Empty data passed in parameter"); - } - if (className == null) { - throw new RuntimeOperationsException(new - IllegalArgumentException(), "Null className passed in parameter"); - } - - Class theClass; - if (loaderName == null) { - // Load the class using the agent class loader - theClass = findClass(className, loader); - - } else { - // Get the class loader MBean - try { - ClassLoader instance = null; - - instance = getClassLoader(loaderName); - if (instance == null) - throw new ClassNotFoundException(className); - theClass = Class.forName(className, false, instance); - } - catch (ClassNotFoundException e) { - throw new ReflectionException(e, - "The MBean class could not be loaded by the " + - loaderName.toString() + " class loader"); - } - } - - // Object deserialization - ByteArrayInputStream bIn; - ObjectInputStream objIn; - - bIn = new ByteArrayInputStream(data); - try { - objIn = new ObjectInputStreamWithLoader(bIn, - theClass.getClassLoader()); - } catch (IOException e) { - throw new OperationsException( - "An IOException occurred trying to de-serialize the data"); - } - - return objIn; + throw new UnsupportedOperationException("Not supported"); } diff --git a/test/jdk/javax/management/loading/ArrayClassTest.java b/test/jdk/javax/management/loading/ArrayClassTest.java index fc911c8018df..3e48e7f27e33 100644 --- a/test/jdk/javax/management/loading/ArrayClassTest.java +++ b/test/jdk/javax/management/loading/ArrayClassTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2004, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -24,8 +24,7 @@ /* * @test * @bug 4974913 - * @summary Test that array classes can be found in signatures always - * and can be deserialized by the deprecated MBeanServer.deserialize method + * @summary Test that array classes found in signatures can be deserialized * @author Eamonn McManus * * @run clean ArrayClassTest @@ -72,16 +71,20 @@ public static void main(String[] args) throws Exception { oout.writeObject(new Z[0]); oout.close(); byte[] bytes = bout.toByteArray(); - ObjectInputStream oin = mbs.deserialize(testName, bytes); - Object zarray = oin.readObject(); + + ClassLoader newLoader = mbs.getClassLoaderFor(testName); + ByteLoaderStream byteLoaderStream = new ByteLoaderStream(new ByteArrayInputStream(bytes), newLoader); + Object zarray = byteLoaderStream.readObject(); + String failed = null; - if (zarray instanceof Z[]) + if (zarray instanceof Z[]) { failed = "read back a real Z[]"; - else if (!zarray.getClass().getName().equals(Z[].class.getName())) { - failed = "returned object of wrong type: " + - zarray.getClass().getName(); - } else if (Array.getLength(zarray) != 0) + } else if (!zarray.getClass().getName().equals(Z[].class.getName())) { + failed = "returned object of wrong type: " + zarray.getClass().getName(); + } else if (Array.getLength(zarray) != 0) { failed = "returned array of wrong size: " + Array.getLength(zarray); + } + if (failed != null) { System.out.println("TEST FAILED: " + failed); System.exit(1); @@ -122,4 +125,22 @@ public Class findClass(String name) throws ClassNotFoundException { return c; } } + + public static class ByteLoaderStream extends ObjectInputStream { + private ClassLoader loader; + + public ByteLoaderStream(InputStream is, ClassLoader loader) throws IOException { + super(is); + this.loader = loader; + } + + @Override + protected Class resolveClass(ObjectStreamClass osc) throws IOException, ClassNotFoundException { + try { + return Class.forName(osc.getName(), false, loader); + } catch (ClassNotFoundException cnfe) { + return super.resolveClass(osc); + } + } + } } From 24347ff8128ba2a04dd9adbc6170bc55d1dceb89 Mon Sep 17 00:00:00 2001 From: Serguei Spitsyn Date: Fri, 16 Oct 2026 15:16:21 +0000 Subject: [PATCH 079/169] 8394696: Need minor correction of SampledObjectAlloc spec Reviewed-by: cjplummer, lmesnik --- src/hotspot/share/prims/jvmti.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hotspot/share/prims/jvmti.xml b/src/hotspot/share/prims/jvmti.xml index 6e6bed1e8495..b7ede129cf5e 100644 --- a/src/hotspot/share/prims/jvmti.xml +++ b/src/hotspot/share/prims/jvmti.xml @@ -14177,7 +14177,7 @@ myInit() { every 512KB).

    If another sampling interval is required, the user can call - with a strictly positive integer value, + with a non-negative integer value, representing the new sampling interval.

    This event is sent once the sampled allocation has been performed. It provides the object, stack trace From b456aed327e3564e6ab155767f01d6b12343ccf7 Mon Sep 17 00:00:00 2001 From: Quan Anh Mai Date: Fri, 16 Oct 2026 16:21:01 +0000 Subject: [PATCH 080/169] 8393492: C2: Missed anti-dependence computation on memory access intrinsics 8372779: C2: Disambiguate Node::adr_type for the IR graph Reviewed-by: kvn, mhaessig, mchevalier --- src/hotspot/share/adlc/output_c.cpp | 1 - src/hotspot/share/adlc/output_h.cpp | 9 +- .../share/gc/shared/c2/barrierSetC2.cpp | 2 +- .../shenandoah/c2/shenandoahBarrierSetC2.cpp | 8 +- src/hotspot/share/opto/arraycopynode.cpp | 2 +- src/hotspot/share/opto/callnode.cpp | 8 +- src/hotspot/share/opto/callnode.hpp | 78 ++++++++------ src/hotspot/share/opto/cfgnode.cpp | 28 ++--- src/hotspot/share/opto/cfgnode.hpp | 7 +- src/hotspot/share/opto/compile.cpp | 24 ++--- src/hotspot/share/opto/escape.cpp | 22 ++-- src/hotspot/share/opto/gcm.cpp | 4 +- src/hotspot/share/opto/generateOptoStub.cpp | 2 +- src/hotspot/share/opto/graphKit.cpp | 33 +++--- src/hotspot/share/opto/idealGraphPrinter.cpp | 22 ++-- src/hotspot/share/opto/idealKit.cpp | 12 ++- src/hotspot/share/opto/intrinsicnode.cpp | 2 +- src/hotspot/share/opto/intrinsicnode.hpp | 67 +++++++----- src/hotspot/share/opto/library_call.cpp | 2 +- src/hotspot/share/opto/loopTransform.cpp | 9 +- src/hotspot/share/opto/loopnode.cpp | 36 +++---- src/hotspot/share/opto/machnode.cpp | 102 ++++++++++++------ src/hotspot/share/opto/machnode.hpp | 41 +++---- src/hotspot/share/opto/macro.cpp | 24 ++--- src/hotspot/share/opto/macroArrayCopy.cpp | 4 +- src/hotspot/share/opto/matcher.cpp | 46 +++++--- src/hotspot/share/opto/matcher.hpp | 1 + src/hotspot/share/opto/memnode.cpp | 80 +++++++------- src/hotspot/share/opto/memnode.hpp | 93 ++++++++-------- src/hotspot/share/opto/mempointer.cpp | 6 +- src/hotspot/share/opto/multnode.cpp | 42 ++++---- src/hotspot/share/opto/multnode.hpp | 21 ++-- src/hotspot/share/opto/node.cpp | 78 +++++++++++++- src/hotspot/share/opto/node.hpp | 27 ++++- src/hotspot/share/opto/stringopts.cpp | 2 +- src/hotspot/share/opto/subnode.hpp | 3 + src/hotspot/share/opto/superword.cpp | 4 +- .../share/opto/superwordVTransformBuilder.cpp | 6 +- src/hotspot/share/opto/vectorization.cpp | 6 +- src/hotspot/share/opto/vectornode.cpp | 12 +-- src/hotspot/share/opto/vectornode.hpp | 1 + src/hotspot/share/opto/vtransform.cpp | 8 +- .../intrinsics/string/TestAntiDependency.java | 60 +++++++++-- 43 files changed, 651 insertions(+), 394 deletions(-) diff --git a/src/hotspot/share/adlc/output_c.cpp b/src/hotspot/share/adlc/output_c.cpp index ec764a72846b..a21c6472ce9d 100644 --- a/src/hotspot/share/adlc/output_c.cpp +++ b/src/hotspot/share/adlc/output_c.cpp @@ -3279,7 +3279,6 @@ void ArchDesc::defineClasses(FILE *fp) { if ( memory_operand != InstructForm::NO_MEMORY_OPERAND ) { if( memory_operand == InstructForm::MANY_MEMORY_OPERANDS ) { - fprintf(fp,"const TypePtr *%sNode::adr_type() const { return TypePtr::BOTTOM; }\n", instr->_ident); fprintf(fp,"const MachOper* %sNode::memory_operand() const { return (MachOper*)-1; }\n", instr->_ident); } else { fprintf(fp,"const MachOper* %sNode::memory_operand() const { return _opnds[%d]; }\n", instr->_ident, memory_operand); diff --git a/src/hotspot/share/adlc/output_h.cpp b/src/hotspot/share/adlc/output_h.cpp index 616e457cdd3e..cd25acea664e 100644 --- a/src/hotspot/share/adlc/output_h.cpp +++ b/src/hotspot/share/adlc/output_h.cpp @@ -728,8 +728,8 @@ void gen_inst_format(FILE *fp, FormDict &globals, InstructForm &inst, bool for_c } else if( inst.is_ideal_mem() ) { // Print out the field name if available to improve readability - fprintf(fp, " if (ra->C->alias_type(adr_type())->field() != nullptr) {\n"); - fprintf(fp, " ciField* f = ra->C->alias_type(adr_type())->field();\n"); + fprintf(fp, " if (ra->C->alias_type(in_adr_type())->field() != nullptr) {\n"); + fprintf(fp, " ciField* f = ra->C->alias_type(in_adr_type())->field();\n"); fprintf(fp, " st->print(\" %s Field: \");\n", commentSeperator); fprintf(fp, " if (f->is_volatile())\n"); fprintf(fp, " st->print(\"volatile \");\n"); @@ -740,7 +740,7 @@ void gen_inst_format(FILE *fp, FormDict &globals, InstructForm &inst, bool for_c fprintf(fp, " st->print(\" (constant)\");\n"); fprintf(fp, " } else {\n"); // Make sure 'Volatile' gets printed out - fprintf(fp, " if (ra->C->alias_type(adr_type())->is_volatile())\n"); + fprintf(fp, " if (ra->C->alias_type(in_adr_type())->is_volatile())\n"); fprintf(fp, " st->print(\" volatile!\");\n"); fprintf(fp, " }\n"); } @@ -1885,9 +1885,6 @@ void ArchDesc::declareClasses(FILE *fp) { // Analyze machine instructions that either USE or DEF memory. int memory_operand = instr->memory_operand(_globalNames); if ( memory_operand != InstructForm::NO_MEMORY_OPERAND ) { - if( memory_operand == InstructForm::MANY_MEMORY_OPERANDS ) { - fprintf(fp," virtual const TypePtr *adr_type() const;\n"); - } fprintf(fp," virtual const MachOper *memory_operand() const;\n"); } diff --git a/src/hotspot/share/gc/shared/c2/barrierSetC2.cpp b/src/hotspot/share/gc/shared/c2/barrierSetC2.cpp index 7a773640466c..511ef579b543 100644 --- a/src/hotspot/share/gc/shared/c2/barrierSetC2.cpp +++ b/src/hotspot/share/gc/shared/c2/barrierSetC2.cpp @@ -747,7 +747,7 @@ void BarrierSetC2::clone(GraphKit* kit, Node* src_base, Node* dst_base, Node* si Node* n = kit->gvn().transform(ac); if (n == ac) { const TypePtr* raw_adr_type = TypeRawPtr::BOTTOM; - ac->set_adr_type(TypeRawPtr::BOTTOM); + ac->set_out_adr_type(TypeRawPtr::BOTTOM); kit->set_predefined_output_for_runtime_call(ac, ac->in(TypeFunc::Memory), raw_adr_type); } else { kit->set_all_memory(n); diff --git a/src/hotspot/share/gc/shenandoah/c2/shenandoahBarrierSetC2.cpp b/src/hotspot/share/gc/shenandoah/c2/shenandoahBarrierSetC2.cpp index 2b8c12c41a68..d876a5517755 100644 --- a/src/hotspot/share/gc/shenandoah/c2/shenandoahBarrierSetC2.cpp +++ b/src/hotspot/share/gc/shenandoah/c2/shenandoahBarrierSetC2.cpp @@ -600,7 +600,7 @@ void ShenandoahBarrierSetC2::clone(GraphKit* kit, Node* src_base, Node* dst_base ac->set_clone_array(); Node* n = kit->gvn().transform(ac); if (n == ac) { - ac->set_adr_type(TypeRawPtr::BOTTOM); + ac->set_out_adr_type(TypeRawPtr::BOTTOM); kit->set_predefined_output_for_runtime_call(ac, ac->in(TypeFunc::Memory), TypeRawPtr::BOTTOM); } else { kit->set_all_memory(n); @@ -775,13 +775,13 @@ void ShenandoahBarrierSetC2::verify_gc_barriers(Compile* compile, CompilePhase p const TypePtr* adr_type = nullptr; if (is_Load(opc)) { bd = n->as_Load()->barrier_data(); - adr_type = n->as_Load()->adr_type(); + adr_type = n->as_Load()->in_adr_type(); } else if (is_Store(opc)) { bd = n->as_Store()->barrier_data(); - adr_type = n->as_Store()->adr_type(); + adr_type = n->as_Store()->out_adr_type(); } else if (is_LoadStore(opc)) { bd = n->as_LoadStore()->barrier_data(); - adr_type = n->as_LoadStore()->adr_type(); + adr_type = n->as_LoadStore()->out_adr_type(); } else if (n->is_Mem()) { bd = MemNode::barrier_data(n); verify_gc_barrier_assert(bd == 0, "Other mem nodes should have no barrier data", bd, n); diff --git a/src/hotspot/share/opto/arraycopynode.cpp b/src/hotspot/share/opto/arraycopynode.cpp index 240750b8416b..d0fa123fda35 100644 --- a/src/hotspot/share/opto/arraycopynode.cpp +++ b/src/hotspot/share/opto/arraycopynode.cpp @@ -35,7 +35,7 @@ const TypeFunc* ArrayCopyNode::_arraycopy_type_Type = nullptr; ArrayCopyNode::ArrayCopyNode(Compile* C, bool alloc_tightly_coupled, bool has_negative_length_guard) - : CallNode(arraycopy_type(), nullptr, TypePtr::BOTTOM), + : CallNode(arraycopy_type(), nullptr, TypePtr::BOTTOM, TypePtr::BOTTOM), _kind(None), _alloc_tightly_coupled(alloc_tightly_coupled), _has_negative_length_guard(has_negative_length_guard), diff --git a/src/hotspot/share/opto/callnode.cpp b/src/hotspot/share/opto/callnode.cpp index be9bf9a20b8a..7e942e2df026 100644 --- a/src/hotspot/share/opto/callnode.cpp +++ b/src/hotspot/share/opto/callnode.cpp @@ -1373,7 +1373,7 @@ bool CallStaticJavaNode::remove_unknown_flat_array_load(PhaseIterGVN* igvn, Node } address call_addr = OptoRuntime::uncommon_trap_blob()->entry_point(); - CallNode* unc = new CallStaticJavaNode(OptoRuntime::uncommon_trap_Type(), call_addr, "uncommon_trap", nullptr); + CallNode* unc = new CallStaticJavaNode(OptoRuntime::uncommon_trap_Type(), call_addr, "uncommon_trap", nullptr, TypePtr::BOTTOM); unc->init_req(TypeFunc::Control, call->in(0)); unc->init_req(TypeFunc::I_O, call->in(TypeFunc::I_O)); unc->init_req(TypeFunc::Memory, call->in(TypeFunc::Memory)); @@ -1456,7 +1456,7 @@ Node* CallStaticJavaNode::replace_identity_hash_code(PhaseIterGVN* igvn) { } Node* new_mem = kit.reset_memory(); assert(in(TypeFunc::Memory) == new_mem, "must not modify memory"); - return TupleNode::make(tf()->range_cc(), adr_type(), igvn->C->top(), kit.i_o(), new_mem, kit.frameptr(), kit.returnadr(), replace); + return TupleNode::make(tf()->range_cc(), out_adr_type(), igvn->C->top(), kit.i_o(), new_mem, kit.frameptr(), kit.returnadr(), replace); } // Try to replace a runtime call to the substitutability test by either a simple pointer comparison @@ -1503,7 +1503,7 @@ Node* CallStaticJavaNode::replace_is_substitutable(PhaseIterGVN* igvn) { } Node* new_mem = kit.reset_memory(); assert(in(TypeFunc::Memory) == new_mem, "must not modify memory"); - return TupleNode::make(tf()->range_cc(), adr_type(), igvn->C->top(), kit.i_o(), new_mem, kit.frameptr(), kit.returnadr(), replace); + return TupleNode::make(tf()->range_cc(), out_adr_type(), igvn->C->top(), kit.i_o(), new_mem, kit.frameptr(), kit.returnadr(), replace); } #ifndef PRODUCT @@ -2090,7 +2090,7 @@ AllocateNode::AllocateNode(Compile* C, const TypeFunc *atype, Node *size, Node *klass_node, Node* initial_test, ValueTypeNode* value_type_node) - : CallNode(atype, nullptr, TypeRawPtr::BOTTOM) + : CallNode(atype, nullptr, TypeRawPtr::BOTTOM, TypePtr::BOTTOM) { init_class_id(Class_Allocate); init_flags(Flag_is_macro); diff --git a/src/hotspot/share/opto/callnode.hpp b/src/hotspot/share/opto/callnode.hpp index 349cef7fe625..c70e812fa5ed 100644 --- a/src/hotspot/share/opto/callnode.hpp +++ b/src/hotspot/share/opto/callnode.hpp @@ -70,7 +70,6 @@ class StartNode : public MultiNode { virtual int Opcode() const; virtual bool pinned() const { return true; }; virtual const Type *bottom_type() const; - virtual const TypePtr *adr_type() const { return TypePtr::BOTTOM; } virtual const Type* Value(PhaseGVN* phase) const; virtual Node *Ideal(PhaseGVN *phase, bool can_reshape); virtual void calling_convention( BasicType* sig_bt, VMRegPair *parm_reg, uint length ) const; @@ -81,6 +80,10 @@ class StartNode : public MultiNode { virtual void dump_spec(outputStream *st) const; virtual void dump_compact_spec(outputStream *st) const; #endif + +private: + const TypePtr* out_adr_type_impl() const final { return TypePtr::BOTTOM; } + const TypePtr* in_adr_type_impl() const final { return nullptr; } }; //------------------------------StartOSRNode----------------------------------- @@ -125,6 +128,10 @@ class ReturnNode : public Node { #ifndef PRODUCT virtual void dump_req(outputStream *st = tty, DumpConfig* dc = nullptr) const; #endif + +private: + const TypePtr* out_adr_type_impl() const final { return nullptr; } + const TypePtr* in_adr_type_impl() const final { return TypePtr::BOTTOM; } }; @@ -145,6 +152,10 @@ class RethrowNode : public Node { #ifndef PRODUCT virtual void dump_req(outputStream *st = tty, DumpConfig* dc = nullptr) const; #endif + +private: + const TypePtr* out_adr_type_impl() const final { return nullptr; } + const TypePtr* in_adr_type_impl() const final { return TypePtr::BOTTOM; } }; @@ -340,7 +351,8 @@ class SafePointNode : public MultiNode { // Many calls take *all* of memory as input, // but some produce a limited subset of that memory as output. // The adr_type reports the call's behavior as a store, not a load. - const TypePtr* _adr_type; // What type of memory does this node produce? + const TypePtr* _out_adr_type; // What type of memory does this node produce? + const TypePtr* _in_adr_type; // What type of memory does this node consume? ReplacedNodes _replaced_nodes; // During parsing: list of pair of nodes from calls to GraphKit::replace_in_map() bool _has_ea_local_in_scope; // NoEscape or ArgEscape objects in JVM States @@ -350,11 +362,14 @@ class SafePointNode : public MultiNode { } public: SafePointNode(uint edges, JVMState* jvms, - // A plain safepoint advertises no memory effects (null): - const TypePtr* adr_type = nullptr) - : MultiNode( edges ), + // A plain safepoint advertises no memory effects (null) + const TypePtr* out_adr_type = nullptr, + // A plain safepoint reads all memory + const TypePtr* in_adr_type = TypePtr::BOTTOM) + : MultiNode(edges), _jvms(jvms), - _adr_type(adr_type), + _out_adr_type(out_adr_type), + _in_adr_type(in_adr_type), _has_ea_local_in_scope(false) { init_class_id(Class_SafePoint); @@ -568,8 +583,7 @@ class SafePointNode : public MultiNode { virtual bool pinned() const { return true; } virtual const Type* Value(PhaseGVN* phase) const; virtual const Type* bottom_type() const { return Type::CONTROL; } - virtual const TypePtr* adr_type() const { return _adr_type; } - void set_adr_type(const TypePtr* adr_type) { _adr_type = adr_type; } + void set_out_adr_type(const TypePtr* out_adr_type) { _out_adr_type = out_adr_type; } virtual Node *Ideal(PhaseGVN *phase, bool can_reshape); virtual Node* Identity(PhaseGVN* phase); virtual uint ideal_reg() const { return 0; } @@ -580,6 +594,10 @@ class SafePointNode : public MultiNode { #ifndef PRODUCT virtual void dump_spec(outputStream *st) const; #endif + +private: + const TypePtr* out_adr_type_impl() const final { return _out_adr_type; } + const TypePtr* in_adr_type_impl() const final { return _in_adr_type; } }; //------------------------------SafePointScalarObjectNode---------------------- @@ -770,8 +788,8 @@ class CallNode : public SafePointNode { CallGenerator* _generator; // corresponding CallGenerator for some late inline calls const char* _name; // Printable name, if _method is null - CallNode(const TypeFunc* tf, address addr, const TypePtr* adr_type, JVMState* jvms = nullptr) - : SafePointNode(tf->domain_cc()->cnt(), jvms, adr_type), + CallNode(const TypeFunc* tf, address addr, const TypePtr* out_adr_type, const TypePtr* in_adr_type, JVMState* jvms = nullptr) + : SafePointNode(tf->domain_cc()->cnt(), jvms, out_adr_type, in_adr_type), _tf(tf), _entry_point(addr), _cnt(COUNT_UNKNOWN), @@ -861,8 +879,8 @@ class CallJavaNode : public CallNode { bool _override_symbolic_info; // Override symbolic call site info from bytecode bool _arg_escape; // ArgEscape in parameter list public: - CallJavaNode(const TypeFunc* tf , address addr, ciMethod* method) - : CallNode(tf, addr, TypePtr::BOTTOM), + CallJavaNode(const TypeFunc* tf , address addr, ciMethod* method, const TypePtr* out_adr_type, const TypePtr* in_adr_type) + : CallNode(tf, addr, out_adr_type, in_adr_type), _method(method), _optimized_virtual(false), _override_symbolic_info(false), @@ -908,7 +926,7 @@ class CallStaticJavaNode : public CallJavaNode { public: CallStaticJavaNode(Compile* C, const TypeFunc* tf, address addr, ciMethod* method) - : CallJavaNode(tf, addr, method), _safe_for_fold_compare(true) { + : CallJavaNode(tf, addr, method, TypePtr::BOTTOM, TypePtr::BOTTOM), _safe_for_fold_compare(true) { init_class_id(Class_CallStaticJava); if (C->eliminate_boxing() && (method != nullptr) && (method->is_boxing_method() || method->is_unboxing_method())) { init_flags(Flag_is_macro); @@ -926,11 +944,9 @@ class CallStaticJavaNode : public CallJavaNode { C->add_macro_node(this); } } - CallStaticJavaNode(const TypeFunc* tf, address addr, const char* name, const TypePtr* adr_type) - : CallJavaNode(tf, addr, nullptr), _safe_for_fold_compare(true) { + CallStaticJavaNode(const TypeFunc* tf, address addr, const char* name, const TypePtr* out_adr_type, const TypePtr* in_adr_type) + : CallJavaNode(tf, addr, nullptr, out_adr_type, in_adr_type), _safe_for_fold_compare(true) { init_class_id(Class_CallStaticJava); - // This node calls a runtime stub, which often has narrow memory effects. - _adr_type = adr_type; _name = name; } @@ -977,7 +993,7 @@ class CallDynamicJavaNode : public CallJavaNode { virtual uint size_of() const; // Size is bigger public: CallDynamicJavaNode(const TypeFunc* tf , address addr, ciMethod* method, int vtable_index) - : CallJavaNode(tf,addr,method), _vtable_index(vtable_index) { + : CallJavaNode(tf, addr, method, TypePtr::BOTTOM, TypePtr::BOTTOM), _vtable_index(vtable_index) { init_class_id(Class_CallDynamicJava); } @@ -1003,8 +1019,8 @@ class CallRuntimeNode : public CallNode { virtual uint size_of() const; // Size is bigger public: CallRuntimeNode(const TypeFunc* tf, address addr, const char* name, - const TypePtr* adr_type, JVMState* jvms = nullptr) - : CallNode(tf, addr, adr_type, jvms) + const TypePtr* out_adr_type, const TypePtr* in_adr_type, JVMState* jvms = nullptr) + : CallNode(tf, addr, out_adr_type, in_adr_type, jvms) { init_class_id(Class_CallRuntime); _name = name; @@ -1024,8 +1040,8 @@ class CallRuntimeNode : public CallNode { class CallLeafNode : public CallRuntimeNode { public: CallLeafNode(const TypeFunc* tf, address addr, const char* name, - const TypePtr* adr_type) - : CallRuntimeNode(tf, addr, name, adr_type) + const TypePtr* out_adr_type, const TypePtr* in_adr_type) + : CallRuntimeNode(tf, addr, name, out_adr_type, in_adr_type) { init_class_id(Class_CallLeaf); } @@ -1055,7 +1071,7 @@ class CallLeafPureNode : public CallLeafNode { public: CallLeafPureNode(const TypeFunc* tf, address addr, const char* name) - : CallLeafNode(tf, addr, name, nullptr) { + : CallLeafNode(tf, addr, name, nullptr, nullptr) { init_class_id(Class_CallLeafPure); } int Opcode() const override; @@ -1070,8 +1086,8 @@ class CallLeafPureNode : public CallLeafNode { class CallLeafNoFPNode : public CallLeafNode { public: CallLeafNoFPNode(const TypeFunc* tf, address addr, const char* name, - const TypePtr* adr_type) - : CallLeafNode(tf, addr, name, adr_type) + const TypePtr* out_adr_type, const TypePtr* in_adr_type) + : CallLeafNode(tf, addr, name, out_adr_type, in_adr_type) { init_class_id(Class_CallLeafNoFP); } @@ -1089,8 +1105,8 @@ class CallLeafVectorNode : public CallLeafNode { virtual uint size_of() const; // Size is bigger public: CallLeafVectorNode(const TypeFunc* tf, address addr, const char* name, - const TypePtr* adr_type, uint num_bits) - : CallLeafNode(tf, addr, name, adr_type), _num_bits(num_bits) + const TypePtr* out_adr_type, const TypePtr* in_adr_type, uint num_bits) + : CallLeafNode(tf, addr, name, out_adr_type, in_adr_type), _num_bits(num_bits) { } virtual int Opcode() const; @@ -1300,8 +1316,8 @@ class AbstractLockNode: public CallNode { void set_eliminated_lock_counter() PRODUCT_RETURN; public: - AbstractLockNode(const TypeFunc *tf) - : CallNode(tf, nullptr, TypeRawPtr::BOTTOM), + AbstractLockNode(const TypeFunc *tf, const TypePtr* out_adr_type, const TypePtr* in_adr_type) + : CallNode(tf, nullptr, out_adr_type, in_adr_type), _kind(Regular) { #ifndef PRODUCT @@ -1380,7 +1396,7 @@ class LockNode : public AbstractLockNode { virtual int Opcode() const; virtual uint size_of() const; // Size is bigger - LockNode(Compile* C, const TypeFunc *tf) : AbstractLockNode( tf ) { + LockNode(Compile* C, const TypeFunc* tf) : AbstractLockNode(tf, TypeRawPtr::BOTTOM, TypePtr::BOTTOM) { init_class_id(Class_Lock); init_flags(Flag_is_macro); C->add_macro_node(this); @@ -1405,7 +1421,7 @@ class UnlockNode : public AbstractLockNode { public: virtual int Opcode() const; virtual uint size_of() const; // Size is bigger - UnlockNode(Compile* C, const TypeFunc *tf) : AbstractLockNode( tf ) + UnlockNode(Compile* C, const TypeFunc* tf) : AbstractLockNode(tf, TypeRawPtr::BOTTOM, TypeRawPtr::BOTTOM) #ifdef ASSERT , _dbg_jvms(nullptr) #endif diff --git a/src/hotspot/share/opto/cfgnode.cpp b/src/hotspot/share/opto/cfgnode.cpp index e91c70769dfa..c560041d1d2d 100644 --- a/src/hotspot/share/opto/cfgnode.cpp +++ b/src/hotspot/share/opto/cfgnode.cpp @@ -1126,13 +1126,13 @@ PhiNode* PhiNode::make(Node* r, Node* x, const Type *t, const TypePtr* at) { PhiNode* PhiNode::make(Node* r, Node* x) { const Type* t = x->bottom_type(); const TypePtr* at = nullptr; - if (t == Type::MEMORY) at = flatten_phi_adr_type(x->adr_type()); + if (t == Type::MEMORY) at = flatten_phi_adr_type(x->out_adr_type()); return make(r, x, t, at); } PhiNode* PhiNode::make_blank(Node* r, Node* x) { const Type* t = x->bottom_type(); const TypePtr* at = nullptr; - if (t == Type::MEMORY) at = flatten_phi_adr_type(x->adr_type()); + if (t == Type::MEMORY) at = flatten_phi_adr_type(x->out_adr_type()); return new PhiNode(r, t, at); } @@ -1162,7 +1162,7 @@ PhiNode* PhiNode::split_out_instance(const TypePtr* at, PhaseIterGVN *igvn) cons Node* use = region->fast_out(k); if( use->is_Phi()) { PhiNode *phi2 = use->as_Phi(); - if (phi2->type() == Type::MEMORY && phi2->adr_type() == at) { + if (phi2->type() == Type::MEMORY && phi2->out_adr_type() == at) { return phi2; } } @@ -1187,7 +1187,7 @@ PhiNode* PhiNode::split_out_instance(const TypePtr* at, PhaseIterGVN *igvn) cons continue; Node *opt = MemNode::optimize_simple_memory_chain(in, t_oop, nullptr, igvn); PhiNode *optphi = opt->is_Phi() ? opt->as_Phi() : nullptr; - if (optphi != nullptr && optphi->adr_type() == TypePtr::BOTTOM) { + if (optphi != nullptr && optphi->out_adr_type() == TypePtr::BOTTOM) { opt = node_map[optphi->_idx]; if (opt == nullptr) { stack.push(ophi, i); @@ -1228,7 +1228,7 @@ void PhiNode::verify_adr_type(VectorSet& visited, const TypePtr* at) const { || (n->is_Mem() && n->in(MemNode::Address)->bottom_type() == Type::TOP)) { // ignore top inputs } else { - const TypePtr* nat = flatten_phi_adr_type(n->adr_type()); + const TypePtr* nat = flatten_phi_adr_type(n->out_adr_type()); // recheck phi/non-phi consistency at leaves: assert((nat != nullptr) == (at != nullptr), ""); assert(nat == at || nat == TypePtr::BOTTOM, @@ -1605,8 +1605,8 @@ Node* PhiNode::Identity(PhaseGVN* phase) { // Looking for phis with identical inputs. If we find one that has // type TypePtr::BOTTOM, replace the current phi with the bottom phi. - if (phase->is_IterGVN() && type() == Type::MEMORY && adr_type() != - TypePtr::BOTTOM && !adr_type()->is_known_instance()) { + if (phase->is_IterGVN() && type() == Type::MEMORY && + out_adr_type() != TypePtr::BOTTOM && !out_adr_type()->is_known_instance()) { uint phi_len = req(); Node* phi_reg = region(); for (DUIterator_Fast imax, i = phi_reg->fast_outs(imax); i < imax; i++) { @@ -2251,7 +2251,7 @@ bool PhiNode::must_wait_for_region_in_irreducible_loop(PhaseGVN* phase) const { // helps simplify the memory graph. Node* PhiNode::split_through_mergemem(PhaseIterGVN& igvn) { assert(type() == Type::MEMORY, "must be a memory Phi"); - assert(adr_type() == TypePtr::BOTTOM, "must be a bottom memory Phi"); + assert(out_adr_type() == TypePtr::BOTTOM, "must be a bottom memory Phi"); bool need_splitting = false; bool need_process = false; @@ -2702,7 +2702,7 @@ Node *PhiNode::Ideal(PhaseGVN *phase, bool can_reshape) { PhaseIterGVN* igvn = phase->is_IterGVN(); assert(igvn != nullptr, "must be during IGVN"); - if (const TypePtr* at = adr_type(); at == TypePtr::BOTTOM) { + if (const TypePtr* at = out_adr_type(); at == TypePtr::BOTTOM) { progress = split_through_mergemem(*igvn); if (progress != nullptr && progress != this) { return progress; @@ -2728,7 +2728,7 @@ Node *PhiNode::Ideal(PhaseGVN *phase, bool can_reshape) { // // Other optimizations on the memory chain // - const TypePtr* at = adr_type(); + const TypePtr* at = out_adr_type(); for( uint i=1; iis_IterGVN(); uint phi_len = req(); Node* phi_reg = region(); @@ -3337,9 +3337,9 @@ const TypeTuple* PhiNode::collect_types(PhaseGVN* phase) const { } bool PhiNode::can_be_replaced_by(PhaseGVN* phase, const PhiNode* other) const { - const TypePtr* at = adr_type(); + const TypePtr* at = out_adr_type(); return type() == Type::MEMORY && other->type() == Type::MEMORY && at != TypePtr::BOTTOM && - other->adr_type() == TypePtr::BOTTOM && has_same_inputs_as(other) && + other->out_adr_type() == TypePtr::BOTTOM && has_same_inputs_as(other) && // Normally, the alias class corresponding to 'at' not being in '_excluded_idx' does not // imply that it is contained in the bottom memory Phi 'other'. However, since 'this' and // 'other' have the same inputs, either both represent the memory state of 'at', or @@ -3487,7 +3487,7 @@ void PhiNode::dump_spec(outputStream *st) const { if (is_tripcount(T_INT) || is_tripcount(T_LONG)) { st->print(" #tripcount"); } - if (type() == Type::MEMORY && adr_type() == TypePtr::BOTTOM && _excluded_idx != nullptr) { + if (type() == Type::MEMORY && out_adr_type() == TypePtr::BOTTOM && _excluded_idx != nullptr) { st->print(" #excluded_alias_idx:{"); bool first = true; for (int alias_idx = Compile::AliasIdxRaw; alias_idx < Compile::current()->num_alias_types(); alias_idx++) { diff --git a/src/hotspot/share/opto/cfgnode.hpp b/src/hotspot/share/opto/cfgnode.hpp index 5dfdbcf38d94..9ee37d7466d9 100644 --- a/src/hotspot/share/opto/cfgnode.hpp +++ b/src/hotspot/share/opto/cfgnode.hpp @@ -391,7 +391,6 @@ class PhiNode : public TypeNode { bool try_clean_memory_phi(PhaseIterGVN* igvn); virtual int Opcode() const; virtual bool pinned() const { return in(0) != nullptr; } - virtual const TypePtr *adr_type() const { verify_adr_type(true); return _adr_type; } void set_inst_mem_id(int inst_mem_id) { _inst_mem_id = inst_mem_id; } int inst_mem_id() const { return _inst_mem_id; } @@ -432,6 +431,12 @@ class PhiNode : public TypeNode { const TypeTuple* collect_types(PhaseGVN* phase) const; bool can_be_replaced_by(PhaseGVN* phase, const PhiNode* other) const; + +private: + const TypePtr* out_adr_type_impl() const final { + verify_adr_type(true); + return _adr_type; + } }; //------------------------------GotoNode--------------------------------------- diff --git a/src/hotspot/share/opto/compile.cpp b/src/hotspot/share/opto/compile.cpp index 54fe6c1baf78..63ed6ed8f394 100644 --- a/src/hotspot/share/opto/compile.cpp +++ b/src/hotspot/share/opto/compile.cpp @@ -2336,7 +2336,7 @@ void Compile::adjust_flat_array_access_aliases(PhaseIterGVN& igvn) { Node* n = wq.at(i); if (n->is_Mem()) { const TypePtr* adr_type = nullptr; - adr_type = get_adr_type(get_alias_index(n->adr_type())); + adr_type = get_adr_type(get_alias_index(n->in_adr_type())); if (adr_type == TypeAryPtr::INLINES) { memnodes.push(n); } @@ -2371,7 +2371,7 @@ void Compile::adjust_flat_array_access_aliases(PhaseIterGVN& igvn) { int start_alias = num_alias_types(); // Start of new aliases for (uint i = 0; i < memnodes.size(); i++) { Node* m = memnodes.at(i); - const TypePtr* adr_type = m->adr_type(); + const TypePtr* adr_type = m->in_adr_type(); #ifdef ASSERT m->as_Mem()->set_adr_type(adr_type); #endif // ASSERT @@ -2401,8 +2401,8 @@ void Compile::adjust_flat_array_access_aliases(PhaseIterGVN& igvn) { // bottom memory, we pop element off the stack one at a // time, in reverse order, and move them to the right slice // by changing their memory edges. - if ((n->is_Phi() && n->adr_type() != TypePtr::BOTTOM) || n->is_Mem() || - (n->adr_type() == TypeAryPtr::INLINES && !n->is_NarrowMemProj())) { + if ((n->is_Phi() && n->out_adr_type() != TypePtr::BOTTOM) || n->is_Mem() || + (n->out_adr_type() == TypeAryPtr::INLINES && !n->is_NarrowMemProj())) { assert(!seen.test_set(n->_idx), ""); // Uses (a load for instance) will need to be moved to the // right slice as well and will get a new memory state @@ -2452,7 +2452,7 @@ void Compile::adjust_flat_array_access_aliases(PhaseIterGVN& igvn) { n = n->in(0)->in(TypeFunc::Memory); } } else { - assert(n->adr_type() == TypePtr::BOTTOM || (n->Opcode() == Op_Node && n->_idx >= last) || n->is_NarrowMemProj(), ""); + assert(n->out_adr_type() == TypePtr::BOTTOM || (n->Opcode() == Op_Node && n->_idx >= last) || n->is_NarrowMemProj(), ""); // Build a new MergeMem node to carry the new memory state // as we build it. IGVN should fold extraneous MergeMem // nodes. @@ -2472,7 +2472,7 @@ void Compile::adjust_flat_array_access_aliases(PhaseIterGVN& igvn) { Node* base = alloc->in(TypeFunc::Memory); assert(base->bottom_type() == Type::MEMORY, "the memory input of AllocateNode must be a memory"); - assert(base->adr_type() == TypePtr::BOTTOM, "the memory input of AllocateNode must be a bottom memory"); + assert(base->out_adr_type() == TypePtr::BOTTOM, "the memory input of AllocateNode must be a bottom memory"); // Must create a MergeMem with base as the base memory, do not clone if base is a // MergeMem because it may not be processed yet mm = MergeMemNode::make(nullptr); @@ -2513,7 +2513,7 @@ void Compile::adjust_flat_array_access_aliases(PhaseIterGVN& igvn) { uint idx = stack.index(); if (m->is_Mem()) { // Move memory node to its new slice - const TypePtr* adr_type = m->adr_type(); + const TypePtr* adr_type = m->in_adr_type(); int alias = get_alias_index(adr_type); Node* prev = mm->memory_at(alias); igvn.replace_input_of(m, MemNode::Memory, prev); @@ -2560,7 +2560,7 @@ void Compile::adjust_flat_array_access_aliases(PhaseIterGVN& igvn) { if (!adr_type->isa_aryptr() || !adr_type->is_flat()) { continue; } - MemBarNode* mb = new MemBarCPUOrderNode(this, j, nullptr); + MemBarNode* mb = new MemBarCPUOrderNode(this, adr_type, adr_type, nullptr); igvn.register_new_node_with_optimizer(mb); Node* mem = mm->memory_at(j); mb->init_req(TypeFunc::Control, ctrl); @@ -2617,7 +2617,7 @@ void Compile::adjust_flat_array_access_aliases(PhaseIterGVN& igvn) { wq.push(root()); for (uint i = 0; i < wq.size(); i++) { Node* n = wq.at(i); - assert(n->adr_type() != TypeAryPtr::INLINES, "should have been removed from the graph"); + assert(n->out_adr_type() != TypeAryPtr::INLINES && n->in_adr_type() != TypeAryPtr::INLINES, "should have been removed from the graph"); for (uint j = 0; j < n->req(); j++) { Node* m = n->in(j); if (m != nullptr) { @@ -4073,7 +4073,7 @@ void Compile::final_graph_reshaping_impl(Node *n, Final_Reshape_Counts& frc, Uni #ifdef ASSERT if( n->is_Mem() ) { - int alias_idx = get_alias_index(n->as_Mem()->adr_type()); + int alias_idx = get_alias_index(n->as_Mem()->in_adr_type()); assert( n->in(0) != nullptr || alias_idx != Compile::AliasIdxRaw || // oop will be recorded in oop map if load crosses safepoint (n->is_Load() && (n->as_Load()->bottom_type()->isa_oopptr() || @@ -4165,7 +4165,7 @@ void Compile::final_graph_reshaping_main_switch(Node* n, Final_Reshape_Counts& f if (!Matcher::match_rule_supported(Op_CallLeafPure)) { CallNode* call = n->as_Call(); CallNode* new_call = new CallLeafNode(call->tf(), call->entry_point(), - call->_name, TypeRawPtr::BOTTOM); + call->_name, nullptr, nullptr); new_call->init_req(TypeFunc::Control, call->in(TypeFunc::Control)); new_call->init_req(TypeFunc::I_O, C->top()); new_call->init_req(TypeFunc::Memory, C->top()); @@ -6293,7 +6293,7 @@ Node* Compile::make_debug_print_call(const char* str, address call_addr, PhaseGV Node* parm6) const { Node* str_node = gvn->transform(new ConPNode(TypeRawPtr::make(((address) str)))); const TypeFunc* type = OptoRuntime::debug_print_Type(parm0, parm1, parm2, parm3, parm4, parm5, parm6); - Node* call = new CallLeafNode(type, call_addr, "debug_print", TypeRawPtr::BOTTOM); + Node* call = new CallLeafNode(type, call_addr, "debug_print", nullptr, nullptr); // find the most suitable control input Unique_Node_List worklist, candidates; diff --git a/src/hotspot/share/opto/escape.cpp b/src/hotspot/share/opto/escape.cpp index 731580155b78..89729574b0e9 100644 --- a/src/hotspot/share/opto/escape.cpp +++ b/src/hotspot/share/opto/escape.cpp @@ -4437,14 +4437,14 @@ PhiNode* ConnectionGraph::create_split_phi(PhiNode* orig_phi, int alias_idx, Uni Compile *C = _compile; PhaseGVN* igvn = _igvn; new_created = false; - int phi_alias_idx = C->get_alias_index(orig_phi->adr_type()); + int phi_alias_idx = C->get_alias_index(orig_phi->out_adr_type()); // nothing to do if orig_phi is bottom memory or matches alias_idx if (phi_alias_idx == alias_idx) { return orig_phi; } // Have we recently created a Phi for this alias index? PhiNode *result = get_map_phi(orig_phi->_idx); - if (result != nullptr && C->get_alias_index(result->adr_type()) == alias_idx) { + if (result != nullptr && C->get_alias_index(result->out_adr_type()) == alias_idx) { return result; } // Previous check may fail when the same wide memory Phi was split into Phis @@ -4454,7 +4454,7 @@ PhiNode* ConnectionGraph::create_split_phi(PhiNode* orig_phi, int alias_idx, Uni for (DUIterator_Fast imax, i = region->fast_outs(imax); i < imax; i++) { Node* phi = region->fast_out(i); if (phi->is_Phi() && - C->get_alias_index(phi->as_Phi()->adr_type()) == alias_idx) { + C->get_alias_index(phi->as_Phi()->out_adr_type()) == alias_idx) { assert(phi->_idx >= nodes_size(), "only new Phi per instance memory slice"); return phi->as_Phi(); } @@ -4596,7 +4596,7 @@ void ConnectionGraph::move_inst_mem(Node* n, Unique_Node_List& orig_phis) { record_for_optimizer(use); continue; } - tp = use->as_MemBar()->adr_type()->isa_ptr(); + tp = use->as_MemBar()->in_adr_type()->isa_ptr(); if ((tp != nullptr && C->get_alias_index(tp) == alias_idx) || alias_idx == general_idx) { continue; // Nothing to do @@ -4631,7 +4631,7 @@ void ConnectionGraph::move_inst_mem(Node* n, Unique_Node_List& orig_phis) { "Following memory nodes should have new memory input or be on the same memory slice"); } else if (use->is_Phi()) { // Phi nodes should be split and moved already. - tp = use->as_Phi()->adr_type()->isa_ptr(); + tp = use->as_Phi()->in_adr_type()->isa_ptr(); assert(tp != nullptr, "ptr type"); int idx = C->get_alias_index(tp); assert(idx == alias_idx, "Following Phi nodes should be on the same memory slice"); @@ -4742,8 +4742,8 @@ Node* ConnectionGraph::find_inst_mem(Node* orig_mem, int alias_idx, Unique_Node_ // which contains this memory slice, otherwise skip over it. if (alloc == nullptr || alloc->_idx != (uint)toop->instance_id()) { result = proj_in->in(TypeFunc::Memory); - } else if (C->get_alias_index(result->adr_type()) != alias_idx) { - assert(C->get_general_index(alias_idx) == C->get_alias_index(result->adr_type()), "should be projection for the same field/array element"); + } else if (C->get_alias_index(result->out_adr_type()) != alias_idx) { + assert(C->get_general_index(alias_idx) == C->get_alias_index(result->out_adr_type()), "should be projection for the same field/array element"); result = get_map(result->_idx); assert(result != nullptr, "new projection should have been allocated"); break; @@ -4794,7 +4794,7 @@ Node* ConnectionGraph::find_inst_mem(Node* orig_mem, int alias_idx, Unique_Node_ mmem->set_memory_at(alias_idx, result); } } else if (result->is_Phi() && - C->get_alias_index(result->as_Phi()->adr_type()) != alias_idx) { + C->get_alias_index(result->as_Phi()->out_adr_type()) != alias_idx) { Node *un = result->as_Phi()->unique_input(igvn); if (un != nullptr) { orig_phis.push(result); @@ -4848,7 +4848,7 @@ Node* ConnectionGraph::find_inst_mem(Node* orig_mem, int alias_idx, Unique_Node_ if (result->is_Phi()) { PhiNode *mphi = result->as_Phi(); assert(mphi->bottom_type() == Type::MEMORY, "memory phi required"); - const TypePtr *t = mphi->adr_type(); + const TypePtr* t = mphi->out_adr_type(); if (!is_instance) { // Push all non-instance Phis on the orig_phis worklist to update inputs // during Phase 4 if needed. @@ -5139,7 +5139,7 @@ void ConnectionGraph::split_unique_types(GrowableArray &alloc_worklist, InitializeNode* init = alloc->as_Allocate()->initialization(); assert(init != nullptr, "can't find Initialization node for this Allocate node"); auto process_narrow_proj = [&](NarrowMemProjNode* proj) { - const TypePtr* adr_type = proj->adr_type(); + const TypePtr* adr_type = proj->out_adr_type(); const TypePtr* new_adr_type = tinst->with_offset(adr_type->offset()); if (adr_type->isa_aryptr()) { // In the case of a flat value type array, each field has its own slice so we need a @@ -5611,7 +5611,7 @@ void ConnectionGraph::split_unique_types(GrowableArray &alloc_worklist, // also be processed here. for (uint j = 0; j < orig_phis.size(); j++) { PhiNode* phi = orig_phis.at(j)->as_Phi(); - int alias_idx = _compile->get_alias_index(phi->adr_type()); + int alias_idx = _compile->get_alias_index(phi->out_adr_type()); igvn->hash_delete(phi); for (uint i = 1; i < phi->req(); i++) { Node *mem = phi->in(i); diff --git a/src/hotspot/share/opto/gcm.cpp b/src/hotspot/share/opto/gcm.cpp index 0c661e02cd29..5d14b608dd7a 100644 --- a/src/hotspot/share/opto/gcm.cpp +++ b/src/hotspot/share/opto/gcm.cpp @@ -736,7 +736,7 @@ Block* PhaseCFG::raise_above_anti_dependences(Block* LCA, Node* load, const bool // Compute the alias index. Loads and stores with different alias indices // do not need anti-dependence edges. - int load_alias_idx = C->get_alias_index(load->adr_type()); + int load_alias_idx = C->get_alias_index(load->in_adr_type()); #ifdef ASSERT assert(Compile::AliasIdxTop <= load_alias_idx && load_alias_idx < C->num_alias_types(), "Invalid alias index"); if (load_alias_idx == Compile::AliasIdxBot && C->do_aliasing() && @@ -904,7 +904,7 @@ Block* PhaseCFG::raise_above_anti_dependences(Block* LCA, Node* load, const bool // Compute the alias index. If the use_mem_state has an alias index // different from the load's, it is not anti-dependent. Wide MemBar's // are anti-dependent with everything (except immutable memories). - const TypePtr* adr_type = use_mem_state->adr_type(); + const TypePtr* adr_type = use_mem_state->out_adr_type(); if (!C->can_alias(adr_type, load_alias_idx)) continue; // Most slow-path runtime calls do NOT modify Java memory, but diff --git a/src/hotspot/share/opto/generateOptoStub.cpp b/src/hotspot/share/opto/generateOptoStub.cpp index a18ef3b1db64..8a420bc1d855 100644 --- a/src/hotspot/share/opto/generateOptoStub.cpp +++ b/src/hotspot/share/opto/generateOptoStub.cpp @@ -163,7 +163,7 @@ void GraphKit::gen_stub(address C_function, //----------------------------- // Make the call node. - CallRuntimeNode* call = new CallRuntimeNode(c_sig, C_function, name, TypePtr::BOTTOM, new (C) JVMState(0)); + CallRuntimeNode* call = new CallRuntimeNode(c_sig, C_function, name, TypePtr::BOTTOM, TypePtr::BOTTOM, new (C) JVMState(0)); //----------------------------- // Fix-up the debug info for the call. diff --git a/src/hotspot/share/opto/graphKit.cpp b/src/hotspot/share/opto/graphKit.cpp index ce93ede92267..a812f5e7b825 100644 --- a/src/hotspot/share/opto/graphKit.cpp +++ b/src/hotspot/share/opto/graphKit.cpp @@ -2265,7 +2265,7 @@ void GraphKit::set_predefined_output_for_runtime_call(Node* call, // Make sure the call advertises its memory effects precisely. // This lets us build accurate anti-dependences in gcm.cpp. - assert(C->alias_type(call->adr_type()) == C->alias_type(hook_mem), + assert(C->alias_type(call->out_adr_type()) == C->alias_type(hook_mem), "call node must be constructed correctly"); } else { assert(hook_mem == nullptr, ""); @@ -2871,7 +2871,7 @@ Node* GraphKit::opt_iff(Node* region, Node* iff) { Node* GraphKit::make_runtime_call(int flags, const TypeFunc* call_type, address call_addr, const char* call_name, - const TypePtr* adr_type, + const TypePtr* out_adr_type, // The following parms are all optional. // The first null ends the list. Node* parm0, Node* parm1, @@ -2887,34 +2887,35 @@ Node* GraphKit::make_runtime_call(int flags, assert(!is_leaf, "must supply name for leaf"); call_name = OptoRuntime::stub_name(call_addr); } + + // Slow path call has no side-effects, uses few values + bool wide_in = !(flags & RC_NARROW_MEM); + bool wide_out = (C->get_alias_index(out_adr_type) == Compile::AliasIdxBot); + + const TypePtr* in_adr_type = wide_in ? TypePtr::BOTTOM : out_adr_type; CallNode* call; if (!is_leaf) { - call = new CallStaticJavaNode(call_type, call_addr, call_name, adr_type); + call = new CallStaticJavaNode(call_type, call_addr, call_name, out_adr_type, in_adr_type); } else if (flags & RC_NO_FP) { - call = new CallLeafNoFPNode(call_type, call_addr, call_name, adr_type); + call = new CallLeafNoFPNode(call_type, call_addr, call_name, out_adr_type, in_adr_type); } else if (flags & RC_VECTOR){ uint num_bits = call_type->range_sig()->field_at(TypeFunc::Parms)->is_vect()->length_in_bytes() * BitsPerByte; - call = new CallLeafVectorNode(call_type, call_addr, call_name, adr_type, num_bits); + call = new CallLeafVectorNode(call_type, call_addr, call_name, out_adr_type, in_adr_type, num_bits); } else if (flags & RC_PURE) { - assert(adr_type == nullptr, "pure call does not touch memory"); + assert(out_adr_type == nullptr, "pure call does not touch memory"); call = new CallLeafPureNode(call_type, call_addr, call_name); } else { - call = new CallLeafNode(call_type, call_addr, call_name, adr_type); + call = new CallLeafNode(call_type, call_addr, call_name, out_adr_type, in_adr_type); } // The following is similar to set_edges_for_java_call, // except that the memory effects of the call are restricted to AliasIdxRaw. - - // Slow path call has no side-effects, uses few values - bool wide_in = !(flags & RC_NARROW_MEM); - bool wide_out = (C->get_alias_index(adr_type) == Compile::AliasIdxBot); - Node* prev_mem = nullptr; if (wide_in) { prev_mem = set_predefined_input_for_runtime_call(call); } else { assert(!wide_out, "narrow in => narrow out"); - Node* narrow_mem = memory(adr_type); + Node* narrow_mem = memory(in_adr_type); prev_mem = set_predefined_input_for_runtime_call(call, narrow_mem); } @@ -2954,7 +2955,7 @@ Node* GraphKit::make_runtime_call(int flags, set_predefined_output_for_runtime_call(call); } else { // Slow path call has few side-effects, and/or sets few values. - set_predefined_output_for_runtime_call(call, prev_mem, adr_type); + set_predefined_output_for_runtime_call(call, prev_mem, out_adr_type); } if (has_io) { @@ -4946,7 +4947,7 @@ void GraphKit::memory_effect(Node* res_mem, const TypePtr* src_type, const TypeP if (src_type != dst_type) { Node* all_mem = reset_memory(); set_all_memory(all_mem); - Node* membar = new MemBarCPUOrderNode(C, C->get_alias_index(src_type), nullptr); + Node* membar = new MemBarCPUOrderNode(C, src_type, TypePtr::BOTTOM, nullptr); membar->init_req(TypeFunc::Control, control()); membar->init_req(TypeFunc::Memory, all_mem); membar = _gvn.transform(membar); @@ -4973,7 +4974,7 @@ void GraphKit::inflate_string(Node* src, Node* dst, const TypeAryPtr* dst_type, const TypePtr* src_type = TypeAryPtr::BYTES; const TypePtr* adr_type; Node* mem = capture_memory(adr_type, src_type, dst_type); - StrInflatedCopyNode* str = new StrInflatedCopyNode(control(), mem, adr_type, src, dst, count); + StrInflatedCopyNode* str = new StrInflatedCopyNode(control(), mem, dst_type, adr_type, src, dst, count); Node* res_mem = _gvn.transform(str); memory_effect(res_mem, src_type, dst_type); } diff --git a/src/hotspot/share/opto/idealGraphPrinter.cpp b/src/hotspot/share/opto/idealGraphPrinter.cpp index 0409224121ea..827c710669b5 100644 --- a/src/hotspot/share/opto/idealGraphPrinter.cpp +++ b/src/hotspot/share/opto/idealGraphPrinter.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2007, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -54,7 +54,7 @@ class PrintProperties }; void PrintProperties::print_alias_properties(Node* node) { - const TypePtr* adr_type = node->adr_type(); + const TypePtr* adr_type = node->in_adr_type(); Compile* C = _printer->C; if (adr_type != nullptr && C->have_alias_type(adr_type)) { Compile::AliasType* at = C->alias_type(adr_type); @@ -611,10 +611,15 @@ void IdealGraphPrinter::visit_node(Node* n, bool edges) { } } } - if (n->adr_type() != nullptr) { + if (const TypePtr* out = n->out_adr_type(); out != nullptr) { stringStream adr_type_stream; - n->adr_type()->dump_on(&adr_type_stream); - print_prop("adr_type", adr_type_stream.freeze()); + out->dump_on(&adr_type_stream); + print_prop("out_adr_type", adr_type_stream.freeze()); + } + if (const TypePtr* in = n->in_adr_type(); in != nullptr) { + stringStream adr_type_stream; + in->dump_on(&adr_type_stream); + print_prop("in_adr_type", adr_type_stream.freeze()); } if (C->cfg() != nullptr) { @@ -705,8 +710,7 @@ void IdealGraphPrinter::visit_node(Node* n, bool edges) { } t->dump_on(&s2); } else if( t == Type::MEMORY ) { - s2.print(" Memory:"); - MemNode::dump_adr_type(node->adr_type(), &s2); + s2.print(" Memory"); } assert(s2.size() < sizeof(buffer), "size in range"); @@ -906,7 +910,7 @@ void IdealGraphPrinter::print_field(const Node* node) { } ciField* IdealGraphPrinter::get_field(const Node* node) { - const TypePtr* adr_type = node->adr_type(); + const TypePtr* adr_type = node->in_adr_type(); Compile::AliasType* atp = nullptr; if (C->have_alias_type(adr_type)) { atp = C->alias_type(adr_type); @@ -929,7 +933,7 @@ ciField* IdealGraphPrinter::find_source_field_of_array_access(const Node* node, } do { - if (node->adr_type() != nullptr && node->adr_type()->isa_aryptr()) { + if (const TypePtr* in = node->in_adr_type(); in != nullptr && in->isa_aryptr()) { // Only process array accesses. Pattern match to find actual field source access. node = get_load_node(node); if (node != nullptr) { diff --git a/src/hotspot/share/opto/idealKit.cpp b/src/hotspot/share/opto/idealKit.cpp index 7d45fb770ef2..02d3aa0fda7e 100644 --- a/src/hotspot/share/opto/idealKit.cpp +++ b/src/hotspot/share/opto/idealKit.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -485,7 +485,7 @@ Node* IdealKit::make_leaf_call(const TypeFunc *slow_call_type, uint adr_idx = C->get_alias_index(adr_type); // Slow-path leaf call - CallNode *call = (CallNode*)new CallLeafNode( slow_call_type, slow_call, leaf_name, adr_type); + CallNode* call = new CallLeafNode(slow_call_type, slow_call, leaf_name, adr_type, adr_type); // Set fixed predefined input arguments call->init_req( TypeFunc::Control, ctrl() ); @@ -514,7 +514,8 @@ Node* IdealKit::make_leaf_call(const TypeFunc *slow_call_type, // Set the RawPtr memory state only. set_memory(mem, adr_idx); - assert(C->alias_type(call->adr_type()) == C->alias_type(adr_type), + assert(C->alias_type(call->out_adr_type()) == C->alias_type(adr_type) && + C->alias_type(call->in_adr_type()) == C->alias_type(adr_type), "call node must be constructed correctly"); Node* res = nullptr; if (slow_call_type->range_sig()->cnt() > TypeFunc::Parms) { @@ -537,7 +538,7 @@ void IdealKit::make_leaf_call_no_fp(const TypeFunc *slow_call_type, uint adr_idx = C->get_alias_index(adr_type); // Slow-path leaf call - CallNode *call = (CallNode*)new CallLeafNoFPNode( slow_call_type, slow_call, leaf_name, adr_type); + CallNode* call = new CallLeafNoFPNode(slow_call_type, slow_call, leaf_name, adr_type, adr_type); // Set fixed predefined input arguments call->init_req( TypeFunc::Control, ctrl() ); @@ -566,6 +567,7 @@ void IdealKit::make_leaf_call_no_fp(const TypeFunc *slow_call_type, // Set the RawPtr memory state only. set_memory(mem, adr_idx); - assert(C->alias_type(call->adr_type()) == C->alias_type(adr_type), + assert(C->alias_type(call->out_adr_type()) == C->alias_type(adr_type) && + C->alias_type(call->in_adr_type()) == C->alias_type(adr_type), "call node must be constructed correctly"); } diff --git a/src/hotspot/share/opto/intrinsicnode.cpp b/src/hotspot/share/opto/intrinsicnode.cpp index 0b7f5ed21eae..c0d8b9b0a536 100644 --- a/src/hotspot/share/opto/intrinsicnode.cpp +++ b/src/hotspot/share/opto/intrinsicnode.cpp @@ -47,7 +47,7 @@ static Node* ideal_mem_intrinsic_node(PhaseGVN* phase, bool can_reshape, Node* n } // If mem input is a MergeMem, get the desired slice - if (const TypePtr* adr_type = n->adr_type(); can_reshape && adr_type != TypePtr::BOTTOM) { + if (const TypePtr* adr_type = n->in_adr_type(); can_reshape && adr_type != TypePtr::BOTTOM) { Node* mem = n->in(MemNode::Memory); uint alias_idx = phase->C->get_alias_index(adr_type); Node* new_mem = mem->is_MergeMem() ? mem->as_MergeMem()->memory_at(alias_idx) : mem; diff --git a/src/hotspot/share/opto/intrinsicnode.hpp b/src/hotspot/share/opto/intrinsicnode.hpp index 230ad8d81fe4..711921c98aa6 100644 --- a/src/hotspot/share/opto/intrinsicnode.hpp +++ b/src/hotspot/share/opto/intrinsicnode.hpp @@ -28,6 +28,7 @@ #include "opto/connode.hpp" #include "opto/node.hpp" #include "opto/opcodes.hpp" +#include "opto/type.hpp" //----------------------PartialSubtypeCheckNode-------------------------------- @@ -81,7 +82,6 @@ class StrIntrinsicNode : public Node { Node(control, char_array_mem, s1, s2), _encoding(encoding) { } - virtual const TypePtr* adr_type() const override = 0; virtual uint match_edge(uint idx) const override; virtual uint ideal_reg() const override { return Op_RegI; } virtual Node* Ideal(PhaseGVN* phase, bool can_reshape) override; @@ -100,7 +100,9 @@ class StrCompNode final : public StrIntrinsicNode { StrIntrinsicNode(control, char_array_mem, s1, c1, s2, c2, encoding) {}; virtual int Opcode() const override; virtual const Type* bottom_type() const override { return TypeInt::INT; } - virtual const TypePtr* adr_type() const override { return TypeAryPtr::BYTES; } + +private: + const TypePtr* in_adr_type_impl() const final { return TypeAryPtr::BYTES; } }; //------------------------------StrEquals------------------------------------- @@ -111,7 +113,9 @@ class StrEqualsNode final : public StrIntrinsicNode { StrIntrinsicNode(control, char_array_mem, s1, s2, c, encoding) {}; virtual int Opcode() const override; virtual const Type* bottom_type() const override { return TypeInt::BOOL; } - virtual const TypePtr* adr_type() const override { return TypeAryPtr::BYTES; } + +private: + const TypePtr* in_adr_type_impl() const final { return TypeAryPtr::BYTES; } }; //------------------------------StrIndexOf------------------------------------- @@ -122,7 +126,9 @@ class StrIndexOfNode final : public StrIntrinsicNode { StrIntrinsicNode(control, char_array_mem, s1, c1, s2, c2, encoding) {}; virtual int Opcode() const override; virtual const Type* bottom_type() const override { return TypeInt::INT; } - virtual const TypePtr* adr_type() const override { return TypeAryPtr::BYTES; } + +private: + const TypePtr* in_adr_type_impl() const final { return TypeAryPtr::BYTES; } }; //------------------------------StrIndexOfChar------------------------------------- @@ -133,49 +139,55 @@ class StrIndexOfCharNode final : public StrIntrinsicNode { StrIntrinsicNode(control, char_array_mem, s1, c1, c, encoding) {}; virtual int Opcode() const override; virtual const Type* bottom_type() const override { return TypeInt::INT; } - virtual const TypePtr* adr_type() const override { return TypeAryPtr::BYTES; } + +private: + const TypePtr* in_adr_type_impl() const final { return TypeAryPtr::BYTES; } }; //--------------------------StrCompressedCopy------------------------------- class StrCompressedCopyNode final : public StrIntrinsicNode { private: - const TypePtr* const _adr_type; + const TypePtr* const _in_adr_type; public: - StrCompressedCopyNode(Node* control, Node* arymem, const TypePtr* adr_type, + StrCompressedCopyNode(Node* control, Node* arymem, const TypePtr* in_adr_type, Node* s1, Node* s2, Node* c): - StrIntrinsicNode(control, arymem, s1, s2, c, none), _adr_type(adr_type) {}; + StrIntrinsicNode(control, arymem, s1, s2, c, none), _in_adr_type(in_adr_type) {}; virtual int Opcode() const override; virtual const Type* bottom_type() const override { return TypeInt::INT; } private: virtual uint size_of() const override { return sizeof(StrCompressedCopyNode); } - virtual uint hash() const override { return StrIntrinsicNode::hash() + (uint)(uintptr_t) _adr_type; } + virtual uint hash() const override { return StrIntrinsicNode::hash() + (uint)(uintptr_t) _in_adr_type; } virtual bool cmp(const Node& n) const override { - return StrIntrinsicNode::cmp(n) && _adr_type == static_cast(n)._adr_type; + return StrIntrinsicNode::cmp(n) && _in_adr_type == static_cast(n)._in_adr_type; } - virtual const TypePtr* adr_type() const override { return _adr_type; } + const TypePtr* out_adr_type_impl() const final { return TypeAryPtr::BYTES; } + const TypePtr* in_adr_type_impl() const final { return _in_adr_type; } }; //--------------------------StrInflatedCopy--------------------------------- class StrInflatedCopyNode final : public StrIntrinsicNode { private: - const TypePtr* const _adr_type; + const TypePtr* const _out_adr_type; + const TypePtr* const _in_adr_type; public: - StrInflatedCopyNode(Node* control, Node* arymem, const TypePtr* adr_type, + StrInflatedCopyNode(Node* control, Node* arymem, const TypePtr* out_adr_type, const TypePtr* in_adr_type, Node* s1, Node* s2, Node* c): - StrIntrinsicNode(control, arymem, s1, s2, c, none), _adr_type(adr_type) {}; + StrIntrinsicNode(control, arymem, s1, s2, c, none), _out_adr_type(out_adr_type), _in_adr_type(in_adr_type) {}; virtual int Opcode() const override; virtual const Type* bottom_type() const override { return Type::MEMORY; } private: virtual uint size_of() const override { return sizeof(StrInflatedCopyNode); } - virtual uint hash() const override { return StrIntrinsicNode::hash() + (uint)(uintptr_t) _adr_type; } + virtual uint hash() const override { return StrIntrinsicNode::hash() + (uint)(uintptr_t) _out_adr_type + (uint)(uintptr_t) _in_adr_type; } virtual bool cmp(const Node& n) const override { - return StrIntrinsicNode::cmp(n) && _adr_type == static_cast(n)._adr_type; + auto& other = static_cast(n); + return StrIntrinsicNode::cmp(n) && _out_adr_type == other._out_adr_type && _in_adr_type == other._in_adr_type; } - virtual const TypePtr* adr_type() const override { return _adr_type; } + const TypePtr* out_adr_type_impl() const final { return _out_adr_type; } + const TypePtr* in_adr_type_impl() const final { return _in_adr_type; } }; //------------------------------AryEq--------------------------------------- @@ -196,7 +208,7 @@ class AryEqNode final : public StrIntrinsicNode { virtual bool cmp(const Node& n) const override { return StrIntrinsicNode::cmp(n) && _in_adr_type == static_cast(n)._in_adr_type; } - virtual const TypePtr* adr_type() const override { return _in_adr_type; } + const TypePtr* in_adr_type_impl() const final { return _in_adr_type; } }; //------------------------------CountPositives------------------------------ @@ -206,7 +218,9 @@ class CountPositivesNode final : public StrIntrinsicNode { StrIntrinsicNode(control, char_array_mem, s1, c1, none) {}; virtual int Opcode() const override; virtual const Type* bottom_type() const override { return TypeInt::POS; } - virtual const TypePtr* adr_type() const override { return TypeAryPtr::BYTES; } + +private: + const TypePtr* in_adr_type_impl() const final { return TypeAryPtr::BYTES; } }; //------------------------------VectorizedHashCodeNode---------------------- @@ -230,20 +244,20 @@ class VectorizedHashCodeNode final : public Node { virtual bool cmp(const Node& n) const override { return Node::cmp(n) && _in_adr_type == static_cast(n)._in_adr_type; } - virtual const TypePtr* adr_type() const override { return _in_adr_type; } virtual bool depends_only_on_test_impl() const override { return false; } + const TypePtr* in_adr_type_impl() const final { return _in_adr_type; } }; //------------------------------EncodeISOArray-------------------------------- // encode char[] to byte[] in ISO_8859_1 or ASCII class EncodeISOArrayNode final : public Node { private: - const TypePtr* const _adr_type; + const TypePtr* const _in_adr_type; bool _ascii; public: - EncodeISOArrayNode(Node* control, Node* arymem, const TypePtr* adr_type, Node* s1, Node* s2, Node* c, bool ascii) - : Node(control, arymem, s1, s2, c), _adr_type(adr_type), _ascii(ascii) {} + EncodeISOArrayNode(Node* control, Node* arymem, const TypePtr* in_adr_type, Node* s1, Node* s2, Node* c, bool ascii) + : Node(control, arymem, s1, s2, c), _in_adr_type(in_adr_type), _ascii(ascii) {} bool is_ascii() { return _ascii; } virtual int Opcode() const override; @@ -255,13 +269,14 @@ class EncodeISOArrayNode final : public Node { private: virtual uint size_of() const override { return sizeof(EncodeISOArrayNode); } - virtual uint hash() const override { return Node::hash() + (uint)(uintptr_t) _adr_type + _ascii; } + virtual uint hash() const override { return Node::hash() + (uint)(uintptr_t) _in_adr_type + _ascii; } virtual bool cmp(const Node& n) const override { const EncodeISOArrayNode& e = static_cast(n); - return Node::cmp(n) && _ascii == e._ascii && _adr_type == e._adr_type; + return Node::cmp(n) && _ascii == e._ascii && _in_adr_type == e._in_adr_type; } - virtual const TypePtr* adr_type() const override { return _adr_type; } virtual bool depends_only_on_test_impl() const override { return false; } + const TypePtr* out_adr_type_impl() const override { return TypeAryPtr::BYTES; } + const TypePtr* in_adr_type_impl() const override { return _in_adr_type; } }; //-------------------------------DigitNode---------------------------------------- diff --git a/src/hotspot/share/opto/library_call.cpp b/src/hotspot/share/opto/library_call.cpp index be0c21e823ec..dfa302ee9a85 100644 --- a/src/hotspot/share/opto/library_call.cpp +++ b/src/hotspot/share/opto/library_call.cpp @@ -6711,7 +6711,7 @@ void LibraryCallKit::arraycopy_move_allocation_here(AllocateArrayNode* alloc, No int klass_idx = C->get_alias_index(ary_type->add_offset(oopDesc::klass_offset_in_bytes())); #endif auto move_proj = [&](ProjNode* proj) { - int alias_idx = C->get_alias_index(proj->adr_type()); + int alias_idx = C->get_alias_index(proj->out_adr_type()); assert(alias_idx == Compile::AliasIdxRaw || alias_idx == elemidx || alias_idx == mark_idx || diff --git a/src/hotspot/share/opto/loopTransform.cpp b/src/hotspot/share/opto/loopTransform.cpp index 209e4bd6bea4..3c809d2a4ed7 100644 --- a/src/hotspot/share/opto/loopTransform.cpp +++ b/src/hotspot/share/opto/loopTransform.cpp @@ -4030,7 +4030,7 @@ bool PhaseIdealLoop::match_fill_loop(IdealLoopTree* lpt, Node*& store, Node*& st // Make sure there is an appropriate fill routine BasicType t = msg == nullptr ? - store->adr_type()->isa_aryptr()->elem()->array_element_basic_type() : T_VOID; + store->out_adr_type()->isa_aryptr()->elem()->array_element_basic_type() : T_VOID; const char* fill_name; if (msg == nullptr && StubRoutines::select_fill_function(t, false, fill_name) == nullptr) { @@ -4277,7 +4277,7 @@ bool PhaseIdealLoop::intrinsify_fill(IdealLoopTree* lpt) { #endif } - BasicType t = store->adr_type()->isa_aryptr()->elem()->array_element_basic_type(); + BasicType t = store->out_adr_type()->isa_aryptr()->elem()->array_element_basic_type(); bool aligned = false; if (offset != nullptr && head->init_trip()->is_Con()) { int element_size = type2aelembytes(t); @@ -4302,8 +4302,9 @@ bool PhaseIdealLoop::intrinsify_fill(IdealLoopTree* lpt) { Node* result_ctrl; Node* result_mem; const TypeFunc* call_type = OptoRuntime::array_fill_Type(); - CallLeafNode *call = new CallLeafNoFPNode(call_type, fill, - fill_name, TypeAryPtr::get_array_body_type(t)); + const TypePtr* adr_type = TypeAryPtr::get_array_body_type(t); + assert(adr_type == mem_phi->out_adr_type(), "incorrect adr_type"); + CallLeafNode* call = new CallLeafNoFPNode(call_type, fill, fill_name, adr_type, adr_type); uint cnt = 0; call->init_req(TypeFunc::Parms + cnt++, from); call->init_req(TypeFunc::Parms + cnt++, store_value); diff --git a/src/hotspot/share/opto/loopnode.cpp b/src/hotspot/share/opto/loopnode.cpp index 6fc07d83a923..1c31a898fb2e 100644 --- a/src/hotspot/share/opto/loopnode.cpp +++ b/src/hotspot/share/opto/loopnode.cpp @@ -548,7 +548,7 @@ void PhaseIdealLoop::add_parse_predicate(Deoptimization::DeoptReason reason, Nod address call_addr = OptoRuntime::uncommon_trap_blob()->entry_point(); const TypePtr* no_memory_effects = nullptr; CallNode* unc = new CallStaticJavaNode(OptoRuntime::uncommon_trap_Type(), call_addr, "uncommon_trap", - no_memory_effects); + no_memory_effects, TypePtr::BOTTOM); Node* mem = nullptr; Node* i_o = nullptr; @@ -599,7 +599,7 @@ static bool no_side_effect_since_safepoint(Compile* C, const Node* head, const N Node* u = head->fast_out(i); if (u->is_memory_phi()) { Node* m = u->in(LoopNode::LoopBackControl); - if (u->adr_type() == TypePtr::BOTTOM) { + if (u->out_adr_type() == TypePtr::BOTTOM) { if (m->is_MergeMem() && mem->is_MergeMem()) { if (m != mem DEBUG_ONLY(|| true)) { // MergeMemStream can modify m, for example to adjust the length to mem. @@ -629,11 +629,11 @@ static bool no_side_effect_since_safepoint(Compile* C, const Node* head, const N } } else { if (mem->is_MergeMem()) { - if (m != mem->as_MergeMem()->memory_at(C->get_alias_index(u->adr_type()))) { + if (m != mem->as_MergeMem()->memory_at(C->get_alias_index(u->out_adr_type()))) { return false; } #ifdef ASSERT - mm->set_memory_at(C->get_alias_index(u->adr_type()), mem->as_MergeMem()->base_memory()); + mm->set_memory_at(C->get_alias_index(u->out_adr_type()), mem->as_MergeMem()->base_memory()); #endif } else { if (m != mem) { @@ -3360,14 +3360,14 @@ void OuterStripMinedLoopNode::fix_sunk_stores_when_back_to_counted_loop(PhaseIte for (DUIterator_Fast imax, i = cle_out->fast_outs(imax); i < imax; i++) { Node* u = cle_out->fast_out(i); if (u->is_Store()) { - int alias_idx = igvn->C->get_alias_index(u->adr_type()); + int alias_idx = igvn->C->get_alias_index(u->out_adr_type()); Node* first = u; for (;;) { Node* next = first->in(MemNode::Memory); if (!next->is_Store() || next->in(0) != cle_out) { break; } - assert(igvn->C->get_alias_index(next->adr_type()) == alias_idx, ""); + assert(igvn->C->get_alias_index(next->out_adr_type()) == alias_idx, ""); first = next; } Node* last = u; @@ -3378,7 +3378,7 @@ void OuterStripMinedLoopNode::fix_sunk_stores_when_back_to_counted_loop(PhaseIte if (uu->is_Store() && uu->in(0) == cle_out) { assert(next == nullptr, "only one in the outer loop"); next = uu; - assert(igvn->C->get_alias_index(next->adr_type()) == alias_idx, ""); + assert(igvn->C->get_alias_index(next->out_adr_type()) == alias_idx, ""); } } if (next == nullptr) { @@ -3392,10 +3392,10 @@ void OuterStripMinedLoopNode::fix_sunk_stores_when_back_to_counted_loop(PhaseIte if (uu->is_Phi()) { Node* be = uu->in(LoopNode::LoopBackControl); if (be->is_Store() && be->in(0) == inner_cl->in(LoopNode::LoopBackControl)) { - assert(igvn->C->get_alias_index(uu->adr_type()) != alias_idx && igvn->C->get_alias_index(uu->adr_type()) != Compile::AliasIdxBot, "unexpected store"); + assert(igvn->C->get_alias_index(uu->out_adr_type()) != alias_idx && igvn->C->get_alias_index(uu->out_adr_type()) != Compile::AliasIdxBot, "unexpected store"); } if (be == last || be == first->in(MemNode::Memory)) { - assert(igvn->C->get_alias_index(uu->adr_type()) == alias_idx || igvn->C->get_alias_index(uu->adr_type()) == Compile::AliasIdxBot, "unexpected alias"); + assert(igvn->C->get_alias_index(uu->out_adr_type()) == alias_idx || igvn->C->get_alias_index(uu->out_adr_type()) == Compile::AliasIdxBot, "unexpected alias"); assert(phi == nullptr, "only one phi"); phi = uu; } @@ -3405,9 +3405,9 @@ void OuterStripMinedLoopNode::fix_sunk_stores_when_back_to_counted_loop(PhaseIte for (DUIterator_Fast jmax, j = inner_cl->fast_outs(jmax); j < jmax; j++) { Node* uu = inner_cl->fast_out(j); if (uu->is_memory_phi()) { - if (uu->adr_type() == igvn->C->get_adr_type(igvn->C->get_alias_index(u->adr_type()))) { + if (uu->out_adr_type() == igvn->C->get_adr_type(igvn->C->get_alias_index(u->out_adr_type()))) { assert(phi == uu, "what's that phi?"); - } else if (uu->adr_type() == TypePtr::BOTTOM) { + } else if (uu->out_adr_type() == TypePtr::BOTTOM) { Node* n = uu->in(LoopNode::LoopBackControl); uint limit = igvn->C->live_nodes(); uint i = 0; @@ -3421,7 +3421,7 @@ void OuterStripMinedLoopNode::fix_sunk_stores_when_back_to_counted_loop(PhaseIte } else if (n->is_Phi()) { n = n->in(1); } else if (n->is_MergeMem()) { - n = n->as_MergeMem()->memory_at(igvn->C->get_alias_index(u->adr_type())); + n = n->as_MergeMem()->memory_at(igvn->C->get_alias_index(u->out_adr_type())); } else if (n->is_Store() || n->is_LoadStore() || n->is_ClearArray()) { n = n->in(MemNode::Memory); } else { @@ -3438,7 +3438,7 @@ void OuterStripMinedLoopNode::fix_sunk_stores_when_back_to_counted_loop(PhaseIte // inner loop has no phi for that memory // slice, create one for the outer loop phi = PhiNode::make(inner_cl, first->in(MemNode::Memory), Type::MEMORY, - igvn->C->get_adr_type(igvn->C->get_alias_index(u->adr_type()))); + igvn->C->get_adr_type(igvn->C->get_alias_index(u->out_adr_type()))); phi->set_req(LoopNode::LoopBackControl, last); phi = register_new_node(phi, inner_cl, igvn, iloop); igvn->replace_input_of(first, MemNode::Memory, phi); @@ -4233,8 +4233,8 @@ void IdealLoopTree::merge_many_backedges( PhaseIdealLoop *phase ) { if( out->is_Phi() ) { PhiNode* n = out->as_Phi(); igvn.hash_delete(n); // Delete from hash before hacking edges - Node *hot_phi = nullptr; - Node *phi = new PhiNode(r, n->type(), n->adr_type()); + Node* hot_phi = nullptr; + Node* phi = new PhiNode(r, n->type(), n->out_adr_type()); // Check all inputs for the ones to peel out uint j = 1; for( uint i = 2; i < n->req(); i++ ) { @@ -6772,7 +6772,7 @@ Node *PhaseIdealLoop::get_late_ctrl( Node *n, Node *early ) { // dominated by early is considered a potentially interfering store. // This can produce false positives. Node* PhaseIdealLoop::get_late_ctrl_with_anti_dep(LoadNode* n, Node* early, Node* LCA) { - int load_alias_idx = C->get_alias_index(n->adr_type()); + int load_alias_idx = C->get_alias_index(n->in_adr_type()); if (C->alias_type(load_alias_idx)->is_rewritable()) { Unique_Node_List worklist; @@ -6796,7 +6796,7 @@ Node* PhaseIdealLoop::get_late_ctrl_with_anti_dep(LoadNode* n, Node* early, Node Node* sctrl = has_ctrl(s) ? get_ctrl(s) : s->in(0); assert(sctrl != nullptr || !s->is_reachable_from_root(), "must have control"); if (sctrl != nullptr && !sctrl->is_top() && is_dominator(early, sctrl)) { - const TypePtr* adr_type = s->adr_type(); + const TypePtr* adr_type = s->out_adr_type(); if (s->is_ArrayCopy()) { // Copy to known instance needs destination type to test for aliasing const TypePtr* dest_type = s->as_ArrayCopy()->_dest_type; @@ -6826,7 +6826,7 @@ Node* PhaseIdealLoop::get_late_ctrl_with_anti_dep(LoadNode* n, Node* early, Node if (LCA != early) { for (uint i = 0; i < worklist.size(); i++) { Node* s = worklist.at(i); - if (s->is_Phi() && C->can_alias(s->adr_type(), load_alias_idx)) { + if (s->is_Phi() && C->can_alias(s->out_adr_type(), load_alias_idx)) { Node* r = s->in(0); for (uint j = 1; j < s->req(); j++) { Node* in = s->in(j); diff --git a/src/hotspot/share/opto/machnode.cpp b/src/hotspot/share/opto/machnode.cpp index b7b50ceaf5a3..3150e7d60387 100644 --- a/src/hotspot/share/opto/machnode.cpp +++ b/src/hotspot/share/opto/machnode.cpp @@ -27,7 +27,10 @@ #include "gc/shared/collectedHeap.hpp" #include "memory/universe.hpp" #include "oops/compressedOops.hpp" +#include "opto/ad.hpp" #include "opto/machnode.hpp" +#include "opto/node.hpp" +#include "opto/opcodes.hpp" #include "opto/output.hpp" #include "opto/regalloc.hpp" #include "utilities/vmError.hpp" @@ -343,9 +346,73 @@ const Node* MachNode::get_base_and_disp(intptr_t &offset, const TypePtr* &adr_ty return base; } +const TypePtr* MachNode::out_adr_type_impl() const { + // A node produces memory if it is a memory node, or if it has a memory Proj + bool produce_memory = bottom_type() == Type::MEMORY; + if (!produce_memory) { + for (DUIterator_Fast imax, i = fast_outs(imax); i < imax; i++) { + const Node* out = fast_out(i); + if (out->is_Proj() && out->bottom_type() == Type::MEMORY) { + produce_memory = true; + break; + } + } + } + + if (const TypePtr* res = _out_adr_type; res != TYPE_PTR_SENTINAL) { +#ifdef ASSERT + if (produce_memory) { + assert(res != nullptr, "must declare an out_adr_type"); + verify_adr_type(res, compute_adr_type_from_inputs()); + } else { + assert(res == nullptr, "unexpected memory producer"); + } +#endif // ASSERT + return res; + } else if (produce_memory) { + return compute_adr_type_from_inputs(); + } else { + return nullptr; + } +} + +const TypePtr* MachNode::in_adr_type_impl() const { + if (const TypePtr* res = _in_adr_type; res != TYPE_PTR_SENTINAL) { + DEBUG_ONLY(verify_adr_type(res, compute_adr_type_from_inputs())); + return res; + } else { + return compute_adr_type_from_inputs(); + } +} + +void MachNode::verify_adr_type(const TypePtr* declared, const TypePtr* verify) const { +#ifdef ASSERT + if (Node::in_dump() || VMError::is_error_reported()) { + return; + } -//---------------------------------adr_type--------------------------------- -const class TypePtr *MachNode::adr_type() const { + if (verify == nullptr) { + return; + } + + if (declared == nullptr) { + assert(ideal_Opcode() == Op_PrefetchAllocation, "must either not declare an adr_type, or declare a non-null adr_type"); + return; + } + + if (declared != verify && declared->cast_to_ptr_type(TypePtr::BotPTR) != verify) { + stringStream ss; + verify->dump_on(&ss); ss.cr(); + declared->dump_on(&ss); ss.cr(); + tty->print_cr("%s", ss.as_string()); + assert(false, "adr_type may be incorrect"); + } +#endif // ASSERT +} + +// Fall-back computation for nodes that miss _adr_type assignments due to post-match expansion, +// seen on PPC +const TypePtr* MachNode::compute_adr_type_from_inputs() const { intptr_t offset = 0; const TypePtr *adr_type = TYPE_PTR_SENTINAL; // attempt computing adr_type const Node *base = get_base_and_disp(offset, adr_type); @@ -573,7 +640,7 @@ void MachNode::dump_spec(outputStream *st) const { st->print(" _"); } } - const TypePtr *t = adr_type(); + const TypePtr* t = in_adr_type(); if( t ) { Compile* C = Compile::current(); if( C->alias_type(t)->is_volatile() ) @@ -660,22 +727,6 @@ const Type *MachProjNode::bottom_type() const { return Type::mreg2type[_ideal_reg]; } -const TypePtr *MachProjNode::adr_type() const { - if (bottom_type() == Type::MEMORY) { - // in(0) might be a narrow MemBar; otherwise we will report TypePtr::BOTTOM - Node* ctrl = in(0); - if (ctrl == nullptr) return nullptr; // node is dead - const TypePtr* adr_type = ctrl->adr_type(); - #ifdef ASSERT - if (!VMError::is_error_reported() && !Node::in_dump()) - assert(adr_type != nullptr, "source must have adr_type"); - #endif - return adr_type; - } - assert(bottom_type()->base() != Type::Memory, "no other memories?"); - return nullptr; -} - #ifndef PRODUCT void MachProjNode::dump_spec(outputStream *st) const { ProjNode::dump_spec(st); @@ -701,12 +752,6 @@ const RegMask &MachReturnNode::in_RegMask( uint idx ) const { return _in_rms[idx]; } -const TypePtr *MachReturnNode::adr_type() const { - // most returns and calls are assumed to consume & modify all of memory - // the matcher will copy non-wide adr_types from ideal originals - return _adr_type; -} - //============================================================================= const Type *MachSafePointNode::bottom_type() const { return TypeTuple::MEMBAR; } @@ -893,13 +938,6 @@ JVMState *MachHaltNode::jvms() const { return &jvms_for_throw; } -uint MachMemBarNode::size_of() const { return sizeof(*this); } - -const TypePtr *MachMemBarNode::adr_type() const { - return _adr_type; -} - - //============================================================================= #ifndef PRODUCT void labelOper::int_format(PhaseRegAlloc *ra, const MachNode *node, outputStream *st) const { diff --git a/src/hotspot/share/opto/machnode.hpp b/src/hotspot/share/opto/machnode.hpp index 254578519258..a84a3328c72f 100644 --- a/src/hotspot/share/opto/machnode.hpp +++ b/src/hotspot/share/opto/machnode.hpp @@ -57,6 +57,8 @@ class PhaseRegAlloc; class RegMask; class State; +#define TYPE_PTR_SENTINAL ((const TypePtr*)-1) + //---------------------------MachOper------------------------------------------ class MachOper : public ResourceObj { public: @@ -220,8 +222,12 @@ class MachOper : public ResourceObj { // Base type for all machine specific nodes. All node classes generated by the // ADLC inherit from this class. class MachNode : public Node { +private: + friend class Matcher; + public: - MachNode() : Node((uint)0), _bottom_type(nullptr), _barrier(0), _num_opnds(0), _opnds(nullptr) { + MachNode() : Node((uint)0), _bottom_type(nullptr), _out_adr_type(TYPE_PTR_SENTINAL), _in_adr_type(TYPE_PTR_SENTINAL), + _barrier(0), _num_opnds(0), _opnds(nullptr) { init_class_id(Class_Mach); } // Required boilerplate @@ -286,6 +292,13 @@ class MachNode : public Node { // Capture the type of the matched ideal node const Type* _bottom_type; +private: + // Capture the adr_type of the matched ideal node, it is the union of the adr_type of all nodes + // that are matched into it + const TypePtr* _out_adr_type; + const TypePtr* _in_adr_type; + +public: // The GC might require some barrier metadata for machine code emission. uint8_t _barrier; @@ -362,7 +375,7 @@ class MachNode : public Node { // which serves as node bottom. // If the offset is not statically determined, set it to Type::OffsetBot. // This method is free to ignore stack slots if that helps. - #define TYPE_PTR_SENTINAL ((const TypePtr*)-1) + // Passing TYPE_PTR_SENTINAL as adr_type asks for computation of the adr_type if possible const Node* get_base_and_disp(intptr_t &offset, const TypePtr* &adr_type) const; @@ -378,9 +391,6 @@ class MachNode : public Node { // If it returns (MachOper*)-1, this means there are multiple memories. virtual const MachOper* memory_operand() const { return nullptr; } - // Call "get_base_and_disp" to decide which category of memory is used here. - virtual const class TypePtr *adr_type() const; - // Apply peephole rule(s) to this instruction virtual int peephole(Block *block, int block_index, PhaseCFG* cfg_, PhaseRegAlloc *ra_); @@ -417,6 +427,14 @@ class MachNode : public Node { virtual void dump_spec(outputStream *st) const; // Print per-node info void dump_format(PhaseRegAlloc *ra, outputStream *st) const; // access to virtual #endif + +private: + const TypePtr* out_adr_type_impl() const final; + const TypePtr* in_adr_type_impl() const final; + + // Call "get_base_and_disp" to decide which category of memory is used here. + void verify_adr_type(const TypePtr* declared, const TypePtr* verify) const; + const TypePtr* compute_adr_type_from_inputs() const; }; //------------------------------MachIdealNode---------------------------- @@ -805,7 +823,6 @@ class MachProjNode : public ProjNode { }; virtual int Opcode() const; virtual const Type *bottom_type() const; - virtual const TypePtr *adr_type() const; virtual const RegMask& in_RegMask(uint) const { return RegMask::EMPTY; } virtual const RegMask &out_RegMask() const { return _rout; } virtual uint ideal_reg() const { return _ideal_reg; } @@ -860,17 +877,12 @@ class MachReturnNode : public MachNode { public: RegMask *_in_rms; // Input register masks, set during allocation ReallocMark _nesting; // assertion check for reallocations - const TypePtr* _adr_type; // memory effects of call or return MachReturnNode() : MachNode() { init_class_id(Class_MachReturn); - _adr_type = TypePtr::BOTTOM; // the default: all of memory } - void set_adr_type(const TypePtr* atp) { _adr_type = atp; } - virtual const RegMask &in_RegMask(uint) const; virtual bool pinned() const { return true; }; - virtual const TypePtr *adr_type() const; }; //------------------------------MachSafePointNode----------------------------- @@ -1087,19 +1099,12 @@ class MachHaltNode : public MachReturnNode { }; class MachMemBarNode : public MachNode { - virtual uint size_of() const; // Size is bigger public: - const TypePtr* _adr_type; // memory effects MachMemBarNode() : MachNode() { init_class_id(Class_MachMemBar); - _adr_type = TypePtr::BOTTOM; // the default: all of memory } - - void set_adr_type(const TypePtr* atp) { _adr_type = atp; } - virtual const TypePtr *adr_type() const; }; - //------------------------------MachTempNode----------------------------------- // Node used by the adlc to construct inputs to represent temporary registers class MachTempNode : public MachNode { diff --git a/src/hotspot/share/opto/macro.cpp b/src/hotspot/share/opto/macro.cpp index 6285f4810a4e..5e5ca35bcdab 100644 --- a/src/hotspot/share/opto/macro.cpp +++ b/src/hotspot/share/opto/macro.cpp @@ -121,9 +121,9 @@ CallNode* PhaseMacroExpand::make_slow_call(CallNode *oldcall, const TypeFunc* sl Node* parm0, Node* parm1, Node* parm2) { // Slow-path call - CallNode *call = leaf_name - ? (CallNode*)new CallLeafNode ( slow_call_type, slow_call, leaf_name, TypeRawPtr::BOTTOM ) - : (CallNode*)new CallStaticJavaNode( slow_call_type, slow_call, OptoRuntime::stub_name(slow_call), TypeRawPtr::BOTTOM ); + CallNode* call = leaf_name + ? (CallNode*)new CallLeafNode (slow_call_type, slow_call, leaf_name, TypeRawPtr::BOTTOM, TypePtr::BOTTOM) + : (CallNode*)new CallStaticJavaNode(slow_call_type, slow_call, OptoRuntime::stub_name(slow_call), TypeRawPtr::BOTTOM, TypePtr::BOTTOM); // Slow path call has no side-effects, uses few values copy_predefined_input_for_runtime_call(slow_path, oldcall, call ); @@ -193,7 +193,7 @@ static Node *scan_mem_chain(Node *mem, int alias_idx, int offset, Node *start_me #endif } } else if (mem->is_Store()) { - const TypePtr* atype = mem->as_Store()->adr_type(); + const TypePtr* atype = mem->as_Store()->out_adr_type(); int adr_idx = phase->C->get_alias_index(atype); if (adr_idx == alias_idx) { assert(atype->isa_oopptr(), "address type must be oopptr"); @@ -598,12 +598,12 @@ Node* PhaseMacroExpand::value_from_mem(Node* origin, Node* ctl, BasicType ft, co if (mem == nullptr) { done = true; // Something went wrong. } else if (mem->is_Store()) { - const TypePtr* atype = mem->as_Store()->adr_type(); + const TypePtr* atype = mem->as_Store()->out_adr_type(); assert(C->get_alias_index(atype) == Compile::AliasIdxRaw, "store is correct memory slice"); done = true; } } else if (mem->is_Store()) { - const TypeOopPtr* atype = mem->as_Store()->adr_type()->isa_oopptr(); + const TypeOopPtr* atype = mem->as_Store()->out_adr_type()->isa_oopptr(); assert(atype != nullptr, "address type must be oopptr"); assert(C->get_alias_index(atype) == alias_idx && atype->is_known_instance_field() && atype->flat_offset() == offset && @@ -1889,9 +1889,9 @@ void PhaseMacroExpand::expand_allocate_common( } // Generate slow-path call - CallNode *call = new CallStaticJavaNode(slow_call_type, slow_call_address, + CallNode* call = new CallStaticJavaNode(slow_call_type, slow_call_address, OptoRuntime::stub_name(slow_call_address), - TypePtr::BOTTOM); + TypePtr::BOTTOM, TypePtr::BOTTOM); call->init_req(TypeFunc::Control, slow_region); call->init_req(TypeFunc::I_O, top()); // does no i/o call->init_req(TypeFunc::Memory, slow_mem); // may gc ptrs @@ -2135,7 +2135,7 @@ void PhaseMacroExpand::expand_initialize_membar(AllocateNode* alloc, InitializeN transform_later(ctrl); Node* old_raw_mem_proj = nullptr; auto find_raw_mem = [&](ProjNode* proj) { - if (C->get_alias_index(proj->adr_type()) == Compile::AliasIdxRaw) { + if (C->get_alias_index(proj->out_adr_type()) == Compile::AliasIdxRaw) { assert(old_raw_mem_proj == nullptr, "only one expected"); old_raw_mem_proj = proj; } @@ -2175,7 +2175,7 @@ void PhaseMacroExpand::expand_dtrace_alloc_probe(AllocateNode* alloc, Node* oop, CAST_FROM_FN_PTR(address, static_cast(SharedRuntime::dtrace_object_alloc)), "dtrace_object_alloc", - TypeRawPtr::BOTTOM); + TypeRawPtr::BOTTOM, TypeRawPtr::BOTTOM); // Get base of thread-local storage area Node* thread = new ThreadLocalNode(); @@ -2896,7 +2896,7 @@ void PhaseMacroExpand::expand_mh_intrinsic_return(CallStaticJavaNode* call) { handler_call = new CallLeafNoFPNode(OptoRuntime::pack_value_type_Type(), nullptr, "pack handler", - TypeRawPtr::BOTTOM); + TypeRawPtr::BOTTOM, TypeRawPtr::BOTTOM); handler_call->init_req(TypeFunc::Control, fast_oop_ctrl); handler_call->init_req(TypeFunc::Memory, fast_oop_rawmem); handler_call->init_req(TypeFunc::I_O, top()); @@ -2912,7 +2912,7 @@ void PhaseMacroExpand::expand_mh_intrinsic_return(CallStaticJavaNode* call) { CallStaticJavaNode* slow_call = new CallStaticJavaNode(OptoRuntime::store_value_type_fields_Type(), SharedRuntime::store_value_type_fields_to_buf_entry(), "store_value_type_fields", - TypePtr::BOTTOM); + TypePtr::BOTTOM, TypePtr::BOTTOM); slow_call->init_req(TypeFunc::Control, needgc_ctrl); slow_call->init_req(TypeFunc::Memory, mem); slow_call->init_req(TypeFunc::I_O, io); diff --git a/src/hotspot/share/opto/macroArrayCopy.cpp b/src/hotspot/share/opto/macroArrayCopy.cpp index f37f66a474eb..8982bacbaa02 100644 --- a/src/hotspot/share/opto/macroArrayCopy.cpp +++ b/src/hotspot/share/opto/macroArrayCopy.cpp @@ -89,7 +89,7 @@ Node* PhaseMacroExpand::make_leaf_call(Node* ctrl, Node* mem, Node* parm2, Node* parm3, Node* parm4, Node* parm5, Node* parm6, Node* parm7) { - Node* call = new CallLeafNoFPNode(call_type, call_addr, call_name, adr_type); + Node* call = new CallLeafNoFPNode(call_type, call_addr, call_name, adr_type, TypePtr::BOTTOM); call->init_req(TypeFunc::Control, ctrl); call->init_req(TypeFunc::I_O , top()); call->init_req(TypeFunc::Memory , mem); @@ -1123,7 +1123,7 @@ MergeMemNode* PhaseMacroExpand::generate_slow_arraycopy(ArrayCopyNode *ac, const TypeFunc* call_type = OptoRuntime::slow_arraycopy_Type(); CallNode* call = new CallStaticJavaNode(call_type, OptoRuntime::slow_arraycopy_Java(), - "slow_arraycopy", TypePtr::BOTTOM); + "slow_arraycopy", TypePtr::BOTTOM, TypePtr::BOTTOM); call->init_req(TypeFunc::Control, *ctrl); call->init_req(TypeFunc::I_O , *io); diff --git a/src/hotspot/share/opto/matcher.cpp b/src/hotspot/share/opto/matcher.cpp index 2cc75b3db8ba..c780710796dc 100644 --- a/src/hotspot/share/opto/matcher.cpp +++ b/src/hotspot/share/opto/matcher.cpp @@ -31,6 +31,7 @@ #include "opto/addnode.hpp" #include "opto/callnode.hpp" #include "opto/idealGraphPrinter.hpp" +#include "opto/machnode.hpp" #include "opto/matcher.hpp" #include "opto/memnode.hpp" #include "opto/movenode.hpp" @@ -965,15 +966,15 @@ void Matcher::init_spill_mask( Node *ret ) { #ifdef ASSERT static void match_alias_type(Compile* C, Node* n, Node* m) { if (!VerifyAliases) return; // do not go looking for trouble by default - const TypePtr* nat = n->adr_type(); - const TypePtr* mat = m->adr_type(); + const TypePtr* nat = n->out_adr_type(); + const TypePtr* mat = m->out_adr_type(); int nidx = C->get_alias_index(nat); int midx = C->get_alias_index(mat); // Detune the assert for cases like (AndI 0xFF (LoadB p)). if (nidx == Compile::AliasIdxTop && midx >= Compile::AliasIdxRaw) { for (uint i = 1; i < n->req(); i++) { Node* n1 = n->in(i); - const TypePtr* n1at = n1->adr_type(); + const TypePtr* n1at = n1->out_adr_type(); if (n1at != nullptr) { nat = n1at; nidx = C->get_alias_index(n1at); @@ -1071,9 +1072,6 @@ Node *Matcher::xform( Node *n, int max_stack ) { m = n->is_SafePoint() ? match_sfpt(n->as_SafePoint()):match_tree(n); if (C->failing()) return nullptr; if (m == nullptr) { Matcher::soft_match_failure(); return nullptr; } - if (n->is_MemBar()) { - m->as_MachMemBar()->set_adr_type(n->adr_type()); - } } else { // Nothing the matcher cares about if (n->is_Proj() && n->in(0) != nullptr && n->in(0)->is_Multi()) { // Projections? if (n->in(0)->is_Initialize() && n->as_Proj()->_con == TypeFunc::Memory) { @@ -1273,9 +1271,6 @@ MachNode *Matcher::match_sfpt( SafePointNode *sfpt ) { } msfpt->_has_ea_local_in_scope = sfpt->has_ea_local_in_scope(); - // Advertise the correct memory effects (for anti-dependence computation). - msfpt->set_adr_type(sfpt->adr_type()); - // Allocate a private array of RegMasks. These RegMasks are not shared. msfpt->_in_rms = NEW_RESOURCE_ARRAY( RegMask, cnt ); // Empty them all. @@ -1779,24 +1774,24 @@ MachNode *Matcher::ReduceInst( State *s, int rule, Node *&mem ) { m = _mem_node; assert(m != nullptr && m->is_Mem(), "expecting memory node"); } - const Type* mach_at = mach->adr_type(); + const TypePtr* mach_at = mach->in_adr_type(); // DecodeN node consumed by an address may have different type // than its input. Don't compare types for such case. - if (m->adr_type() != mach_at && + if (m->in_adr_type() != mach_at && (m->in(MemNode::Address)->is_DecodeNarrowPtr() || (m->in(MemNode::Address)->is_AddP() && m->in(MemNode::Address)->in(AddPNode::Address)->is_DecodeNarrowPtr()) || (m->in(MemNode::Address)->is_AddP() && m->in(MemNode::Address)->in(AddPNode::Address)->is_AddP() && m->in(MemNode::Address)->in(AddPNode::Address)->in(AddPNode::Address)->is_DecodeNarrowPtr()))) { - mach_at = m->adr_type(); + mach_at = m->in_adr_type(); } - if (m->adr_type() != mach_at) { + if (m->in_adr_type() != mach_at) { m->dump(); tty->print_cr("mach:"); mach->dump(1); } - assert(m->adr_type() == mach_at, "matcher should not change adr type"); + assert(m->in_adr_type() == mach_at, "matcher should not change adr type"); } #endif } @@ -1851,6 +1846,27 @@ void Matcher::handle_precedence_edges(Node* n, MachNode *mach) { } } +void Matcher::combine_adr_type(const Node* n, MachNode* mach) const { + if (mach->_out_adr_type == TYPE_PTR_SENTINAL) { + assert(mach->_in_adr_type == TYPE_PTR_SENTINAL, "must initialize at the same time"); + mach->_out_adr_type = nullptr; + mach->_in_adr_type = nullptr; + } + + // Merge the adr_type of all nodes in the subtree into the adr_type of 'mach' + if (const TypePtr* leaf_out_adr_type = n->out_adr_type(); leaf_out_adr_type != nullptr) { + // Only support a single out_adr_type, there must not be multiple memory-producing nodes + assert(mach->_out_adr_type == nullptr, "must only have a single memory-producing node"); + mach->_out_adr_type = leaf_out_adr_type; + } + if (const TypePtr* leaf_in_adr_type = n->in_adr_type(); leaf_in_adr_type != nullptr) { + // Only support a single in_adr_type, even if there are multiple nodes that consume memory, + // they must consume the same memory + assert(mach->_in_adr_type == nullptr || mach->_in_adr_type == leaf_in_adr_type, "inconsistent in_adr_type in a subtree"); + mach->_in_adr_type = leaf_in_adr_type; + } +} + void Matcher::ReduceInst_Chain_Rule(State* s, int rule, Node* &mem, MachNode* mach) { // 'op' is what I am expecting to receive int op = _leftOp[rule]; @@ -1885,6 +1901,7 @@ void Matcher::ReduceInst_Chain_Rule(State* s, int rule, Node* &mem, MachNode* ma uint Matcher::ReduceInst_Interior( State *s, int rule, Node *&mem, MachNode *mach, uint num_opnds ) { handle_precedence_edges(s->_leaf, mach); + combine_adr_type(s->_leaf, mach); if( s->_leaf->is_Load() ) { Node *mem2 = s->_leaf->in(MemNode::Memory); @@ -1970,6 +1987,7 @@ void Matcher::ReduceOper( State *s, int rule, Node *&mem, MachNode *mach ) { } handle_precedence_edges(s->_leaf, mach); + combine_adr_type(s->_leaf, mach); if( s->_leaf->in(0) && s->_leaf->req() > 1) { if( !mach->in(0) ) diff --git a/src/hotspot/share/opto/matcher.hpp b/src/hotspot/share/opto/matcher.hpp index 721b977c48f0..c5f656b738f6 100644 --- a/src/hotspot/share/opto/matcher.hpp +++ b/src/hotspot/share/opto/matcher.hpp @@ -165,6 +165,7 @@ class Matcher : public PhaseTransform { MachNode* _mach_null; void handle_precedence_edges(Node* n, MachNode *mach); + void combine_adr_type(const Node* n, MachNode* mach) const; public: int LabelRootDepth; diff --git a/src/hotspot/share/opto/memnode.cpp b/src/hotspot/share/opto/memnode.cpp index a522ea77064e..2659eff3fa92 100644 --- a/src/hotspot/share/opto/memnode.cpp +++ b/src/hotspot/share/opto/memnode.cpp @@ -58,6 +58,7 @@ #include "opto/regmask.hpp" #include "opto/rootnode.hpp" #include "opto/traceMergeStoresTag.hpp" +#include "opto/type.hpp" #include "opto/valuetypenode.hpp" #include "opto/vectornode.hpp" #include "runtime/arguments.hpp" @@ -77,7 +78,7 @@ static Node *step_through_mergemem(PhaseGVN *phase, MergeMemNode *mmem, const T //============================================================================= uint MemNode::size_of() const { return sizeof(*this); } -const TypePtr *MemNode::adr_type() const { +const TypePtr* MemNode::in_adr_type_impl() const { Node* adr = in(Address); if (adr == nullptr) return nullptr; // node is dead const TypePtr* cross_check = nullptr; @@ -363,7 +364,7 @@ Node *MemNode::optimize_memory_chain(Node *mchain, const TypePtr *t_adr, Node *l if (is_instance && igvn != nullptr && result->is_Phi()) { PhiNode* phi = result->as_Phi(); assert(phi->bottom_type() == Type::MEMORY, "memory phi required"); - const TypePtr* phi_at = phi->adr_type(); + const TypePtr* phi_at = phi->out_adr_type(); assert(t_oop->higher_equal(phi_at), "a memory Phi must have the a wider adr_type than an access"); const TypePtr* t_oop_widened = phase->C->get_adr_type(phase->C->get_alias_index(t_oop)); if (t_oop_widened != phi_at) { @@ -501,7 +502,7 @@ Node *MemNode::Ideal_common(PhaseGVN *phase, bool can_reshape) { if (can_reshape && igvn != nullptr && (igvn->_worklist.member(address) || - (igvn->_worklist.size() > 0 && t_adr != adr_type())) ) { + (igvn->_worklist.size() > 0 && t_adr != in_adr_type())) ) { // The address's base and type may change when the address is processed. // Delay this mem node transformation until the address is processed. igvn->_worklist.push(this); @@ -537,7 +538,7 @@ Node *MemNode::Ideal_common(PhaseGVN *phase, bool can_reshape) { MergeMemNode* mmem = mem->as_MergeMem(); const TypePtr *tp = t_adr->is_ptr(); - mem = step_through_mergemem(phase, mmem, tp, adr_type(), tty); + mem = step_through_mergemem(phase, mmem, tp, in_adr_type(), tty); } if (mem != old_mem) { @@ -899,7 +900,7 @@ AccessAnalyzer::AccessAnalyzer(PhaseGVN* phase, MemNode* n) _base = AddPNode::Ideal_base_and_offset(adr, _phase, _offset); _maybe_raw = MemNode::check_if_adr_maybe_raw(adr); _alloc = AllocateNode::Ideal_allocation(_base); - _adr_type = _n->adr_type(); + _adr_type = _n->in_adr_type(); if (_adr_type != nullptr && _adr_type->base() != TypePtr::AnyPtr) { // Avoid the cases that will upset Compile::get_alias_index @@ -1090,7 +1091,7 @@ void LoadNode::dump_spec(outputStream *st) const { st->print("unknown control"); } else if (control_dependency() == Pinned) { st->print("pinned"); - } else if (adr_type() == TypeRawPtr::BOTTOM) { + } else if (in_adr_type() == TypeRawPtr::BOTTOM) { st->print("raw access"); } else { st->print("unknown reason"); @@ -2210,7 +2211,7 @@ Node* LoadNode::Ideal_load_common(PhaseGVN* phase, bool can_reshape) { // Is there a dominating load that loads the same value? Leave // anything that is not a load of a field/array element (like // barriers etc.) alone - if (in(0) != nullptr && !adr_type()->isa_rawptr() && can_reshape) { + if (in(0) != nullptr && !in_adr_type()->isa_rawptr() && can_reshape) { for (DUIterator_Fast imax, i = mem->fast_outs(imax); i < imax; i++) { Node* use = mem->fast_out(i); if (use != this && @@ -2974,7 +2975,7 @@ LoadNode* LoadNode::clone_pinned() const { // those case, since there is not a dependency between the node and its control input, we do not // need to pin it. LoadNode* LoadNode::pin_node_under_control_impl() const { - const TypePtr* adr_type = this->adr_type(); + const TypePtr* adr_type = this->in_adr_type(); if (adr_type != nullptr && adr_type->isa_aryptr()) { // Only array accesses have dependencies on their control input return clone_pinned(); @@ -3754,7 +3755,7 @@ StoreNode* MergePrimitiveStores::make_merged_store(const Node_List& merge_list, Node* first_mem = first_store->in(MemNode::Memory); Node* first_adr = first_store->in(MemNode::Address); - const TypePtr* new_adr_type = _store->adr_type(); + const TypePtr* new_adr_type = _store->out_adr_type(); int new_memory_size = _store->memory_size() * merge_list.size(); BasicType bt = T_ILLEGAL; @@ -3805,7 +3806,7 @@ Node *StoreNode::Ideal(PhaseGVN *phase, bool can_reshape) { // Back-to-back stores to same address? Fold em up. Generally // unsafe if I have intervening uses... if ((!this->is_StoreVector() || this->Opcode() == Op_StoreVector) && - phase->C->get_adr_type(phase->C->get_alias_index(adr_type())) != TypeAryPtr::INLINES) { + phase->C->get_adr_type(phase->C->get_alias_index(out_adr_type())) != TypeAryPtr::INLINES) { Node* st = mem; // If Store 'st' has more than one use, we cannot fold 'st' away. // For example, 'st' might be the final state at a conditional @@ -3821,7 +3822,7 @@ Node *StoreNode::Ideal(PhaseGVN *phase, bool can_reshape) { assert(Opcode() == st->Opcode() || st->Opcode() == Op_StoreVector || Opcode() == Op_StoreVector || - phase->C->get_alias_index(adr_type()) == Compile::AliasIdxRaw || + phase->C->get_alias_index(out_adr_type()) == Compile::AliasIdxRaw || (Opcode() == Op_StoreL && st->Opcode() == Op_StoreI) || // expanded ClearArrayNode (Opcode() == Op_StoreI && st->Opcode() == Op_StoreL) || // initialization by arraycopy (Opcode() == Op_StoreL && st->Opcode() == Op_StoreN) || @@ -4354,7 +4355,7 @@ LoadStoreNode::LoadStoreNode( Node *c, Node *mem, Node *adr, Node *val, const Ty init_req(MemNode::Address, adr); init_req(MemNode::ValueIn, val); init_class_id(Class_LoadStore); - DEBUG_ONLY(_adr_type = at; adr_type();) + DEBUG_ONLY(_adr_type = at; out_adr_type();) } //------------------------------Value----------------------------------------- @@ -4378,7 +4379,7 @@ const Type* LoadStoreNode::Value(PhaseGVN* phase) const { return bottom_type(); } -const TypePtr* LoadStoreNode::adr_type() const { +const TypePtr* LoadStoreNode::out_adr_type_impl() const { const TypePtr* cross_check = DEBUG_ONLY(_adr_type) NOT_DEBUG(nullptr); return MemNode::calculate_adr_type(in(MemNode::Address)->bottom_type(), cross_check); } @@ -4471,7 +4472,7 @@ const Type* LoadStoreConditionalNode::Value(PhaseGVN* phase) const { //============================================================================= //-------------------------------adr_type-------------------------------------- -const TypePtr* ClearArrayNode::adr_type() const { +const TypePtr* ClearArrayNode::out_adr_type_impl() const { Node *adr = in(3); if (adr == nullptr) return nullptr; // node is dead return MemNode::calculate_adr_type(adr->bottom_type()); @@ -4677,9 +4678,9 @@ Node* ClearArrayNode::clear_memory(Node* ctl, Node* mem, Node* dest, } //============================================================================= -MemBarNode::MemBarNode(Compile* C, int alias_idx, Node* precedent) +MemBarNode::MemBarNode(Compile* C, const TypePtr* out_adr_type, const TypePtr* in_adr_type, Node* precedent) : MultiNode(TypeFunc::Parms + (precedent == nullptr? 0: 1)), - _adr_type(C->get_adr_type(alias_idx)), _kind(Standalone) + _out_adr_type(out_adr_type), _in_adr_type(in_adr_type), _kind(Standalone) #ifdef ASSERT , _pair_idx(0) #endif @@ -4701,22 +4702,23 @@ bool MemBarNode::cmp( const Node &n ) const { //------------------------------make------------------------------------------- MemBarNode* MemBarNode::make(Compile* C, int opcode, int atp, Node* pn) { + const TypePtr* adr_type = C->get_adr_type(atp); switch (opcode) { - case Op_MemBarAcquire: return new MemBarAcquireNode(C, atp, pn); - case Op_LoadFence: return new LoadFenceNode(C, atp, pn); - case Op_MemBarRelease: return new MemBarReleaseNode(C, atp, pn); - case Op_StoreFence: return new StoreFenceNode(C, atp, pn); - case Op_MemBarStoreStore: return new MemBarStoreStoreNode(C, atp, pn); - case Op_StoreStoreFence: return new StoreStoreFenceNode(C, atp, pn); - case Op_MemBarAcquireLock: return new MemBarAcquireLockNode(C, atp, pn); - case Op_MemBarReleaseLock: return new MemBarReleaseLockNode(C, atp, pn); - case Op_MemBarStoreLoad: return new MemBarStoreLoadNode(C, atp, pn); - case Op_MemBarVolatile: return new MemBarVolatileNode(C, atp, pn); - case Op_MemBarFull: return new MemBarFullNode(C, atp, pn); - case Op_MemBarCPUOrder: return new MemBarCPUOrderNode(C, atp, pn); - case Op_OnSpinWait: return new OnSpinWaitNode(C, atp, pn); - case Op_Initialize: return new InitializeNode(C, atp, pn); - default: ShouldNotReachHere(); return nullptr; + case Op_MemBarAcquire: return new MemBarAcquireNode(C, adr_type, adr_type, pn); + case Op_LoadFence: return new LoadFenceNode(C, adr_type, adr_type, pn); + case Op_MemBarRelease: return new MemBarReleaseNode(C, adr_type, adr_type, pn); + case Op_StoreFence: return new StoreFenceNode(C, adr_type, adr_type, pn); + case Op_MemBarStoreStore: return new MemBarStoreStoreNode(C, adr_type, adr_type, pn); + case Op_StoreStoreFence: return new StoreStoreFenceNode(C, adr_type, adr_type, pn); + case Op_MemBarAcquireLock: return new MemBarAcquireLockNode(C, adr_type, adr_type, pn); + case Op_MemBarReleaseLock: return new MemBarReleaseLockNode(C, adr_type, adr_type, pn); + case Op_MemBarStoreLoad: return new MemBarStoreLoadNode(C, adr_type, adr_type, pn); + case Op_MemBarVolatile: return new MemBarVolatileNode(C, adr_type, adr_type, pn); + case Op_MemBarFull: return new MemBarFullNode(C, adr_type, adr_type, pn); + case Op_MemBarCPUOrder: return new MemBarCPUOrderNode(C, adr_type, adr_type, pn); + case Op_OnSpinWait: return new OnSpinWaitNode(C, adr_type, adr_type, pn); + case Op_Initialize: return new InitializeNode(C, pn); + default: ShouldNotReachHere(); return nullptr; } } @@ -5080,13 +5082,11 @@ MemBarNode* MemBarNode::leading_membar() const { // reasonable limit on the complexity of optimized initializations. //---------------------------InitializeNode------------------------------------ -InitializeNode::InitializeNode(Compile* C, int adr_type, Node* rawoop) - : MemBarNode(C, adr_type, rawoop), +InitializeNode::InitializeNode(Compile* C, Node* rawoop) + : MemBarNode(C, TypeRawPtr::BOTTOM, TypePtr::BOTTOM, rawoop), _is_complete(Incomplete), _does_not_escape(false) { init_class_id(Class_Initialize); - - assert(adr_type == Compile::AliasIdxRaw, "only valid atp"); assert(in(RawAddress) == rawoop, "proper init"); // Note: allocation() can be null, for secondary initialization barriers } @@ -6003,7 +6003,7 @@ void InitializeNode::replace_mem_projs_by(Node* mem, PhaseIterGVN* igvn) { bool InitializeNode::already_has_narrow_mem_proj_with_adr_type(const TypePtr* adr_type) const { auto find_proj = [&](ProjNode* proj) { - if (proj->adr_type() == adr_type) { + if (proj->out_adr_type() == adr_type) { return BREAK_AND_RETURN_CURRENT_PROJ; } return CONTINUE; @@ -6408,7 +6408,7 @@ static void verify_memory_slice(const MergeMemNode* m, int alias_idx, Node* n) { n = n->as_MergeMem()->memory_at(alias_idx); } Compile* C = Compile::current(); - const TypePtr* n_adr_type = n->adr_type(); + const TypePtr* n_adr_type = n->out_adr_type(); if (n == m->empty_memory()) { // Implicit copy of base_memory() } else if (n_adr_type != TypePtr::BOTTOM) { @@ -6449,9 +6449,9 @@ Node* MergeMemNode::memory_at(uint alias_idx) const { n = base_memory(); assert(Node::in_dump() || n == nullptr || n->bottom_type() == Type::TOP - || n->adr_type() == nullptr // address is TOP - || n->adr_type() == TypePtr::BOTTOM - || n->adr_type() == TypeRawPtr::BOTTOM + || n->out_adr_type() == nullptr // address is TOP + || n->out_adr_type() == TypePtr::BOTTOM + || n->out_adr_type() == TypeRawPtr::BOTTOM || n->is_NarrowMemProj() || !Compile::current()->do_aliasing(), "must be a wide memory"); diff --git a/src/hotspot/share/opto/memnode.hpp b/src/hotspot/share/opto/memnode.hpp index 95d36b86a535..033f5d438f2b 100644 --- a/src/hotspot/share/opto/memnode.hpp +++ b/src/hotspot/share/opto/memnode.hpp @@ -74,7 +74,7 @@ class MemNode : public Node { _unsafe_access(false), _barrier_data(0) { init_class_id(Class_Mem); - DEBUG_ONLY(_adr_type=at; adr_type();) + DEBUG_ONLY(_adr_type=at; in_adr_type();) } MemNode( Node *c0, Node *c1, Node *c2, const TypePtr* at, Node *c3 ) : Node(c0,c1,c2,c3), @@ -83,7 +83,7 @@ class MemNode : public Node { _unsafe_access(false), _barrier_data(0) { init_class_id(Class_Mem); - DEBUG_ONLY(_adr_type=at; adr_type();) + DEBUG_ONLY(_adr_type=at; in_adr_type();) } MemNode( Node *c0, Node *c1, Node *c2, const TypePtr* at, Node *c3, Node *c4) : Node(c0,c1,c2,c3,c4), @@ -92,7 +92,7 @@ class MemNode : public Node { _unsafe_access(false), _barrier_data(0) { init_class_id(Class_Mem); - DEBUG_ONLY(_adr_type=at; adr_type();) + DEBUG_ONLY(_adr_type=at; in_adr_type();) } virtual Node* find_previous_arraycopy(PhaseValues* phase, Node* ld_alloc, Node*& mem, bool can_see_stored_value) const { return nullptr; } @@ -115,8 +115,6 @@ class MemNode : public Node { return dom_result == DomResult::Dominate; } - virtual const class TypePtr *adr_type() const; // returns bottom_type of address - // Shared code for Ideal methods: Node *Ideal_common(PhaseGVN *phase, bool can_reshape); // Return -1 for short-circuit null. @@ -189,6 +187,10 @@ class MemNode : public Node { // Support for reinterpret variants used by StoreNode and LoadNode static BasicType get_reinterpret_variant(BasicType bt); bool has_reinterpret_variant(const Type* vt) const; + +private: + const TypePtr* out_adr_type_impl() const final { return is_Load() ? nullptr : in_adr_type_impl(); } + const TypePtr* in_adr_type_impl() const final; }; // Analyze a MemNode to try to prove that it is independent from other memory accesses @@ -383,7 +385,7 @@ class LoadNode : public MemNode { // manner of other optimizations). Basically, it's ugly but so is the alternative. // See comment in macro.cpp, around line 125 expand_allocate_common(). virtual bool depends_only_on_test_impl() const { - return adr_type() != TypeRawPtr::BOTTOM && _control_dependency == DependsOnlyOnTest; + return in_adr_type() != TypeRawPtr::BOTTOM && _control_dependency == DependsOnlyOnTest; } LoadNode* clone_pinned() const; @@ -899,7 +901,6 @@ class LoadStoreNode : public Node { virtual const Type *bottom_type() const { return _type; } virtual uint ideal_reg() const; - virtual const TypePtr* adr_type() const; virtual const Type* Value(PhaseGVN* phase) const; bool result_not_used() const; @@ -914,6 +915,7 @@ class LoadStoreNode : public Node { private: virtual bool depends_only_on_test_impl() const { return false; } + const TypePtr* out_adr_type_impl() const final; }; class LoadStoreConditionalNode : public LoadStoreNode { @@ -1172,7 +1174,6 @@ class ClearArrayNode: public Node { virtual const Type *bottom_type() const { return Type::MEMORY; } // ClearArray modifies array elements, and so affects only the // array memory addressed by the bottom_type of its base address. - virtual const class TypePtr *adr_type() const; virtual Node* Identity(PhaseGVN* phase); virtual Node *Ideal(PhaseGVN *phase, bool can_reshape); virtual uint match_edge(uint idx) const; @@ -1215,6 +1216,7 @@ class ClearArrayNode: public Node { private: virtual bool depends_only_on_test_impl() const { return false; } + const TypePtr* out_adr_type_impl() const final; }; //------------------------------MemBar----------------------------------------- @@ -1232,7 +1234,8 @@ class MemBarNode: public MultiNode { virtual uint size_of() const { return sizeof(*this); } // Memory type this node is serializing. Usually either rawptr or bottom. - const TypePtr* _adr_type; + const TypePtr* _out_adr_type; + const TypePtr* _in_adr_type; // How is this membar related to a nearby memory access? enum { @@ -1253,9 +1256,8 @@ class MemBarNode: public MultiNode { enum { Precedent = TypeFunc::Parms // optional edge to force precedence }; - MemBarNode(Compile* C, int alias_idx, Node* precedent); + MemBarNode(Compile* C, const TypePtr* out_adr_type, const TypePtr* in_adr_type, Node* precedent); virtual int Opcode() const = 0; - virtual const class TypePtr *adr_type() const { return _adr_type; } virtual const Type* Value(PhaseGVN* phase) const; virtual Node *Ideal(PhaseGVN *phase, bool can_reshape); virtual uint match_edge(uint idx) const { return 0; } @@ -1290,6 +1292,10 @@ class MemBarNode: public MultiNode { #ifndef PRODUCT virtual void dump_spec(outputStream *st) const; #endif + +private: + const TypePtr* out_adr_type_impl() const final { return _out_adr_type; } + const TypePtr* in_adr_type_impl() const final { return _in_adr_type; } }; // "Acquire" - no following ref can move before (but earlier refs can @@ -1297,8 +1303,8 @@ class MemBarNode: public MultiNode { // visibility. Inserted after a volatile load. class MemBarAcquireNode: public MemBarNode { public: - MemBarAcquireNode(Compile* C, int alias_idx, Node* precedent) - : MemBarNode(C, alias_idx, precedent) {} + MemBarAcquireNode(Compile* C, const TypePtr* out_adr_type, const TypePtr* in_adr_type, Node* precedent) + : MemBarNode(C, out_adr_type, in_adr_type, precedent) {} virtual int Opcode() const; }; @@ -1308,8 +1314,8 @@ class MemBarAcquireNode: public MemBarNode { // for intrinsic Unsafe.loadFence(). class LoadFenceNode: public MemBarNode { public: - LoadFenceNode(Compile* C, int alias_idx, Node* precedent) - : MemBarNode(C, alias_idx, precedent) {} + LoadFenceNode(Compile* C, const TypePtr* out_adr_type, const TypePtr* in_adr_type, Node* precedent) + : MemBarNode(C, out_adr_type, in_adr_type, precedent) {} virtual int Opcode() const; }; @@ -1318,8 +1324,8 @@ class LoadFenceNode: public MemBarNode { // multi-cpu visibility. Inserted before a volatile store. class MemBarReleaseNode: public MemBarNode { public: - MemBarReleaseNode(Compile* C, int alias_idx, Node* precedent) - : MemBarNode(C, alias_idx, precedent) {} + MemBarReleaseNode(Compile* C, const TypePtr* out_adr_type, const TypePtr* in_adr_type, Node* precedent) + : MemBarNode(C, out_adr_type, in_adr_type, precedent) {} virtual int Opcode() const; }; @@ -1329,8 +1335,8 @@ class MemBarReleaseNode: public MemBarNode { // for intrinsic Unsafe.storeFence(). class StoreFenceNode: public MemBarNode { public: - StoreFenceNode(Compile* C, int alias_idx, Node* precedent) - : MemBarNode(C, alias_idx, precedent) {} + StoreFenceNode(Compile* C, const TypePtr* out_adr_type, const TypePtr* in_adr_type, Node* precedent) + : MemBarNode(C, out_adr_type, in_adr_type, precedent) {} virtual int Opcode() const; }; @@ -1339,8 +1345,8 @@ class StoreFenceNode: public MemBarNode { // visibility. Inserted after a Lock. class MemBarAcquireLockNode: public MemBarNode { public: - MemBarAcquireLockNode(Compile* C, int alias_idx, Node* precedent) - : MemBarNode(C, alias_idx, precedent) {} + MemBarAcquireLockNode(Compile* C, const TypePtr* out_adr_type, const TypePtr* in_adr_type, Node* precedent) + : MemBarNode(C, out_adr_type, in_adr_type, precedent) {} virtual int Opcode() const; }; @@ -1349,15 +1355,15 @@ class MemBarAcquireLockNode: public MemBarNode { // multi-cpu visibility. Inserted before a FastUnLock. class MemBarReleaseLockNode: public MemBarNode { public: - MemBarReleaseLockNode(Compile* C, int alias_idx, Node* precedent) - : MemBarNode(C, alias_idx, precedent) {} + MemBarReleaseLockNode(Compile* C, const TypePtr* out_adr_type, const TypePtr* in_adr_type, Node* precedent) + : MemBarNode(C, out_adr_type, in_adr_type, precedent) {} virtual int Opcode() const; }; class MemBarStoreStoreNode: public MemBarNode { public: - MemBarStoreStoreNode(Compile* C, int alias_idx, Node* precedent) - : MemBarNode(C, alias_idx, precedent) { + MemBarStoreStoreNode(Compile* C, const TypePtr* out_adr_type, const TypePtr* in_adr_type, Node* precedent) + : MemBarNode(C, out_adr_type, in_adr_type, precedent) { init_class_id(Class_MemBarStoreStore); } virtual int Opcode() const; @@ -1365,15 +1371,15 @@ class MemBarStoreStoreNode: public MemBarNode { class StoreStoreFenceNode: public MemBarNode { public: - StoreStoreFenceNode(Compile* C, int alias_idx, Node* precedent) - : MemBarNode(C, alias_idx, precedent) {} + StoreStoreFenceNode(Compile* C, const TypePtr* out_adr_type, const TypePtr* in_adr_type, Node* precedent) + : MemBarNode(C, out_adr_type, in_adr_type, precedent) {} virtual int Opcode() const; }; class MemBarStoreLoadNode : public MemBarNode { public: - MemBarStoreLoadNode(Compile* C, int alias_idx, Node* precedent) - : MemBarNode(C, alias_idx, precedent) {} + MemBarStoreLoadNode(Compile* C, const TypePtr* out_adr_type, const TypePtr* in_adr_type, Node* precedent) + : MemBarNode(C, out_adr_type, in_adr_type, precedent) {} virtual int Opcode() const; }; @@ -1381,16 +1387,16 @@ class MemBarStoreLoadNode : public MemBarNode { // Requires multi-CPU visibility? class MemBarVolatileNode: public MemBarNode { public: - MemBarVolatileNode(Compile* C, int alias_idx, Node* precedent) - : MemBarNode(C, alias_idx, precedent) {} + MemBarVolatileNode(Compile* C, const TypePtr* out_adr_type, const TypePtr* in_adr_type, Node* precedent) + : MemBarNode(C, out_adr_type, in_adr_type, precedent) {} virtual int Opcode() const; }; // A full barrier blocks all loads and stores from moving across it class MemBarFullNode : public MemBarNode { public: - MemBarFullNode(Compile* C, int alias_idx, Node* precedent) - : MemBarNode(C, alias_idx, precedent) {} + MemBarFullNode(Compile* C, const TypePtr* out_adr_type, const TypePtr* in_adr_type, Node* precedent) + : MemBarNode(C, out_adr_type, in_adr_type, precedent) {} virtual int Opcode() const; }; @@ -1399,16 +1405,16 @@ class MemBarFullNode : public MemBarNode { // compiler because the CPU does all the ordering for us. class MemBarCPUOrderNode: public MemBarNode { public: - MemBarCPUOrderNode(Compile* C, int alias_idx, Node* precedent) - : MemBarNode(C, alias_idx, precedent) {} + MemBarCPUOrderNode(Compile* C, const TypePtr* out_adr_type, const TypePtr* in_adr_type, Node* precedent) + : MemBarNode(C, out_adr_type, in_adr_type, precedent) {} virtual int Opcode() const; virtual uint ideal_reg() const { return 0; } // not matched in the AD file }; class OnSpinWaitNode: public MemBarNode { public: - OnSpinWaitNode(Compile* C, int alias_idx, Node* precedent) - : MemBarNode(C, alias_idx, precedent) {} + OnSpinWaitNode(Compile* C, const TypePtr* out_adr_type, const TypePtr* in_adr_type, Node* precedent) + : MemBarNode(C, out_adr_type, in_adr_type, precedent) {} virtual int Opcode() const; }; @@ -1434,7 +1440,7 @@ class InitializeNode: public MemBarNode { RawStores = TypeFunc::Parms+1 // zero or more stores (or TOP) }; - InitializeNode(Compile* C, int adr_type, Node* rawoop); + InitializeNode(Compile* C, Node* rawoop); virtual int Opcode() const; virtual uint size_of() const { return sizeof(*this); } virtual uint ideal_reg() const { return 0; } // not matched in the AD file @@ -1571,7 +1577,6 @@ class MergeMemNode: public Node { virtual uint match_edge(uint idx) const { return 0; } virtual const RegMask &out_RegMask() const; virtual const Type *bottom_type() const { return Type::MEMORY; } - virtual const TypePtr *adr_type() const { return TypePtr::BOTTOM; } // sparse accessors // Fetch the previously stored "set_memory_at", or else the base memory. // (Caller should clone it if it is a phi-nest.) @@ -1594,6 +1599,9 @@ class MergeMemNode: public Node { #ifndef PRODUCT virtual void dump_spec(outputStream *st) const; #endif + +private: + const TypePtr* out_adr_type_impl() const final { return TypePtr::BOTTOM; } }; class MergeMemStream : public StackObj { @@ -1794,11 +1802,11 @@ class CacheWBNode : public Node { virtual int Opcode() const; virtual uint ideal_reg() const { return NotAMachineReg; } virtual uint match_edge(uint idx) const { return (idx == 2); } - virtual const TypePtr *adr_type() const { return TypePtr::BOTTOM; } virtual const Type *bottom_type() const { return Type::MEMORY; } private: virtual bool depends_only_on_test_impl() const { return false; } + const TypePtr* out_adr_type_impl() const final { return TypeRawPtr::BOTTOM; } }; // cachewb pre sync node for ensuring that writebacks are serialised @@ -1809,11 +1817,11 @@ class CacheWBPreSyncNode : public Node { virtual int Opcode() const; virtual uint ideal_reg() const { return NotAMachineReg; } virtual uint match_edge(uint idx) const { return false; } - virtual const TypePtr *adr_type() const { return TypePtr::BOTTOM; } virtual const Type *bottom_type() const { return Type::MEMORY; } private: virtual bool depends_only_on_test_impl() const { return false; } + const TypePtr* out_adr_type_impl() const final { return TypeRawPtr::BOTTOM; } }; // cachewb pre sync node for ensuring that writebacks are serialised @@ -1824,11 +1832,11 @@ class CacheWBPostSyncNode : public Node { virtual int Opcode() const; virtual uint ideal_reg() const { return NotAMachineReg; } virtual uint match_edge(uint idx) const { return false; } - virtual const TypePtr *adr_type() const { return TypePtr::BOTTOM; } virtual const Type *bottom_type() const { return Type::MEMORY; } private: virtual bool depends_only_on_test_impl() const { return false; } + const TypePtr* out_adr_type_impl() const final { return TypeRawPtr::BOTTOM; } }; //------------------------------Prefetch--------------------------------------- @@ -1844,6 +1852,7 @@ class PrefetchAllocationNode : public Node { private: virtual bool depends_only_on_test_impl() const { return false; } + const TypePtr* out_adr_type_impl() const final { return bottom_type() == Type::MEMORY ? TypeRawPtr::BOTTOM : nullptr; } }; #endif // SHARE_OPTO_MEMNODE_HPP diff --git a/src/hotspot/share/opto/mempointer.cpp b/src/hotspot/share/opto/mempointer.cpp index 68abaffe6429..2a6aa0afd991 100644 --- a/src/hotspot/share/opto/mempointer.cpp +++ b/src/hotspot/share/opto/mempointer.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -344,7 +344,7 @@ bool MemPointerParser::is_native_memory_base_candidate(Node* n) { if (n->Opcode() != Op_LoadL) { return false; } LoadNode* load = n->as_Load(); - const TypeInstPtr* inst_ptr = load->adr_type()->isa_instptr(); + const TypeInstPtr* inst_ptr = load->in_adr_type()->isa_instptr(); if (inst_ptr == nullptr) { return false; } ciInstanceKlass* klass = inst_ptr->instance_klass(); @@ -396,7 +396,7 @@ bool MemPointerParser::is_safe_to_decompose_op(const int opc, const NoOverflowIn return false; } - const TypeAryPtr* ary_ptr_t = _mem->adr_type()->isa_aryptr(); + const TypeAryPtr* ary_ptr_t = _mem->in_adr_type()->isa_aryptr(); if (ary_ptr_t != nullptr) { // Array accesses that are not Unsafe always have a RangeCheck which ensures // that there is no int overflow. And without overflows, all decompositions diff --git a/src/hotspot/share/opto/multnode.cpp b/src/hotspot/share/opto/multnode.cpp index 6f763563f690..29bb6cd8b793 100644 --- a/src/hotspot/share/opto/multnode.cpp +++ b/src/hotspot/share/opto/multnode.cpp @@ -151,27 +151,6 @@ const Type *ProjNode::bottom_type() const { return proj_type(in(0)->bottom_type()); } -const TypePtr* ProjNode::adr_type() const { - if (bottom_type() != Type::MEMORY) { - assert(bottom_type()->base() != Type::Memory, "no other memories?"); - return nullptr; - } - - Node* ctrl = in(0); - // node is dead or we are in the process of removing a dead subgraph - if (ctrl == nullptr || ctrl->is_top()) { - return nullptr; - } - - const TypePtr* adr_type = ctrl->adr_type(); -#ifdef ASSERT - if (!VMError::is_error_reported() && !Node::in_dump()) { - assert(adr_type != nullptr, "source must have adr_type"); - } -#endif // ASSERT - return adr_type; -} - bool ProjNode::pinned() const { return in(0)->pinned(); } #ifndef PRODUCT void ProjNode::dump_spec(outputStream *st) const { st->print("#%d",_con); if(_is_io_use) st->print(" (i_o_use)");} @@ -301,6 +280,27 @@ CallStaticJavaNode* ProjNode::is_uncommon_trap_if_pattern(Deoptimization::DeoptR return as_IfProj()->other_if_proj()->is_uncommon_trap_proj(reason); } +const TypePtr* ProjNode::out_adr_type_impl() const { + if (bottom_type() != Type::MEMORY) { + assert(bottom_type()->base() != Type::Memory, "no other memories?"); + return nullptr; + } + + Node* ctrl = in(0); + // node is dead or we are in the process of removing a dead subgraph + if (ctrl == nullptr || ctrl->is_top()) { + return nullptr; + } + + const TypePtr* adr_type = ctrl->out_adr_type(); +#ifdef ASSERT + if (!VMError::is_error_reported() && !Node::in_dump()) { + assert(adr_type != nullptr, "source must have adr_type"); + } +#endif // ASSERT + return adr_type; +} + NarrowMemProjNode::NarrowMemProjNode(InitializeNode* src, const TypePtr* adr_type) : ProjNode(src, TypeFunc::Memory), _adr_type(adr_type) { assert(Compile::current()->have_alias_type(adr_type), "alias index should have been allocated already"); diff --git a/src/hotspot/share/opto/multnode.hpp b/src/hotspot/share/opto/multnode.hpp index 8cc6cc1e0dfa..5af3d65df54d 100644 --- a/src/hotspot/share/opto/multnode.hpp +++ b/src/hotspot/share/opto/multnode.hpp @@ -205,7 +205,6 @@ class ProjNode : public Node { virtual int Opcode() const; virtual bool is_CFG() const; virtual const Type *bottom_type() const; - virtual const TypePtr *adr_type() const; virtual bool pinned() const; virtual Node* Identity(PhaseGVN* phase); virtual const Type* Value(PhaseGVN* phase) const; @@ -226,6 +225,9 @@ class ProjNode : public Node { // other_proj->[region->..]call_uct" // null otherwise CallStaticJavaNode* is_uncommon_trap_if_pattern(Deoptimization::DeoptReason reason = Deoptimization::Reason_none) const; + +private: + virtual const TypePtr* out_adr_type_impl() const; }; // A ProjNode variant that captures an adr_type(). Used as a projection of InitializeNode to have the right adr_type() @@ -246,11 +248,12 @@ class NarrowMemProjNode : public ProjNode { public: NarrowMemProjNode(InitializeNode* src, const TypePtr* adr_type); - virtual const TypePtr* adr_type() const { + virtual int Opcode() const; + +private: + const TypePtr* out_adr_type_impl() const final { return _adr_type; } - - virtual int Opcode() const; }; template ProjNode* MultiNode::apply_to_projs(DUIterator_Fast& imax, DUIterator_Fast& i, Callback callback, uint which_proj) const { @@ -284,7 +287,7 @@ class TupleNode : public MultiNode { const TypeTuple* _tf; const TypePtr* _adr_type; - TupleNode(const TypeTuple* tf, const TypePtr* adr_type) : MultiNode(tf->cnt()), _tf(tf), _adr_type(adr_type) {} + TupleNode(const TypeTuple* tf, const TypePtr* out_adr_type) : MultiNode(tf->cnt()), _tf(tf), _adr_type(out_adr_type) {} template static void make_helper(TupleNode* tn, uint i, Node* node, NN... nn) { @@ -300,16 +303,18 @@ class TupleNode : public MultiNode { * TupleNode::make(tf, adr_type, input1, input2, input3, input4) */ template - static TupleNode* make(const TypeTuple* tf, const TypePtr* adr_type, NN... nn) { - TupleNode* tn = new TupleNode(tf, adr_type); + static TupleNode* make(const TypeTuple* tf, const TypePtr* out_adr_type, NN... nn) { + TupleNode* tn = new TupleNode(tf, out_adr_type); make_helper(tn, 0, nn...); return tn; } int Opcode() const override; const Type* bottom_type() const override { return _tf; } - const TypePtr* adr_type() const override { return _adr_type; } uint size_of() const override { return sizeof(TupleNode); } + +private: + const TypePtr* out_adr_type_impl() const final { return _adr_type; } }; #endif // SHARE_OPTO_MULTNODE_HPP diff --git a/src/hotspot/share/opto/node.cpp b/src/hotspot/share/opto/node.cpp index 163b9ee89783..0619fadd23d1 100644 --- a/src/hotspot/share/opto/node.cpp +++ b/src/hotspot/share/opto/node.cpp @@ -45,8 +45,10 @@ #include "opto/valuetypenode.hpp" #include "utilities/copy.hpp" #include "utilities/macros.hpp" +#include "utilities/ostream.hpp" #include "utilities/powerOfTwo.hpp" #include "utilities/stringUtils.hpp" +#include "utilities/vmError.hpp" class RegMask; // #include "phase.hpp" @@ -1146,6 +1148,80 @@ void Node::raise_bottom_type(const Type* new_type) { } } +const TypePtr* Node::out_adr_type() const { + const TypePtr* res = out_adr_type_impl(); + +#ifdef ASSERT + if (Node::in_dump() || VMError::is_error_reported()) { + return res; + } + + // Verify that in_adr_type contains out_adr_type, except for Start, obviously it consumes nothing + if (is_Start()) { + return res; + } + + // Load must have no out_adr_type + if (is_Load() && res != nullptr) { + stringStream ss; + res->dump_on(&ss); + assert(false, "Node %s must have no out_adr_type, out: %s", Name(), ss.as_string()); + } + + const TypePtr* in_type = in_adr_type_impl(); + // Store, LoadStore, and Phi must have the same out_adr_type and in_adr_type + if ((is_Store() || is_LoadStore() || is_Phi()) && res != in_type) { + stringStream ss; + ss.print(", out: "); + if (res == nullptr) { + ss.print("nullptr"); + } else { + res->dump_on(&ss); + } + ss.print(", in: "); + if (in_type == nullptr) { + ss.print("nullptr"); + } else { + in_type->dump_on(&ss); + } + assert(false, "Node %s must have the same out_adr_type and in_adr_type%s", Name(), ss.as_string()); + } + + // For some reasons, Raw can be used as Bot (see GraphKit::set_output_for_allocation for + // example), so be lenient here + if (res != nullptr && in_type != TypePtr::BOTTOM && in_type != TypeRawPtr::BOTTOM && res != in_type) { + stringStream ss; + ss.print(", out: "); + res->dump_on(&ss); + ss.print(", in: "); + if (in_type == nullptr) { + ss.print("nullptr"); + } else { + in_type->dump_on(&ss); + } + assert(false, "Node %s: in_adr_type must contain out_adr_type%s", Name(), ss.as_string()); + } + + // Unless this node is pinned, we must either have no out_adr_type or have the same out_adr_type + // and in_adr_type. This is because we DO NOT know how to compute the anti-dependence of any + // other node. The exceptions are the intrinsic nodes which are constructed specially to + // materialize anti-dependencies as def-use dependencies. + bool has_special_anti_dependence_construction = Opcode() == Op_StrCompressedCopy || + Opcode() == Op_StrInflatedCopy || + Opcode() == Op_EncodeISOArray; + if (!is_Mach() && !is_CFG() && !pinned() && res != nullptr && res != in_type && !has_special_anti_dependence_construction) { + stringStream ss; + ss.print(", out: "); + res->dump_on(&ss); + ss.print(", in: "); + in_type->dump_on(&ss); + assert(false, "Node %s: cannot compute anti-dependencies%s", Name(), ss.as_string()); + } +#endif // ASSERT + + return res; +} + //------------------------------Identity--------------------------------------- // Return a node that the given node is equivalent to. Node* Node::Identity(PhaseGVN* phase) { @@ -2634,7 +2710,7 @@ void Node::dump(const char* suffix, bool mark, outputStream* st, DumpConfig* dc) t->dump_on(st); } else if (t == Type::MEMORY) { st->print(" Memory:"); - MemNode::dump_adr_type(adr_type(), st); + MemNode::dump_adr_type(out_adr_type(), st); } else if (Verbose || WizardMode) { st->print(" Type:"); if (t) { diff --git a/src/hotspot/share/opto/node.hpp b/src/hotspot/share/opto/node.hpp index 18efd08c82bc..cbf817aac4fb 100644 --- a/src/hotspot/share/opto/node.hpp +++ b/src/hotspot/share/opto/node.hpp @@ -30,6 +30,7 @@ #include "opto/compile.hpp" #include "opto/type.hpp" #include "utilities/copy.hpp" +#include "utilities/debug.hpp" // Portions of code courtesy of Clifford Click @@ -1268,12 +1269,28 @@ class Node { // Be sure to do the hash_delete game in the "rehash" variant. void raise_bottom_type(const Type* new_type); - // Get the address type with which this node uses and/or defs memory, - // or null if none. The address type is conservatively wide. - // Returns non-null for calls, membars, loads, stores, etc. - // Returns TypePtr::BOTTOM if the node touches memory "broadly". - virtual const class TypePtr *adr_type() const { return nullptr; } + // Get the memory which this node produces. + // Generally, if you are unsure whether you need out_adr_type or in_adr_type: + // - A Store, a LoadStore, or a Phi must have the same out_adr_type and in_adr_type, this is + // essential in its semantics, so sometimes it is unclear whether out_adr_type or in_adr_type + // should be invoked. For those nodes, calling either is fine correctness-wise. + // - A Load has no out_adr_type, combined with the fact that a Store or a LoadStore has the same + // out_adr_type and in_adr_type, it is always correct to use in_adr_type for a Mem or a + // LoadStore (these nodes answer true for is_Mem()). + // - For other nodes, it is almost always correct to use out_adr_type, except when we are + // computing a node's anti-dependence, or when we are trying to modify the memory input of a + // node. + const TypePtr* out_adr_type() const; + + // Get the memory which this node consumes, it must include the out_adr_type, since if the node + // produces some memory, it also kills that memory, which is a form of consuming + const TypePtr* in_adr_type() const { return in_adr_type_impl(); } +private: + virtual const TypePtr* out_adr_type_impl() const { return nullptr; } + virtual const TypePtr* in_adr_type_impl() const { return out_adr_type_impl(); } + +public: // Return an existing node which computes the same function as this node. // The optimistic combined algorithm requires this to return a Node which // is a small number of steps away (e.g., one of my inputs). diff --git a/src/hotspot/share/opto/stringopts.cpp b/src/hotspot/share/opto/stringopts.cpp index 16b34e149cf9..ceea0136b52d 100644 --- a/src/hotspot/share/opto/stringopts.cpp +++ b/src/hotspot/share/opto/stringopts.cpp @@ -216,7 +216,7 @@ class StringConcat : public ResourceObj { const TypePtr* no_memory_effects = nullptr; Compile* C = _stringopts->C; CallStaticJavaNode* call = new CallStaticJavaNode(call_type, call_addr, "uncommon_trap", - no_memory_effects); + no_memory_effects, TypePtr::BOTTOM); for (int e = 0; e < TypeFunc::Parms; e++) { call->init_req(e, uct->in(e)); } diff --git a/src/hotspot/share/opto/subnode.hpp b/src/hotspot/share/opto/subnode.hpp index f3f1f9a265d1..862b34eebdbb 100644 --- a/src/hotspot/share/opto/subnode.hpp +++ b/src/hotspot/share/opto/subnode.hpp @@ -329,6 +329,9 @@ class FlatArrayCheckNode : public CmpNode { virtual const Type* sub(const Type*, const Type*) const { ShouldNotReachHere(); return nullptr; } const Type* Value(PhaseGVN* phase) const; virtual Node* Ideal(PhaseGVN* phase, bool can_reshape); + +private: + const TypePtr* in_adr_type_impl() const final { return TypeRawPtr::BOTTOM; } }; //------------------------------BoolTest--------------------------------------- diff --git a/src/hotspot/share/opto/superword.cpp b/src/hotspot/share/opto/superword.cpp index 62fa32c7045b..3c03b492b0ff 100644 --- a/src/hotspot/share/opto/superword.cpp +++ b/src/hotspot/share/opto/superword.cpp @@ -2684,8 +2684,8 @@ const Type* VLoopTypes::container_type(Node* n) const { } bool VLoopMemorySlices::same_memory_slice(MemNode* m1, MemNode* m2) const { - return _vloop.phase()->C->get_alias_index(m1->adr_type()) == - _vloop.phase()->C->get_alias_index(m2->adr_type()); + return _vloop.phase()->C->get_alias_index(m1->in_adr_type()) == + _vloop.phase()->C->get_alias_index(m2->in_adr_type()); } LoadNode::ControlDependency SuperWordVTransformBuilder::load_control_dependency(const Node_List* pack) const { diff --git a/src/hotspot/share/opto/superwordVTransformBuilder.cpp b/src/hotspot/share/opto/superwordVTransformBuilder.cpp index f8b8bfe2ed04..b42397177c34 100644 --- a/src/hotspot/share/opto/superwordVTransformBuilder.cpp +++ b/src/hotspot/share/opto/superwordVTransformBuilder.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -189,11 +189,11 @@ VTransformVectorNode* SuperWordVTransformBuilder::make_vector_vtnode_for_pack(co const VPointer& scalar_p = _vloop_analyzer.vpointers().vpointer(p0->as_Load()); const VPointer vector_p(scalar_p.make_with_size(scalar_p.size() * vlen)); const LoadNode::ControlDependency control_dependency = load_control_dependency(pack); - vtn = new (_vtransform.arena()) VTransformLoadVectorNode(_vtransform, properties, vector_p, p0->adr_type(), control_dependency); + vtn = new (_vtransform.arena()) VTransformLoadVectorNode(_vtransform, properties, vector_p, p0->in_adr_type(), control_dependency); } else if (p0->is_Store()) { const VPointer& scalar_p = _vloop_analyzer.vpointers().vpointer(p0->as_Store()); const VPointer vector_p(scalar_p.make_with_size(scalar_p.size() * vlen)); - vtn = new (_vtransform.arena()) VTransformStoreVectorNode(_vtransform, properties, vector_p, p0->adr_type()); + vtn = new (_vtransform.arena()) VTransformStoreVectorNode(_vtransform, properties, vector_p, p0->in_adr_type()); } else if (p0->is_Cmp()) { vtn = new (_vtransform.arena()) VTransformCmpVectorNode(_vtransform, properties); } else if (p0->is_Bool()) { diff --git a/src/hotspot/share/opto/vectorization.cpp b/src/hotspot/share/opto/vectorization.cpp index 96ac4d78dcde..0fe022615300 100644 --- a/src/hotspot/share/opto/vectorization.cpp +++ b/src/hotspot/share/opto/vectorization.cpp @@ -223,13 +223,13 @@ VStatus VLoopMemorySlices::find_memory_slices() { if (n->is_memory_phi()) { // Memory slice with stores (and maybe loads) PhiNode* phi = n->as_Phi(); - int alias_idx = C->get_alias_index(phi->adr_type()); + int alias_idx = C->get_alias_index(phi->out_adr_type()); assert(_inputs.at(alias_idx) == nullptr, "did not yet touch this slice"); _inputs.at_put(alias_idx, phi->in(1)); _heads.at_put(alias_idx, phi); } else if (n->is_Load()) { LoadNode* load = n->as_Load(); - int alias_idx = C->get_alias_index(load->adr_type()); + int alias_idx = C->get_alias_index(load->in_adr_type()); PhiNode* head = _heads.at(alias_idx); if (head == nullptr) { // We did not find a phi on this slice yet -> must be a slice with only loads. @@ -248,7 +248,7 @@ VStatus VLoopMemorySlices::find_memory_slices() { } else if (n->is_Store()) { // Found a store. Make sure it is in a slice with a Phi. StoreNode* store = n->as_Store(); - int alias_idx = C->get_alias_index(store->adr_type()); + int alias_idx = C->get_alias_index(store->out_adr_type()); PhiNode* head = _heads.at(alias_idx); assert(head != nullptr, "should have found a mem phi for this slice"); #endif diff --git a/src/hotspot/share/opto/vectornode.cpp b/src/hotspot/share/opto/vectornode.cpp index fc0c35057bf3..d90b75e7579b 100644 --- a/src/hotspot/share/opto/vectornode.cpp +++ b/src/hotspot/share/opto/vectornode.cpp @@ -1130,18 +1130,18 @@ static Node* ideal_partial_operations(PhaseGVN* phase, Node* node, const TypeVec switch(vopc) { case Op_LoadVector: return new LoadVectorMaskedNode(node->in(0), node->in(1), node->in(2), - node->as_LoadVector()->adr_type(), vt, mask, + node->as_LoadVector()->in_adr_type(), vt, mask, node->as_LoadVector()->control_dependency()); case Op_LoadVectorGather: return new LoadVectorGatherMaskedNode(node->in(0), node->in(1), node->in(2), - node->as_LoadVector()->adr_type(), vt, + node->as_LoadVector()->in_adr_type(), vt, node->in(3), mask); case Op_StoreVector: return new StoreVectorMaskedNode(node->in(0), node->in(1), node->in(2), node->in(3), - node->as_StoreVector()->adr_type(), mask); + node->as_StoreVector()->in_adr_type(), mask); case Op_StoreVectorScatter: return new StoreVectorScatterMaskedNode(node->in(0), node->in(1), node->in(2), - node->as_StoreVector()->adr_type(), + node->as_StoreVector()->in_adr_type(), node->in(3), node->in(4), mask); default: // Add the mask as an additional input to the original vector node by default. @@ -1603,7 +1603,7 @@ Node* LoadVectorMaskedNode::Ideal(PhaseGVN* phase, bool can_reshape) { Node* ctr = in(MemNode::Control); Node* mem = in(MemNode::Memory); Node* adr = in(MemNode::Address); - return phase->transform(new LoadVectorNode(ctr, mem, adr, adr_type(), vect_type())); + return phase->transform(new LoadVectorNode(ctr, mem, adr, in_adr_type(), vect_type())); } } } @@ -1630,7 +1630,7 @@ Node* StoreVectorMaskedNode::Ideal(PhaseGVN* phase, bool can_reshape) { Node* mem = in(MemNode::Memory); Node* adr = in(MemNode::Address); Node* val = in(MemNode::ValueIn); - return phase->transform(new StoreVectorNode(ctr, mem, adr, adr_type(), val)); + return phase->transform(new StoreVectorNode(ctr, mem, adr, in_adr_type(), val)); } } } diff --git a/src/hotspot/share/opto/vectornode.hpp b/src/hotspot/share/opto/vectornode.hpp index 4657050af6ef..af06dbc42c9d 100644 --- a/src/hotspot/share/opto/vectornode.hpp +++ b/src/hotspot/share/opto/vectornode.hpp @@ -2221,6 +2221,7 @@ class VectorUnboxNode : public VectorNode { private: uint size_of() const { return sizeof(*this); } bool depends_only_on_test_impl() const { return false; } + const TypePtr* in_adr_type_impl() const final { return TypePtr::BOTTOM; } }; // Lane-wise right rotation of the first input by the second input. diff --git a/src/hotspot/share/opto/vtransform.cpp b/src/hotspot/share/opto/vtransform.cpp index 93aefe0d5057..161438565f93 100644 --- a/src/hotspot/share/opto/vtransform.cpp +++ b/src/hotspot/share/opto/vtransform.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -972,10 +972,10 @@ float VTransformMemopScalarNode::cost(const VLoopAnalyzer& vloop_analyzer) const VTransformApplyResult VTransformMemopScalarNode::apply(VTransformApplyState& apply_state) const { apply_vtn_inputs_to_node(_node, apply_state); // The memory state has to be applied separately: the vtn does not hold it. This allows reordering. - Node* mem = apply_state.memory_state(_node->adr_type()); + Node* mem = apply_state.memory_state(_node->in_adr_type()); apply_state.phase()->igvn().replace_input_of(_node, 1, mem); if (_node->is_Store()) { - apply_state.set_memory_state(_node->adr_type(), _node); + apply_state.set_memory_state(_node->out_adr_type(), _node); } return VTransformApplyResult::make_scalar(_node); @@ -1016,7 +1016,7 @@ void VTransformPhiScalarNode::apply_backedge(VTransformApplyState& apply_state) if (_node->is_memory_phi()) { // Memory phi/backedge // The last memory state of that slice is the backedge. - Node* last_state = apply_state.memory_state(_node->adr_type()); + Node* last_state = apply_state.memory_state(_node->in_adr_type()); phase->igvn().replace_input_of(_node, 2, last_state); } else { // Data phi/backedge diff --git a/test/hotspot/jtreg/compiler/intrinsics/string/TestAntiDependency.java b/test/hotspot/jtreg/compiler/intrinsics/string/TestAntiDependency.java index 341396e56dd7..5fa8f448a461 100644 --- a/test/hotspot/jtreg/compiler/intrinsics/string/TestAntiDependency.java +++ b/test/hotspot/jtreg/compiler/intrinsics/string/TestAntiDependency.java @@ -31,14 +31,14 @@ import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; +import java.util.Arrays; import jdk.test.lib.Asserts; /* * @test - * @bug 8373591 - * @summary Verify that StringLatin1::inflate, StringUTF16::compress, and - * StringCoding::implEncodeAsciiArray are scheduled properly + * @bug 8373591 8393492 + * @summary Verify that several memory access intrinsics are scheduled properly * @library /test/lib / * @modules java.base/java.lang:+open * @run driver ${test.main.class} @@ -70,7 +70,7 @@ public class TestAntiDependency { public static void main(String[] args) { var testFramework = new TestFramework(); testFramework.setDefaultWarmup(1); - testFramework.addFlags("--add-opens=java.base/java.lang=ALL-UNNAMED"); + testFramework.addFlags("--add-opens=java.base/java.lang=ALL-UNNAMED", "-XX:CompileCommand=inline,*::hashCode"); testFramework.start(); } @@ -92,6 +92,11 @@ static int testStringCompress() throws Throwable { return dst[0]; } + @Run(test = "testStringCompress") + public void runStringCompress() throws Throwable { + Asserts.assertEQ(0, testStringCompress()); + } + @Test static int testStringInflate() throws Throwable { char[] dst = new char[4]; @@ -107,6 +112,11 @@ static int testStringInflate() throws Throwable { return dst[0]; } + @Run(test = "testStringInflate") + public void runStringInflate() throws Throwable { + Asserts.assertEQ(0, testStringInflate()); + } + @Test static int testEncodeISO() throws Throwable { byte[] dst = new byte[4]; @@ -122,10 +132,44 @@ static int testEncodeISO() throws Throwable { return dst[0]; } - @Run(test = {"testStringCompress", "testStringInflate", "testEncodeISO"}) - public void run() throws Throwable { - Asserts.assertEQ(0, testStringCompress()); - Asserts.assertEQ(0, testStringInflate()); + @Run(test = "testEncodeISO") + public void runEncodeISO() throws Throwable { Asserts.assertEQ(0, testEncodeISO()); } + + @Test + static boolean testAryEq(byte[] a1, byte[] a2) { + // The second store must introduce no trap + a1[0] = 0; + + // The backend must have enough information to calculate the anti-dependencies of this node + boolean result = Arrays.equals(a1, a2); + a1[0] = 1; + return result; + } + + @Run(test = "testAryEq") + public void runAryEq() { + byte[] a1 = new byte[100]; + byte[] a2 = new byte[100]; + Asserts.assertEQ(true, testAryEq(a1, a2)); + } + + @Test + static int testVectorizedHashCode() { + int[] a = new int[100]; + consume(a, null); + + // The backend must have enough information to calculate the anti-dependencies of this node + int result = Arrays.hashCode(a); + a[0] = 1; + return result; + } + + @Run(test = "testVectorizedHashCode") + public void runVectorizedHashCode() { + int[] a = new int[100]; + int expected = Arrays.hashCode(a); + Asserts.assertEQ(expected, testVectorizedHashCode()); + } } From d320466c5969191d1d8363e8bd3f5a012f83ac76 Mon Sep 17 00:00:00 2001 From: Bill Huang Date: Fri, 16 Oct 2026 16:29:37 +0000 Subject: [PATCH 081/169] 8394676: Remove duplicated VClass test code from java/util/Collections tests Reviewed-by: smarks --- test/jdk/java/util/Collections/AddAll.java | 35 +------------------ .../java/util/Collections/AsLifoQueue.java | 15 +------- .../util/Collections/BigBinarySearch.java | 19 +--------- .../BinarySearchNullComparator.java | 10 +----- .../util/Collections/CheckedListBash.java | 20 +---------- .../Collections/CheckedListReplaceAll.java | 16 +-------- .../java/util/Collections/CheckedMapBash.java | 20 ++--------- .../Collections/CheckedMapReplaceAll.java | 18 +--------- .../java/util/Collections/CheckedQueue.java | 17 +-------- .../java/util/Collections/CheckedSetBash.java | 23 ++---------- test/jdk/java/util/Collections/Disjoint.java | 11 +----- test/jdk/java/util/Collections/Enum.java | 13 +------ .../Collections/EnumerationAsIterator.java | 11 ++---- .../java/util/Collections/FindSubList.java | 12 +------ test/jdk/java/util/Collections/Frequency.java | 17 +-------- test/jdk/java/util/Collections/NCopies.java | 15 +------- .../java/util/Collections/NullComparator.java | 18 +--------- .../jdk/java/util/Collections/ReplaceAll.java | 11 +----- .../java/util/Collections/ReverseOrder.java | 10 +----- .../java/util/Collections/ReverseOrder2.java | 11 +----- test/jdk/java/util/Collections/Rotate.java | 10 +----- test/jdk/java/util/Collections/Shuffle.java | 15 +------- .../util/Collections/SingletonIterator.java | 16 +-------- test/jdk/java/util/Collections/Swap.java | 10 +----- test/jdk/java/util/Collections/T6433170.java | 11 +----- 25 files changed, 28 insertions(+), 356 deletions(-) diff --git a/test/jdk/java/util/Collections/AddAll.java b/test/jdk/java/util/Collections/AddAll.java index f81e149fad7d..fe30ece8884b 100644 --- a/test/jdk/java/util/Collections/AddAll.java +++ b/test/jdk/java/util/Collections/AddAll.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,11 +27,8 @@ * @summary Basic test for Collections.addAll * @author Josh Bloch * @key randomness - * @library /test/lib */ -import jdk.test.lib.valueclass.VClass; -import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -49,11 +46,6 @@ public static void main(String[] args) { test(new LinkedList()); test(new HashSet()); test(new LinkedHashSet()); - - testTuple(new ArrayList()); - testTuple(new LinkedList()); - testTuple(new HashSet()); - testTuple(new LinkedHashSet()); } private static Random rnd = new Random(); @@ -82,29 +74,4 @@ private static Integer[] range(int from, int to) { result[j] = new Integer(i); return result; } - - static void testTuple(Collection c) { - int x = 0; - for (int i = 0; i < N; i++) { - int rangeLen = rnd.nextInt(10); - if (Collections.addAll(c, rangeTuple(x, x + rangeLen)) != - (rangeLen != 0)) - throw new RuntimeException("" + rangeLen); - x += rangeLen; - } - if (c instanceof List) { - if (!c.equals(Arrays.asList(rangeTuple(0, x)))) - throw new RuntimeException(x + ": " + c); - } else { - if (!c.equals(new HashSet(Arrays.asList(rangeTuple(0, x))))) - throw new RuntimeException(x + ": " + c); - } - } - - private static VClass[] rangeTuple(int from, int to) { - VClass[] result = new VClass[to - from]; - for (int i = from, j = 0; i < to; i++, j++) - result[j] = new VClass(i, new int[] { i }); - return result; - } } diff --git a/test/jdk/java/util/Collections/AsLifoQueue.java b/test/jdk/java/util/Collections/AsLifoQueue.java index 95825c4c64f2..c573e32f49bc 100644 --- a/test/jdk/java/util/Collections/AsLifoQueue.java +++ b/test/jdk/java/util/Collections/AsLifoQueue.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2014, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,10 +26,8 @@ * @bug 6301085 6192552 6365601 * @summary Basic tests for asLifoQueue * @author Martin Buchholz - * @library /test/lib */ -import jdk.test.lib.valueclass.VClass; import java.util.*; import java.util.concurrent.*; @@ -74,17 +72,6 @@ private static void realMain(String[] args) throws Throwable { } catch (Throwable t) { unexpected(t); } THROWS(NullPointerException.class, () -> Collections.asLifoQueue(null)); - - try { - Deque deq = new ArrayDeque<>(); - Queue q = Collections.asLifoQueue(deq); - check(q.add(new VClass(1, new int[] { 1 }))); - check(q.add(new VClass(2, new int[] { 2 }))); - equal(q.peek(), new VClass(2, new int[] { 2 })); - equal(q.remove(), new VClass(2, new int[] { 2 })); - equal(q.poll(), new VClass(1, new int[] { 1 })); - check(q.isEmpty()); - } catch (Throwable t) { unexpected(t); } } //--------------------- Infrastructure --------------------------- diff --git a/test/jdk/java/util/Collections/BigBinarySearch.java b/test/jdk/java/util/Collections/BigBinarySearch.java index 5a1681e4a4e3..b10081c7582a 100644 --- a/test/jdk/java/util/Collections/BigBinarySearch.java +++ b/test/jdk/java/util/Collections/BigBinarySearch.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2006, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2006, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,10 +26,8 @@ * @bug 5045582 * @summary binarySearch of Collections larger than 1<<30 * @author Martin Buchholz - * @library /test/lib */ -import jdk.test.lib.valueclass.VClass; import java.util.AbstractList; import java.util.Collections; import java.util.Comparator; @@ -40,13 +38,6 @@ public class BigBinarySearch { - static class SparseTupleList extends AbstractList implements RandomAccess { - final Map m = new HashMap<>(); - public VClass get(int i) { return m.getOrDefault(i, new VClass(0, new int[] { 0 })); } - public int size() { return Collections.max(m.keySet()) + 1; } - public VClass set(int i, VClass v) { return m.put(i, v); } - } - // Allows creation of very "big" collections without using too // many real resources static class SparseIntegerList @@ -111,14 +102,6 @@ private static void realMain(String[] args) throws Throwable { big.set(i, - big.get(i)); for (int i : ints) checkBinarySearch(big, i, reverse); - - System.out.println("binarySearch(SparseTupleList, Tuple)"); - SparseTupleList vl = new SparseTupleList(); - vl.set(0, new VClass(0, new int[] { 0 })); - vl.set(1, new VClass(1, new int[] { 1 })); - vl.set(n - 2, new VClass(n - 2, new int[] { n - 2 })); - vl.set(n - 1, new VClass(n - 1, new int[] { n - 1 })); - equal(n - 1, Collections.binarySearch(vl, new VClass(n - 1, new int[] { n - 1 }))); } //--------------------- Infrastructure --------------------------- diff --git a/test/jdk/java/util/Collections/BinarySearchNullComparator.java b/test/jdk/java/util/Collections/BinarySearchNullComparator.java index 972e252a9923..62bf55f9b0ea 100644 --- a/test/jdk/java/util/Collections/BinarySearchNullComparator.java +++ b/test/jdk/java/util/Collections/BinarySearchNullComparator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2004, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,26 +25,18 @@ * @test * @bug 4528331 5006032 * @summary Test Collections.binarySearch() with a null comparator - * @library /test/lib */ -import jdk.test.lib.valueclass.VClass; import java.util.Arrays; import java.util.Collections; import java.util.List; public class BinarySearchNullComparator { - public static void main(String[] args) throws Exception { List list = Arrays.asList(new String[] {"I", "Love", "You"}); int result = Collections.binarySearch(list, "You", null); if (result != 2) throw new Exception("Result: " + result); - - List values = Arrays.asList(new VClass(1, new int[] { 1 }), new VClass(2, new int[] { 2 }), new VClass(3, new int[] { 3 })); - int vresult = Collections.binarySearch(values, new VClass(3, new int[] { 3 }), null); - if (vresult != 2) - throw new Exception("Value result: " + vresult); } } diff --git a/test/jdk/java/util/Collections/CheckedListBash.java b/test/jdk/java/util/Collections/CheckedListBash.java index 8d19df4dced9..1adfc0ddd4a7 100644 --- a/test/jdk/java/util/Collections/CheckedListBash.java +++ b/test/jdk/java/util/Collections/CheckedListBash.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,10 +27,8 @@ * @summary Unit test for Collections.checkedList * @author Josh Bloch * @key randomness - * @library /test/lib */ -import jdk.test.lib.valueclass.VClass; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -184,7 +182,6 @@ public static void main(String[] args) { || !l.equals(Arrays.asList(ia).subList(0, listSize))) fail("toArray(Object[]) is hosed (3)"); - testValueCheckedList(); } // Done inefficiently so as to exercise toArray @@ -228,19 +225,4 @@ static void AddRandoms(List s, int n) { static void fail(String s) { throw new RuntimeException(s); } - - static void testValueCheckedList() { - List list = Collections.checkedList(new ArrayList<>(), VClass.class); - list.add(new VClass(1, new int[] { 1 })); - list.add(new VClass(2, new int[] { 2 })); - if (!list.contains(new VClass(1, new int[] { 1 })) || list.indexOf(new VClass(2, new int[] { 2 })) != 1) - fail("value checkedList lookup failed"); - VClass[] a = list.toArray(new VClass[0]); - if (!Arrays.asList(a).equals(list)) - fail("value checkedList toArray failed"); - try { - ((List) list).add("not a Tuple"); - fail("value checkedList accepted wrong type"); - } catch (ClassCastException expected) { } - } } diff --git a/test/jdk/java/util/Collections/CheckedListReplaceAll.java b/test/jdk/java/util/Collections/CheckedListReplaceAll.java index d79a83b2f3df..19042db24d6b 100644 --- a/test/jdk/java/util/Collections/CheckedListReplaceAll.java +++ b/test/jdk/java/util/Collections/CheckedListReplaceAll.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,15 +26,12 @@ * @bug 8047795 8053938 * @summary Ensure that replaceAll operator cannot add bad elements * @author Mike Duigou - * @library /test/lib */ -import jdk.test.lib.valueclass.VClass; import java.util.*; import java.util.function.UnaryOperator; public class CheckedListReplaceAll { - public static void main(String[] args) { List unwrapped = Arrays.asList(new Object[]{1, 2, 3}); List wrapped = Collections.checkedList(unwrapped, Integer.class); @@ -60,16 +57,5 @@ public static void main(String[] args) { thwarted.printStackTrace(System.out); System.out.println("Curses! Foiled again!"); } - - List vList = Collections.checkedList(new ArrayList(Arrays.asList(new VClass(1, new int[] { 1 }))), VClass.class); - vList.replaceAll(v -> new VClass(v.x + 1, new int[] { v.x + 1 })); - if (!vList.get(0).equals(new VClass(2, new int[] { 2 }))) - throw new RuntimeException("value checkedList replaceAll failed"); - - List raw = Collections.checkedList(new ArrayList(Arrays.asList(new VClass(1, new int[] { 1 }))), VClass.class); - try { - raw.replaceAll(e -> "not a Tuple"); - throw new RuntimeException("value checkedList replaceAll accepted wrong type"); - } catch (ClassCastException expected) { } } } diff --git a/test/jdk/java/util/Collections/CheckedMapBash.java b/test/jdk/java/util/Collections/CheckedMapBash.java index 8a03f5bca31a..a8e3fd9e9528 100644 --- a/test/jdk/java/util/Collections/CheckedMapBash.java +++ b/test/jdk/java/util/Collections/CheckedMapBash.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,12 +26,10 @@ * @bug 4904067 5023830 7129185 8072015 8292955 * @summary Unit test for Collections.checkedMap * @author Josh Bloch - * @key randomness - * @library /test/lib * @run testng CheckedMapBash + * @key randomness */ -import jdk.test.lib.valueclass.VClass; import org.testng.Assert; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; @@ -193,18 +191,4 @@ public static void testCheckedMapMerge() { Assert.assertThrows(ClassCastException.class, () -> m.merge("key", "value", (v1, v2) -> null)); Assert.assertThrows(ClassCastException.class, () -> m.merge("key", 3, (v1, v2) -> v2)); } - - @Test - public static void testValueCheckedMap() { - Map m = Collections.checkedMap(new HashMap<>(), VClass.class, VClass.class); - m.put(new VClass(1, new int[] { 1 }), new VClass(10, new int[] { 10 })); - if (!m.containsKey(new VClass(1, new int[] { 1 })) || !m.containsValue(new VClass(10, new int[] { 10 }))) - fail("value checkedMap lookup failed"); - if (!new HashMap<>(m).equals(m)) - fail("value checkedMap equals failed"); - try { - ((Map) m).put("not a Tuple", new VClass(2, new int[] { 2 })); - fail("value checkedMap accepted wrong type"); - } catch (ClassCastException expected) { } - } } diff --git a/test/jdk/java/util/Collections/CheckedMapReplaceAll.java b/test/jdk/java/util/Collections/CheckedMapReplaceAll.java index 6fd4fb455449..05c4da3ad5ed 100644 --- a/test/jdk/java/util/Collections/CheckedMapReplaceAll.java +++ b/test/jdk/java/util/Collections/CheckedMapReplaceAll.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,15 +26,12 @@ * @bug 8047795 * @summary Ensure that replaceAll operator cannot add bad elements * @author Mike Duigou - * @library /test/lib */ -import jdk.test.lib.valueclass.VClass; import java.util.*; import java.util.function.BiFunction; public class CheckedMapReplaceAll { - public static void main(String[] args) { Map unwrapped = new HashMap<>(); unwrapped.put(1, 1.0); @@ -53,18 +50,5 @@ public static void main(String[] args) { thwarted.printStackTrace(System.out); System.out.println("Curses! Foiled again!"); } - - Map vMap = Collections.checkedMap(new HashMap<>(), VClass.class, VClass.class); - vMap.put(new VClass(1, new int[] { 1 }), new VClass(2, new int[] { 2 })); - vMap.replaceAll((k, v) -> new VClass(v.x + 1, new int[] { v.x + 1 })); - if (!vMap.get(new VClass(1, new int[] { 1 })).equals(new VClass(3, new int[] { 3 }))) - throw new RuntimeException("value checkedMap replaceAll failed"); - - Map raw = Collections.checkedMap(new HashMap(), VClass.class, VClass.class); - raw.put(new VClass(1, new int[] { 1 }), new VClass(2, new int[] { 2 })); - try { - raw.replaceAll((k, v) -> "not a Tuple"); - throw new RuntimeException("value checkedMap replaceAll accepted wrong type"); - } catch (ClassCastException expected) { } } } diff --git a/test/jdk/java/util/Collections/CheckedQueue.java b/test/jdk/java/util/Collections/CheckedQueue.java index ea3ebb517b5e..fb47e2e5ca16 100644 --- a/test/jdk/java/util/Collections/CheckedQueue.java +++ b/test/jdk/java/util/Collections/CheckedQueue.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2014, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,12 +25,9 @@ * @test * @bug 5020931 8048207 * @summary Unit test for Collections.checkedQueue - * @library /test/lib * @run testng CheckedQueue */ -import jdk.test.lib.valueclass.VClass; -import java.util.ArrayDeque; import java.util.Collections; import java.util.Queue; import java.util.concurrent.ArrayBlockingQueue; @@ -152,16 +149,4 @@ public void testOffer() { // no room at the inn! assertFalse(q.offer("1"), "queue should be full"); } - - @Test - public void testValueCheckedQueue() { - Queue q = Collections.checkedQueue(new ArrayDeque<>(), VClass.class); - q.add(new VClass(1, new int[] { 1 })); - if (!q.peek().equals(new VClass(1, new int[] { 1 }))) - fail("value checkedQueue peek failed"); - try { - ((Queue) q).add("not a Tuple"); - fail("value checkedQueue accepted wrong type"); - } catch (ClassCastException expected) { } - } } diff --git a/test/jdk/java/util/Collections/CheckedSetBash.java b/test/jdk/java/util/Collections/CheckedSetBash.java index 96780ff178a3..16dfb11b4497 100644 --- a/test/jdk/java/util/Collections/CheckedSetBash.java +++ b/test/jdk/java/util/Collections/CheckedSetBash.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,12 +26,10 @@ * @bug 4904067 7129185 * @summary Unit test for Collections.checkedSet * @author Josh Bloch - * @key randomness - * @library /test/lib * @run testng CheckedSetBash + * @key randomness */ -import jdk.test.lib.valueclass.VClass; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; @@ -179,21 +177,4 @@ public static Collection makeCheckedSets() { }; return Arrays.asList(params); } - - @Test - public static void testValueCheckedSet() { - Set s = Collections.checkedSet(new HashSet<>(), VClass.class); - s.add(new VClass(1, new int[] { 1 })); - s.add(new VClass(2, new int[] { 2 })); - if (!s.contains(new VClass(1, new int[] { 1 }))) - fail("value checkedSet lookup failed"); - Set copy = Collections.checkedSet(new HashSet<>(), VClass.class); - copy.addAll(Arrays.asList(s.toArray(new VClass[0]))); - if (!s.equals(copy)) - fail("value checkedSet addAll failed"); - try { - ((Set) s).add("not a Tuple"); - fail("value checkedSet accepted wrong type"); - } catch (ClassCastException expected) { } - } } diff --git a/test/jdk/java/util/Collections/Disjoint.java b/test/jdk/java/util/Collections/Disjoint.java index dc109dc55734..13bdac86d5e4 100644 --- a/test/jdk/java/util/Collections/Disjoint.java +++ b/test/jdk/java/util/Collections/Disjoint.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,12 +27,9 @@ * @summary Basic test for Collections.disjoint * @author Josh Bloch * @key randomness - * @library /test/lib */ -import jdk.test.lib.valueclass.VClass; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; @@ -74,11 +71,5 @@ public static void main(String[] args) { throw new RuntimeException("C: " + i + ", " + j); } } - - List va = Arrays.asList(new VClass(1, new int[] { 1 }), new VClass(2, new int[] { 2 })); - List vb = Arrays.asList(new VClass(3, new int[] { 3 }), new VClass(4, new int[] { 4 })); - List vc = Arrays.asList(new VClass(2, new int[] { 2 }), new VClass(5, new int[] { 5 })); - if (!Collections.disjoint(va, vb)) throw new RuntimeException("value disjoint failed"); - if (Collections.disjoint(va, vc)) throw new RuntimeException("value non-disjoint failed"); } } diff --git a/test/jdk/java/util/Collections/Enum.java b/test/jdk/java/util/Collections/Enum.java index 3d7b16aa90f5..432d226aaca4 100644 --- a/test/jdk/java/util/Collections/Enum.java +++ b/test/jdk/java/util/Collections/Enum.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,18 +25,13 @@ * @test * @bug 4323074 * @summary Basic test for new Enumeration -> List converter - * @library /test/lib */ -import jdk.test.lib.valueclass.VClass; import java.util.Collections; import java.util.List; import java.util.Vector; public class Enum { - - static final int SIZE = 10; - public static void main(String[] args) throws Exception { int[] sizes = {0, 10, 100}; for (int i=0; i vv = new Vector<>(); - for (int j = 0; j < SIZE; j++) vv.add(new VClass(j, new int[] { j })); - List vl = Collections.list(vv.elements()); - if (!vl.equals(vv)) - throw new RuntimeException("value Enumeration -> List failed"); } } diff --git a/test/jdk/java/util/Collections/EnumerationAsIterator.java b/test/jdk/java/util/Collections/EnumerationAsIterator.java index 3c9da3bfce3c..4901c40303c8 100644 --- a/test/jdk/java/util/Collections/EnumerationAsIterator.java +++ b/test/jdk/java/util/Collections/EnumerationAsIterator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,11 +25,9 @@ * @test * @bug 8072726 * @summary Tests for Enumeration-to-Iterator conversion. - * @library /test/lib * @run testng EnumerationAsIterator */ -import jdk.test.lib.valueclass.VClass; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; @@ -48,7 +46,6 @@ @Test public class EnumerationAsIterator { - static Object[] of(String description, Supplier> s, Collection exp) { return new Object[]{description, s, exp}; } @@ -114,11 +111,7 @@ public static Iterator others() { of("Arrays.asList(...)", Arrays.asList("a", "b", "c"), - Arrays.asList("a", "b", "c")), - - of("Value Collections.enumeration()", - () -> Collections.enumeration(Arrays.asList(new VClass(1, new int[] { 1 }), new VClass(2, new int[] { 2 }))), - Arrays.asList(new VClass(1, new int[] { 1 }), new VClass(2, new int[] { 2 }))) + Arrays.asList("a", "b", "c")) ).iterator(); } diff --git a/test/jdk/java/util/Collections/FindSubList.java b/test/jdk/java/util/Collections/FindSubList.java index 3f5b93d3d3dc..31bfdc7fb249 100644 --- a/test/jdk/java/util/Collections/FindSubList.java +++ b/test/jdk/java/util/Collections/FindSubList.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,10 +25,8 @@ * @test * @bug 4323074 * @summary Basic test for Collections.indexOfSubList/lastIndexOfSubList - * @library /test/lib */ -import jdk.test.lib.valueclass.VClass; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -37,7 +35,6 @@ import java.util.Vector; public class FindSubList { - public static void main(String[] args) throws Exception { int N = 500; List source = new ArrayList(3 * N); @@ -106,12 +103,5 @@ public static void main(String[] args) throws Exception { "0")) != -1) throw new Exception(s.getClass()+" lastIndexOfSubList: big tgt"); } - - List vsource = Arrays.asList(new VClass(1, new int[] { 1 }), new VClass(2, new int[] { 2 }), new VClass(3, new int[] { 3 }), new VClass(2, new int[] { 2 }), new VClass(3, new int[] { 3 })); - List vtarget = Arrays.asList(new VClass(2, new int[] { 2 }), new VClass(3, new int[] { 3 })); - if (Collections.indexOfSubList(vsource, vtarget) != 1) - throw new Exception("value indexOfSubList failed"); - if (Collections.lastIndexOfSubList(vsource, vtarget) != 3) - throw new Exception("value lastIndexOfSubList failed"); } } diff --git a/test/jdk/java/util/Collections/Frequency.java b/test/jdk/java/util/Collections/Frequency.java index 72b2c4454a31..b10ecc63edd6 100644 --- a/test/jdk/java/util/Collections/Frequency.java +++ b/test/jdk/java/util/Collections/Frequency.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,10 +26,8 @@ * @bug 4193200 * @summary Basic test for Collections.frequency * @author Josh Bloch - * @library /test/lib */ -import jdk.test.lib.valueclass.VClass; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedList; @@ -37,11 +35,9 @@ public class Frequency { static final int N = 100; - public static void main(String[] args) { test(new ArrayList()); test(new LinkedList()); - testValue(); } static void test(List list) { @@ -54,15 +50,4 @@ static void test(List list) { if (Collections.frequency(list, i) != i) throw new RuntimeException(list.getClass() + ": " + i); } - - static void testValue() { - List values = new ArrayList<>(); - for (int i = 0; i < N; i++) - for (int j = 0; j < i; j++) - values.add(new VClass(i, new int[] { i })); - Collections.shuffle(values); - for (int i = 0; i < N; i++) - if (Collections.frequency(values, new VClass(i, new int[] { i })) != i) - throw new RuntimeException("value frequency: " + i); - } } diff --git a/test/jdk/java/util/Collections/NCopies.java b/test/jdk/java/util/Collections/NCopies.java index 7fe206ac72d5..48ba92ad842a 100644 --- a/test/jdk/java/util/Collections/NCopies.java +++ b/test/jdk/java/util/Collections/NCopies.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,10 +26,8 @@ * @bug 6267846 6275009 * @summary Test Collections.nCopies * @author Martin Buchholz - * @library /test/lib */ -import jdk.test.lib.valueclass.VClass; import java.util.ArrayList; import java.util.Collections; import java.util.AbstractList; @@ -144,15 +142,6 @@ private static void checkReversed() { check(empty.equals(empty.reversed())); } - private static void checkValueCopies() { - List copies = Collections.nCopies(10, new VClass(7, new int[] { 7 })); - check(copies.indexOf(new VClass(7, new int[] { 7 })) == 0); - check(copies.lastIndexOf(new VClass(7, new int[] { 7 })) == 9); - check(copies.equals(referenceNCopies(10, new VClass(7, new int[] { 7 })))); - check(copies.hashCode() == referenceNCopies(10, new VClass(7, new int[] { 7 })).hashCode()); - check(copies.equals(copies.reversed())); - } - public static void main(String[] args) { try { List empty = Collections.nCopies(0, "foo"); @@ -169,8 +158,6 @@ public static void main(String[] args) { checkReversed(); - checkValueCopies(); - } catch (Throwable t) { unexpected(t); } System.out.printf("%nPassed = %d, failed = %d%n%n", passed, failed); diff --git a/test/jdk/java/util/Collections/NullComparator.java b/test/jdk/java/util/Collections/NullComparator.java index e43525ab2ce0..fd1134cb9051 100644 --- a/test/jdk/java/util/Collections/NullComparator.java +++ b/test/jdk/java/util/Collections/NullComparator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,17 +25,14 @@ * @test * @bug 4224271 * @summary A null Comparator is now specified to indicate natural ordering. - * @library /test/lib */ -import jdk.test.lib.valueclass.VClass; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; public class NullComparator { - public static void main(String[] args) throws Exception { List list = new ArrayList(100); for (int i=0; i<100; i++) @@ -64,18 +61,5 @@ public static void main(String[] args) throws Exception { throw new Exception("Collections.min"); if (!Collections.max(list, null).equals(new Integer(99))) throw new Exception("Collections.max"); - - List vlist = new ArrayList<>(); - for (int i = 0; i < 100; i++) vlist.add(new VClass(i, new int[] { i })); - Collections.shuffle(vlist); - Collections.sort(vlist, null); - for (int i = 0; i < 100; i++) - if (!vlist.get(i).equals(new VClass(i, new int[] { i }))) throw new Exception("value Collections.sort"); - if (Collections.binarySearch(vlist, new VClass(69, new int[] { 69 }), null) != 69) - throw new Exception("value binarySearch"); - if (!Collections.min(vlist, null).equals(new VClass(0, new int[] { 0 }))) - throw new Exception("value min"); - if (!Collections.max(vlist, null).equals(new VClass(99, new int[] { 99 }))) - throw new Exception("value max"); } } diff --git a/test/jdk/java/util/Collections/ReplaceAll.java b/test/jdk/java/util/Collections/ReplaceAll.java index 3eeb57908fbd..498e4c25ce6e 100644 --- a/test/jdk/java/util/Collections/ReplaceAll.java +++ b/test/jdk/java/util/Collections/ReplaceAll.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,10 +25,8 @@ * @test * @bug 4323074 * @summary Basic test for new replaceAll algorithm - * @library /test/lib */ -import jdk.test.lib.valueclass.VClass; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedList; @@ -59,12 +57,5 @@ public static void main(String[] args) throws Exception { if (Collections.replaceAll(lst, "love", "hate")) throw new Exception("True return value: "+i); } - - List values = new ArrayList<>(); - for (int j = 1; j <= SIZE; j++) values.add(new VClass(j % 3, new int[] { j % 3 })); - if (!Collections.replaceAll(values, new VClass(1, new int[] { 1 }), new VClass(99, new int[] { 99 }))) - throw new Exception("value false return"); - if (Collections.replaceAll(values, new VClass(100, new int[] { 100 }), new VClass(0, new int[] { 0 }))) - throw new Exception("value true return for absent element"); } } diff --git a/test/jdk/java/util/Collections/ReverseOrder.java b/test/jdk/java/util/Collections/ReverseOrder.java index e634ceadaf1a..ceaa5441a0dc 100644 --- a/test/jdk/java/util/Collections/ReverseOrder.java +++ b/test/jdk/java/util/Collections/ReverseOrder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2002, 2013, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,10 +26,8 @@ * @bug 4593209 8001667 * @summary Reverse comparator was subtly broken * @author Josh Bloch - * @library /test/lib */ -import jdk.test.lib.valueclass.VClass; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; @@ -40,7 +38,6 @@ import java.util.List; public class ReverseOrder { - static byte[] serialBytes(Object o) { try { ByteArrayOutputStream bos = new ByteArrayOutputStream(); @@ -82,11 +79,6 @@ public static void main(String[] args) throws Exception { Collections.sort(list2, clone); if (!list2.equals(goldenList)) throw new Exception(list.toString()); - - List vl = Arrays.asList(new VClass(1, new int[] { 1 }), new VClass(3, new int[] { 3 }), new VClass(2, new int[] { 2 })); - Collections.sort(vl, Collections.reverseOrder()); - if (!vl.equals(Arrays.asList(new VClass(3, new int[] { 3 }), new VClass(2, new int[] { 2 }), new VClass(1, new int[] { 1 })))) - throw new RuntimeException("value reverseOrder failed"); } } diff --git a/test/jdk/java/util/Collections/ReverseOrder2.java b/test/jdk/java/util/Collections/ReverseOrder2.java index 4aa88ef0002b..0369b389a80e 100644 --- a/test/jdk/java/util/Collections/ReverseOrder2.java +++ b/test/jdk/java/util/Collections/ReverseOrder2.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2007, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,10 +26,8 @@ * @bug 4809442 6366832 4974878 6372554 4890211 6483125 * @summary Basic test for Collections.reverseOrder * @author Josh Bloch, Martin Buchholz - * @library /test/lib */ -import jdk.test.lib.valueclass.VClass; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -37,7 +35,6 @@ import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.LinkedList; @@ -69,12 +66,6 @@ static void realMain(String[] args) throws Throwable { test(new LinkedList()); test2(new ArrayList()); test2(new LinkedList()); - - List values = new ArrayList<>(Arrays.asList(new VClass(0, new int[] { 0 }), new VClass(1, new int[] { 1 }), new VClass(2, new int[] { 2 }))); - Collections.shuffle(values); - Collections.sort(values, Collections.reverseOrder()); - if (!values.equals(Arrays.asList(new VClass(2, new int[] { 2 }), new VClass(1, new int[] { 1 }), new VClass(0, new int[] { 0 })))) - throw new RuntimeException("value reverseOrder2 failed"); } static void test(List list) { diff --git a/test/jdk/java/util/Collections/Rotate.java b/test/jdk/java/util/Collections/Rotate.java index 204812c14bbf..7ec42f9d2e94 100644 --- a/test/jdk/java/util/Collections/Rotate.java +++ b/test/jdk/java/util/Collections/Rotate.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,12 +26,9 @@ * @bug 4323074 * @summary Basic test for new rotate algorithm * @key randomness - * @library /test/lib */ -import jdk.test.lib.valueclass.VClass; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.LinkedList; import java.util.List; @@ -72,10 +69,5 @@ public static void main(String[] args) throws Exception { ", should be "+index); } } - - List vl = new ArrayList<>(Arrays.asList(new VClass(1, new int[] { 1 }), new VClass(2, new int[] { 2 }), new VClass(3, new int[] { 3 }), new VClass(4, new int[] { 4 }))); - Collections.rotate(vl, 1); - if (!vl.equals(Arrays.asList(new VClass(4, new int[] { 4 }), new VClass(1, new int[] { 1 }), new VClass(2, new int[] { 2 }), new VClass(3, new int[] { 3 })))) - throw new RuntimeException("value rotate failed"); } } diff --git a/test/jdk/java/util/Collections/Shuffle.java b/test/jdk/java/util/Collections/Shuffle.java index e0242f6dc9b5..3a33f27f473e 100644 --- a/test/jdk/java/util/Collections/Shuffle.java +++ b/test/jdk/java/util/Collections/Shuffle.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,10 +26,8 @@ * @bug 8294693 * @summary Basic test for Collections.shuffle * @key randomness - * @library /test/lib */ -import jdk.test.lib.valueclass.VClass; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedList; @@ -44,7 +42,6 @@ public class Shuffle { public static void main(String[] args) { test(new ArrayList<>()); test(new LinkedList<>()); - testValue(); } static void test(List list) { @@ -65,16 +62,6 @@ static void test(List list) { checkRandom(list, l -> Collections.shuffle(l, generator.copy())); } - static void testValue() { - List values = new ArrayList<>(); - for (int i = 0; i < N; i++) values.add(new VClass(i, new int[] { i })); - Collections.shuffle(values, new Random(1)); - if (values.size() != N) throw new RuntimeException("value shuffle size"); - for (int i = 0; i < N; i++) - if (Collections.frequency(values, new VClass(i, new int[] { i })) != 1) - throw new RuntimeException("value shuffle lost " + i); - } - private static void checkRandom(List list, Consumer> randomizer) { list.sort(null); randomizer.accept(list); diff --git a/test/jdk/java/util/Collections/SingletonIterator.java b/test/jdk/java/util/Collections/SingletonIterator.java index 006d06261846..0f7dfd172521 100644 --- a/test/jdk/java/util/Collections/SingletonIterator.java +++ b/test/jdk/java/util/Collections/SingletonIterator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -24,11 +24,9 @@ /* * @test * @bug 8024500 8166446 - * @library /test/lib * @run testng SingletonIterator */ -import jdk.test.lib.valueclass.VClass; import java.util.Collections; import java.util.Iterator; import java.util.NoSuchElementException; @@ -65,18 +63,6 @@ public void testForEachRemaining() { static class SingletonException extends RuntimeException { } - @Test - public void testValueSingletonIterator() { - Iterator it = Collections.singleton(new VClass(42, new int[] { 42 })).iterator(); - AtomicInteger cnt = new AtomicInteger(0); - it.forEachRemaining(v -> { - if (!v.equals(new VClass(42, new int[] { 42 }))) throw new RuntimeException("wrong value"); - cnt.incrementAndGet(); - }); - assertEquals(cnt.get(), 1); - assertIteratorExhausted(it); - } - public void testThrowFromForEachRemaining() { Iterator it = Collections.singleton("TheOne").iterator(); diff --git a/test/jdk/java/util/Collections/Swap.java b/test/jdk/java/util/Collections/Swap.java index 726b6d1a7546..488feeda1eae 100644 --- a/test/jdk/java/util/Collections/Swap.java +++ b/test/jdk/java/util/Collections/Swap.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,10 +26,8 @@ * @bug 4323074 * @summary Basic test for newly public swap algorithm * @author Josh Bloch - * @library /test/lib */ -import jdk.test.lib.valueclass.VClass; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -47,11 +45,5 @@ public static void main(String[] args) throws Exception { l2.set(SIZE-1, Boolean.TRUE); if (!l.equals(l2)) throw new RuntimeException("Wrong result"); - - List vl = new ArrayList<>(Collections.nCopies(SIZE, new VClass(0, new int[] { 0 }))); - vl.set(0, new VClass(1, new int[] { 1 })); - for (int i = 0; i < SIZE - 1; i++) Collections.swap(vl, i, i + 1); - if (!vl.get(SIZE - 1).equals(new VClass(1, new int[] { 1 }))) - throw new RuntimeException("value swap failed"); } } diff --git a/test/jdk/java/util/Collections/T6433170.java b/test/jdk/java/util/Collections/T6433170.java index fe59ffff0a68..65de4ccdbedc 100644 --- a/test/jdk/java/util/Collections/T6433170.java +++ b/test/jdk/java/util/Collections/T6433170.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2007, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,10 +25,8 @@ * @test * @bug 6433170 * @summary CheckedCollection.addAll should be all-or-nothing - * @library /test/lib */ -import jdk.test.lib.valueclass.VClass; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -42,7 +40,6 @@ @SuppressWarnings("unchecked") public class T6433170 { - private void checkEmpty(Collection x) { check(x.isEmpty()); check(x.size() == 0); @@ -59,12 +56,6 @@ void test(String[] args) throws Throwable { test(checkedCollection( checkedCollection(new Vector(), String.class), Object.class)); - - Collection vc = checkedCollection(new ArrayList<>(), VClass.class); - List mixed = Arrays.asList(new VClass(1, new int[] { 1 }), new VClass(2, new int[] { 2 }), "not a Tuple"); - THROWS(ClassCastException.class, - new F(){void f(){ vc.addAll(mixed); }}); - checkEmpty(vc); } void test(final Collection checked) { From 44f2d94db4641a2e04bb2b8a67f6c50f697fee8f Mon Sep 17 00:00:00 2001 From: Vladimir Kozlov Date: Fri, 16 Oct 2026 16:54:49 +0000 Subject: [PATCH 082/169] 8394515: AOT code cache: guarantee(chk == -1 || chk == 0) failed: Field too big for insn Reviewed-by: dlong, qamai --- src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp index dd9215c55bd6..d083fd5b7610 100644 --- a/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp @@ -5887,7 +5887,11 @@ void MacroAssembler::adrp(Register reg1, const Address &dest, uint64_t &byte_off code_section()->relocate(inst_mark(), dest.rspec()); // 8143067: Ensure that the adrp can reach the dest from anywhere within // the code cache so that if it is relocated we know it will still reach - if (offset_high >= -(1<<20) && offset_low < (1<<20)) { + // For AOT code force far distance for external_word relocation. + const bool force_far = AOTCodeCache::is_on_for_dump() && + (dest.rspec().type() == relocInfo::external_word_type) && + !CodeCache::contains(dest.target()); + if (!force_far && offset_high >= -(1<<20) && offset_low < (1<<20)) { _adrp(reg1, dest.target()); } else { uint64_t target = (uint64_t)dest.target(); From d0287a5e875e74cb90713beaa45672326807e2d1 Mon Sep 17 00:00:00 2001 From: Kevin Walls Date: Fri, 16 Oct 2026 19:04:35 +0000 Subject: [PATCH 083/169] 8394269: Deprecate OpenType.ALLOWED_CLASSNAMES for removal Reviewed-by: cjplummer, dfuchs --- .../share/classes/javax/management/openmbean/OpenType.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/java.management/share/classes/javax/management/openmbean/OpenType.java b/src/java.management/share/classes/javax/management/openmbean/OpenType.java index de2663bdc7fe..ed59311e3f8c 100644 --- a/src/java.management/share/classes/javax/management/openmbean/OpenType.java +++ b/src/java.management/share/classes/javax/management/openmbean/OpenType.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -113,7 +113,7 @@ public abstract class OpenType implements Serializable { /** * @deprecated Use {@link #ALLOWED_CLASSNAMES_LIST ALLOWED_CLASSNAMES_LIST} instead. */ - @Deprecated + @Deprecated(since="6", forRemoval=true) public static final String[] ALLOWED_CLASSNAMES = ALLOWED_CLASSNAMES_LIST.toArray(new String[0]); From 005b7266cda335cf81519c57a92e5c9f1586e1f8 Mon Sep 17 00:00:00 2001 From: Phil Race Date: Fri, 16 Oct 2026 19:47:09 +0000 Subject: [PATCH 084/169] 8394487: medialib has many unused C function prototypes Reviewed-by: jdv, kizune, serb --- .../native/libmlib_image/mlib_image_proto.h | 3349 +---------------- 1 file changed, 25 insertions(+), 3324 deletions(-) diff --git a/src/java.desktop/share/native/libmlib_image/mlib_image_proto.h b/src/java.desktop/share/native/libmlib_image/mlib_image_proto.h index a8ad65e7c4b9..a87f79c9b157 100644 --- a/src/java.desktop/share/native/libmlib_image/mlib_image_proto.h +++ b/src/java.desktop/share/native/libmlib_image/mlib_image_proto.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -31,9 +31,6 @@ #include #include #include "jni.h" -#if defined ( __MEDIALIB_OLD_NAMES_ADDED ) -#include <../include/mlib_image_proto.h> -#endif /* defined ( __MEDIALIB_OLD_NAMES_ADDED ) */ #ifdef __cplusplus extern "C" { @@ -43,2603 +40,41 @@ extern "C" { #include "j2d_names.h" #endif // __USE_J2D_NAMES -#if defined ( _MSC_VER ) -#if ! defined ( __MEDIALIB_OLD_NAMES ) -#define __MEDIALIB_OLD_NAMES -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -#endif /* defined ( _MSC_VER ) */ - -/* Arithmetic Operations ( arith ) */ - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageAbs mlib_ImageAbs -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageAbs(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageAbs_Fp mlib_ImageAbs_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageAbs_Fp(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageAbs_Fp_Inp mlib_ImageAbs_Fp_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageAbs_Fp_Inp(mlib_image *srcdst); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageAbs_Inp mlib_ImageAbs_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageAbs_Inp(mlib_image *srcdst); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageAdd mlib_ImageAdd -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageAdd(mlib_image *dst, - const mlib_image *src1, - const mlib_image *src2); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageAdd_Fp mlib_ImageAdd_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageAdd_Fp(mlib_image *dst, - const mlib_image *src1, - const mlib_image *src2); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageAdd_Fp_Inp mlib_ImageAdd_Fp_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageAdd_Fp_Inp(mlib_image *src1dst, - const mlib_image *src2); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageAdd_Inp mlib_ImageAdd_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageAdd_Inp(mlib_image *src1dst, - const mlib_image *src2); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageAve mlib_ImageAve -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageAve(mlib_image *dst, - const mlib_image *src1, - const mlib_image *src2); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageAve_Fp mlib_ImageAve_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageAve_Fp(mlib_image *dst, - const mlib_image *src1, - const mlib_image *src2); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageAve_Fp_Inp mlib_ImageAve_Fp_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageAve_Fp_Inp(mlib_image *src1dst, - const mlib_image *src2); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageAve_Inp mlib_ImageAve_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageAve_Inp(mlib_image *src1dst, - const mlib_image *src2); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageBlend mlib_ImageBlend -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageBlend(mlib_image *dst, - const mlib_image *src1, - const mlib_image *src2, - const mlib_image *alpha); - -/* src1dst = src1dst * alpha + src2 * (1 - alpha) */ - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageBlend1_Fp_Inp mlib_ImageBlend1_Fp_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageBlend1_Fp_Inp(mlib_image *src1dst, - const mlib_image *src2, - const mlib_image *alpha); - -/* src1dst = src1dst * alpha + src2 * (1 - alpha) */ - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageBlend1_Inp mlib_ImageBlend1_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageBlend1_Inp(mlib_image *src1dst, - const mlib_image *src2, - const mlib_image *alpha); - -/* src2dst = src1 * alpha + src2dst * (1 - alpha) */ - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageBlend2_Fp_Inp mlib_ImageBlend2_Fp_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageBlend2_Fp_Inp(mlib_image *src2dst, - const mlib_image *src1, - const mlib_image *alpha); - -/* src2dst = src1 * alpha + src2dst * (1 - alpha) */ - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageBlend2_Inp mlib_ImageBlend2_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageBlend2_Inp(mlib_image *src2dst, - const mlib_image *src1, - const mlib_image *alpha); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageBlend_Fp mlib_ImageBlend_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageBlend_Fp(mlib_image *dst, - const mlib_image *src1, - const mlib_image *src2, - const mlib_image *alpha); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageBlendMulti mlib_ImageBlendMulti -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageBlendMulti(mlib_image *dst, - const mlib_image **srcs, - const mlib_image **alphas, - const mlib_s32 *c, - mlib_s32 n); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageBlendMulti_Fp mlib_ImageBlendMulti_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageBlendMulti_Fp(mlib_image *dst, - const mlib_image **srcs, - const mlib_image **alphas, - const mlib_d64 *c, - mlib_s32 n); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageBlendRGBA2ARGB mlib_ImageBlendRGBA2ARGB -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageBlendRGBA2ARGB(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageBlendRGBA2BGRA mlib_ImageBlendRGBA2BGRA -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageBlendRGBA2BGRA(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageColorBlend mlib_ImageColorBlend -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageColorBlend(mlib_image *dst, - const mlib_image *src, - const mlib_s32 *color, - mlib_s32 cmask); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageColorBlend_Fp mlib_ImageColorBlend_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageColorBlend_Fp(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *color, - mlib_s32 cmask); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageColorBlend_Fp_Inp mlib_ImageColorBlend_Fp_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageColorBlend_Fp_Inp(mlib_image *srcdst, - const mlib_d64 *color, - mlib_s32 cmask); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageColorBlend_Inp mlib_ImageColorBlend_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageColorBlend_Inp(mlib_image *srcdst, - const mlib_s32 *color, - mlib_s32 cmask); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConstAdd mlib_ImageConstAdd -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConstAdd(mlib_image *dst, - const mlib_image *src, - const mlib_s32 *c); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConstAdd_Fp mlib_ImageConstAdd_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConstAdd_Fp(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *c); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConstAdd_Fp_Inp mlib_ImageConstAdd_Fp_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConstAdd_Fp_Inp(mlib_image *srcdst, - const mlib_d64 *c); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConstAdd_Inp mlib_ImageConstAdd_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConstAdd_Inp(mlib_image *srcdst, - const mlib_s32 *c); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConstDiv mlib_ImageConstDiv -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConstDiv(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *consts); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConstDivShift mlib_ImageConstDivShift -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConstDivShift(mlib_image *dst, - const mlib_image *src, - const mlib_s32 *consts, - mlib_s32 shift); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConstDivShift_Inp mlib_ImageConstDivShift_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConstDivShift_Inp(mlib_image *srcdst, - const mlib_s32 *consts, - mlib_s32 shift); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConstDiv_Fp mlib_ImageConstDiv_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConstDiv_Fp(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *consts); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConstDiv_Fp_Inp mlib_ImageConstDiv_Fp_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConstDiv_Fp_Inp(mlib_image *srcdst, - const mlib_d64 *consts); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConstDiv_Inp mlib_ImageConstDiv_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConstDiv_Inp(mlib_image *srcdst, - const mlib_d64 *consts); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConstMul mlib_ImageConstMul -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConstMul(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *consts); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConstMulShift mlib_ImageConstMulShift -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConstMulShift(mlib_image *dst, - const mlib_image *src, - const mlib_s32 *consts, - mlib_s32 shift); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConstMulShift_Inp mlib_ImageConstMulShift_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConstMulShift_Inp(mlib_image *srcdst, - const mlib_s32 *consts, - mlib_s32 shift); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConstMul_Fp mlib_ImageConstMul_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConstMul_Fp(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *consts); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConstMul_Fp_Inp mlib_ImageConstMul_Fp_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConstMul_Fp_Inp(mlib_image *srcdst, - const mlib_d64 *consts); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConstMul_Inp mlib_ImageConstMul_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConstMul_Inp(mlib_image *srcdst, - const mlib_d64 *consts); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConstSub mlib_ImageConstSub -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConstSub(mlib_image *dst, - const mlib_image *src, - const mlib_s32 *c); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConstSub_Fp mlib_ImageConstSub_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConstSub_Fp(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *c); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConstSub_Fp_Inp mlib_ImageConstSub_Fp_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConstSub_Fp_Inp(mlib_image *srcdst, - const mlib_d64 *c); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConstSub_Inp mlib_ImageConstSub_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConstSub_Inp(mlib_image *srcdst, - const mlib_s32 *c); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageDiv1_Fp_Inp mlib_ImageDiv1_Fp_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageDiv1_Fp_Inp(mlib_image *src1dst, - const mlib_image *src2); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageDiv2_Fp_Inp mlib_ImageDiv2_Fp_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageDiv2_Fp_Inp(mlib_image *src2dst, - const mlib_image *src1); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageDivAlpha mlib_ImageDivAlpha -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageDivAlpha(mlib_image *dst, - const mlib_image *src, - mlib_s32 cmask); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageDivAlpha_Fp mlib_ImageDivAlpha_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageDivAlpha_Fp(mlib_image *dst, - const mlib_image *src, - mlib_s32 cmask); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageDivAlpha_Fp_Inp mlib_ImageDivAlpha_Fp_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageDivAlpha_Fp_Inp(mlib_image *img, - mlib_s32 cmask); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageDivAlpha_Inp mlib_ImageDivAlpha_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageDivAlpha_Inp(mlib_image *img, - mlib_s32 cmask); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageDivConstShift mlib_ImageDivConstShift -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageDivConstShift(mlib_image *dst, - const mlib_image *src, - const mlib_s32 *consts, - mlib_s32 shift); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageDivConstShift_Inp mlib_ImageDivConstShift_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageDivConstShift_Inp(mlib_image *srcdst, - const mlib_s32 *consts, - mlib_s32 shift); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageDivShift mlib_ImageDivShift -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageDivShift(mlib_image *dst, - const mlib_image *src1, - const mlib_image *src2, - mlib_s32 shift); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageDivShift1_Inp mlib_ImageDivShift1_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageDivShift1_Inp(mlib_image *src1dst, - const mlib_image *src2, - mlib_s32 shift); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageDivShift2_Inp mlib_ImageDivShift2_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageDivShift2_Inp(mlib_image *src2dst, - const mlib_image *src1, - mlib_s32 shift); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageDiv_Fp mlib_ImageDiv_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageDiv_Fp(mlib_image *dst, - const mlib_image *src1, - const mlib_image *src2); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageExp mlib_ImageExp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageExp(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageExp_Fp mlib_ImageExp_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageExp_Fp(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageExp_Fp_Inp mlib_ImageExp_Fp_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageExp_Fp_Inp(mlib_image *srcdst); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageExp_Inp mlib_ImageExp_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageExp_Inp(mlib_image *srcdst); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageInvert mlib_ImageInvert -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageInvert(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageInvert_Fp mlib_ImageInvert_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageInvert_Fp(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageInvert_Fp_Inp mlib_ImageInvert_Fp_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageInvert_Fp_Inp(mlib_image *srcdst); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageInvert_Inp mlib_ImageInvert_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageInvert_Inp(mlib_image *srcdst); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageLog mlib_ImageLog -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageLog(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageLog_Fp mlib_ImageLog_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageLog_Fp(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageLog_Fp_Inp mlib_ImageLog_Fp_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageLog_Fp_Inp(mlib_image *srcdst); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageLog_Inp mlib_ImageLog_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageLog_Inp(mlib_image *srcdst); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMax mlib_ImageMax -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMax(mlib_image *dst, - const mlib_image *src1, - const mlib_image *src2); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMax_Fp mlib_ImageMax_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMax_Fp(mlib_image *dst, - const mlib_image *src1, - const mlib_image *src2); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMax_Fp_Inp mlib_ImageMax_Fp_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMax_Fp_Inp(mlib_image *src1dst, - const mlib_image *src2); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMax_Inp mlib_ImageMax_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMax_Inp(mlib_image *src1dst, - const mlib_image *src2); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMin mlib_ImageMin -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMin(mlib_image *dst, - const mlib_image *src1, - const mlib_image *src2); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMin_Fp mlib_ImageMin_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMin_Fp(mlib_image *dst, - const mlib_image *src1, - const mlib_image *src2); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMin_Fp_Inp mlib_ImageMin_Fp_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMin_Fp_Inp(mlib_image *src1dst, - const mlib_image *src2); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMin_Inp mlib_ImageMin_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMin_Inp(mlib_image *src1dst, - const mlib_image *src2); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMulAlpha mlib_ImageMulAlpha -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMulAlpha(mlib_image *dst, - const mlib_image *src, - mlib_s32 cmask); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMulAlpha_Fp mlib_ImageMulAlpha_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMulAlpha_Fp(mlib_image *dst, - const mlib_image *src, - mlib_s32 cmask); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMulAlpha_Fp_Inp mlib_ImageMulAlpha_Fp_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMulAlpha_Fp_Inp(mlib_image *img, - mlib_s32 cmask); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMulAlpha_Inp mlib_ImageMulAlpha_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMulAlpha_Inp(mlib_image *img, - mlib_s32 cmask); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMulShift mlib_ImageMulShift -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMulShift(mlib_image *dst, - const mlib_image *src1, - const mlib_image *src2, - mlib_s32 shift); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMulShift_Inp mlib_ImageMulShift_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMulShift_Inp(mlib_image *src1dst, - const mlib_image *src2, - mlib_s32 shift); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMul_Fp mlib_ImageMul_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMul_Fp(mlib_image *dst, - const mlib_image *src1, - const mlib_image *src2); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMul_Fp_Inp mlib_ImageMul_Fp_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMul_Fp_Inp(mlib_image *src1dst, - const mlib_image *src2); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageScalarBlend mlib_ImageScalarBlend -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageScalarBlend(mlib_image *dst, - const mlib_image *src1, - const mlib_image *src2, - const mlib_s32 *alpha); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageScalarBlend_Fp mlib_ImageScalarBlend_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageScalarBlend_Fp(mlib_image *dst, - const mlib_image *src1, - const mlib_image *src2, - const mlib_d64 *alpha); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageScalarBlend_Fp_Inp mlib_ImageScalarBlend_Fp_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageScalarBlend_Fp_Inp(mlib_image *src1dst, - const mlib_image *src2, - const mlib_d64 *alpha); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageScalarBlend_Inp mlib_ImageScalarBlend_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageScalarBlend_Inp(mlib_image *src1dst, - const mlib_image *src2, - const mlib_s32 *alpha); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageScale mlib_ImageScale -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageScale(mlib_image *dst, - const mlib_image *src, - const mlib_s32 *alpha, - const mlib_s32 *beta, - mlib_s32 shift); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageScale2 mlib_ImageScale2 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageScale2(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *alpha, - const mlib_d64 *beta); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageScale2_Inp mlib_ImageScale2_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageScale2_Inp(mlib_image *srcdst, - const mlib_d64 *alpha, - const mlib_d64 *beta); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageScale_Fp mlib_ImageScale_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageScale_Fp(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *alpha, - const mlib_d64 *beta); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageScale_Fp_Inp mlib_ImageScale_Fp_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageScale_Fp_Inp(mlib_image *srcdst, - const mlib_d64 *alpha, - const mlib_d64 *beta); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageScale_Inp mlib_ImageScale_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageScale_Inp(mlib_image *srcdst, - const mlib_s32 *alpha, - const mlib_s32 *beta, - mlib_s32 shift); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageSqrShift mlib_ImageSqrShift -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageSqrShift(mlib_image *dst, - const mlib_image *src, - mlib_s32 shift); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageSqrShift_Inp mlib_ImageSqrShift_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageSqrShift_Inp(mlib_image *srcdst, - mlib_s32 shift); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageSqr_Fp mlib_ImageSqr_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageSqr_Fp(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageSqr_Fp_Inp mlib_ImageSqr_Fp_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageSqr_Fp_Inp(mlib_image *srcdst); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageSub mlib_ImageSub -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageSub(mlib_image *dst, - const mlib_image *src1, - const mlib_image *src2); - -/* src1dst = src1dst - src2 */ - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageSub1_Fp_Inp mlib_ImageSub1_Fp_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageSub1_Fp_Inp(mlib_image *src1dst, - const mlib_image *src2); - -/* src1dst = src1dst - src2 */ - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageSub1_Inp mlib_ImageSub1_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageSub1_Inp(mlib_image *src1dst, - const mlib_image *src2); - -/* src2dst = src1 - src2dst */ - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageSub2_Fp_Inp mlib_ImageSub2_Fp_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageSub2_Fp_Inp(mlib_image *src2dst, - const mlib_image *src1); - -/* src2dst = src1 - src2dst */ - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageSub2_Inp mlib_ImageSub2_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageSub2_Inp(mlib_image *src2dst, - const mlib_image *src1); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageSub_Fp mlib_ImageSub_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageSub_Fp(mlib_image *dst, - const mlib_image *src1, - const mlib_image *src2); - -/* Color Space Conversion ( color ) */ - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageColorConvert1 mlib_ImageColorConvert1 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageColorConvert1(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *cmat); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageColorConvert1_Fp mlib_ImageColorConvert1_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageColorConvert1_Fp(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *cmat); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageColorConvert2 mlib_ImageColorConvert2 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageColorConvert2(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *cmat, - const mlib_d64 *offset); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageColorConvert2_Fp mlib_ImageColorConvert2_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageColorConvert2_Fp(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *cmat, - const mlib_d64 *offset); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageColorHSL2RGB mlib_ImageColorHSL2RGB -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageColorHSL2RGB(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageColorHSL2RGB_Fp mlib_ImageColorHSL2RGB_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageColorHSL2RGB_Fp(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageColorHSV2RGB mlib_ImageColorHSV2RGB -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageColorHSV2RGB(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageColorHSV2RGB_Fp mlib_ImageColorHSV2RGB_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageColorHSV2RGB_Fp(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageColorRGB2CIEMono mlib_ImageColorRGB2CIEMono -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageColorRGB2CIEMono(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageColorRGB2CIEMono_Fp mlib_ImageColorRGB2CIEMono_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageColorRGB2CIEMono_Fp(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageColorRGB2HSL mlib_ImageColorRGB2HSL -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageColorRGB2HSL(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageColorRGB2HSL_Fp mlib_ImageColorRGB2HSL_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageColorRGB2HSL_Fp(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageColorRGB2HSV mlib_ImageColorRGB2HSV -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageColorRGB2HSV(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageColorRGB2HSV_Fp mlib_ImageColorRGB2HSV_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageColorRGB2HSV_Fp(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageColorRGB2Mono mlib_ImageColorRGB2Mono -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageColorRGB2Mono(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *weight); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageColorRGB2Mono_Fp mlib_ImageColorRGB2Mono_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageColorRGB2Mono_Fp(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *weight); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageColorRGB2XYZ mlib_ImageColorRGB2XYZ -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageColorRGB2XYZ(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageColorRGB2XYZ_Fp mlib_ImageColorRGB2XYZ_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageColorRGB2XYZ_Fp(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageColorRGB2YCC mlib_ImageColorRGB2YCC -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageColorRGB2YCC(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageColorRGB2YCC_Fp mlib_ImageColorRGB2YCC_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageColorRGB2YCC_Fp(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageColorXYZ2RGB mlib_ImageColorXYZ2RGB -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageColorXYZ2RGB(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageColorXYZ2RGB_Fp mlib_ImageColorXYZ2RGB_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageColorXYZ2RGB_Fp(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageColorYCC2RGB mlib_ImageColorYCC2RGB -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageColorYCC2RGB(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageColorYCC2RGB_Fp mlib_ImageColorYCC2RGB_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageColorYCC2RGB_Fp(mlib_image *dst, - const mlib_image *src); - -/* Image Creation, Deletion and Query ( common ) */ - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageCreate mlib_ImageCreate -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -JNIEXPORT mlib_image * -__mlib_ImageCreate(mlib_type type, - mlib_s32 channels, - mlib_s32 width, - mlib_s32 height); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageCreateStruct mlib_ImageCreateStruct -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -JNIEXPORT mlib_image * -__mlib_ImageCreateStruct(mlib_type type, - mlib_s32 channels, - mlib_s32 width, - mlib_s32 height, - mlib_s32 stride, - const void *data); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageCreateSubimage mlib_ImageCreateSubimage -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_image * __mlib_ImageCreateSubimage(mlib_image *img, - mlib_s32 x, - mlib_s32 y, - mlib_s32 w, - mlib_s32 h); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageDelete mlib_ImageDelete -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -JNIEXPORT void -__mlib_ImageDelete(mlib_image *img); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageSetPaddings mlib_ImageSetPaddings -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageSetPaddings(mlib_image *img, - mlib_u8 left, - mlib_u8 top, - mlib_u8 right, - mlib_u8 bottom); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageSetFormat mlib_ImageSetFormat -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageSetFormat(mlib_image *img, - mlib_format format); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageGetType mlib_ImageGetType -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -static mlib_type __mlib_ImageGetType(const mlib_image *img); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageGetChannels mlib_ImageGetChannels -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -static mlib_s32 __mlib_ImageGetChannels(const mlib_image *img); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageGetWidth mlib_ImageGetWidth -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -static mlib_s32 __mlib_ImageGetWidth(const mlib_image *img); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageGetHeight mlib_ImageGetHeight -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -static mlib_s32 __mlib_ImageGetHeight(const mlib_image *img); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageGetStride mlib_ImageGetStride -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -static mlib_s32 __mlib_ImageGetStride(const mlib_image *img); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageGetData mlib_ImageGetData -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -static void * __mlib_ImageGetData(const mlib_image *img); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageGetFlags mlib_ImageGetFlags -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -static mlib_s32 __mlib_ImageGetFlags(const mlib_image *img); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageGetPaddings mlib_ImageGetPaddings -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -static mlib_u8 * __mlib_ImageGetPaddings(const mlib_image *img); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageGetBitOffset mlib_ImageGetBitOffset -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -static mlib_s32 __mlib_ImageGetBitOffset(const mlib_image *img); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageGetFormat mlib_ImageGetFormat -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -static mlib_format __mlib_ImageGetFormat(const mlib_image *img); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageIsNotAligned2 mlib_ImageIsNotAligned2 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -static mlib_s32 __mlib_ImageIsNotAligned2(const mlib_image *img); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageIsNotAligned4 mlib_ImageIsNotAligned4 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -static mlib_s32 __mlib_ImageIsNotAligned4(const mlib_image *img); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageIsNotAligned64 mlib_ImageIsNotAligned64 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -static mlib_s32 __mlib_ImageIsNotAligned64(const mlib_image *img); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageIsNotAligned8 mlib_ImageIsNotAligned8 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -static mlib_s32 __mlib_ImageIsNotAligned8(const mlib_image *img); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageIsNotHeight2X mlib_ImageIsNotHeight2X -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -static mlib_s32 __mlib_ImageIsNotHeight2X(const mlib_image *img); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageIsNotHeight4X mlib_ImageIsNotHeight4X -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -static mlib_s32 __mlib_ImageIsNotHeight4X(const mlib_image *img); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageIsNotHeight8X mlib_ImageIsNotHeight8X -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -static mlib_s32 __mlib_ImageIsNotHeight8X(const mlib_image *img); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageIsNotOneDvector mlib_ImageIsNotOneDvector -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -static mlib_s32 __mlib_ImageIsNotOneDvector(const mlib_image *img); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageIsNotStride8X mlib_ImageIsNotStride8X -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -static mlib_s32 __mlib_ImageIsNotStride8X(const mlib_image *img); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageIsNotWidth2X mlib_ImageIsNotWidth2X -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -static mlib_s32 __mlib_ImageIsNotWidth2X(const mlib_image *img); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageIsNotWidth4X mlib_ImageIsNotWidth4X -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -static mlib_s32 __mlib_ImageIsNotWidth4X(const mlib_image *img); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageIsNotWidth8X mlib_ImageIsNotWidth8X -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -static mlib_s32 __mlib_ImageIsNotWidth8X(const mlib_image *img); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageIsUserAllocated mlib_ImageIsUserAllocated -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -static mlib_s32 __mlib_ImageIsUserAllocated(const mlib_image *img); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageTestFlags mlib_ImageTestFlags -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -static mlib_s32 __mlib_ImageTestFlags(const mlib_image *img, - mlib_s32 flags); - -/* Image Copying and Clearing ( copy ) */ - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageClear mlib_ImageClear -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageClear(mlib_image *img, - const mlib_s32 *color); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageClearEdge mlib_ImageClearEdge -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageClearEdge(mlib_image *img, - mlib_s32 dx, - mlib_s32 dy, - const mlib_s32 *color); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageClearEdge_Fp mlib_ImageClearEdge_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageClearEdge_Fp(mlib_image *img, - mlib_s32 dx, - mlib_s32 dy, - const mlib_d64 *color); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageClear_Fp mlib_ImageClear_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageClear_Fp(mlib_image *img, - const mlib_d64 *color); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageCopy mlib_ImageCopy -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageCopy(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageCopyArea mlib_ImageCopyArea -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageCopyArea(mlib_image *img, - mlib_s32 x, - mlib_s32 y, - mlib_s32 w, - mlib_s32 h, - mlib_s32 dx, - mlib_s32 dy); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageCopyMask mlib_ImageCopyMask -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageCopyMask(mlib_image *dst, - const mlib_image *src, - const mlib_image *mask, - const mlib_s32 *thresh); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageCopyMask_Fp mlib_ImageCopyMask_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageCopyMask_Fp(mlib_image *dst, - const mlib_image *src, - const mlib_image *mask, - const mlib_d64 *thresh); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageCopySubimage mlib_ImageCopySubimage -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageCopySubimage(mlib_image *dst, - const mlib_image *src, - mlib_s32 xd, - mlib_s32 yd, - mlib_s32 xs, - mlib_s32 ys, - mlib_s32 w, - mlib_s32 h); - -/* Data Format Conversion ( format ) */ - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageChannelCopy mlib_ImageChannelCopy -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageChannelCopy(mlib_image *dst, - const mlib_image *src, - mlib_s32 cmask); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageChannelExtract mlib_ImageChannelExtract -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageChannelExtract(mlib_image *dst, - const mlib_image *src, - mlib_s32 cmask); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageChannelInsert mlib_ImageChannelInsert -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageChannelInsert(mlib_image *dst, - const mlib_image *src, - mlib_s32 cmask); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageChannelMerge mlib_ImageChannelMerge -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageChannelMerge(mlib_image *dst, - const mlib_image ** srcs); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageChannelSplit mlib_ImageChannelSplit -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageChannelSplit(mlib_image ** dsts, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageDataTypeConvert mlib_ImageDataTypeConvert -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageDataTypeConvert(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageReformat mlib_ImageReformat -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageReformat(void **dstData, - const void **srcData, - mlib_s32 numChannels, - mlib_s32 xSize, - mlib_s32 ySize, - mlib_type dstDataType, - const mlib_s32 *dstBandoffsets, - mlib_s32 dstScanlinestride, - mlib_s32 dstPixelstride, - mlib_type srcDataType, - const mlib_s32 *srcBandoffsets, - mlib_s32 srcScanlinestride, - mlib_s32 srcPixelstride); - -/* Fourier Transformation ( fourier ) */ - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageFourierTransform mlib_ImageFourierTransform -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageFourierTransform(mlib_image *dst, - const mlib_image *src, - mlib_fourier_mode mode); - -/* Geometric Operations ( geom ) */ - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageAffine mlib_ImageAffine -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -JNIEXPORT mlib_status -__mlib_ImageAffine(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *mtx, - mlib_filter filter, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageAffineTable mlib_ImageAffineTable -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageAffineTable(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *mtx, - const void *interp_table, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageAffineTable_Fp mlib_ImageAffineTable_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageAffineTable_Fp(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *mtx, - const void *interp_table, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageAffineTransform mlib_ImageAffineTransform -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageAffineTransform(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *mtx, - mlib_filter filter, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageAffineTransform_Fp mlib_ImageAffineTransform_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageAffineTransform_Fp(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *mtx, - mlib_filter filter, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageAffine_Fp mlib_ImageAffine_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageAffine_Fp(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *mtx, - mlib_filter filter, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageFilteredSubsample mlib_ImageFilteredSubsample -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageFilteredSubsample(mlib_image *dst, - const mlib_image *src, - mlib_s32 scaleX, - mlib_s32 scaleY, - mlib_s32 transX, - mlib_s32 transY, - const mlib_d64 *hKernel, - const mlib_d64 *vKernel, - mlib_s32 hSize, - mlib_s32 vSize, - mlib_s32 hParity, - mlib_s32 vParity, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageFilteredSubsample_Fp mlib_ImageFilteredSubsample_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageFilteredSubsample_Fp(mlib_image *dst, - const mlib_image *src, - mlib_s32 scaleX, - mlib_s32 scaleY, - mlib_s32 transX, - mlib_s32 transY, - const mlib_d64 *hKernel, - const mlib_d64 *vKernel, - mlib_s32 hSize, - mlib_s32 vSize, - mlib_s32 hParity, - mlib_s32 vParity, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageFlipAntiDiag mlib_ImageFlipAntiDiag -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageFlipAntiDiag(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageFlipAntiDiag_Fp mlib_ImageFlipAntiDiag_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageFlipAntiDiag_Fp(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageFlipMainDiag mlib_ImageFlipMainDiag -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageFlipMainDiag(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageFlipMainDiag_Fp mlib_ImageFlipMainDiag_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageFlipMainDiag_Fp(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageFlipX mlib_ImageFlipX -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageFlipX(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageFlipX_Fp mlib_ImageFlipX_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageFlipX_Fp(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageFlipY mlib_ImageFlipY -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageFlipY(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageFlipY_Fp mlib_ImageFlipY_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageFlipY_Fp(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageGridWarp mlib_ImageGridWarp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageGridWarp(mlib_image *dst, - const mlib_image *src, - const mlib_f32 *xWarpPos, - const mlib_f32 *yWarpPos, - mlib_d64 postShiftX, - mlib_d64 postShiftY, - mlib_s32 xStart, - mlib_s32 xStep, - mlib_s32 xNumCells, - mlib_s32 yStart, - mlib_s32 yStep, - mlib_s32 yNumCells, - mlib_filter filter, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageGridWarpTable mlib_ImageGridWarpTable -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageGridWarpTable(mlib_image *dst, - const mlib_image *src, - const mlib_f32 *xWarpPos, - const mlib_f32 *yWarpPos, - mlib_d64 postShiftX, - mlib_d64 postShiftY, - mlib_s32 xStart, - mlib_s32 xStep, - mlib_s32 xNumCells, - mlib_s32 yStart, - mlib_s32 yStep, - mlib_s32 yNumCells, - const void *table, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageGridWarpTable_Fp mlib_ImageGridWarpTable_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageGridWarpTable_Fp(mlib_image *dst, - const mlib_image *src, - const mlib_f32 *xWarpPos, - const mlib_f32 *yWarpPos, - mlib_d64 postShiftX, - mlib_d64 postShiftY, - mlib_s32 xStart, - mlib_s32 xStep, - mlib_s32 xNumCells, - mlib_s32 yStart, - mlib_s32 yStep, - mlib_s32 yNumCells, - const void *table, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageGridWarp_Fp mlib_ImageGridWarp_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageGridWarp_Fp(mlib_image *dst, - const mlib_image *src, - const mlib_f32 *xWarpPos, - const mlib_f32 *yWarpPos, - mlib_d64 postShiftX, - mlib_d64 postShiftY, - mlib_s32 xStart, - mlib_s32 xStep, - mlib_s32 xNumCells, - mlib_s32 yStart, - mlib_s32 yStep, - mlib_s32 yNumCells, - mlib_filter filter, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageInterpTableCreate mlib_ImageInterpTableCreate -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -void * __mlib_ImageInterpTableCreate(mlib_type type, - mlib_s32 width, - mlib_s32 height, - mlib_s32 leftPadding, - mlib_s32 topPadding, - mlib_s32 subsampleBitsH, - mlib_s32 subsampleBitsV, - mlib_s32 precisionBits, - const void *dataH, - const void *dataV); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageInterpTableDelete mlib_ImageInterpTableDelete -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -void __mlib_ImageInterpTableDelete(void *interp_table); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImagePolynomialWarp mlib_ImagePolynomialWarp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImagePolynomialWarp(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *xCoeffs, - const mlib_d64 *yCoeffs, - mlib_s32 n, - mlib_d64 preShiftX, - mlib_d64 preShiftY, - mlib_d64 postShiftX, - mlib_d64 postShiftY, - mlib_d64 preScaleX, - mlib_d64 preScaleY, - mlib_d64 postScaleX, - mlib_d64 postScaleY, - mlib_filter filter, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImagePolynomialWarpTable mlib_ImagePolynomialWarpTable -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImagePolynomialWarpTable(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *xCoeffs, - const mlib_d64 *yCoeffs, - mlib_s32 n, - mlib_d64 preShiftX, - mlib_d64 preShiftY, - mlib_d64 postShiftX, - mlib_d64 postShiftY, - mlib_d64 preScaleX, - mlib_d64 preScaleY, - mlib_d64 postScaleX, - mlib_d64 postScaleY, - const void *interp_table, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImagePolynomialWarpTable_Fp mlib_ImagePolynomialWarpTable_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImagePolynomialWarpTable_Fp(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *xCoeffs, - const mlib_d64 *yCoeffs, - mlib_s32 n, - mlib_d64 preShiftX, - mlib_d64 preShiftY, - mlib_d64 postShiftX, - mlib_d64 postShiftY, - mlib_d64 preScaleX, - mlib_d64 preScaleY, - mlib_d64 postScaleX, - mlib_d64 postScaleY, - const void *interp_table, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImagePolynomialWarp_Fp mlib_ImagePolynomialWarp_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImagePolynomialWarp_Fp(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *xCoeffs, - const mlib_d64 *yCoeffs, - mlib_s32 n, - mlib_d64 preShiftX, - mlib_d64 preShiftY, - mlib_d64 postShiftX, - mlib_d64 postShiftY, - mlib_d64 preScaleX, - mlib_d64 preScaleY, - mlib_d64 postScaleX, - mlib_d64 postScaleY, - mlib_filter filter, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageRotate mlib_ImageRotate -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageRotate(mlib_image *dst, - const mlib_image *src, - mlib_d64 angle, - mlib_d64 xcenter, - mlib_d64 ycenter, - mlib_filter filter, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageRotate180 mlib_ImageRotate180 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageRotate180(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageRotate180_Fp mlib_ImageRotate180_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageRotate180_Fp(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageRotate270 mlib_ImageRotate270 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageRotate270(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageRotate270_Fp mlib_ImageRotate270_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageRotate270_Fp(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageRotate90 mlib_ImageRotate90 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageRotate90(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageRotate90_Fp mlib_ImageRotate90_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageRotate90_Fp(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageRotate_Fp mlib_ImageRotate_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageRotate_Fp(mlib_image *dst, - const mlib_image *src, - mlib_d64 angle, - mlib_d64 xcenter, - mlib_d64 ycenter, - mlib_filter filter, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageSubsampleAverage mlib_ImageSubsampleAverage -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageSubsampleAverage(mlib_image *dst, - const mlib_image *src, - mlib_d64 scalex, - mlib_d64 scaley); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageSubsampleAverage_Fp mlib_ImageSubsampleAverage_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageSubsampleAverage_Fp(mlib_image *dst, - const mlib_image *src, - mlib_d64 scalex, - mlib_d64 scaley); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageSubsampleBinaryToGray mlib_ImageSubsampleBinaryToGray -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageSubsampleBinaryToGray(mlib_image *dst, - const mlib_image *src, - mlib_d64 xscale, - mlib_d64 yscale, - const mlib_u8 *lutGray); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageZoomIn2X mlib_ImageZoomIn2X -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageZoomIn2X(mlib_image *dst, - const mlib_image *src, - mlib_filter filter, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageZoomIn2X_Fp mlib_ImageZoomIn2X_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageZoomIn2X_Fp(mlib_image *dst, - const mlib_image *src, - mlib_filter filter, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageZoomOut2X mlib_ImageZoomOut2X -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageZoomOut2X(mlib_image *dst, - const mlib_image *src, - mlib_filter filter, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageZoomOut2X_Fp mlib_ImageZoomOut2X_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageZoomOut2X_Fp(mlib_image *dst, - const mlib_image *src, - mlib_filter filter, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageZoomTranslate mlib_ImageZoomTranslate -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageZoomTranslate(mlib_image *dst, - const mlib_image *src, - mlib_d64 zoomx, - mlib_d64 zoomy, - mlib_d64 tx, - mlib_d64 ty, - mlib_filter filter, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageZoomTranslateTable mlib_ImageZoomTranslateTable -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageZoomTranslateTable(mlib_image *dst, - const mlib_image *src, - mlib_d64 zoomx, - mlib_d64 zoomy, - mlib_d64 tx, - mlib_d64 ty, - const void *interp_table, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageZoomTranslateTable_Fp mlib_ImageZoomTranslateTable_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageZoomTranslateTable_Fp(mlib_image *dst, - const mlib_image *src, - mlib_d64 zoomx, - mlib_d64 zoomy, - mlib_d64 tx, - mlib_d64 ty, - const void *interp_table, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageZoomTranslateToGray mlib_ImageZoomTranslateToGray -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageZoomTranslateToGray(mlib_image *dst, - const mlib_image *src, - mlib_d64 zoomx, - mlib_d64 zoomy, - mlib_d64 tx, - mlib_d64 ty, - mlib_filter filter, - mlib_edge edge, - const mlib_s32 *ghigh, - const mlib_s32 *glow); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageZoomTranslate_Fp mlib_ImageZoomTranslate_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageZoomTranslate_Fp(mlib_image *dst, - const mlib_image *src, - mlib_d64 zoomx, - mlib_d64 zoomy, - mlib_d64 tx, - mlib_d64 ty, - mlib_filter filter, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageZoom mlib_ImageZoom -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageZoom(mlib_image *dst, - const mlib_image *src, - mlib_d64 zoomx, - mlib_d64 zoomy, - mlib_filter filter, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageZoom_Fp mlib_ImageZoom_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageZoom_Fp(mlib_image *dst, - const mlib_image *src, - mlib_d64 zoomx, - mlib_d64 zoomy, - mlib_filter filter, - mlib_edge edge); - -/* Logical Operations ( logic ) */ - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageAnd mlib_ImageAnd -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageAnd(mlib_image *dst, - const mlib_image *src1, - const mlib_image *src2); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageAndNot mlib_ImageAndNot -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageAndNot(mlib_image *dst, - const mlib_image *src1, - const mlib_image *src2); - -/* src1dst = src1dst & (~src2) */ - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageAndNot1_Inp mlib_ImageAndNot1_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageAndNot1_Inp(mlib_image *src1dst, - const mlib_image *src2); - -/* src2dst = src1 & (~src2dst) */ - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageAndNot2_Inp mlib_ImageAndNot2_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageAndNot2_Inp(mlib_image *src2dst, - const mlib_image *src1); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageAnd_Inp mlib_ImageAnd_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageAnd_Inp(mlib_image *src1dst, - const mlib_image *src2); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConstAnd mlib_ImageConstAnd -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConstAnd(mlib_image *dst, - const mlib_image *src, - const mlib_s32 *c); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConstAndNot mlib_ImageConstAndNot -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConstAndNot(mlib_image *dst, - const mlib_image *src, - const mlib_s32 *c); - -/* srcdst = (~srcdst) & c */ - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConstAndNot_Inp mlib_ImageConstAndNot_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConstAndNot_Inp(mlib_image *srcdst, - const mlib_s32 *c); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConstAnd_Inp mlib_ImageConstAnd_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConstAnd_Inp(mlib_image *srcdst, - const mlib_s32 *c); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConstNotAnd mlib_ImageConstNotAnd -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConstNotAnd(mlib_image *dst, - const mlib_image *src, - const mlib_s32 *c); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConstNotAnd_Inp mlib_ImageConstNotAnd_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConstNotAnd_Inp(mlib_image *srcdst, - const mlib_s32 *c); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConstNotOr mlib_ImageConstNotOr -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConstNotOr(mlib_image *dst, - const mlib_image *src, - const mlib_s32 *c); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConstNotOr_Inp mlib_ImageConstNotOr_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConstNotOr_Inp(mlib_image *srcdst, - const mlib_s32 *c); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConstNotXor mlib_ImageConstNotXor -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConstNotXor(mlib_image *dst, - const mlib_image *src, - const mlib_s32 *c); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConstNotXor_Inp mlib_ImageConstNotXor_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConstNotXor_Inp(mlib_image *srcdst, - const mlib_s32 *c); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConstOr mlib_ImageConstOr -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConstOr(mlib_image *dst, - const mlib_image *src, - const mlib_s32 *c); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConstOrNot mlib_ImageConstOrNot -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConstOrNot(mlib_image *dst, - const mlib_image *src, - const mlib_s32 *c); - -/* srcdst = (~srcdst) | c */ - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConstOrNot_Inp mlib_ImageConstOrNot_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConstOrNot_Inp(mlib_image *srcdst, - const mlib_s32 *c); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConstOr_Inp mlib_ImageConstOr_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConstOr_Inp(mlib_image *srcdst, - const mlib_s32 *c); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConstXor mlib_ImageConstXor -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConstXor(mlib_image *dst, - mlib_image *src, - mlib_s32 *c); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConstXor_Inp mlib_ImageConstXor_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConstXor_Inp(mlib_image *srcdst, - const mlib_s32 *c); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageNot mlib_ImageNot -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageNot(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageNotAnd mlib_ImageNotAnd -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageNotAnd(mlib_image *dst, - const mlib_image *src1, - const mlib_image *src2); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageNotAnd_Inp mlib_ImageNotAnd_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageNotAnd_Inp(mlib_image *src1dst, - const mlib_image *src2); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageNotOr mlib_ImageNotOr -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageNotOr(mlib_image *dst, - const mlib_image *src1, - const mlib_image *src2); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageNotOr_Inp mlib_ImageNotOr_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageNotOr_Inp(mlib_image *src1dst, - const mlib_image *src2); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageNotXor mlib_ImageNotXor -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageNotXor(mlib_image *dst, - const mlib_image *src1, - const mlib_image *src2); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageNotXor_Inp mlib_ImageNotXor_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageNotXor_Inp(mlib_image *src1dst, - const mlib_image *src2); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageNot_Inp mlib_ImageNot_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageNot_Inp(mlib_image *srcdst); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageOr mlib_ImageOr -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageOr(mlib_image *dst, - const mlib_image *src1, - const mlib_image *src2); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageOrNot mlib_ImageOrNot -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageOrNot(mlib_image *dst, - const mlib_image *src1, - const mlib_image *src2); - -/* src1dst = src1dst | (~src2) */ - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageOrNot1_Inp mlib_ImageOrNot1_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageOrNot1_Inp(mlib_image *src1dst, - const mlib_image *src2); - -/* src2dst = src1 | (~src2dst) */ - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageOrNot2_Inp mlib_ImageOrNot2_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageOrNot2_Inp(mlib_image *src2dst, - const mlib_image *src1); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageOr_Inp mlib_ImageOr_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageOr_Inp(mlib_image *src1dst, - const mlib_image *src2); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageXor mlib_ImageXor -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageXor(mlib_image *dst, - mlib_image *src1, - mlib_image *src2); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageXor_Inp mlib_ImageXor_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageXor_Inp(mlib_image *src1dst, - const mlib_image *src2); - -/* Radiometric Operations ( radio ) */ - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageColorReplace mlib_ImageColorReplace -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageColorReplace(mlib_image *dst, - const mlib_image *src, - const mlib_s32 *color1, - const mlib_s32 *color2); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageColorReplace_Fp mlib_ImageColorReplace_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageColorReplace_Fp(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *color1, - const mlib_d64 *color2); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageColorReplace_Fp_Inp mlib_ImageColorReplace_Fp_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageColorReplace_Fp_Inp(mlib_image *srcdst, - const mlib_d64 *color1, - const mlib_d64 *color2); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageColorReplace_Inp mlib_ImageColorReplace_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageColorReplace_Inp(mlib_image *srcdst, - const mlib_s32 *color1, - const mlib_s32 *color2); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageHistogram mlib_ImageHistogram -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageHistogram(mlib_s32 ** histo, - const mlib_image *img); +JNIEXPORT mlib_image * +mlib_ImageCreate(mlib_type type, + mlib_s32 channels, + mlib_s32 width, + mlib_s32 height); -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageHistogram2 mlib_ImageHistogram2 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageHistogram2(mlib_s32 ** histo, - const mlib_image *img, - const mlib_s32 *numBins, - const mlib_s32 *lowValue, - const mlib_s32 *highValue, - mlib_s32 xStart, - mlib_s32 yStart, - mlib_s32 xPeriod, - mlib_s32 yPeriod); +JNIEXPORT mlib_image * +mlib_ImageCreateStruct(mlib_type type, + mlib_s32 channels, + mlib_s32 width, + mlib_s32 height, + mlib_s32 stride, + const void *data); +JNIEXPORT void +mlib_ImageDelete(mlib_image *img); -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageLookUp mlib_ImageLookUp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ JNIEXPORT mlib_status -__mlib_ImageLookUp(mlib_image *dst, +mlib_ImageAffine(mlib_image *dst, const mlib_image *src, - const void **table); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageLookUp2 mlib_ImageLookUp2 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageLookUp2(mlib_image *dst, - const mlib_image *src, - const void **table, - const mlib_s32 *offsets, - mlib_s32 channels); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageLookUpMask mlib_ImageLookUpMask -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageLookUpMask(mlib_image *dst, - const mlib_image *src, - const void **table, - mlib_s32 channels, - mlib_s32 cmask); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageLookUp_Inp mlib_ImageLookUp_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageLookUp_Inp(mlib_image *srcdst, - const void **table); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageThresh1 mlib_ImageThresh1 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageThresh1(mlib_image *dst, - const mlib_image *src, - const mlib_s32 *thresh, - const mlib_s32 *ghigh, - const mlib_s32 *glow); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageThresh1_Fp mlib_ImageThresh1_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageThresh1_Fp(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *thresh, - const mlib_d64 *ghigh, - const mlib_d64 *glow); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageThresh1_Fp_Inp mlib_ImageThresh1_Fp_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageThresh1_Fp_Inp(mlib_image *srcdst, - const mlib_d64 *thresh, - const mlib_d64 *ghigh, - const mlib_d64 *glow); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageThresh1_Inp mlib_ImageThresh1_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageThresh1_Inp(mlib_image *srcdst, - const mlib_s32 *thresh, - const mlib_s32 *ghigh, - const mlib_s32 *glow); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageThresh2 mlib_ImageThresh2 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageThresh2(mlib_image *dst, - const mlib_image *src, - const mlib_s32 *thresh, - const mlib_s32 *glow); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageThresh2_Fp mlib_ImageThresh2_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageThresh2_Fp(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *thresh, - const mlib_d64 *glow); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageThresh2_Fp_Inp mlib_ImageThresh2_Fp_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageThresh2_Fp_Inp(mlib_image *srcdst, - const mlib_d64 *thresh, - const mlib_d64 *glow); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageThresh2_Inp mlib_ImageThresh2_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageThresh2_Inp(mlib_image *srcdst, - const mlib_s32 *thresh, - const mlib_s32 *glow); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageThresh3 mlib_ImageThresh3 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageThresh3(mlib_image *dst, - const mlib_image *src, - const mlib_s32 *thresh, - const mlib_s32 *ghigh); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageThresh3_Fp mlib_ImageThresh3_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageThresh3_Fp(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *thresh, - const mlib_d64 *ghigh); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageThresh3_Fp_Inp mlib_ImageThresh3_Fp_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageThresh3_Fp_Inp(mlib_image *srcdst, - const mlib_d64 *thresh, - const mlib_d64 *ghigh); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageThresh3_Inp mlib_ImageThresh3_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageThresh3_Inp(mlib_image *srcdst, - const mlib_s32 *thresh, - const mlib_s32 *ghigh); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageThresh4 mlib_ImageThresh4 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageThresh4(mlib_image *dst, - const mlib_image *src, - const mlib_s32 *thigh, - const mlib_s32 *tlow, - const mlib_s32 *ghigh, - const mlib_s32 *glow); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageThresh4_Fp mlib_ImageThresh4_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageThresh4_Fp(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *thigh, - const mlib_d64 *tlow, - const mlib_d64 *ghigh, - const mlib_d64 *glow); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageThresh4_Fp_Inp mlib_ImageThresh4_Fp_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageThresh4_Fp_Inp(mlib_image *srcdst, - const mlib_d64 *thigh, - const mlib_d64 *tlow, - const mlib_d64 *ghigh, - const mlib_d64 *glow); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageThresh4_Inp mlib_ImageThresh4_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageThresh4_Inp(mlib_image *srcdst, - const mlib_s32 *thigh, - const mlib_s32 *tlow, - const mlib_s32 *ghigh, - const mlib_s32 *glow); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageThresh5 mlib_ImageThresh5 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageThresh5(mlib_image *dst, - const mlib_image *src, - const mlib_s32 *thigh, - const mlib_s32 *tlow, - const mlib_s32 *gmid); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageThresh5_Fp mlib_ImageThresh5_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageThresh5_Fp(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *thigh, - const mlib_d64 *tlow, - const mlib_d64 *gmid); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageThresh5_Fp_Inp mlib_ImageThresh5_Fp_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageThresh5_Fp_Inp(mlib_image *srcdst, - const mlib_d64 *thigh, - const mlib_d64 *tlow, - const mlib_d64 *gmid); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageThresh5_Inp mlib_ImageThresh5_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageThresh5_Inp(mlib_image *srcdst, - const mlib_s32 *thigh, - const mlib_s32 *tlow, - const mlib_s32 *gmid); - -/* Linear and Not-Linear Spatial Operations, Morphological Operations ( spatial ) */ - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConv2x2 mlib_ImageConv2x2 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConv2x2(mlib_image *dst, - const mlib_image *src, - const mlib_s32 *kernel, - mlib_s32 scale, - mlib_s32 cmask, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConv2x2_Fp mlib_ImageConv2x2_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConv2x2_Fp(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *kernel, - mlib_s32 cmask, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConv3x3 mlib_ImageConv3x3 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConv3x3(mlib_image *dst, - const mlib_image *src, - const mlib_s32 *kernel, - mlib_s32 scale, - mlib_s32 cmask, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConv3x3_Fp mlib_ImageConv3x3_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConv3x3_Fp(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *kernel, - mlib_s32 cmask, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConv4x4 mlib_ImageConv4x4 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConv4x4(mlib_image *dst, - const mlib_image *src, - const mlib_s32 *kernel, - mlib_s32 scale, - mlib_s32 cmask, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConv4x4_Fp mlib_ImageConv4x4_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConv4x4_Fp(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *kernel, - mlib_s32 cmask, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConv5x5 mlib_ImageConv5x5 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConv5x5(mlib_image *dst, - const mlib_image *src, - const mlib_s32 *kernel, - mlib_s32 scale, - mlib_s32 cmask, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConv5x5_Fp mlib_ImageConv5x5_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConv5x5_Fp(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *kernel, - mlib_s32 cmask, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConv7x7 mlib_ImageConv7x7 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConv7x7(mlib_image *dst, - const mlib_image *src, - const mlib_s32 *kernel, - mlib_s32 scale, - mlib_s32 cmask, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConv7x7_Fp mlib_ImageConv7x7_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConv7x7_Fp(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *kernel, - mlib_s32 cmask, - mlib_edge edge); - + const mlib_d64 *mtx, + mlib_filter filter, + mlib_edge edge); -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConvKernelConvert mlib_ImageConvKernelConvert -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ JNIEXPORT mlib_status -__mlib_ImageConvKernelConvert(mlib_s32 *ikernel, +mlib_ImageConvKernelConvert(mlib_s32 *ikernel, mlib_s32 *iscale, const mlib_d64 *fkernel, mlib_s32 m, mlib_s32 n, mlib_type type); - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConvMxN mlib_ImageConvMxN -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ JNIEXPORT mlib_status -__mlib_ImageConvMxN(mlib_image *dst, +mlib_ImageConvMxN(mlib_image *dst, const mlib_image *src, const mlib_s32 *kernel, mlib_s32 m, @@ -2650,744 +85,10 @@ __mlib_ImageConvMxN(mlib_image *dst, mlib_s32 cmask, mlib_edge edge); - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConvolveMxN mlib_ImageConvolveMxN -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConvolveMxN(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *kernel, - mlib_s32 m, - mlib_s32 n, - mlib_s32 dm, - mlib_s32 dn, - mlib_s32 cmask, - mlib_edge edge); - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageDilate4 mlib_ImageDilate4 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageDilate4(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageDilate4_Fp mlib_ImageDilate4_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageDilate4_Fp(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageDilate8 mlib_ImageDilate8 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageDilate8(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageDilate8_Fp mlib_ImageDilate8_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageDilate8_Fp(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageErode4 mlib_ImageErode4 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageErode4(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageErode4_Fp mlib_ImageErode4_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageErode4_Fp(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageErode8 mlib_ImageErode8 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageErode8(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageErode8_Fp mlib_ImageErode8_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageErode8_Fp(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageGradient3x3 mlib_ImageGradient3x3 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageGradient3x3(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *hmask, - const mlib_d64 *vmask, - mlib_s32 cmask, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageGradient3x3_Fp mlib_ImageGradient3x3_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageGradient3x3_Fp(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *hmask, - const mlib_d64 *vmask, - mlib_s32 cmask, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageGradientMxN mlib_ImageGradientMxN -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageGradientMxN(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *hmask, - const mlib_d64 *vmask, - mlib_s32 m, - mlib_s32 n, - mlib_s32 dm, - mlib_s32 dn, - mlib_s32 cmask, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageGradientMxN_Fp mlib_ImageGradientMxN_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageGradientMxN_Fp(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *hmask, - const mlib_d64 *vmask, - mlib_s32 m, - mlib_s32 n, - mlib_s32 dm, - mlib_s32 dn, - mlib_s32 cmask, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMaxFilter3x3 mlib_ImageMaxFilter3x3 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMaxFilter3x3(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMaxFilter3x3_Fp mlib_ImageMaxFilter3x3_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMaxFilter3x3_Fp(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMaxFilter5x5 mlib_ImageMaxFilter5x5 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMaxFilter5x5(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMaxFilter5x5_Fp mlib_ImageMaxFilter5x5_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMaxFilter5x5_Fp(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMaxFilter7x7 mlib_ImageMaxFilter7x7 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMaxFilter7x7(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMaxFilter7x7_Fp mlib_ImageMaxFilter7x7_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMaxFilter7x7_Fp(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMedianFilter3x3 mlib_ImageMedianFilter3x3 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMedianFilter3x3(mlib_image *dst, - const mlib_image *src, - mlib_median_mask mmask, - mlib_s32 cmask, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMedianFilter3x3_Fp mlib_ImageMedianFilter3x3_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMedianFilter3x3_Fp(mlib_image *dst, - const mlib_image *src, - mlib_median_mask mmask, - mlib_s32 cmask, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMedianFilter3x3_US mlib_ImageMedianFilter3x3_US -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMedianFilter3x3_US(mlib_image *dst, - const mlib_image *src, - mlib_median_mask mmask, - mlib_s32 cmask, - mlib_edge edge, - mlib_s32 bits); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMedianFilter5x5 mlib_ImageMedianFilter5x5 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMedianFilter5x5(mlib_image *dst, - const mlib_image *src, - mlib_median_mask mmask, - mlib_s32 cmask, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMedianFilter5x5_Fp mlib_ImageMedianFilter5x5_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMedianFilter5x5_Fp(mlib_image *dst, - const mlib_image *src, - mlib_median_mask mmask, - mlib_s32 cmask, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMedianFilter5x5_US mlib_ImageMedianFilter5x5_US -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMedianFilter5x5_US(mlib_image *dst, - const mlib_image *src, - mlib_median_mask mmask, - mlib_s32 cmask, - mlib_edge edge, - mlib_s32 bits); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMedianFilter7x7 mlib_ImageMedianFilter7x7 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMedianFilter7x7(mlib_image *dst, - const mlib_image *src, - mlib_median_mask mmask, - mlib_s32 cmask, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMedianFilter7x7_Fp mlib_ImageMedianFilter7x7_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMedianFilter7x7_Fp(mlib_image *dst, - const mlib_image *src, - mlib_median_mask mmask, - mlib_s32 cmask, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMedianFilter7x7_US mlib_ImageMedianFilter7x7_US -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMedianFilter7x7_US(mlib_image *dst, - const mlib_image *src, - mlib_median_mask mmask, - mlib_s32 cmask, - mlib_edge edge, - mlib_s32 bits); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMedianFilterMxN mlib_ImageMedianFilterMxN -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMedianFilterMxN(mlib_image *dst, - const mlib_image *src, - mlib_s32 m, - mlib_s32 n, - mlib_median_mask mmask, - mlib_s32 cmask, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMedianFilterMxN_Fp mlib_ImageMedianFilterMxN_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMedianFilterMxN_Fp(mlib_image *dst, - const mlib_image *src, - mlib_s32 m, - mlib_s32 n, - mlib_median_mask mmask, - mlib_s32 cmask, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMedianFilterMxN_US mlib_ImageMedianFilterMxN_US -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMedianFilterMxN_US(mlib_image *dst, - const mlib_image *src, - mlib_s32 m, - mlib_s32 n, - mlib_median_mask mmask, - mlib_s32 cmask, - mlib_edge edge, - mlib_s32 bits); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMinFilter3x3 mlib_ImageMinFilter3x3 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMinFilter3x3(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMinFilter3x3_Fp mlib_ImageMinFilter3x3_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMinFilter3x3_Fp(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMinFilter5x5 mlib_ImageMinFilter5x5 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMinFilter5x5(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMinFilter5x5_Fp mlib_ImageMinFilter5x5_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMinFilter5x5_Fp(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMinFilter7x7 mlib_ImageMinFilter7x7 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMinFilter7x7(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMinFilter7x7_Fp mlib_ImageMinFilter7x7_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMinFilter7x7_Fp(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageRankFilter3x3 mlib_ImageRankFilter3x3 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageRankFilter3x3(mlib_image *dst, - const mlib_image *src, - mlib_s32 rank); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageRankFilter3x3_Fp mlib_ImageRankFilter3x3_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageRankFilter3x3_Fp(mlib_image *dst, - const mlib_image *src, - mlib_s32 rank); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageRankFilter3x3_US mlib_ImageRankFilter3x3_US -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageRankFilter3x3_US(mlib_image *dst, - const mlib_image *src, - mlib_s32 rank, - mlib_s32 bits); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageRankFilter5x5 mlib_ImageRankFilter5x5 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageRankFilter5x5(mlib_image *dst, - const mlib_image *src, - mlib_s32 rank); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageRankFilter5x5_Fp mlib_ImageRankFilter5x5_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageRankFilter5x5_Fp(mlib_image *dst, - const mlib_image *src, - mlib_s32 rank); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageRankFilter5x5_US mlib_ImageRankFilter5x5_US -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageRankFilter5x5_US(mlib_image *dst, - const mlib_image *src, - mlib_s32 rank, - mlib_s32 bits); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageRankFilter7x7 mlib_ImageRankFilter7x7 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageRankFilter7x7(mlib_image *dst, - const mlib_image *src, - mlib_s32 rank); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageRankFilter7x7_Fp mlib_ImageRankFilter7x7_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageRankFilter7x7_Fp(mlib_image *dst, - const mlib_image *src, - mlib_s32 rank); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageRankFilter7x7_US mlib_ImageRankFilter7x7_US -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageRankFilter7x7_US(mlib_image *dst, - const mlib_image *src, - mlib_s32 rank, - mlib_s32 bits); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageRankFilterMxN mlib_ImageRankFilterMxN -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageRankFilterMxN(mlib_image *dst, - const mlib_image *src, - mlib_s32 m, - mlib_s32 n, - mlib_s32 rank); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageRankFilterMxN_Fp mlib_ImageRankFilterMxN_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageRankFilterMxN_Fp(mlib_image *dst, - const mlib_image *src, - mlib_s32 m, - mlib_s32 n, - mlib_s32 rank); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageRankFilterMxN_US mlib_ImageRankFilterMxN_US -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageRankFilterMxN_US(mlib_image *dst, - const mlib_image *src, - mlib_s32 m, - mlib_s32 n, - mlib_s32 rank, - mlib_s32 bits); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageSConv3x3 mlib_ImageSConv3x3 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageSConv3x3(mlib_image *dst, - const mlib_image *src, - const mlib_s32 *hkernel, - const mlib_s32 *vkernel, - mlib_s32 scale, - mlib_s32 cmask, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageSConv3x3_Fp mlib_ImageSConv3x3_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageSConv3x3_Fp(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *hkernel, - const mlib_d64 *vkernel, - mlib_s32 cmask, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageSConv5x5 mlib_ImageSConv5x5 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageSConv5x5(mlib_image *dst, - const mlib_image *src, - const mlib_s32 *hkernel, - const mlib_s32 *vkernel, - mlib_s32 scale, - mlib_s32 cmask, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageSConv5x5_Fp mlib_ImageSConv5x5_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageSConv5x5_Fp(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *hkernel, - const mlib_d64 *vkernel, - mlib_s32 cmask, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageSConv7x7 mlib_ImageSConv7x7 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageSConv7x7(mlib_image *dst, - const mlib_image *src, - const mlib_s32 *hkernel, - const mlib_s32 *vkernel, - mlib_s32 scale, - mlib_s32 cmask, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageSConv7x7_Fp mlib_ImageSConv7x7_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageSConv7x7_Fp(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *hkernel, - const mlib_d64 *vkernel, - mlib_s32 cmask, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageSConvKernelConvert mlib_ImageSConvKernelConvert -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageSConvKernelConvert(mlib_s32 *ihkernel, - mlib_s32 *ivkernel, - mlib_s32 *iscale, - const mlib_d64 *fhkernel, - const mlib_d64 *fvkernel, - mlib_s32 m, - mlib_s32 n, - mlib_type type); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageSobel mlib_ImageSobel -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageSobel(mlib_image *dst, - const mlib_image *src, - mlib_s32 cmask, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageSobel_Fp mlib_ImageSobel_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageSobel_Fp(mlib_image *dst, - const mlib_image *src, - mlib_s32 cmask, - mlib_edge edge); - -/* Image Staistics ( stat ) */ - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageAutoCorrel mlib_ImageAutoCorrel -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageAutoCorrel(mlib_d64 *correl, - const mlib_image *img, - mlib_s32 dx, - mlib_s32 dy); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageAutoCorrel_Fp mlib_ImageAutoCorrel_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageAutoCorrel_Fp(mlib_d64 *correl, - const mlib_image *img, - mlib_s32 dx, - mlib_s32 dy); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageCrossCorrel mlib_ImageCrossCorrel -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageCrossCorrel(mlib_d64 *correl, - const mlib_image *img1, - const mlib_image *img2); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageCrossCorrel_Fp mlib_ImageCrossCorrel_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageCrossCorrel_Fp(mlib_d64 *correl, - const mlib_image *img1, - const mlib_image *img2); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageExtrema2 mlib_ImageExtrema2 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageExtrema2(mlib_s32 *min, - mlib_s32 *max, - const mlib_image *img, - mlib_s32 xStart, - mlib_s32 yStart, - mlib_s32 xPeriod, - mlib_s32 yPeriod); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageExtrema2_Fp mlib_ImageExtrema2_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageExtrema2_Fp(mlib_d64 *min, - mlib_d64 *max, - const mlib_image *img, - mlib_s32 xStart, - mlib_s32 yStart, - mlib_s32 xPeriod, - mlib_s32 yPeriod); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageExtremaLocations mlib_ImageExtremaLocations -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageExtremaLocations(mlib_s32 *min, - mlib_s32 *max, - const mlib_image *img, - mlib_s32 xStart, - mlib_s32 yStart, - mlib_s32 xPeriod, - mlib_s32 yPeriod, - mlib_s32 saveLocations, - mlib_s32 maxRuns, - mlib_s32 *minCounts, - mlib_s32 *maxCounts, - mlib_s32 **minLocations, - mlib_s32 **maxLocations, - mlib_s32 len); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageExtremaLocations_Fp mlib_ImageExtremaLocations_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageExtremaLocations_Fp(mlib_d64 *min, - mlib_d64 *max, - const mlib_image *img, - mlib_s32 xStart, - mlib_s32 yStart, - mlib_s32 xPeriod, - mlib_s32 yPeriod, - mlib_s32 saveLocations, - mlib_s32 maxRuns, - mlib_s32 *minCounts, - mlib_s32 *maxCounts, - mlib_s32 **minLocations, - mlib_s32 **maxLocations, - mlib_s32 len); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMaximum mlib_ImageMaximum -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMaximum(mlib_s32 *max, - const mlib_image *img); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMaximum_Fp mlib_ImageMaximum_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMaximum_Fp(mlib_d64 *max, - const mlib_image *img); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMean mlib_ImageMean -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMean(mlib_d64 *mean, - const mlib_image *img); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMean_Fp mlib_ImageMean_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMean_Fp(mlib_d64 *mean, - const mlib_image *img); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMinimum mlib_ImageMinimum -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMinimum(mlib_s32 *min, - const mlib_image *img); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMinimum_Fp mlib_ImageMinimum_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMinimum_Fp(mlib_d64 *min, - const mlib_image *img); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMoment2 mlib_ImageMoment2 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMoment2(mlib_d64 *moment, - const mlib_image *img); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMoment2_Fp mlib_ImageMoment2_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMoment2_Fp(mlib_d64 *moment, - const mlib_image *img); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageStdDev mlib_ImageStdDev -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageStdDev(mlib_d64 *sdev, - const mlib_image *img, - const mlib_d64 *mean); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageStdDev_Fp mlib_ImageStdDev_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageStdDev_Fp(mlib_d64 *sdev, - const mlib_image *img, - const mlib_d64 *mean); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageXProj mlib_ImageXProj -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageXProj(mlib_d64 *xproj, - const mlib_image *img); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageXProj_Fp mlib_ImageXProj_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageXProj_Fp(mlib_d64 *xproj, - const mlib_image *img); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageYProj mlib_ImageYProj -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageYProj(mlib_d64 *yproj, - const mlib_image *img); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageYProj_Fp mlib_ImageYProj_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageYProj_Fp(mlib_d64 *yproj, - const mlib_image *img); +JNIEXPORT mlib_status +mlib_ImageLookUp(mlib_image *dst, + const mlib_image *src, + const void **table); #ifdef __cplusplus } From 93376e455d8d321648994c9f91ea1c7cc55c513a Mon Sep 17 00:00:00 2001 From: Phil Race Date: Fri, 16 Oct 2026 19:52:48 +0000 Subject: [PATCH 085/169] 8392424: sun/font/StandardGlyphVector.getGlyphPosition accepts invalid negative indices Reviewed-by: kizune, serb, azvegint --- .../classes/sun/font/StandardGlyphVector.java | 12 ++++ .../awt/font/GlyphVector/GlyphVectorIOBE.java | 67 +++++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 test/jdk/java/awt/font/GlyphVector/GlyphVectorIOBE.java diff --git a/src/java.desktop/share/classes/sun/font/StandardGlyphVector.java b/src/java.desktop/share/classes/sun/font/StandardGlyphVector.java index 1c3ad7db47fd..d67c12580904 100644 --- a/src/java.desktop/share/classes/sun/font/StandardGlyphVector.java +++ b/src/java.desktop/share/classes/sun/font/StandardGlyphVector.java @@ -430,15 +430,27 @@ public Shape getOutline(float x, float y) { // relative to gv origin public Shape getGlyphOutline(int ix) { + if (ix < 0 || ix >= glyphs.length) { + throw new IndexOutOfBoundsException("ix = " + ix); + } + return getGlyphsOutline(ix, 1, 0, 0); } // relative to gv origin offset by x, y public Shape getGlyphOutline(int ix, float x, float y) { + if (ix < 0 || ix >= glyphs.length) { + throw new IndexOutOfBoundsException("ix = " + ix); + } + return getGlyphsOutline(ix, 1, x, y); } public Point2D getGlyphPosition(int ix) { + if (ix < 0 || ix > glyphs.length) { + throw new IndexOutOfBoundsException("ix = " + ix); + } + initPositions(); ix *= 2; diff --git a/test/jdk/java/awt/font/GlyphVector/GlyphVectorIOBE.java b/test/jdk/java/awt/font/GlyphVector/GlyphVectorIOBE.java new file mode 100644 index 000000000000..d381cb476e23 --- /dev/null +++ b/test/jdk/java/awt/font/GlyphVector/GlyphVectorIOBE.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @bug 8392424 + */ + +import java.awt.Font; +import java.awt.font.FontRenderContext; +import java.awt.font.GlyphVector; +import java.awt.geom.Point2D; + +public class GlyphVectorIOBE { + + static Font font = new Font(Font.DIALOG, Font.PLAIN, 12); + static FontRenderContext frc = new FontRenderContext(null, true, true); + static GlyphVector gv = font.createGlyphVector(frc, "A"); + + public static void main(String[] args) { + runTests(-1); + runTests(gv.getNumGlyphs()); + runTests(2); + runTests(Integer.MIN_VALUE); + + // test no exception for these cases. + gv.getGlyphPosition(gv.getNumGlyphs()); + gv.setGlyphPosition(gv.getNumGlyphs(), new Point2D.Float()); + } + + static void runTests(int ix) { + if (ix != gv.getNumGlyphs()) { + test(() -> gv.getGlyphPosition(ix)); + test(() -> gv.setGlyphPosition(ix, new Point2D.Float())); + } + test(() -> gv.getGlyphOutline(ix)); + test(() -> gv.getGlyphOutline(ix, 0f, 0f)); + } + + static void test(Runnable r) { + try { + r.run(); + throw new RuntimeException("Test failed for " + r); + } catch (IndexOutOfBoundsException e) { + } + } +} From cd37d2bb07b7a050b4925ed3312c2a8dd7a801b1 Mon Sep 17 00:00:00 2001 From: Alan Bateman Date: Sat, 17 Oct 2026 06:06:44 +0000 Subject: [PATCH 086/169] 8391460: Implement JEP 543: Structured Concurrency Reviewed-by: vklang --- .../StructureViolationException.java | 7 +- .../util/concurrent/StructuredTaskScope.java | 671 +++++++++--------- .../concurrent/StructuredTaskScopeImpl.java | 60 +- .../jdk/internal/javac/PreviewFeature.java | 1 - .../lang/ScopedValue/StressStackOverflow.java | 4 - .../StressCancellation.java | 1 - .../StructuredTaskScopeTest.java | 273 +++++-- .../StructuredThreadDumpTest.java | 1 - .../StructuredTaskScope/WithScopedValue.java | 14 +- .../auth/Subject/CallAsWithScopedValue.java | 2 - .../misc/ThreadFlock/ThreadFlockTest.java | 9 +- .../misc/ThreadFlock/WithScopedValue.java | 3 +- 12 files changed, 612 insertions(+), 434 deletions(-) diff --git a/src/java.base/share/classes/java/util/concurrent/StructureViolationException.java b/src/java.base/share/classes/java/util/concurrent/StructureViolationException.java index 36224483f8b3..d4525f7da66c 100644 --- a/src/java.base/share/classes/java/util/concurrent/StructureViolationException.java +++ b/src/java.base/share/classes/java/util/concurrent/StructureViolationException.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -24,16 +24,13 @@ */ package java.util.concurrent; -import jdk.internal.javac.PreviewFeature; - /** * Thrown when a structure violation is detected. * * @see StructuredTaskScope#close() * - * @since 21 + * @since 28 */ -@PreviewFeature(feature = PreviewFeature.Feature.STRUCTURED_CONCURRENCY) public final class StructureViolationException extends RuntimeException { @java.io.Serial private static final long serialVersionUID = -7705327650798235468L; diff --git a/src/java.base/share/classes/java/util/concurrent/StructuredTaskScope.java b/src/java.base/share/classes/java/util/concurrent/StructuredTaskScope.java index 39ad6074b313..b05354284c13 100644 --- a/src/java.base/share/classes/java/util/concurrent/StructuredTaskScope.java +++ b/src/java.base/share/classes/java/util/concurrent/StructuredTaskScope.java @@ -30,7 +30,6 @@ import java.util.function.Predicate; import java.util.function.Supplier; import java.util.function.UnaryOperator; -import jdk.internal.javac.PreviewFeature; /** * An API for structured concurrency. {@code StructuredTaskScope} supports cases @@ -44,40 +43,26 @@ * and open a new {@code StructuredTaskScope}. It defines the {@link #close() close()} * method to close it. The API is designed to be used with the {@code try}-with-resources * statement where a {@code StructuredTaskScope} is opened as a resource and then closed - * automatically. The code inside the {@code try} block uses the {@link #fork(Callable)} - * method to fork subtasks. Each call to the {@code fork(Callable)} method starts - * a new {@link Thread} (typically a {@linkplain Thread##virtual-threads virtual thread}) - * to execute a subtask as a {@linkplain Callable value-returning method}. The subtask - * executes concurrently with the code inside the {@code try} block, and concurrently with - * other subtasks forked in the scope. After forking all subtasks, the code inside the - * block uses the {@link #join() join()} method to wait for all subtasks to finish (or - * some other outcome) as a single operation. The code after the {@code join()} method - * processes the outcome. Execution does not continue beyond the {@code try} block (or - * {@code close} method) until all threads started in the scope to execute subtasks have - * finished. - * - *

    To ensure correct usage, the {@link #fork(Callable)}, {@link #join()} and {@link - * #close()} methods may only be invoked by the owner thread (the thread that - * opened the {@code StructuredTaskScope}), the {@code fork(Callable)} method may not be - * called after {@code join()}, the {@code join()} method must be invoked to get the outcome - * after forking subtasks, and the {@code close()} method throws an exception after closing - * if the owner did not invoke the {@code join()} method after forking subtasks. + * automatically. The code inside the {@code try} block forks subtasks with the + * {@link #fork(Callable)} or {@link #fork(Runnable)} methods. Each call to {@code + * fork(Callable)} starts a new {@link Thread} (typically a {@linkplain + * Thread##virtual-threads virtual thread}) to execute a {@linkplain Callable + * value-returning method} as a subtask. Each call to {@code fork(Runnable)} starts a new + * {@link Thread} to execute a method that does not return a result as a subtask. The + * threads executing subtasks run concurrently with each other and with the code inside + * the {@code try} block. After forking all subtasks, the code inside the block uses the + * {@link #join() join()} method to wait for all subtasks to finish (or for another + * outcome) as a single operation. The code after the {@code join()} method processes the + * outcome. Execution does not continue beyond the {@code try} block (or {@code close} + * method) until all threads started in the scope to execute subtasks have finished. * *

    As a first example, consider a "main" task that splits into two subtasks to * concurrently fetch values from two remote services. The main task aggregates the results * of both subtasks. The example invokes {@link #fork(Callable)} to fork the two subtasks. * Each call to {@code fork(Callable)} returns a {@link Subtask Subtask} as a handle to - * the forked subtask. Both subtasks may complete successfully, one subtask may succeed - * and the other may fail, or both subtasks may fail. + * the forked subtask. Both subtasks may succeed, one may succeed and the other fail, or + * both may fail. * - *

    The main task in the example is interested in the successful result from both - * subtasks. It waits in the {@link #join()} method for both subtasks to complete - * successfully or for either subtask to fail. If both subtasks complete successfully then - * the {@code join()} method completes normally and the task uses the {@link Subtask#get() - * Subtask.get()} method to get the result of each subtask. If one of the subtasks fails - * then the other subtask is cancelled, and the {@code join()} method throws {@link - * ExecutionException} with the exception from the failed subtask as the {@linkplain - * Throwable#getCause() cause}. * {@snippet lang=java : * // @link substring="open()" target="#open()" : * try (var scope = StructuredTaskScope.open()) { @@ -97,19 +82,40 @@ * } // close * } * + *

    The main task in the example is interested in the successful result of both + * subtasks. It waits in the {@link #join()} method for both subtasks to complete + * successfully or for either subtask to fail. If both subtasks complete successfully then + * the {@code join()} method completes normally and the task uses the {@link Subtask#get() + * Subtask.get()} method to get the result of each subtask. If one of the subtasks fails + * then the other subtask is cancelled, and the {@code join()} method throws {@link + * ExecutionException} with the exception from the failed subtask as the {@linkplain + * Throwable#getCause() cause}. + * *

    The {@link #close() close()} method always waits for threads executing subtasks to * finish, even if the {@code join()} method throws, so that execution cannot continue - * beyond the {@code close()} method until the interrupted threads finish. + * beyond the {@code close()} method until all subtasks have finished. + * + *

    To ensure correct usage, the following restrictions are enforced: + *

      + *
    • The {@code fork} methods, the {@link #join()} method and {@link #close()} method + * may only be invoked by the owner thread (the thread that opened the {@code + * StructuredTaskScope}). + *
    • The {@code join()} method must be invoked to get the outcome after forking + * subtasks (the {@code close} method throws an exception after closing if the owner + * did not invoke the {@code join} method after forking subtasks). + *
    • Neither {@code fork} method may be called after {@code join()} is invoked. + *
    * *

    To allow for cancellation, subtasks must be coded so that they finish as soon as - * possible when interrupted. Subtasks that do not respond to interrupt, e.g. block on - * methods that are not interruptible, may delay the {@link #close() close()} method - * indefinitely. + * possible when {@link Thread##thread-interruption interrupted}. Subtasks that do not + * respond to interrupt, e.g. block on methods that are not interruptible, may delay the + * {@link #close() close()} method indefinitely. * *

    In the example, the subtasks produce results of different types ({@code String} and - * {@code Integer}). In other cases the subtasks may all produce results of the same type. - * If the example had used {@code StructuredTaskScope.open()} to open the scope - * then it could only be used to fork subtasks that return a {@code String} result. + * {@code Integer}). In other cases where the subtasks produce results of the same type, + * the scope can be opened with that type as the subtask result type. For example, + * {@code StructuredTaskScope.open()} opens a scope that can only be used to + * fork subtasks that produce a {@code String} result. * *

    Joiners

    * @@ -124,28 +130,29 @@ * is an exception, a {@code Joiner} may cause {@code join()} to throw an exception * other than {@code ExecutionException}. * - *

    A {@code Joiner} may cancel the scope (sometimes - * called "short-circuiting") when some condition is reached, e.g. a subtask fails, that - * does not require the outcome of other subtasks that are still executing. Cancelling the - * scope prevents new threads from being started in the scope, cancels subtasks in the - * scope that have not completed execution, and causes the {@code join()} method to wake up - * with the outcome (result or exception). In the above example, the outcome is that {@code - * join()} completes normally when all subtasks succeed. The scope is cancelled if any - * subtask fails and {@code join()} throws {@code ExecutionException} with the exception - * from the failed subtask as the {@linkplain Throwable#getCause() cause}. Other {@code - * Joiner} implementations may cancel the scope for other reasons, and may cause {@code - * join()} to throw a different exception when the outcome is an exception. + *

    Cancellation: A {@code Joiner} may cancel + * the scope (sometimes called "short-circuiting") when some condition is reached, for + * example, when a subtask fails, and the outcomes (results or exceptions) of other + * subtasks that are still executing are not required. Cancelling the scope prevents new + * threads from being started in the scope, cancels subtasks in the scope that have not + * completed execution (this will {@linkplain Thread#interrupt() interrupt} the threads + * executing the subtasks) and causes the {@code join()} method to wake up with the outcome. + * In the above example, the outcome is that {@code join()} completes normally when all + * subtasks succeed. The scope is cancelled if any subtask fails and {@code join()} throws + * {@code ExecutionException} with the exception from a failed subtask as the {@linkplain + * Throwable#getCause() cause}. Other {@code Joiner} implementations may cancel the scope + * for other reasons, and may cause {@code join()} to throw a different exception when + * the outcome is an exception. * *

    The {@link Joiner Joiner} interface defines static factory methods to create a * {@code Joiner} for a number of common cases. The interface can be implemented when a * more advanced or custom policy is required. A {@code Joiner} that returns a * non-{@code null} result may remove the need for bookkeeping and the need to keep * a reference to {@code Subtask} objects returned by the {@link #fork(Callable)} method. - - *

    Now consider another example where a main task splits into two subtasks. In this - * example, each subtask produces a {@code String} result and the main task is only - * interested in the result from the first subtask to complete successfully. The example - * uses {@link Joiner#anySuccessfulOrThrow() Joiner.anySuccessfulOrThrow()} to create a + * + *

    Now consider an example where a main task splits into two subtasks and where the + * main task is interested in the successful outcome of either subtask. The example uses + * {@link Joiner#anySuccessfulOrThrow() Joiner.anySuccessfulOrThrow()} to create a * {@code Joiner} that produces the result of any subtask that completes successfully. * {@snippet lang=java : * // @link substring="anySuccessfulOrThrow()" target="Joiner#anySuccessfulOrThrow()" : @@ -156,23 +163,22 @@ * scope.fork(callable2); * * // throws ExecutionException if both subtasks fail - * String firstResult = scope.join(); // @link substring="join" target="#join()" + * String anyResult = scope.join(); // @link substring="join" target="#join()" * * // @link substring="close" target="#close()" : * } // close * } * - *

    In the example, the task forks the two subtasks, then waits in the {@code - * join()} method for either subtask to complete successfully or for both subtasks to fail. - * If one of the subtasks completes successfully then the {@code Joiner} causes the other - * subtask to be cancelled (this will interrupt the thread executing the subtask), and - * the {@code join()} method returns the result from the successful subtask. Cancelling the - * other subtask avoids the task waiting for a result that it doesn't care about. If - * both subtasks fail then the {@code join()} method throws {@link ExecutionException} with - * the exception from one of the subtasks as the {@linkplain Throwable#getCause() cause}. - * {@link Joiner#anySuccessfulOrThrow(Function) Joiner.anySuccessfulOrThrow(Function)} can - * be used with a function that produces an exception other than {@code ExecutionException} - * to throw when all subtasks fail. + *

    The main task waits in the {@code join()} method for either subtask to complete + * successfully or for both subtasks to fail. If one of the subtasks completes successfully, + * then the {@code Joiner} causes the other subtask to be cancelled (this will interrupt + * the thread executing the subtask), and the {@code join()} method returns the result of + * the successful subtask. Cancelling the other subtask avoids the task waiting for a + * result that it doesn't care about. If both subtasks fail, then the {@code join()} method + * throws {@link ExecutionException} with the exception from one of the subtasks as the + * {@linkplain Throwable#getCause() cause}. {@link Joiner#anySuccessfulOrThrow(Function) + * Joiner.anySuccessfulOrThrow(Function)} can be used with a function that produces an + * exception other than {@code ExecutionException} to throw when all subtasks fail. * *

    Configuration

    * @@ -189,10 +195,10 @@ *

    The {@link #open(UnaryOperator)} and {@link #open(Joiner, UnaryOperator)} methods * can be used to create a {@code StructuredTaskScope} that uses a different {@code * ThreadFactory}, is named for monitoring and management purposes, or has a timeout that - * cancels the scope if the timeout expires before or while waiting for subtasks to - * complete. The {@code open} methods are called with an {@linkplain UnaryOperator operator} - * that is applied to the default configuration and returns a {@link Configuration - * Configuration} for the {@code StructuredTaskScope} under construction. + * cancels the scope if the timeout expires before the {@code join()} method is invoked + * or completes. The {@code open} methods are called with an {@linkplain UnaryOperator + * operator} that is applied to the default configuration and returns a {@link + * Configuration Configuration} for the {@code StructuredTaskScope} under construction. * *

    The following example opens a new {@code StructuredTaskScope} with a {@code * ThreadFactory} that creates virtual threads {@linkplain Thread#getName() named} @@ -215,8 +221,8 @@ *} * *

    A second example sets a timeout, represented by a {@link Duration}. The timeout - * starts when the new scope is opened. If the timeout expires before or while waiting in - * the {@link #join()} method then the scope is {@linkplain ##Cancellation cancelled} + * starts when the new scope is opened. If the timeout expires before the {@code join()} + * method is invoked or completes then the scope is {@linkplain ##Cancellation cancelled} * (this interrupts the threads executing the subtasks that have not completed), and the * {@code join()} method throws {@link ExecutionException} with a {@link * CancelledByTimeoutException CancelledByTimeoutException} as the cause. @@ -239,18 +245,19 @@ * *

    Exception handling

    * - *

    The outcome of the {@link #join()} method is a result or exception. When the outcome - * is an exception then its {@linkplain Throwable#getCause() cause} will typically be - * the exception from a failed subtask or {@link CancelledByTimeoutException + *

    The outcome of the {@link #join()} method is a result or exception. When the + * outcome is an exception, its {@linkplain Throwable#getCause() cause} will typically + * be the exception from a failed subtask or {@link CancelledByTimeoutException * CancelledByTimeoutException} if a timeout was configured. * *

    In some cases it may be useful to add a {@code catch} block to the * {@code try}-with-resources statement to handle the exception. The following example * uses the {@link #open(UnaryOperator)} method to open a scope with a timeout configured. * The {@code join()} method in this example throws {@link ExecutionException} if any - * subtask fails or the timeout expires. The exception cause is the exception from a failed - * subtask or {@code CancelledByTimeoutException}. The example uses the {@code switch} - * statement to select based on the cause. + * subtask fails or the timeout expires before the {@code join()} method is invoked or + * completes. The exception cause is the exception from a failed subtask or {@code + * CancelledByTimeoutException}. The example uses the {@code switch} statement to select + * based on the cause. * {@snippet lang=java : * try (var scope = StructuredTaskScope.open(cf -> cf.withTimeout(timeout))) { * @@ -273,9 +280,9 @@ * may be more appropriate to handle this in the subtask itself rather than the subtask * failing and the scope owner handling the exception. * - *

    The {@link #join()} method throws {@link InterruptedException} when interrupted - * before or while waiting in the {@code join()} method. - * The {@link Thread##thread-interruption Thread Interruption} section of the {@code Thread} + *

    The {@link #join()} method throws {@link InterruptedException} if the owner thread + * is interrupted while waiting in the {@code join()} method. The {@link + * Thread##thread-interruption Thread Interruption} section of the {@code Thread} * specification provides guidance on handling this exception. * *

    Inheritance of scoped value bindings

    @@ -298,7 +305,7 @@ * use structured concurrency. * *

    To ensure correctness, opening a new {@code StructuredTaskScope} captures the - * current thread's scoped value bindings. These are the scoped values bindings that are + * current thread's scoped value bindings. These are the scoped value bindings that are * inherited by the threads created to execute subtasks in the scope. Forking a * subtask checks that the bindings in effect at the time that the subtask is forked * match the bindings when the {@code StructuredTaskScope} was created. This check ensures @@ -311,10 +318,10 @@ *

    The following example demonstrates the inheritance of scoped value bindings. The * scoped value USERNAME is bound to the value "duke" for the bounded period of a lambda * expression by the thread executing it. The code in the block opens a {@code - * StructuredTaskScope} and forks two subtasks, it then waits in the {@code join()} method + * StructuredTaskScope} and forks two subtasks, then waits in the {@code join()} method * and aggregates the results from both subtasks. If code executed by the threads - * running subtask1 and subtask2 uses {@link ScopedValue#get()}, to get the value of - * USERNAME, then value "duke" will be returned. + * running subtask1 and subtask2 uses {@link ScopedValue#get()} to get the value of + * USERNAME, then the value "duke" will be returned. * {@snippet lang=java : * // @link substring="newInstance" target="ScopedValue#newInstance()" : * private static final ScopedValue USERNAME = ScopedValue.newInstance(); @@ -368,9 +375,8 @@ * @param the type of the result returned by the {@link #join() join()} method * @param the type of the exception thrown by the {@link #join() join()} method * @jls 17.4.5 Happens-before Order - * @since 21 + * @since 28 */ -@PreviewFeature(feature = PreviewFeature.Feature.STRUCTURED_CONCURRENCY) public sealed interface StructuredTaskScope extends AutoCloseable permits StructuredTaskScopeImpl { @@ -381,26 +387,23 @@ public sealed interface StructuredTaskScope * *

    The scope owner can use the {@link #get() get()} method after {@linkplain * #join() joining} to obtain the result of a subtask that completed successfully. It - * can use the {@link #exception()} method to obtain the exception thrown by a subtask - * that failed. + * can use the {@link #exception()} method to obtain the exception of a failed subtask. * * @param the result type - * @since 21 + * @since 28 */ - @PreviewFeature(feature = PreviewFeature.Feature.STRUCTURED_CONCURRENCY) sealed interface Subtask extends Supplier permits StructuredTaskScopeImpl.SubtaskImpl { /** * Represents the state of a {@link Subtask Subtask}. * @see Subtask#state() - * @since 21 + * @since 28 */ - @PreviewFeature(feature = PreviewFeature.Feature.STRUCTURED_CONCURRENCY) enum State { /** - * The subtask result or exception is not available. This state indicates that - * the subtask was forked but has not completed, the subtask was forked after + * The subtask result or exception is not available. This state does not + * distinguish whether the subtask is still executing, was not started because * the scope was {@linkplain StructuredTaskScope##Cancellation cancelled}, or - * the subtask completed after the scope was cancelled. + * completed after the scope was cancelled. */ UNAVAILABLE, /** @@ -412,10 +415,12 @@ enum State { */ SUCCESS, /** - * The subtask failed with an exception. If the scope is {@linkplain - * StructuredTaskScope##Cancellation cancelled}, the subtask failed before - * the scope was cancelled. The {@link Subtask#exception() Subtask.exception()} - * method can be used to get the exception. This is a terminal state. + * The subtask failed with an exception or error, or a thread could not be + * created to execute the subtask. If the scope is {@linkplain + * StructuredTaskScope##Cancellation cancelled}, the subtask failed or + * the {@code fork} method failed before the scope was cancelled. The + * {@link Subtask#exception() Subtask.exception()} method can be used to + * get the exception or error. This is a terminal state. */ FAILED, } @@ -429,20 +434,19 @@ enum State { * Returns the result of this subtask if it completed successfully. If the scope * is {@linkplain StructuredTaskScope##Cancellation cancelled}, the subtask * completed successfully before the scope was cancelled. If the subtask was - * forked with {@link #fork(Callable) fork(Callable)} then the result from the + * forked with {@link #fork(Callable) fork(Callable)} then the result of the * {@link Callable#call() call()} method is returned. If the subtask was forked * with {@link #fork(Runnable) fork(Runnable)} then {@code null} is returned. * - *

    Code executing in the scope owner thread can use this method to get the - * result of a successful subtask after it has {@linkplain #join() joined}. - * - *

    Code executing in the {@code Joiner} {@link Joiner#onComplete(Subtask) - * onComplete(Subtask)} method should test that the {@linkplain #state() state} is - * {@link State#SUCCESS SUCCESS} before using this method to get the result. - * *

    This method may be invoked by any thread after the scope owner has joined. - * The only case where this method can be used to get the result before the scope - * owner has joined is when called from the {@code onComplete(Subtask)} method. + * The only case where this method may be used to get the result before the scope + * owner has joined is when called from a {@link Joiner Joiner}'s {@link + * Joiner#onComplete(Subtask) onComplete(Subtask)} method. The {@code + * onComplete(Subtask)} method should test that the {@linkplain #state() state} + * is {@link State#SUCCESS SUCCESS} before invoking the {@code get()} method to + * get the result. + * + * @apiNote This method does not wait for the subtask to complete. * * @return the possibly-null result * @throws IllegalStateException if the subtask has not completed or did not @@ -453,24 +457,20 @@ enum State { T get(); /** - * {@return the exception or error thrown by this subtask if it failed} + * {@return the exception or error for this subtask if it failed} * If the scope is {@linkplain StructuredTaskScope##Cancellation cancelled}, the - * subtask failed before the scope was cancelled. If the subtask was forked with - * {@link #fork(Callable) fork(Callable)} then the exception or error thrown by - * the {@link Callable#call() call()} method is returned. If the subtask was - * forked with {@link #fork(Runnable) fork(Runnable)} then the exception or error - * thrown by the {@link Runnable#run() run()} method is returned. - * - *

    Code executing in the scope owner thread can use this method to get the - * exception thrown by a failed subtask after it has {@linkplain #join() joined}. - * - *

    Code executing in a {@code Joiner} {@link Joiner#onComplete(Subtask) - * onComplete(Subtask)} method should test that the {@linkplain #state() state} is - * {@link State#FAILED FAILED} before using this method to get the exception. + * subtask failed before the scope was cancelled. If the subtask executed, then + * the exception or error thrown by the {@link Callable#call() Callable.call()} + * or {@link Runnable#run() Runnable.run()} method is returned. * *

    This method may be invoked by any thread after the scope owner has joined. - * The only case where this method can be used to get the exception before the scope - * owner has joined is when called from the {@code onComplete(Subtask)} method. + * The only case where this method may be used before the scope owner has joined + * is when called from a {@link Joiner Joiner}'s {@link Joiner#onComplete(Subtask) + * onComplete(Subtask)} method. The {@code onComplete(Subtask)} method should + * test that the {@linkplain #state() state} is {@link State#FAILED FAILED} + * before invoking this method. + * + * @apiNote This method does not wait for the subtask to complete. * * @throws IllegalStateException if the subtask has not completed or completed * with a result, or this method is invoked outside the context of the {@code @@ -505,7 +505,8 @@ enum State { * exception returned by an exception supplying function, if any subtask fails. *

  • {@link #anySuccessfulOrThrow()} and {@link #anySuccessfulOrThrow(Function)} * create a {@code Joiner} that produces the result of any successful subtask for - * {@code join()} to return. The {@code Joiner} causes {@code join()} to throw + * {@code join()} to return. The {@code Joiner} cancels the scope when any subtask + * completes successfully. The {@code Joiner} causes {@code join()} to throw * {@code ExecutionException}, or an exception returned by an exception supplying * function, if all subtasks fail. *
  • {@link #awaitAllSuccessfulOrThrow()} and {@link #awaitAllSuccessfulOrThrow(Function)} @@ -527,13 +528,14 @@ enum State { * *

    More advanced policies can be developed by implementing the {@code Joiner} * interface. The {@link #onFork(Subtask)} method is invoked when subtasks are forked. - * The {@link #onComplete(Subtask)} method is invoked when subtasks complete with a - * result or exception. These methods return a {@code boolean} to indicate whether the - * scope should be cancelled. These methods can be used to collect subtasks, results, - * or exceptions, and control when to cancel the scope. The {@link #result()} method - * must be implemented to produce the outcome (result or exception) for the {@code - * join()} method. The {@link #timeout()} method must be implemented to produce the - * outcome for the {@linkplain Configuration#withTimeout(Duration) timeout} case. + * The {@link #onComplete(Subtask)} method is invoked when subtasks complete or when + * a thread cannot be created to execute a subtask. These methods return a {@code + * boolean} to indicate whether the scope should be cancelled. These methods can be + * used to collect subtasks, results, or exceptions, and control when to cancel the + * scope. The {@link #result()} method must be implemented to produce the outcome + * (result or exception) for the {@code join()} method. The {@link #timeout()} method + * must be implemented to produce the outcome for the + * {@linkplain Configuration#withTimeout(Duration) timeout} case. * *

    Unless otherwise specified, passing a {@code null} argument to a method * in this class will cause a {@link NullPointerException} to be thrown. @@ -548,37 +550,35 @@ enum State { * Configuration#withTimeout(Duration) timeouts}. In many cases, a timeout will cause * the {@code join()} method to throw an exception with {@link * CancelledByTimeoutException CancelledByTimeoutException} as the cause. Some - * {@code Joiner} implementation may be capable of returning a result for the + * {@code Joiner} implementations may be capable of returning a result for the * timeout case. * - *

    Designing a {@code Joiner} should take into account the code at the use-site + *

    Designing a {@code Joiner} should take into account the code at the use site * where the results from the {@link StructuredTaskScope#join() join()} method are - * processed. It should be clear what the {@code Joiner} does vs. the application - * code at the use-site. In general, the {@code Joiner} implementation is not the - * place for "business logic". A {@code Joiner} should be designed to be as general - * purpose as possible. + * processed. It should be clear what the {@code Joiner} does as opposed to the + * application code at the use site. In general, the {@code Joiner} implementation is + * not the place for "business logic". A {@code Joiner} should be designed to be as + * general purpose as possible. * * @apiNote It is very important that a new {@code Joiner} object is created for each * {@code StructuredTaskScope}. {@code Joiner} objects should never be shared with - * different scopes or re-used after a scope is closed. + * different scopes or reused after a scope is closed. * * @param the result type of subtasks {@linkplain #fork(Callable) forked} in the scope * @param the type of the result returned by the {@link #join() join()} method * @param the type of the exception thrown by the {@link #join() join()} method - * @since 25 + * @since 28 * @see #open(Joiner) */ - @PreviewFeature(feature = PreviewFeature.Feature.STRUCTURED_CONCURRENCY) interface Joiner { /** - * Invoked by {@link #fork(Callable) fork(Callable)} and {@link #fork(Runnable) - * fork(Runnable)} when forking a subtask. The method is invoked before a thread - * is created to execute the subtask. + * Invoked by the scope owner thread when {@linkplain #fork(Callable) forking} a + * subtask. The method is invoked before a thread is created to execute the subtask. * * @implSpec The default implementation throws {@code NullPointerException} if the * subtask is {@code null}. It throws {@code IllegalArgumentException} if the - * subtask is not in the {@link Subtask.State#UNAVAILABLE UNAVAILABLE} state, it - * otherwise returns {@code false}. + * subtask is not in the {@link Subtask.State#UNAVAILABLE UNAVAILABLE} state, + * and otherwise returns {@code false}. * * @apiNote This method is invoked by the {@code fork} methods. It should not be * invoked directly. @@ -594,18 +594,27 @@ default boolean onFork(Subtask subtask) { } /** - * Invoked by the thread that executed a subtask after the subtask completes - * successfully or fails with an exception. This method is not invoked by subtasks - * that complete after the scope is {@linkplain StructuredTaskScope##Cancellation - * cancelled}. + * Invoked by the thread that executed a subtask after the subtask completes, or + * by the scope owner thread when {@linkplain #fork(Callable) forking} is unable + * to create a thread to execute the subtask. + * + *

    This method is not invoked for subtasks that are forked or complete after + * the scope is {@linkplain StructuredTaskScope##Cancellation cancelled}. + * + *

    If {@link #fork(Callable) fork(Callable)} or {@link #fork(Runnable) + * fork(Runnable)} fails to create a thread to execute a subtask, the {@code + * fork} method invokes this method directly with the subtask in the {@link + * Subtask.State#FAILED FAILED} state. In this case, the {@link + * Subtask#exception() Subtask.exception()} method returns the exception or error + * that prevented the thread from being created. * * @implSpec The default implementation throws {@code NullPointerException} if the * subtask is {@code null}. It throws {@code IllegalArgumentException} if the * subtask is not in the {@link Subtask.State#SUCCESS SUCCESS} or {@link - * Subtask.State#FAILED FAILED} state, it otherwise returns {@code false}. + * Subtask.State#FAILED FAILED} state, and otherwise returns {@code false}. * - * @apiNote This method is invoked by subtasks when they complete. It should not - * be invoked directly. + * @apiNote This method is invoked by the thread that executed a subtask or by a + * {@code fork} method. It should not be invoked directly. * * @param subtask the subtask * @return {@code true} to cancel the scope, otherwise {@code false} @@ -618,13 +627,15 @@ default boolean onComplete(Subtask subtask) { } /** - * Invoked by the {@link #join() join()} method to produce the outcome (result or - * exception) after waiting for all subtasks to complete or the scope is {@linkplain + * Invoked by the {@link #join() join()} method to produce the outcome (result + * or exception) after all subtasks complete or the scope is {@linkplain * StructuredTaskScope##Cancellation cancelled}. This method is not invoked if the - * scope was opened with a timeout and the timeout expires before or while waiting. + * scope was opened with a timeout and the timeout expires before the {@code join()} + * method is invoked or completes. * - *

    This method will be called at most once, by the {@code join()} method, to - * produce the outcome. The behavior of this method when invoked directly is undefined. + *

    This method will be called at most once by the {@code join()} method to + * produce the outcome. The behavior of this method when invoked directly is + * undefined. * * @apiNote This method is invoked by the {@code join()} method. It should not be * invoked directly. @@ -636,15 +647,16 @@ default boolean onComplete(Subtask subtask) { /** * Invoked by the {@link #join() join()} method to produce the outcome (result or - * exception) when the scope was opened with a timeout and the timeout expires before - * or while waiting in the {@code join()} method. + * exception) when the scope was opened with a timeout and the timeout expires + * before the {@code join()} method is invoked or completes. * *

    If the outcome is an exception, this method throws the exception with a * {@link CancelledByTimeoutException CancelledByTimeoutException} as the * {@link Throwable#getCause() cause}. * - *

    This method will be called at most once, by the {@code join()} method, to - * produce the outcome. The behavior of this method when invoked directly is undefined. + *

    This method will be called at most once by the {@code join()} method to + * produce the outcome. The behavior of this method when invoked directly is + * undefined. * * @apiNote This method is invoked by the {@code join()} method. It should not be * invoked directly. @@ -652,7 +664,6 @@ default boolean onComplete(Subtask subtask) { * @return the result * @throws R_X with a cause of {@code CancelledByTimeoutException}, if the outcome * is an exception - * @since 27 */ R timeout() throws R_X; @@ -668,7 +679,7 @@ default boolean onComplete(Subtask subtask) { * An empty list is returned if no subtasks were forked. If any subtask fails then * the {@code Joiner} causes the {@code join()} method to throw the exception * returned by the given exception supplying function when {@linkplain - * Function#apply(Object) applied} to the exception from the first subtask to fail. + * Function#apply(Object) applied} to the exception from a failed subtask. * The function should return an exception with the exception from the failed * subtask (the function argument) as the {@linkplain Throwable#getCause() cause}. * If the function returns {@code null} then it causes the {@code join()} method @@ -676,19 +687,20 @@ default boolean onComplete(Subtask subtask) { * *

    Timeout Handling: The {@code Joiner} cannot produce a result when * the scope is cancelled by a timeout. If the scope was opened with a {@linkplain - * Configuration#withTimeout(Duration) timeout}, and the timeout expires before or - * while waiting for all subtasks to complete successfully, then the {@code Joiner} - * causes the {@code join()} method to throw the exception returned by the - * exception supplying function when applied to a {@link CancelledByTimeoutException + * Configuration#withTimeout(Duration) timeout}, and the timeout expires before + * the {@code join()} method is invoked or completes, then the {@code Joiner} + * causes the {@code join()} method to throw the exception returned by the exception + * supplying function when applied to a {@link CancelledByTimeoutException * CancelledByTimeoutException}. * - * @apiNote Joiners returned by this method are suited to cases where all subtasks - * return a result of the same type. It removes the need for bookkeeping - * and the need to keep a reference to the {@link Subtask Subtask} objects returned - * by the {@link #fork(Callable) fork(Callable)} method. Joiners returned by {@link - * #awaitAllSuccessfulOrThrow(Function)} and {@link #awaitAllSuccessfulOrThrow()} - * are suited to cases where the subtasks return results of different types and - * where it is necessary to keep a reference to the {@code Subtask} objects. + * @apiNote A Joiner returned by this method is suited to cases where all subtasks + * return a result of the same type. The Joiner removes the need for + * bookkeeping and the need to keep a reference to the {@link Subtask Subtask} + * objects returned by the {@link #fork(Callable) fork(Callable)} method. Joiners + * returned by {@link #awaitAllSuccessfulOrThrow(Function)} and + * {@link #awaitAllSuccessfulOrThrow()} are suited to cases where the subtasks + * return results of different types and where it is necessary to keep a reference + * to the {@code Subtask} objects. * *

    The following example is a method that opens a {@code StructuredTaskScope} * with a Joiner created with {@code allSuccessfulOrThrow(Function)}. The method @@ -696,8 +708,7 @@ default boolean onComplete(Subtask subtask) { * #fork(Callable) forks} a subtask to execute each callable, waits in {@link * #join() join()} for all subtasks to complete successfully, and then returns a * list of the results. It throws the runtime exception {@link CompletionException} - * if any subtask fails, with the exception from the first subtask to fail as the - * cause. + * if any subtask fails, with the exception from a failed subtask as the cause. * {@snippet lang=java : * List invokeAll(Collection> tasks) throws InterruptedException { * try (var scope = StructuredTaskScope.open( @@ -712,7 +723,6 @@ default boolean onComplete(Subtask subtask) { * @param esf the exception supplying function * @param the result type of subtasks * @param the type of the exception thrown by the {@link #join() join()} method - * @since 27 * @see #allSuccessfulOrThrow() */ static Joiner, R_X> @@ -732,18 +742,18 @@ default boolean onComplete(Subtask subtask) { * were {@linkplain #fork(Callable) forked}, when all subtasks complete successfully. * An empty list is returned if no subtasks were forked. If any subtask fails then * the Joiner causes the {@code join()} method to throw {@code ExecutionException} - * with the exception from the first subtask to fail as the {@linkplain - * Throwable#getCause() cause}. + * with the exception from a failed subtask as the {@linkplain Throwable#getCause() + * cause}. * *

    Timeout Handling: The {@code Joiner} cannot produce a result when * the scope is cancelled by a timeout. If the scope was opened with a {@linkplain - * Configuration#withTimeout(Duration) timeout}, and the timeout expires before or - * while waiting for all subtasks to complete successfully, then the {@code Joiner} - * causes the {@code join()} method to throw {@code ExecutionException} with - * a {@link CancelledByTimeoutException CancelledByTimeoutException} as the cause. + * Configuration#withTimeout(Duration) timeout}, and the timeout expires before + * the {@code join()} method is invoked or completes, then the {@code Joiner} + * causes the {@code join()} method to throw {@code ExecutionException} with a + * {@link CancelledByTimeoutException CancelledByTimeoutException} as the cause. * - * @implSpec A Joiner returned by this method is equivalent to invoking {@link - * #allSuccessfulOrThrow(Function) allSuccessfulOrThrow(ExecutionException::new)}. + *

    This method is equivalent to invoking {@link #allSuccessfulOrThrow(Function) + * allSuccessfulOrThrow(ExecutionException::new)}. * * @param the result type of subtasks */ @@ -764,7 +774,7 @@ static Joiner, ExecutionException> allSuccessfulOrThrow() { * {@code join()} method to throw the exception returned by the given exception * supplying function when {@linkplain Function#apply(Object) applied} to the * exception from one of the failed subtasks. The function should return an - * exception with the exception from the failed subtask (the function argument) as + * exception with the exception from a failed subtask (the function argument) as * the {@linkplain Throwable#getCause() cause}. If the function returns {@code null} * then it causes the {@code join()} method to throw {@code NullPointerException}. * If no subtasks were forked then the Joiner causes the {@code join()} method to @@ -773,10 +783,10 @@ static Joiner, ExecutionException> allSuccessfulOrThrow() { * *

    Timeout Handling: The {@code Joiner} cannot produce a result when * the scope is cancelled by a timeout. If the scope was opened with a {@linkplain - * Configuration#withTimeout(Duration) timeout}, and the timeout expires before or - * while waiting for a subtask to complete successfully, then the {@code Joiner} - * causes the {@code join()} method to throw the exception returned by the - * exception supplying function when applied to a {@link CancelledByTimeoutException + * Configuration#withTimeout(Duration) timeout}, and the timeout expires before + * the {@code join()} method is invoked or completes, then the {@code Joiner} + * causes the {@code join()} method to throw the exception returned by the exception + * supplying function when applied to a {@link CancelledByTimeoutException * CancelledByTimeoutException}. * * @apiNote The following example is a method that opens a {@code StructuredTaskScope} @@ -800,7 +810,6 @@ static Joiner, ExecutionException> allSuccessfulOrThrow() { * @param esf the exception supplying function * @param the result type of subtasks * @param the type of the exception thrown by the {@link #join() join()} method - * @since 27 * @see #anySuccessfulOrThrow() */ static Joiner anySuccessfulOrThrow(Function esf) { @@ -817,24 +826,23 @@ static Joiner anySuccessfulOrThrow(Functio * with this Joiner returns the result of a successful subtask. If a subtask * completes successfully then the scope is cancelled and the {@code join()} method * returns its result. If all subtasks fail then the Joiner causes the {@code - * join()} method to throw {@code ExecutionException} with the exception from one - * of the failed subtasks as the {@linkplain Throwable#getCause() cause}. If no - * subtasks were forked then the {@code Joiner} causes the {@code join()} method - * to throw {@code ExecutionException} with {@link java.util.NoSuchElementException} - * as the cause. + * join()} method to throw {@code ExecutionException} with the exception from a + * failed subtask as the {@linkplain Throwable#getCause() cause}. If no subtasks + * were forked then the {@code Joiner} causes the {@code join()} method to throw + * {@code ExecutionException} with {@link java.util.NoSuchElementException} as + * the cause. * *

    Timeout Handling: The {@code Joiner} cannot produce a result when * the scope is cancelled by a timeout. If the scope was opened with a {@linkplain - * Configuration#withTimeout(Duration) timeout}, and the timeout expires before or - * while waiting for a subtask to complete successfully, then the {@code Joiner} + * Configuration#withTimeout(Duration) timeout}, and the timeout expires before + * the {@code join()} method is invoked or completes, then the {@code Joiner} * causes the {@code join()} method to throw {@code ExecutionException} with a * {@link CancelledByTimeoutException CancelledByTimeoutException} as the cause. * - * @implSpec A Joiner returned by this method is equivalent to invoking {@link - * #anySuccessfulOrThrow(Function) anySuccessfulOrThrow(ExecutionException::new)}. + *

    This method is equivalent to invoking {@link #anySuccessfulOrThrow(Function) + * anySuccessfulOrThrow(ExecutionException::new)}. * * @param the result type of subtasks - * @since 26 */ static Joiner anySuccessfulOrThrow() { return anySuccessfulOrThrow(ExecutionException::new); @@ -850,7 +858,7 @@ static Joiner anySuccessfulOrThrow() { * with this Joiner returns {@code null} when all subtasks complete successfully. * If any subtask fails then the Joiner causes the {@code join()} method to throw * the exception returned by the given exception supplying function when {@linkplain - * Function#apply(Object) applied} to the exception from the first subtask to fail. + * Function#apply(Object) applied} to the exception from a failed subtask. * The function should return an exception with the exception from the failed * subtask (the function argument) as the {@linkplain Throwable#getCause() cause}. * If the function returns {@code null} then it causes the {@code join()} method @@ -858,10 +866,10 @@ static Joiner anySuccessfulOrThrow() { * *

    Timeout Handling: The {@code Joiner} cannot produce a result when * the scope is cancelled by a timeout. If the scope was opened with a {@linkplain - * Configuration#withTimeout(Duration) timeout}, and the timeout expires before or - * while waiting for all subtasks to complete successfully, then the {@code Joiner} - * causes the {@code join()} method to throw the exception returned by the - * exception supplying function when applied to a {@link CancelledByTimeoutException + * Configuration#withTimeout(Duration) timeout}, and the timeout expires before + * the {@code join()} method is invoked or completes, then the {@code Joiner} + * causes the {@code join()} method to throw the exception returned by the exception + * supplying function when applied to a {@link CancelledByTimeoutException * CancelledByTimeoutException}. * * @apiNote Joiners returned by this method are suited to cases where subtasks @@ -894,7 +902,6 @@ static Joiner anySuccessfulOrThrow() { * @param esf the exception supplying function * @param the result type of subtasks * @param the type of the exception thrown by the {@link #join() join()} method - * @since 27 */ static Joiner awaitAllSuccessfulOrThrow(Function esf) { @@ -911,18 +918,18 @@ static Joiner anySuccessfulOrThrow() { *

    The {@link #join() join()} method of a {@link StructuredTaskScope} opened * with this Joiner returns {@code null} when all subtasks complete successfully. * If any subtask fails then the Joiner causes the {@code join()} method to throw - * {@code ExecutionException} with the exception from the first subtask to fail - * as the {@linkplain Throwable#getCause() cause}. + * {@code ExecutionException} with the exception from a failed subtask as the + * {@linkplain Throwable#getCause() cause}. * *

    Timeout Handling: The {@code Joiner} cannot produce a result when * the scope is cancelled by a timeout. If the scope was opened with a {@linkplain - * Configuration#withTimeout(Duration) timeout}, and the timeout expires before or - * while waiting for all subtasks to complete successfully, then the {@code Joiner} + * Configuration#withTimeout(Duration) timeout}, and the timeout expires before + * the {@code join()} method is invoked or completes, then the {@code Joiner} * causes the {@code join()} method to throw {@code ExecutionException} with a * {@link CancelledByTimeoutException CancelledByTimeoutException} as the cause. * - * @implSpec A Joiner returned by this method is equivalent to invoking {@link - * #awaitAllSuccessfulOrThrow(Function) awaitAllSuccessfulOrThrow(ExecutionException::new)}. + *

    This method is equivalent to invoking {@link #awaitAllSuccessfulOrThrow(Function) + * awaitAllSuccessfulOrThrow(ExecutionException::new)}. * * @param the result type of subtasks * @see #open() @@ -934,8 +941,8 @@ static Joiner awaitAllSuccessfulOrThrow() { /** * {@return a new Joiner that produces a list of all subtasks when all subtasks - * complete or {@linkplain Predicate#test(Object) evaluating} a predicate on a - * completed subtask causes the scope to be {@linkplain + * complete, or when {@linkplain Predicate#test(Object) evaluation} of a predicate + * on a completed subtask causes the scope to be {@linkplain * StructuredTaskScope##Cancellation cancelled}} * The {@code Joiner} does not cause the {@link #join() join()} method to throw * if subtasks fail or a configured {@linkplain Configuration#withTimeout(Duration) @@ -950,18 +957,25 @@ static Joiner awaitAllSuccessfulOrThrow() { * or subtasks in the {@link Subtask.State#UNAVAILABLE UNAVAILABLE} state if * they were forked or completed after the scope was cancelled. The scope is * cancelled if the given predicate {@linkplain Predicate#test(Object) evaluates} - * to {@code true}, or a configured timeout expires before or while waiting in the - * {@code join()} method. + * to {@code true}, or a configured timeout expires before the {@code join()} + * method is invoked or completes. * - *

    The given {@code Predicate}'s {@link Predicate#test(Object) test(Object)} - * method is invoked on a completed subtask by the thread that executed the subtask. + *

    If a subtask completes before the scope is cancelled, the thread that + * executed the subtask invokes the given {@code Predicate}'s {@link + * Predicate#test(Object) test(Object)} method with the completed subtask. * The method is invoked after the subtask completes (successfully or with an - * exception) before the thread terminates. The scope is cancelled if the {@code - * test} method returns {@code true}. The {@code test} method must be thread safe. - * It may be invoked concurrently from several threads as multiple subtasks can - * complete at the same time. If the method throws an exception or error, the thread - * invokes the {@linkplain Thread.UncaughtExceptionHandler uncaught exception handler} - * with the exception or error before the thread terminates. + * exception) and before the thread terminates. The scope is cancelled if the + * {@code test} method returns {@code true}. The {@code test} method must be + * thread-safe. It may be invoked concurrently from several threads as multiple + * subtasks can complete at the same time. If the method throws an exception or + * error, the thread invokes the {@linkplain Thread.UncaughtExceptionHandler + * uncaught exception handler} with the exception or error before the thread + * terminates. + * + *

    If {@link #fork(Callable) fork(Callable)} or {@link #fork(Runnable) + * fork(Runnable)} fails to create a thread to execute a subtask, the {@code fork} + * method invokes the predicate directly with the subtask in the {@link + * Subtask.State#FAILED FAILED} state. * *

    Timeout Handling: If used with a scope that has a {@linkplain * Configuration#withTimeout(Duration) timeout} set, and the timeout expires before @@ -1004,10 +1018,10 @@ static Joiner awaitAllSuccessfulOrThrow() { * } * *

    The following example is a method that uses {@code allUntil} to create a - * Joiner that does not cancel the scope. The method waits for all subtasks to - * complete or a timeout to expire. It returns a list of all subtasks, in the + * Joiner that does not itself cancel the scope. The method waits for all subtasks + * to complete or a timeout to expire. It returns a list of all subtasks, in the * same order as the collection of callables, even if the timeout expires before - * or while waiting in {@code join()}. + * the {@code join()} method is invoked or completes. * {@snippet lang=java : * List> invokeAll(Collection> tasks, Duration timeout) throws InterruptedException { * // @link substring="withTimeout" target="Configuration#withTimeout(Duration)" : @@ -1045,9 +1059,8 @@ static Joiner awaitAllSuccessfulOrThrow() { *

    Unless otherwise specified, passing a {@code null} argument to a method * in this class will cause a {@link NullPointerException} to be thrown. * - * @since 25 + * @since 28 */ - @PreviewFeature(feature = PreviewFeature.Feature.STRUCTURED_CONCURRENCY) sealed interface Configuration permits StructuredTaskScopeImpl.ConfigImpl { /** * {@return a new {@code Configuration} object with the given thread factory} @@ -1092,10 +1105,9 @@ sealed interface Configuration permits StructuredTaskScopeImpl.ConfigImpl { * throws because the scope was {@linkplain StructuredTaskScope##Cancellation cancelled} * by a timeout. * - * @since 27 + * @since 28 * @see Configuration#withTimeout(Duration) */ - @PreviewFeature(feature = PreviewFeature.Feature.STRUCTURED_CONCURRENCY) final class CancelledByTimeoutException extends RuntimeException { @java.io.Serial static final long serialVersionUID = 705788143955048766L; @@ -1121,23 +1133,23 @@ public CancelledByTimeoutException() { } * method with the default configuration to produce the configuration for the new * scope: *

      - *
    • If the {@code apply} method returns a {@code Configuration} with a {@link - * ThreadFactory}, set using {@link Configuration#withThreadFactory(ThreadFactory) - * withThreadFactory(ThreadFactory)}, its {@link ThreadFactory#newThread(Runnable) - * newThread(Runnable)} method will be invoked to create threads when {@linkplain - * #fork(Callable) forking} subtasks in the scope. If a {@code ThreadFactory} is not set - * then forking subtasks will create an unnamed virtual thread for each subtask.
    • - *
    • If the {@code apply} method returns a {@code Configuration} with a timeout, set - * using {@link Configuration#withTimeout(Duration) withTimeout(Duration)}, the timeout - * will start when the scope is opened. If the timeout expires before or while waiting in - * {@link #join()} then the scope will be {@linkplain ##Cancellation cancelled}. It is - * {@link Joiner Joiner} specific as to whether the {@code join()} method returns a - * result or throws an exception when a timeout occurs. If the outcome is an exception - * then it will be thrown with a {@link CancelledByTimeoutException - * CancelledByTimeoutException} as the {@linkplain Throwable#getCause() cause}.
    • - *
    • If the {@code apply} method returns a {@code Configuration} with a name, set - * using {@linkplain Configuration#withName(String) withName(String)}, the name will - * be used for monitoring and management purposes.
    • + *
    • If the {@code apply} method returns a configuration with a {@linkplain + * Configuration#withThreadFactory(ThreadFactory) ThreadFactory}, the {@code + * ThreadFactory}'s {@link ThreadFactory#newThread(Runnable) newThread(Runnable)} + * method is invoked to create threads when {@linkplain #fork(Callable) forking} + * subtasks in the scope. If a {@code ThreadFactory} is not set then forking subtasks + * will create an unnamed virtual thread for each subtask.
    • + *
    • If the {@code apply} method returns a configuration with a {@linkplain + * Configuration#withTimeout(Duration) timeout}, the timeout starts when the scope is + * opened. If the timeout expires before {@link #join()} is invoked or completes then + * the scope is {@linkplain ##Cancellation cancelled} asynchronously. Whether the + * {@code join()} method returns a result or throws an exception when a timeout occurs + * is Joiner-specific. If the outcome is an exception then it will be thrown with a + * {@link CancelledByTimeoutException CancelledByTimeoutException} as the {@linkplain + * Throwable#getCause() cause}.
    • + *
    • If the {@code apply} method returns a configuration with a {@linkplain + * Configuration#withName(String) name}, the new scope is created with this name for + * monitoring and management purposes.
    • *
    • If the {@code apply} method throws an exception or error then it is propagated * by this method.
    • *
    • If the {@code apply} method returns {@code null} then {@code NullPointerException} @@ -1157,7 +1169,6 @@ public CancelledByTimeoutException() { } * @param the result type of subtasks {@linkplain #fork(Callable) forked} in the scope * @param the type of the result returned by the {@link #join() join()} method * @param the type of the exception thrown by the {@link #join() join()} method - * @since 26 */ static StructuredTaskScope open(Joiner joiner, UnaryOperator configOperator) { @@ -1175,6 +1186,10 @@ public CancelledByTimeoutException() { } * unnamed {@linkplain Thread##virtual-threads virtual threads}, does not name the * scope, and has no timeout. * + *

      This method is equivalent to invoking the {@linkplain #open(Joiner, UnaryOperator) + * 2-arg open} method with the given Joiner and the {@linkplain UnaryOperator#identity() + * identity operator}. + * *

      The new scope is owned by the current thread. Only code executing in this * thread can {@linkplain #fork(Callable) fork}, {@linkplain #join() join}, or * {@linkplain #close close} the scope. @@ -1182,16 +1197,11 @@ public CancelledByTimeoutException() { } *

      Construction captures the current thread's {@linkplain ScopedValue scoped * value} bindings for inheritance by threads forked in the scope. * - * @implSpec This factory method is equivalent to invoking the - * {@linkplain #open(Joiner, UnaryOperator) 2-arg open} method with the given Joiner - * and the {@linkplain UnaryOperator#identity() identity operator}. - * * @param joiner the Joiner * @return a new scope * @param the result type of subtasks {@linkplain #fork(Callable) forked} in the scope * @param the type of the result returned by the {@link #join() join()} method * @param the type of the exception thrown by the {@link #join() join()} method - * @since 25 */ static StructuredTaskScope open(Joiner joiner) { @@ -1207,13 +1217,17 @@ public CancelledByTimeoutException() { } * method. * *

      The {@link #join()} method of the new scope waits for all subtasks to succeed - * or any subtask to fail. The {@code join() method} returns {@code null} if all + * or any subtask to fail. The {@code join()} method returns {@code null} if all * subtasks complete successfully. It throws {@link ExecutionException} if any subtask - * fails, with the exception from the first subtask to fail as the {@linkplain - * Throwable#getCause() cause}. If a {@linkplain Configuration#withTimeout(Duration) - * timeout} is configured, and the timeout expires before or while waiting in the - * {@code join()} method, it throws {@code ExecutionException} with a {@link - * CancelledByTimeoutException CancelledByTimeoutException} as the cause. + * fails, with the exception from a failed subtask as the {@linkplain Throwable#getCause() + * cause}. If a {@linkplain Configuration#withTimeout(Duration) timeout} is configured, + * and the timeout expires before the {@link #join()} method is invoked or completes, + * it throws {@code ExecutionException} with a {@link CancelledByTimeoutException + * CancelledByTimeoutException} as the cause. + * + *

      This method is equivalent to invoking the {@linkplain #open(Joiner, UnaryOperator) + * 2-arg open} method with a Joiner created with {@link Joiner#awaitAllSuccessfulOrThrow() + * Joiner.awaitAllSuccessfulOrThrow()} and the given configuration operator. * *

      The new scope is owned by the current thread. Only code executing in this * thread can {@linkplain #fork(Callable) fork}, {@linkplain #join() join}, or @@ -1222,15 +1236,9 @@ public CancelledByTimeoutException() { } *

      Construction captures the current thread's {@linkplain ScopedValue scoped * value} bindings for inheritance by threads forked in the scope. * - * @implSpec This factory method is equivalent to invoking the {@linkplain - * #open(Joiner, UnaryOperator) 2-arg open} method with a Joiner created with {@link - * Joiner#awaitAllSuccessfulOrThrow() awaitAllSuccessfulOrThrow()} and the given - * configuration operator. - * * @param configOperator the operator to produce the configuration * @return a new scope * @param the result type of subtasks forked in the scope - * @since 27 */ static StructuredTaskScope open(UnaryOperator configOperator) { @@ -1241,7 +1249,7 @@ public CancelledByTimeoutException() { } * Opens a new {@code StructuredTaskScope} where {@link #join()} waits for all subtasks * to succeed or any subtask to fail. The {@code join()} method returns {@code null} * if all subtasks complete successfully. It throws {@link ExecutionException} if any - * subtask fails, with the exception from the first subtask to fail as the {@linkplain + * subtask fails, with the exception from a failed subtask as the {@linkplain * Throwable#getCause() cause}. * *

      The scope is created with the {@linkplain ##DefaultConfiguration default @@ -1249,6 +1257,11 @@ public CancelledByTimeoutException() { } * unnamed {@linkplain Thread##virtual-threads virtual threads}, does not name the * scope, and has no timeout. * + *

      This method is equivalent to invoking the {@linkplain #open(Joiner, UnaryOperator) + * 2-arg open} method with a Joiner created with {@link Joiner#awaitAllSuccessfulOrThrow() + * Joiner.awaitAllSuccessfulOrThrow()} and the {@linkplain UnaryOperator#identity() + * identity operator}. + * *

      The new scope is owned by the current thread. Only code executing in this * thread can {@linkplain #fork(Callable) fork}, {@linkplain #join() join}, or * {@linkplain #close close} the scope. @@ -1256,14 +1269,8 @@ public CancelledByTimeoutException() { } *

      Construction captures the current thread's {@linkplain ScopedValue scoped * value} bindings for inheritance by threads forked in the scope. * - * @implSpec This factory method is equivalent to invoking the - * {@linkplain #open(Joiner, UnaryOperator) 2-arg open} method with a Joiner created - * with {@link Joiner#awaitAllSuccessfulOrThrow() awaitAllSuccessfulOrThrow()} and - * the {@linkplain UnaryOperator#identity() identity operator}. - * * @param the result type of subtasks * @return a new scope - * @since 25 */ static StructuredTaskScope open() { return open(Joiner.awaitAllSuccessfulOrThrow(), UnaryOperator.identity()); @@ -1272,7 +1279,7 @@ static StructuredTaskScope open() { /** * Forks a subtask by starting a new thread in this scope to execute a value-returning * method. The new thread executes the subtask concurrently with the current thread. - * The parameter to this method is a {@link Callable}, the new thread executes its + * The parameter to this method is a {@link Callable}. The new thread executes its * {@link Callable#call() call()} method. The thread inherits the current thread's * {@linkplain ScopedValue scoped value bindings} that must match the bindings * captured when the scope was opened. @@ -1282,53 +1289,62 @@ static StructuredTaskScope open() { * Configuration#withThreadFactory(ThreadFactory) set}, then its {@link * ThreadFactory#newThread(Runnable) newThread(Runnable)} method is invoked to create * the thread that will execute the subtask. {@link RejectedExecutionException} is - * thrown if the {@code newThread(Runnable)} method returns {@code null}. - * If a {@code ThreadFactory} is not set, the {@code fork(Callable)} method creates an - * unnamed {@linkplain Thread##virtual-threads virtual thread} to execute the subtask. + * thrown if the {@code newThread(Runnable)} method returns {@code null}. If the + * {@code newThread(Runnable)} method throws an exception or error then it is + * propagated by this method. + * + *

      If a {@code ThreadFactory} is not set, the {@code fork(Callable)} method + * creates an unnamed {@linkplain Thread##virtual-threads virtual thread} to execute + * the subtask. * *

      This method returns a {@link Subtask Subtask} object as a handle to the * forked subtask. If the scope is {@linkplain ##Cancellation cancelled}, the * {@code Subtask} is returned in the {@link Subtask.State#UNAVAILABLE UNAVAILABLE} - * state without creating a thread. In some usages, {@code Subtask} object will be used - * by the "main" task (the scope owner) to get the subtask's outcome (result or - * exception) after it has invoked {@link #join() join()} to wait for all subtasks to - * complete. In other usages, the scope is created with a {@link Joiner Joiner} that + * state without creating a thread. In some usages, the {@code Subtask} object will be + * used by the "main" task (the scope owner) to get the subtask's outcome (result or + * exception) after it has invoked {@link #join() join()}. + * In other usages, the scope is created with a {@link Joiner Joiner} that * produces the outcome for the main task to process after joining. A {@code Joiner} * that produces a result reduces the need for bookkeeping and the need for the * main task to retain references to {@code Subtask} objects for correlation purposes. * - *

      To ensure correct usage, the {@link Subtask#get() Subtask.get()} method may - * only be called by the scope owner to get the result of a successful subtask after - * it has waited for subtasks to complete with the {@link #join() join()} method. - * Similarly, the {@link Subtask#exception() Subtask.exception()} method may only be - * called by the scope owner to get the exception (or error) of a failed subtask after - * it has joined. + *

      After the scope owner has {@linkplain #join() joined} to obtain the + * scope's outcome, {@link Subtask#get() Subtask.get()} and {@link Subtask#exception() + * Subtask.exception()} can be used to get the subtask's result or exception. * *

      This method may only be invoked by the scope owner. * *

      Joiner Implementers: * - *

      If the scope was opened with a {@link Joiner Joiner}, its {@link + *

      If the scope was opened with a {@link Joiner Joiner}, the Joiner's {@link * Joiner#onFork(Subtask) onFork(Subtask)} method is invoked with the newly created - * {@code Subtask} object before the thread is created. It is invoked with the subtask - * in the {@code UNAVAILABLE} state. If the method throws an exception or error then - * it is propagated by the {@code fork(Callable)} method without creating a thread to - * execute the subtask. If the scope is not already cancelled, and the {@code onFork} - * method return {@code false}, then a thread is created and {@linkplain Thread#start() - * scheduled} to start execution of the subtask. If the scope is cancelled, or the - * {@code onFork} method returns {@code true} to cancel the scope, {@code fork(Callable)} - * returns the subtask in the {@code UNAVAILABLE} state. + * {@code Subtask} object in the {@code UNAVAILABLE} state and before a thread is + * created to execute it. If the {@code onFork(Subtask)} method returns {@code true} + * then the scope is cancelled if not already cancelled. This causes the {@code + * fork(Callable)} method to return the {@code Subtask} in the {@code UNAVAILABLE} state + * without creating a thread. If the {@code onFork(Subtask)} method returns {@code + * false}, and the scope is not cancelled, then the {@code fork(Callable)} method + * continues on to create a thread to execute the subtask. If the {@code + * onFork(Subtask)} method completes with an exception or error, then it is propagated + * by the {@code fork(Callable)} method without creating a thread to execute the subtask. * *

      If the subtask executes and completes (successfully or with an exception) before * the scope is cancelled, then the thread invokes the Joiner's {@link * Joiner#onComplete(Subtask) onComplete(Subtask)} method with the subtask in the * {@link Subtask.State#SUCCESS SUCCESS} or {@link Subtask.State#FAILED FAILED} state. - * If the {@code onComplete(Subtask}) method returns {@code true} then the scope is - * cancelled, if not already cancelled. If the {@code onComplete(Subtask)} method - * completes with an exception or error, then the thread executes the {@linkplain + * If the {@code onComplete(Subtask)} method returns {@code true}, then the scope is + * cancelled, if not already cancelled. If the Joiner's {@code onComplete(Subtask)} + * method completes with an exception or error, then the thread executes the {@linkplain * Thread.UncaughtExceptionHandler uncaught exception handler} before the thread * terminates. * + *

      The subtask is not executed if the {@code fork} method is unable to create a + * thread to execute the subtask. In that case, the {@code fork} method invokes the + * Joiner's {@link Joiner#onComplete(Subtask) onComplete(Subtask)} method directly + * with the subtask in the {@link Subtask.State#FAILED FAILED} state. The Joiner can + * invoke the subtask's {@link Subtask#exception() exception()} method to get the + * exception or error encountered when attempting to create the thread. + * * @param task the value-returning task for the thread to execute * @param the result type * @return the subtask @@ -1348,7 +1364,7 @@ static StructuredTaskScope open() { * does not return a result. * *

      This method works exactly the same as {@link #fork(Callable)} except that the - * parameter to this method is a {@link Runnable}, the new thread executes its + * parameter to this method is a {@link Runnable}. The new thread executes its * {@link Runnable#run() run()} method, and {@link Subtask#get() Subtask.get()} returns * {@code null} if the subtask completes successfully. * @@ -1362,27 +1378,26 @@ static StructuredTaskScope open() { * the same as when the scope was created * @throws RejectedExecutionException if the thread factory rejected creating a * thread to execute the subtask - * @since 25 */ Subtask fork(Runnable task); /** - * Returns a result, or throws, after waiting for all subtasks to complete or the - * scope to be {@linkplain ##Cancellation cancelled}. + * Returns a result or throws an exception after waiting for all subtasks to complete + * or the scope to be {@linkplain ##Cancellation cancelled}. * *

      If the scope was opened with the {@link #open()} or {@link #open(UnaryOperator)} - * method, then {@code join()} waits for all subtasks to succeed or any subtask to fail. + * method then {@code join()} waits for all subtasks to succeed or any subtask to fail. * It returns {@code null} if all subtasks complete successfully. It throws {@link - * ExecutionException} if any subtask fails, with exception from the first subtask to - * fail as the {@linkplain Throwable#getCause() cause}. If a {@linkplain + * ExecutionException} if any subtask fails, with the exception from any failed + * subtask as the {@linkplain Throwable#getCause() cause}. If a {@linkplain * Configuration#withTimeout(Duration) timeout} is configured and the timeout expires - * before or while waiting, it throws {@code ExecutionException} with a {@link - * CancelledByTimeoutException CancelledByTimeoutException} as the cause. + * before {@code join()} is invoked or completes, it throws {@code ExecutionException} + * with a {@link CancelledByTimeoutException CancelledByTimeoutException} as the cause. * - *

      If the scope was opened with a {@link Joiner Joiner}, it is invoked after - * waiting or cancellation to produce the outcome (result or exception). This includes - * the timeout case where a timeout is configured and it expires before or - * while waiting in the {@code join()} method. + *

      If the scope was opened with a {@link Joiner Joiner}, the {@code Joiner} is + * invoked after waiting or cancellation to produce the outcome (result or exception). + * This includes the timeout case where a timeout is configured and the + * timeout expires before this method completes. * *

      This method may only be invoked by the scope owner. It may only be invoked once * to get the result, exception or timeout outcome, unless the previous invocation @@ -1390,7 +1405,7 @@ static StructuredTaskScope open() { * *

      Joiner Implementers: * - *

      When all subtasks complete, or the scope cancelled, this method invokes the + *

      When all subtasks complete, or the scope is cancelled, this method invokes the * {@code Joiner}'s {@link Joiner#result() result()} or {@link Joiner#timeout() timeout()} * method to produce the outcome (result or exception) for the {@code join()} method. * The {@code result()} method is invoked for the "no timeout" case. The {@code timeout()} @@ -1400,17 +1415,14 @@ static StructuredTaskScope open() { * @apiNote When the outcome is an exception, the {@linkplain Throwable#getStackTrace() * stack trace} will be the stack trace of the call to the {@code join()} method. * Its {@linkplain Throwable#getCause() cause} will typically be the exception - * thrown by a failed subtask with the stack trace of the failed subtask. - * + * thrown by a failed subtask. * @return the result * @throws WrongThreadException if the current thread is not the scope owner * @throws IllegalStateException if already joined or this scope is closed * @throws R_X when the outcome is an exception * @throws InterruptedException if the current thread is {@linkplain Thread#interrupt() - * interrupted} while waiting or this method is invoked with the current thread's - * {@linkplain Thread#isInterrupted() interrupted status} set. The current thread's - * interrupted status is cleared when this exception is thrown. - * @since 25 + * interrupted} while waiting. The current thread's interrupted status is cleared + * when this exception is thrown. * @see Thread##thread-interruption Thread Interruption */ R join() throws R_X, InterruptedException; @@ -1419,7 +1431,7 @@ static StructuredTaskScope open() { * {@return {@code true} if this scope is {@linkplain ##Cancellation cancelled} or in * the process of being cancelled, otherwise {@code false}} * - *

      Cancelling the scope prevents new threads from starting in the scope and + *

      Cancelling the scope prevents new threads from being started in the scope and * {@linkplain Thread#interrupt() interrupts} threads executing unfinished subtasks. * It may take some time before the interrupted threads finish execution; this * method may return {@code true} before all threads have been interrupted or before @@ -1429,22 +1441,21 @@ static StructuredTaskScope open() { * that forks subtasks before the {@link #join()} method is invoked) may use this * method to avoid doing work in cases where the scope is cancelled by the completion * of a previously forked subtask or a timeout. - * - * @since 25 */ boolean isCancelled(); /** * Closes this scope. * - *

      This method first {@linkplain ##Cancellation cancels} the scope, if not already - * cancelled. This {@linkplain Thread#interrupt() interrupts} the threads executing - * unfinished subtasks. This method then waits for all threads to finish. If interrupted - * while waiting then it will continue to wait until the threads finish, before - * completing with the {@linkplain Thread#isInterrupted() interrupted status} set. + *

      If the {@link #join()} method has not completed with an outcome, this method + * first {@linkplain ##Cancellation cancels} the scope, if not already cancelled. + * This {@linkplain Thread#interrupt() interrupts} the threads executing unfinished + * subtasks. This method then waits for all threads to finish. If interrupted + * while waiting, it continues to wait until the threads finish, then completes + * with the {@linkplain Thread#isInterrupted() interrupted status} set. If the scope + * is already closed then this method has no effect. * - *

      This method may only be invoked by the scope owner. If the scope - * is already closed then the scope owner invoking this method has no effect. + *

      This method may only be invoked by the scope owner. * *

      A {@code StructuredTaskScope} is intended to be used in a structured * manner. If this method is called to close a scope before nested scopes are diff --git a/src/java.base/share/classes/java/util/concurrent/StructuredTaskScopeImpl.java b/src/java.base/share/classes/java/util/concurrent/StructuredTaskScopeImpl.java index fbd58fc2a211..5b813b6c435d 100644 --- a/src/java.base/share/classes/java/util/concurrent/StructuredTaskScopeImpl.java +++ b/src/java.base/share/classes/java/util/concurrent/StructuredTaskScopeImpl.java @@ -46,10 +46,12 @@ final class StructuredTaskScopeImpl private final ThreadFlock flock; // scope state, set by owner thread, read by any thread - private static final int ST_FORKED = 1, // subtasks forked, need to join - ST_JOIN_STARTED = 2, // join started, can no longer fork - ST_JOIN_COMPLETED = 3, // join completed - ST_CLOSED = 4; // closed + private static final int ST_FORKED = 1, // subtasks forked, need to join + ST_JOIN_STARTED = 2, // join started, can no longer fork + ST_JOIN_COMPLETED = 3, // join completed + ST_CLOSED_BEFORE_JOIN = 4, // closed, join did not complete + ST_CLOSED_AFTER_JOIN = 5; // closed, join completed + private volatile int state; // set or read by any thread @@ -112,7 +114,8 @@ private void ensureOwner() { * Returns true if join has been invoked and there is an outcome. */ private boolean isJoinCompleted() { - return state >= ST_JOIN_COMPLETED; + int s = state; + return (s == ST_JOIN_COMPLETED || s == ST_CLOSED_AFTER_JOIN); } /** @@ -200,9 +203,15 @@ public Subtask fork(Callable task) { if (!cancelled) { // create thread to run task - Thread thread = threadFactory.newThread(subtask); - if (thread == null) { - throw new RejectedExecutionException("Rejected by thread factory"); + Thread thread; + try { + thread = threadFactory.newThread(subtask); + if (thread == null) { + throw new RejectedExecutionException("Rejected by thread factory"); + } + } catch (Throwable e) { + subtask.notRun(e); + throw e; } // attempt to start the thread @@ -210,8 +219,14 @@ public Subtask fork(Callable task) { try { flock.start(thread); } catch (IllegalStateException e) { - // shutdown by another thread, or underlying flock is shutdown due - // to unstructured use + // thread flock may be shutdown + if (!cancelled) { + subtask.notRun(e); + throw e; + } + } catch (Throwable e) { + subtask.notRun(e); + throw e; } } @@ -265,7 +280,7 @@ public boolean isCancelled() { public void close() { ensureOwner(); int s = state; - if (s == ST_CLOSED) { + if (s == ST_CLOSED_BEFORE_JOIN || s == ST_CLOSED_AFTER_JOIN) { return; } @@ -279,7 +294,7 @@ public void close() { try { flock.close(); } finally { - state = ST_CLOSED; + state = (s == ST_JOIN_COMPLETED) ? ST_CLOSED_AFTER_JOIN : ST_CLOSED_BEFORE_JOIN; } // throw ISE if the owner didn't join after forking @@ -309,6 +324,7 @@ private record AltResult(Subtask.State state, Throwable exception) { private final Callable task; private volatile Object result; @Stable private Thread thread; + private Thread caller; SubtaskImpl(StructuredTaskScopeImpl scope, Callable task) { this.scope = scope; @@ -360,6 +376,20 @@ public void run() { scope.onComplete(this); } + void notRun(Throwable ex) { + result = new AltResult(State.FAILED, ex); + caller = Thread.currentThread(); + try { + scope.onComplete(this); + } catch (Throwable e) { + if (e != ex) { + ex.addSuppressed(e); + } + } finally { + caller = null; + } + } + @Override public Subtask.State state() { Object result = this.result; @@ -390,7 +420,11 @@ public T get() { @Override public Throwable exception() { - ensureJoinedIfNotSubtask(); + // allow main task to invoke Subtask::exception from onComplete callback + Thread caller = this.caller; + if (caller == null || caller != Thread.currentThread()) { + ensureJoinedIfNotSubtask(); + } Object result = this.result; if (result instanceof AltResult alt && alt.state() == State.FAILED) { return alt.exception(); diff --git a/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java b/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java index bc7b77110a84..9f4cb9f370e1 100644 --- a/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java +++ b/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java @@ -73,7 +73,6 @@ public enum Feature { STRICT_FIELDS, @JEP(number=401, title="Value Classes and Objects", status = "Preview") VALUE_OBJECTS, - @JEP(number=533, title="Structured Concurrency", status="Seventh Preview") STRUCTURED_CONCURRENCY, @JEP(number = 531, title = "Lazy Constants", status = "Third Preview") LAZY_CONSTANTS, diff --git a/test/jdk/java/lang/ScopedValue/StressStackOverflow.java b/test/jdk/java/lang/ScopedValue/StressStackOverflow.java index 5014a8eeae94..4b34c346d3ad 100644 --- a/test/jdk/java/lang/ScopedValue/StressStackOverflow.java +++ b/test/jdk/java/lang/ScopedValue/StressStackOverflow.java @@ -24,26 +24,22 @@ /* * @test id=default * @summary Stress ScopedValue stack overflow recovery path - * @enablePreview * @run main/othervm/timeout=300 ${test.main.class} */ /* * @test id=no-TieredCompilation - * @enablePreview * @run main/othervm/timeout=300 -XX:-TieredCompilation ${test.main.class} */ /* * @test id=TieredStopAtLevel1 - * @enablePreview * @run main/othervm/timeout=300 -XX:TieredStopAtLevel=1 ${test.main.class} */ /* * @test id=no-vmcontinuations * @requires vm.continuations - * @enablePreview * @run main/othervm/timeout=300 -XX:+UnlockExperimentalVMOptions -XX:-VMContinuations ${test.main.class} */ diff --git a/test/jdk/java/util/concurrent/StructuredTaskScope/StressCancellation.java b/test/jdk/java/util/concurrent/StructuredTaskScope/StressCancellation.java index 3fdc582a29c1..62ee6d3861bd 100644 --- a/test/jdk/java/util/concurrent/StructuredTaskScope/StressCancellation.java +++ b/test/jdk/java/util/concurrent/StructuredTaskScope/StressCancellation.java @@ -24,7 +24,6 @@ /* * @test * @summary Stress test of StructuredTaskScope cancellation with running and starting threads - * @enablePreview * @run junit ${test.main.class} */ diff --git a/test/jdk/java/util/concurrent/StructuredTaskScope/StructuredTaskScopeTest.java b/test/jdk/java/util/concurrent/StructuredTaskScope/StructuredTaskScopeTest.java index fa08834a559f..7712feb1f3d8 100644 --- a/test/jdk/java/util/concurrent/StructuredTaskScope/StructuredTaskScopeTest.java +++ b/test/jdk/java/util/concurrent/StructuredTaskScope/StructuredTaskScopeTest.java @@ -23,15 +23,13 @@ /* * @test id=platform - * @bug 8284199 8296779 8306647 8380109 + * @bug 8284199 8296779 8306647 8380109 8391460 * @summary Basic tests for StructuredTaskScope - * @enablePreview * @run junit/othervm -DthreadFactory=platform ${test.main.class} */ /* * @test id=virtual - * @enablePreview * @run junit/othervm -DthreadFactory=virtual ${test.main.class} */ @@ -53,6 +51,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.SynchronousQueue; import java.util.concurrent.StructuredTaskScope; import java.util.concurrent.StructuredTaskScope.Configuration; import java.util.concurrent.StructuredTaskScope.Joiner; @@ -73,6 +72,7 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; import static org.junit.jupiter.api.Assertions.*; class StructuredTaskScopeTest { @@ -328,13 +328,24 @@ void testForkAfterClose(ThreadFactory factory) { * Test fork with a ThreadFactory that rejects creating a thread. */ @Test - void testForkRejectedExecutionException() { + void testForkWithRejectingThreadFactory() { ThreadFactory factory = task -> null; try (var scope = StructuredTaskScope.open(cf -> cf.withThreadFactory(factory))) { assertThrows(RejectedExecutionException.class, () -> scope.fork(() -> null)); } } + /** + * Test fork with a ThreadFactory that throws. + */ + @Test + void testForkWithThrowingThreadFactory() { + ThreadFactory factory = task -> { throw new FooException(); }; + try (var scope = StructuredTaskScope.open(cf -> cf.withThreadFactory(factory))) { + assertThrows(FooException.class, () -> scope.fork(() -> null)); + } + } + /** * Test join with no subtasks. */ @@ -496,50 +507,74 @@ void testJoinConfined(ThreadFactory factory) throws Exception { } /** - * Test join with interrupted status set. + * Test interrupting join. */ - @ParameterizedTest - @MethodSource("factories") - void testInterruptJoin1(ThreadFactory factory) throws Exception { + void testInterruptJoin(ThreadFactory factory, Runnable interrupter) throws Exception { try (var scope = StructuredTaskScope.open(cf -> cf.withThreadFactory(factory))) { + // subtask1 completes + var queue = new SynchronousQueue(); + Subtask subtask1 = scope.fork(() -> { + queue.put(Thread.currentThread()); + return "foo"; + }); - Subtask subtask = scope.fork(() -> { + // wait for thread executing subtask to terminate + Thread thread = queue.take(); + thread.join(); + + // subtask2 does not complete, causes join to wait + Subtask subtask2 = scope.fork(() -> { Thread.sleep(Duration.ofDays(1)); return "foo"; }); + // before join + assertEquals(Subtask.State.SUCCESS, subtask1.state()); + assertEquals(Subtask.State.UNAVAILABLE, subtask2.state()); + // join should throw - Thread.currentThread().interrupt(); - try { - scope.join(); - fail("join did not throw"); - } catch (InterruptedException expected) { - assertFalse(Thread.interrupted()); // interrupted status should be cleared - } + interrupter.run(); + assertThrows(InterruptedException.class, scope::join); + assertFalse(Thread.interrupted()); + + // after interrupted join + assertEquals(Subtask.State.SUCCESS, subtask1.state()); + assertThrows(IllegalStateException.class, subtask1::get); + assertThrows(IllegalStateException.class, subtask1::exception); + assertEquals(Subtask.State.UNAVAILABLE, subtask2.state()); + assertThrows(IllegalStateException.class, subtask2::get); + assertThrows(IllegalStateException.class, subtask2::exception); + + // close after interrupted join + scope.close(); + + // after close + assertEquals(Subtask.State.SUCCESS, subtask1.state()); + assertThrows(IllegalStateException.class, subtask1::get); + assertThrows(IllegalStateException.class, subtask1::exception); + assertEquals(Subtask.State.UNAVAILABLE, subtask2.state()); + assertThrows(IllegalStateException.class, subtask2::get); + assertThrows(IllegalStateException.class, subtask2::exception); } } + /** + * Test join with interrupted status set. + */ + @ParameterizedTest + @MethodSource("factories") + void testInterruptJoin1(ThreadFactory factory) throws Exception { + testInterruptJoin(factory, () -> Thread.currentThread().interrupt()); + } + /** * Test interrupt of thread blocked in join. */ @ParameterizedTest @MethodSource("factories") void testInterruptJoin2(ThreadFactory factory) throws Exception { - try (var scope = StructuredTaskScope.open(cf -> cf.withThreadFactory(factory))) { - Subtask subtask = scope.fork(() -> { - Thread.sleep(Duration.ofDays(1)); - return "foo"; - }); - - // interrupt main thread when it blocks in join - scheduleInterruptAt("java.util.concurrent.StructuredTaskScopeImpl.join"); - try { - scope.join(); - fail("join did not throw"); - } catch (InterruptedException expected) { - assertFalse(Thread.interrupted()); // interrupted status should be clear - } - } + testInterruptJoin(factory, + () -> scheduleInterruptAt("java.util.concurrent.StructuredTaskScopeImpl.join")); } /** @@ -738,7 +773,7 @@ void testCloseWithoutJoin1() { } /** - * Test close without join, subtasks forked. + * Test close without join, forked subtask does not complete before close. */ @ParameterizedTest @MethodSource("factories") @@ -752,15 +787,62 @@ void testCloseWithoutJoin2(ThreadFactory factory) { // first call to close should throw assertThrows(IllegalStateException.class, scope::close); + // subtask result not available after first close + assertEquals(Subtask.State.UNAVAILABLE, subtask.state()); + assertThrows(IllegalStateException.class, subtask::get); + assertThrows(IllegalStateException.class, subtask::exception); + // subsequent calls to close should not throw for (int i = 0; i < 3; i++) { scope.close(); + + // subtask result not available after additional calls to close + assertEquals(Subtask.State.UNAVAILABLE, subtask.state()); + assertThrows(IllegalStateException.class, subtask::get); + assertThrows(IllegalStateException.class, subtask::exception); } + } + } - // subtask result/exception not available - assertEquals(Subtask.State.UNAVAILABLE, subtask.state()); + /** + * Test close without join, forked subtask completes before close. + */ + @ParameterizedTest + @MethodSource("factories") + void testCloseWithoutJoin3(ThreadFactory factory) throws Exception { + try (var scope = StructuredTaskScope.open(cf -> cf.withThreadFactory(factory))) { + var queue = new SynchronousQueue(); + Subtask subtask = scope.fork(() -> { + queue.put(Thread.currentThread()); + return "foo"; + }); + + // wait for thread executing subtask to terminate + Thread thread = queue.take(); + thread.join(); + + // subtask result not available before close + assertEquals(Subtask.State.SUCCESS, subtask.state()); assertThrows(IllegalStateException.class, subtask::get); assertThrows(IllegalStateException.class, subtask::exception); + + // close should throw + assertThrows(IllegalStateException.class, scope::close); + + // subtask result not available after first close + assertEquals(Subtask.State.SUCCESS, subtask.state()); + assertThrows(IllegalStateException.class, subtask::get); + assertThrows(IllegalStateException.class, subtask::exception); + + // subsequent calls to close should not throw + for (int i = 0; i < 3; i++) { + scope.close(); + + // subtask result not available after additional calls to close + assertEquals(Subtask.State.SUCCESS, subtask.state()); + assertThrows(IllegalStateException.class, subtask::get); + assertThrows(IllegalStateException.class, subtask::exception); + } } } @@ -907,22 +989,45 @@ void testInterruptClose2(ThreadFactory factory) throws Exception { */ @Test void testCloseThrowsStructureViolation() throws Exception { - try (var scope1 = StructuredTaskScope.open()) { - try (var scope2 = StructuredTaskScope.open()) { + var joiner = new Joiner() { + Subtask completedSubtask; + @Override + public boolean onComplete(Subtask subtask) { + completedSubtask = subtask; + return false; + } + @Override + public Void result() { + return null; + } + @Override + public Void timeout() { + throw new UnsupportedOperationException(); + } + }; + try (var scope1 = StructuredTaskScope.open()) { + try (var scope2 = StructuredTaskScope.open(joiner)) { // close enclosing scope - try { - scope1.close(); - fail("close did not throw"); - } catch (StructureViolationException expected) { } + assertThrows(StructureViolationException.class, scope1::close); - // underlying flock should be closed + // nested scope is borked var executed = new AtomicBoolean(); - Subtask subtask = scope2.fork(() -> executed.set(true)); - assertEquals(Subtask.State.UNAVAILABLE, subtask.state()); + assertThrows(IllegalStateException.class, + () -> scope2.fork(() -> executed.set(true))); + + // onComplete should have been invoked + Subtask subtask = joiner.completedSubtask; + assertEquals(Subtask.State.FAILED, subtask.state()); + + // before join + assertThrows(IllegalStateException.class, subtask::exception); + scope2.join(); + + // after join assertFalse(executed.get()); - assertEquals(Subtask.State.UNAVAILABLE, subtask.state()); + assertTrue(subtask.exception() instanceof IllegalStateException); } } } @@ -1048,6 +1153,65 @@ public Void timeout() { } } + /** + * Test Joiner.onComplete is invoked by main thread when fork unable to create thread. + */ + @ParameterizedTest + @ValueSource(booleans = { true, false }) + void testOnCompleteAfterForkFails(boolean cancel) throws Exception { + Thread mainThread = Thread.currentThread(); + var joiner = new Joiner() { + @Override + public boolean onComplete(Subtask subtask) { + assertSame(mainThread, Thread.currentThread()); + assertEquals(Subtask.State.FAILED, subtask.state()); + assertTrue(subtask.exception() instanceof RejectedExecutionException); + return cancel; + } + @Override + public Void result() { + return null; + } + @Override + public Void timeout() { + return null; + } + }; + ThreadFactory factory = task -> null; + try (var scope = StructuredTaskScope.open(joiner, cf -> cf.withThreadFactory(factory))) { + assertThrows(RejectedExecutionException.class, () -> scope.fork(() -> null)); + assertEquals(cancel, scope.isCancelled()); + } + } + + /** + * Test Joiner.onComplete throwing exception when invoked by main thread when fork + * unable to create thread. + */ + @Test + void testOnCompleteThrowsAfterForkFails() throws Exception { + var joiner = new Joiner() { + @Override + public boolean onComplete(Subtask subtask) { + throw new FooException(); + } + @Override + public Void result() { + return null; + } + @Override + public Void timeout() { + return null; + } + }; + ThreadFactory factory = task -> null; + try (var scope = StructuredTaskScope.open(joiner, cf -> cf.withThreadFactory(factory))) { + var e = assertThrows(RejectedExecutionException.class, () -> scope.fork(() -> null)); + Throwable[] suppressed = e.getSuppressed(); + assertTrue(suppressed[0] instanceof FooException); + } + } + /** * Test Joiner.timeout() invoked by owner thread when timeout expires. */ @@ -1321,11 +1485,8 @@ void testAllSuccessfulOrThrow3(ThreadFactory factory) throws Throwable { cf -> cf.withThreadFactory(factory))) { scope.fork(() -> "foo"); scope.fork(() -> { throw new FooException(); }); - try { - scope.join(); - } catch (ExecutionException e) { - assertTrue(e.getCause() instanceof FooException); - } + var e = assertThrows(ExecutionException.class, scope::join); + assertTrue(e.getCause() instanceof FooException); } } @@ -1405,11 +1566,8 @@ void testAllSuccessfulOrThrow6() throws Exception { @Test void testAnySuccessfulOrThrow1() throws Exception { try (var scope = StructuredTaskScope.open(Joiner.anySuccessfulOrThrow())) { - try { - scope.join(); - } catch (ExecutionException e) { - assertTrue(e.getCause() instanceof NoSuchElementException); - } + var e = assertThrows(ExecutionException.class, scope::join); + assertTrue(e.getCause() instanceof NoSuchElementException); } } @@ -1562,11 +1720,8 @@ void testAwaitSuccessfulOrThrow3(ThreadFactory factory) throws Throwable { cf -> cf.withThreadFactory(factory))) { scope.fork(() -> "foo"); scope.fork(() -> { throw new FooException(); }); - try { - scope.join(); - } catch (ExecutionException e) { - assertTrue(e.getCause() instanceof FooException); - } + var e = assertThrows(ExecutionException.class, scope::join); + assertInstanceOf(FooException.class, e.getCause()); } } diff --git a/test/jdk/java/util/concurrent/StructuredTaskScope/StructuredThreadDumpTest.java b/test/jdk/java/util/concurrent/StructuredTaskScope/StructuredThreadDumpTest.java index 372d3e9a543c..d6d06a9720b2 100644 --- a/test/jdk/java/util/concurrent/StructuredTaskScope/StructuredThreadDumpTest.java +++ b/test/jdk/java/util/concurrent/StructuredTaskScope/StructuredThreadDumpTest.java @@ -25,7 +25,6 @@ * @test * @bug 8284199 8296779 8306647 * @summary Test thread dumps with StructuredTaskScope - * @enablePreview * @library /test/lib * @run junit/othervm ${test.main.class} */ diff --git a/test/jdk/java/util/concurrent/StructuredTaskScope/WithScopedValue.java b/test/jdk/java/util/concurrent/StructuredTaskScope/WithScopedValue.java index 3c5a928cba2a..432bdba2d260 100644 --- a/test/jdk/java/util/concurrent/StructuredTaskScope/WithScopedValue.java +++ b/test/jdk/java/util/concurrent/StructuredTaskScope/WithScopedValue.java @@ -25,7 +25,6 @@ * @test * @bug 8284199 8296779 8306647 * @summary Basic tests for StructuredTaskScope with scoped values - * @enablePreview * @run junit ${test.main.class} */ @@ -145,14 +144,11 @@ class Box { // underlying flock should be closed and fork should fail to start a thread StructuredTaskScope scope = box.scope; - AtomicBoolean ran = new AtomicBoolean(); - Subtask subtask = scope.fork(() -> { - ran.set(true); - return null; - }); - scope.join(); - assertEquals(Subtask.State.UNAVAILABLE, subtask.state()); - assertFalse(ran.get()); + var executed = new AtomicBoolean(); + assertThrows(IllegalStateException.class, + () -> scope.fork(() -> executed.set(true))); + assertThrows(ExecutionException.class, scope::join); + assertFalse(executed.get()); } finally { StructuredTaskScope scope = box.scope; if (scope != null) { diff --git a/test/jdk/javax/security/auth/Subject/CallAsWithScopedValue.java b/test/jdk/javax/security/auth/Subject/CallAsWithScopedValue.java index bfd7ca715771..a06671b982cf 100644 --- a/test/jdk/javax/security/auth/Subject/CallAsWithScopedValue.java +++ b/test/jdk/javax/security/auth/Subject/CallAsWithScopedValue.java @@ -24,9 +24,7 @@ /* * @test * @bug 8296244 - * @enablePreview * @summary Implement Subject.current and Subject.callAs using scoped values. - * Need enablePreview to use StructuredTaskScope. * @run main/othervm ${test.main.class} true */ import com.sun.security.auth.UserPrincipal; diff --git a/test/jdk/jdk/internal/misc/ThreadFlock/ThreadFlockTest.java b/test/jdk/jdk/internal/misc/ThreadFlock/ThreadFlockTest.java index 3fc143f38c83..82ca2069f966 100644 --- a/test/jdk/jdk/internal/misc/ThreadFlock/ThreadFlockTest.java +++ b/test/jdk/jdk/internal/misc/ThreadFlock/ThreadFlockTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -895,12 +895,7 @@ void testInterruptClose2(ThreadFactory factory) { void testStructureViolation() { try (var flock1 = ThreadFlock.open("flock1")) { try (var flock2 = ThreadFlock.open("flock2")) { - try { - flock1.close(); - fail("close did not throw"); - } catch (RuntimeException e) { - assertTrue(e.toString().contains("Structure")); - } + assertThrows(StructureViolationException.class, flock1::close); assertTrue(flock1.isClosed()); assertTrue(flock2.isClosed()); } diff --git a/test/jdk/jdk/internal/misc/ThreadFlock/WithScopedValue.java b/test/jdk/jdk/internal/misc/ThreadFlock/WithScopedValue.java index 19b829bc7e81..86556be0980b 100644 --- a/test/jdk/jdk/internal/misc/ThreadFlock/WithScopedValue.java +++ b/test/jdk/jdk/internal/misc/ThreadFlock/WithScopedValue.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -24,7 +24,6 @@ /* * @test * @summary Test ThreadFlock with scoped values - * @enablePreview * @modules java.base/jdk.internal.misc * @run junit WithScopedValue */ From 8610e806260f4eec6d98a5b418e4a1bc11a9a3cb Mon Sep 17 00:00:00 2001 From: Matthias Baesken Date: Sat, 17 Oct 2026 14:30:16 +0000 Subject: [PATCH 087/169] 8392559: [GCC static analyzer] function methodSignature should check for JVMTI errors Reviewed-by: sspitsyn, cjplummer --- src/jdk.jdwp.agent/share/native/libjdwp/util.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/jdk.jdwp.agent/share/native/libjdwp/util.c b/src/jdk.jdwp.agent/share/native/libjdwp/util.c index 3146d0cfe16b..2c36c0d51ba5 100644 --- a/src/jdk.jdwp.agent/share/native/libjdwp/util.c +++ b/src/jdk.jdwp.agent/share/native/libjdwp/util.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -717,6 +717,10 @@ methodSignature(jmethodID method, error = JVMTI_FUNC_PTR(gdata->jvmti,GetMethodName) (gdata->jvmti, method, &name, &signature, &generic_signature); + if (error != JVMTI_ERROR_NONE) { + EXIT_ERROR(error, "GetMethodName failed"); + } + if ( pname != NULL ) { *pname = name; } else if ( name != NULL ) { From 3104115c2a0587895997965c9e88a1181f52ee47 Mon Sep 17 00:00:00 2001 From: Jatin Bhateja Date: Sun, 18 Oct 2026 06:06:07 +0000 Subject: [PATCH 088/169] 8393338: C2: Out-of-bounds constant VectorSlice index crashes x86 code generation Reviewed-by: qamai, vlivanov --- src/hotspot/share/opto/vectorIntrinsics.cpp | 8 +- .../vectorapi/TestVectorSliceOutOfBounds.java | 100 ++++++++++++++++++ 2 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 test/hotspot/jtreg/compiler/vectorapi/TestVectorSliceOutOfBounds.java diff --git a/src/hotspot/share/opto/vectorIntrinsics.cpp b/src/hotspot/share/opto/vectorIntrinsics.cpp index 0b5bfd5e8d2e..0aaffe789af8 100644 --- a/src/hotspot/share/opto/vectorIntrinsics.cpp +++ b/src/hotspot/share/opto/vectorIntrinsics.cpp @@ -1826,6 +1826,12 @@ bool LibraryCallKit::inline_vector_slice() { int num_elem = vlen->get_con(); BasicType elem_bt = get_vector_primitive_lane_type(vltype); + jint origin_con = origin->get_con(); + if (origin_con < 0 || origin_con > num_elem) { + log_if_needed(" ** vector slice origin %d out of range [0, %d]", origin_con, num_elem); + return false; + } + if (!arch_supports_vector(Op_VectorSlice, num_elem, elem_bt, VecMaskNotUsed)) { log_if_needed(" ** not supported: arity=2 op=slice vlen=%d etype=%s", num_elem, type2name(elem_bt)); @@ -1842,7 +1848,7 @@ bool LibraryCallKit::inline_vector_slice() { } // Defining origin in terms of number of bytes to make it type agnostic value. - Node* origin_node = gvn().intcon(origin->get_con() * type2aelembytes(elem_bt)); + Node* origin_node = gvn().intcon(origin_con * type2aelembytes(elem_bt)); const TypeVect* vector_type = TypeVect::make(elem_bt, num_elem); Node* operation = gvn().transform(trace_vector(new VectorSliceNode(v1, v2, origin_node, vector_type))); Node* box = box_vector(operation, vbox_type, elem_bt, num_elem); diff --git a/test/hotspot/jtreg/compiler/vectorapi/TestVectorSliceOutOfBounds.java b/test/hotspot/jtreg/compiler/vectorapi/TestVectorSliceOutOfBounds.java new file mode 100644 index 000000000000..c202d5a9bcf0 --- /dev/null +++ b/test/hotspot/jtreg/compiler/vectorapi/TestVectorSliceOutOfBounds.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.vectorapi; + +import compiler.lib.verify.Verify; +import jdk.incubator.vector.ShortVector; +import jdk.internal.vm.vector.VectorSupport; + +/* + * @test id=Test1 + * @bug 8393338 + * @summary Out-of-range constant vector slice index must throw, not crash C2. + * @modules java.base/jdk.internal.vm.vector + * jdk.incubator.vector + * @library /test/lib / + * @run main/othervm -Xcomp -XX:-TieredCompilation + * -XX:CompileCommand=compileonly,${test.main.class}$Test1::test + * -XX:CompileCommand=dontinline,java.util.Objects::checkIndex + * -XX:CompileCommand=dontinline,jdk.internal.util.Preconditions::checkIndex + * ${test.main.class} Test1 + * @run main ${test.main.class} Test1 + */ + +/* + * @test id=Test2 + * @bug 8393338 + * @summary Out-of-range constant vector slice index must throw, not crash C2. + * @modules java.base/jdk.internal.vm.vector + * jdk.incubator.vector + * @library /test/lib / + * @run main/othervm -Xbatch -XX:-TieredCompilation + * -XX:CompileCommand=compileonly,${test.main.class}$Test2::test + * ${test.main.class} Test2 + * @run main ${test.main.class} Test2 + */ + +public class TestVectorSliceOutOfBounds { + public static class Test1 { + static void test(short[] a, short[] b) { + try { + ShortVector.fromArray(ShortVector.SPECIES_512, a, 0) + .slice(-271, ShortVector.fromArray(ShortVector.SPECIES_512, b, 0)) + .intoArray(a, 0); + throw new RuntimeException("missing IndexOutOfBoundsException"); + } catch (IndexOutOfBoundsException e) { + } + } + + public static void main(String[] args) { + short[] a = new short[32]; + ShortVector.fromArray(ShortVector.SPECIES_512, a, 0) + .slice(0, ShortVector.fromArray(ShortVector.SPECIES_512, a, 0)); + test(a, a); + } + } + + public static class Test2 { + static ShortVector test() { + ShortVector sv = ShortVector.broadcast(ShortVector.SPECIES_128, (short) -5); + return (ShortVector) VectorSupport.sliceOp(128, sv.getClass(), VectorSupport.LT_SHORT, + 8, sv, sv, (v1, v2, v3) -> v2); + } + + public static void main(String[] args) { + for (int i = 0; i < 10_000; i++) { + test(); + } + Verify.checkEQ(test().lane(0), (short) -5); + } + } + + public static void main(String[] args) { + switch (args[0]) { + case "Test1" -> Test1.main(args); + case "Test2" -> Test2.main(args); + default -> throw new IllegalArgumentException(args[0]); + } + } +} From 573661580c57c9d286b36563621acfd991d97a99 Mon Sep 17 00:00:00 2001 From: Gui Cao Date: Mon, 19 Oct 2026 01:36:15 +0000 Subject: [PATCH 089/169] 8388475: RISC-V: Optimize MD5 intrinsics by reducing data dependency Co-authored-by: gns Reviewed-by: ayang, weihe, gli, dzhang, fyang --- src/hotspot/cpu/riscv/stubGenerator_riscv.cpp | 186 ++++++++++-------- 1 file changed, 101 insertions(+), 85 deletions(-) diff --git a/src/hotspot/cpu/riscv/stubGenerator_riscv.cpp b/src/hotspot/cpu/riscv/stubGenerator_riscv.cpp index 074f80297aa1..5fea2288b3c6 100644 --- a/src/hotspot/cpu/riscv/stubGenerator_riscv.cpp +++ b/src/hotspot/cpu/riscv/stubGenerator_riscv.cpp @@ -5816,10 +5816,11 @@ class StubGenerator: public StubCodeGenerator { } } - // generate load for the i'th register - void gen_load(uint i, Register base) { - assert(i < L, "invalid i: %u", i); - __ ld(_regs[i], Address(base, 8 * i)); + // Generate loads for all cached registers. + void gen_loads(Register base) { + for (uint i = 0; i < L; i++) { + __ ld(_regs[i], Address(base, 8 * i)); + } } // add i'th 32-bit integer to dest @@ -5840,72 +5841,84 @@ class StubGenerator: public StubCodeGenerator { typedef RegCache<8> BufRegCache; - // a += value + x + ac; + // a += (d ^ (b & (c ^ d))) + x + ac; // a = Integer.rotateLeft(a, s) + b; - void m5_FF_GG_HH_II_epilogue(BufRegCache& reg_cache, - Register a, Register b, Register c, Register d, - int k, int s, int t, - Register value) { + void md5_FF(BufRegCache& reg_cache, + Register a, Register b, Register c, Register d, + int k, int s, int t, + Register rtmp1) { + // rtmp1 = c ^ d + __ xorr(rtmp1, c, d); + // a += ac __ addw(a, a, t, t1); - // a += x; + // rtmp1 = b & (c ^ d) = b & rtmp1 + __ andr(rtmp1, b, rtmp1); + + // a += x reg_cache.add_u32(a, k); - // a += value; - __ addw(a, a, value); - // a = Integer.rotateLeft(a, s) + b; + // rtmp1 = d ^ (b & (c ^ d)) = d ^ rtmp1 + __ xorr(rtmp1, d, rtmp1); + + // a += d ^ (b & (c ^ d)) + __ addw(a, a, rtmp1); + + // a = Integer.rotateLeft(a, s) + b __ rolw(a, a, s); __ addw(a, a, b); } - // a += ((b & c) | ((~b) & d)) + x + ac; + // a += ((c & (~d)) + (b & d)) + x + ac; // a = Integer.rotateLeft(a, s) + b; - void md5_FF(BufRegCache& reg_cache, + void md5_GG(BufRegCache& reg_cache, Register a, Register b, Register c, Register d, int k, int s, int t, Register rtmp1, Register rtmp2) { - // rtmp1 = b & c - __ andr(rtmp1, b, c); + // a += x + ac + reg_cache.add_u32(a, k); + __ addw(a, a, t, t1); - // rtmp2 = (~b) & d - __ andn(rtmp2, d, b); + // rtmp1 = c & (~d) + __ andn(rtmp1, c, d); - // rtmp1 = (b & c) | ((~b) & d) - __ orr(rtmp1, rtmp1, rtmp2); + // rtmp2 = b & d + __ andr(rtmp2, b, d); - m5_FF_GG_HH_II_epilogue(reg_cache, a, b, c, d, k, s, t, rtmp1); - } - - // a += ((b & d) | (c & (~d))) + x + ac; - // a = Integer.rotateLeft(a, s) + b; - void md5_GG(BufRegCache& reg_cache, - Register a, Register b, Register c, Register d, - int k, int s, int t, - Register rtmp1, Register rtmp2) { - // rtmp1 = b & d - __ andr(rtmp1, b, d); + // a += c & (~d) + __ addw(a, a, rtmp1); - // rtmp2 = c & (~d) - __ andn(rtmp2, c, d); + // a += b & d + __ addw(a, a, rtmp2); - // rtmp1 = (b & d) | (c & (~d)) - __ orr(rtmp1, rtmp1, rtmp2); - - m5_FF_GG_HH_II_epilogue(reg_cache, a, b, c, d, k, s, t, rtmp1); + // a = Integer.rotateLeft(a, s) + b + __ rolw(a, a, s); + __ addw(a, a, b); } - // a += ((b ^ c) ^ d) + x + ac; + // a += (b ^ (c ^ d)) + x + ac; // a = Integer.rotateLeft(a, s) + b; void md5_HH(BufRegCache& reg_cache, Register a, Register b, Register c, Register d, int k, int s, int t, - Register rtmp1, Register rtmp2) { - // rtmp1 = (b ^ c) ^ d - __ xorr(rtmp2, b, c); - __ xorr(rtmp1, rtmp2, d); + Register rtmp1) { + // rtmp1 = c ^ d + __ xorr(rtmp1, c, d); + + // a += x + ac + reg_cache.add_u32(a, k); + __ addw(a, a, t, t1); + + // rtmp1 = b ^ (c ^ d) + __ xorr(rtmp1, b, rtmp1); + + // a += b ^ (c ^ d) + __ addw(a, a, rtmp1); - m5_FF_GG_HH_II_epilogue(reg_cache, a, b, c, d, k, s, t, rtmp1); + // a = Integer.rotateLeft(a, s) + b + __ rolw(a, a, s); + __ addw(a, a, b); } // a += (c ^ (b | (~d))) + x + ac; @@ -5914,11 +5927,20 @@ class StubGenerator: public StubCodeGenerator { Register a, Register b, Register c, Register d, int k, int s, int t, Register rtmp1, Register rtmp2) { + // a += x + ac + reg_cache.add_u32(a, k); + __ addw(a, a, t, t1); + // rtmp1 = c ^ (b | (~d)) __ orn(rtmp2, b, d); __ xorr(rtmp1, c, rtmp2); - m5_FF_GG_HH_II_epilogue(reg_cache, a, b, c, d, k, s, t, rtmp1); + // a += c ^ (b | (~d)) + __ addw(a, a, rtmp1); + + // a = Integer.rotateLeft(a, s) + b + __ rolw(a, a, s); + __ addw(a, a, b); } // Arguments: @@ -6077,31 +6099,25 @@ class StubGenerator: public StubCodeGenerator { __ mv(c, state2); __ mv(d, state3); + reg_cache.gen_loads(buf); + // Round 1 - reg_cache.gen_load(0, buf); - md5_FF(reg_cache, a, b, c, d, 0, S11, 0xd76aa478, rtmp1, rtmp2); - md5_FF(reg_cache, d, a, b, c, 1, S12, 0xe8c7b756, rtmp1, rtmp2); - reg_cache.gen_load(1, buf); - md5_FF(reg_cache, c, d, a, b, 2, S13, 0x242070db, rtmp1, rtmp2); - md5_FF(reg_cache, b, c, d, a, 3, S14, 0xc1bdceee, rtmp1, rtmp2); - reg_cache.gen_load(2, buf); - md5_FF(reg_cache, a, b, c, d, 4, S11, 0xf57c0faf, rtmp1, rtmp2); - md5_FF(reg_cache, d, a, b, c, 5, S12, 0x4787c62a, rtmp1, rtmp2); - reg_cache.gen_load(3, buf); - md5_FF(reg_cache, c, d, a, b, 6, S13, 0xa8304613, rtmp1, rtmp2); - md5_FF(reg_cache, b, c, d, a, 7, S14, 0xfd469501, rtmp1, rtmp2); - reg_cache.gen_load(4, buf); - md5_FF(reg_cache, a, b, c, d, 8, S11, 0x698098d8, rtmp1, rtmp2); - md5_FF(reg_cache, d, a, b, c, 9, S12, 0x8b44f7af, rtmp1, rtmp2); - reg_cache.gen_load(5, buf); - md5_FF(reg_cache, c, d, a, b, 10, S13, 0xffff5bb1, rtmp1, rtmp2); - md5_FF(reg_cache, b, c, d, a, 11, S14, 0x895cd7be, rtmp1, rtmp2); - reg_cache.gen_load(6, buf); - md5_FF(reg_cache, a, b, c, d, 12, S11, 0x6b901122, rtmp1, rtmp2); - md5_FF(reg_cache, d, a, b, c, 13, S12, 0xfd987193, rtmp1, rtmp2); - reg_cache.gen_load(7, buf); - md5_FF(reg_cache, c, d, a, b, 14, S13, 0xa679438e, rtmp1, rtmp2); - md5_FF(reg_cache, b, c, d, a, 15, S14, 0x49b40821, rtmp1, rtmp2); + md5_FF(reg_cache, a, b, c, d, 0, S11, 0xd76aa478, rtmp1); + md5_FF(reg_cache, d, a, b, c, 1, S12, 0xe8c7b756, rtmp1); + md5_FF(reg_cache, c, d, a, b, 2, S13, 0x242070db, rtmp1); + md5_FF(reg_cache, b, c, d, a, 3, S14, 0xc1bdceee, rtmp1); + md5_FF(reg_cache, a, b, c, d, 4, S11, 0xf57c0faf, rtmp1); + md5_FF(reg_cache, d, a, b, c, 5, S12, 0x4787c62a, rtmp1); + md5_FF(reg_cache, c, d, a, b, 6, S13, 0xa8304613, rtmp1); + md5_FF(reg_cache, b, c, d, a, 7, S14, 0xfd469501, rtmp1); + md5_FF(reg_cache, a, b, c, d, 8, S11, 0x698098d8, rtmp1); + md5_FF(reg_cache, d, a, b, c, 9, S12, 0x8b44f7af, rtmp1); + md5_FF(reg_cache, c, d, a, b, 10, S13, 0xffff5bb1, rtmp1); + md5_FF(reg_cache, b, c, d, a, 11, S14, 0x895cd7be, rtmp1); + md5_FF(reg_cache, a, b, c, d, 12, S11, 0x6b901122, rtmp1); + md5_FF(reg_cache, d, a, b, c, 13, S12, 0xfd987193, rtmp1); + md5_FF(reg_cache, c, d, a, b, 14, S13, 0xa679438e, rtmp1); + md5_FF(reg_cache, b, c, d, a, 15, S14, 0x49b40821, rtmp1); // Round 2 md5_GG(reg_cache, a, b, c, d, 1, S21, 0xf61e2562, rtmp1, rtmp2); @@ -6122,22 +6138,22 @@ class StubGenerator: public StubCodeGenerator { md5_GG(reg_cache, b, c, d, a, 12, S24, 0x8d2a4c8a, rtmp1, rtmp2); // Round 3 - md5_HH(reg_cache, a, b, c, d, 5, S31, 0xfffa3942, rtmp1, rtmp2); - md5_HH(reg_cache, d, a, b, c, 8, S32, 0x8771f681, rtmp1, rtmp2); - md5_HH(reg_cache, c, d, a, b, 11, S33, 0x6d9d6122, rtmp1, rtmp2); - md5_HH(reg_cache, b, c, d, a, 14, S34, 0xfde5380c, rtmp1, rtmp2); - md5_HH(reg_cache, a, b, c, d, 1, S31, 0xa4beea44, rtmp1, rtmp2); - md5_HH(reg_cache, d, a, b, c, 4, S32, 0x4bdecfa9, rtmp1, rtmp2); - md5_HH(reg_cache, c, d, a, b, 7, S33, 0xf6bb4b60, rtmp1, rtmp2); - md5_HH(reg_cache, b, c, d, a, 10, S34, 0xbebfbc70, rtmp1, rtmp2); - md5_HH(reg_cache, a, b, c, d, 13, S31, 0x289b7ec6, rtmp1, rtmp2); - md5_HH(reg_cache, d, a, b, c, 0, S32, 0xeaa127fa, rtmp1, rtmp2); - md5_HH(reg_cache, c, d, a, b, 3, S33, 0xd4ef3085, rtmp1, rtmp2); - md5_HH(reg_cache, b, c, d, a, 6, S34, 0x04881d05, rtmp1, rtmp2); - md5_HH(reg_cache, a, b, c, d, 9, S31, 0xd9d4d039, rtmp1, rtmp2); - md5_HH(reg_cache, d, a, b, c, 12, S32, 0xe6db99e5, rtmp1, rtmp2); - md5_HH(reg_cache, c, d, a, b, 15, S33, 0x1fa27cf8, rtmp1, rtmp2); - md5_HH(reg_cache, b, c, d, a, 2, S34, 0xc4ac5665, rtmp1, rtmp2); + md5_HH(reg_cache, a, b, c, d, 5, S31, 0xfffa3942, rtmp1); + md5_HH(reg_cache, d, a, b, c, 8, S32, 0x8771f681, rtmp1); + md5_HH(reg_cache, c, d, a, b, 11, S33, 0x6d9d6122, rtmp1); + md5_HH(reg_cache, b, c, d, a, 14, S34, 0xfde5380c, rtmp1); + md5_HH(reg_cache, a, b, c, d, 1, S31, 0xa4beea44, rtmp1); + md5_HH(reg_cache, d, a, b, c, 4, S32, 0x4bdecfa9, rtmp1); + md5_HH(reg_cache, c, d, a, b, 7, S33, 0xf6bb4b60, rtmp1); + md5_HH(reg_cache, b, c, d, a, 10, S34, 0xbebfbc70, rtmp1); + md5_HH(reg_cache, a, b, c, d, 13, S31, 0x289b7ec6, rtmp1); + md5_HH(reg_cache, d, a, b, c, 0, S32, 0xeaa127fa, rtmp1); + md5_HH(reg_cache, c, d, a, b, 3, S33, 0xd4ef3085, rtmp1); + md5_HH(reg_cache, b, c, d, a, 6, S34, 0x04881d05, rtmp1); + md5_HH(reg_cache, a, b, c, d, 9, S31, 0xd9d4d039, rtmp1); + md5_HH(reg_cache, d, a, b, c, 12, S32, 0xe6db99e5, rtmp1); + md5_HH(reg_cache, c, d, a, b, 15, S33, 0x1fa27cf8, rtmp1); + md5_HH(reg_cache, b, c, d, a, 2, S34, 0xc4ac5665, rtmp1); // Round 4 md5_II(reg_cache, a, b, c, d, 0, S41, 0xf4292244, rtmp1, rtmp2); From 042f64e81f8e11e677062717d0f5bfc43212e7ab Mon Sep 17 00:00:00 2001 From: Jatin Bhateja Date: Mon, 19 Oct 2026 05:47:05 +0000 Subject: [PATCH 090/169] 8392548: Optimize instruction sequence for x86 ReplHF_reg instruction selection pattern Reviewed-by: sviswanathan, qamai --- src/hotspot/cpu/x86/assembler_x86.cpp | 4 +- src/hotspot/cpu/x86/x86.ad | 8 +- .../incubator/vector/ReplHFRegBenchmark.java | 90 +++++++++++++++++++ 3 files changed, 95 insertions(+), 7 deletions(-) create mode 100644 test/micro/org/openjdk/bench/jdk/incubator/vector/ReplHFRegBenchmark.java diff --git a/src/hotspot/cpu/x86/assembler_x86.cpp b/src/hotspot/cpu/x86/assembler_x86.cpp index b020c817dd19..2191eb676a66 100644 --- a/src/hotspot/cpu/x86/assembler_x86.cpp +++ b/src/hotspot/cpu/x86/assembler_x86.cpp @@ -10790,14 +10790,14 @@ void Assembler::vpbroadcastb(XMMRegister dst, Address src, int vector_len) { // duplicate 2-byte integer data from src into programmed locations in dest : requires AVX512BW and AVX512VL void Assembler::vpbroadcastw(XMMRegister dst, XMMRegister src, int vector_len) { - assert(VM_Version::supports_avx2(), ""); + assert(vector_len == AVX_512bit ? VM_Version::supports_avx512bw() : VM_Version::supports_avx2(), ""); InstructionAttr attributes(vector_len, /* vex_w */ false, /* legacy_mode */ _legacy_mode_bw, /* no_mask_reg */ true, /* uses_vl */ true); int encode = vex_prefix_and_encode(dst->encoding(), 0, src->encoding(), VEX_SIMD_66, VEX_OPCODE_0F_38, &attributes); emit_int16(0x79, (0xC0 | encode)); } void Assembler::vpbroadcastw(XMMRegister dst, Address src, int vector_len) { - assert(VM_Version::supports_avx2(), ""); + assert(vector_len == AVX_512bit ? VM_Version::supports_avx512bw() : VM_Version::supports_avx2(), ""); assert(dst != xnoreg, "sanity"); InstructionMark im(this); InstructionAttr attributes(vector_len, /* vex_w */ false, /* legacy_mode */ _legacy_mode_bw, /* no_mask_reg */ true, /* uses_vl */ true); diff --git a/src/hotspot/cpu/x86/x86.ad b/src/hotspot/cpu/x86/x86.ad index 62c30a668d7f..fc6f07d3a8de 100644 --- a/src/hotspot/cpu/x86/x86.ad +++ b/src/hotspot/cpu/x86/x86.ad @@ -18676,15 +18676,13 @@ instruct ReplHF_imm(vec dst, immH con, rRegI rtmp) %{ ins_pipe( pipe_slow ); %} -instruct ReplHF_reg(vec dst, regF src, rRegI rtmp) %{ +instruct ReplHF_reg(vec dst, regF src) %{ predicate(VM_Version::supports_avx512_fp16() && Matcher::vector_element_basic_type(n) == T_SHORT); match(Set dst (Replicate src)); - effect(TEMP rtmp); - format %{ "replicateHF $dst, $src \t! using $rtmp as TEMP" %} + format %{ "replicateHF $dst, $src" %} ins_encode %{ int vlen_enc = vector_length_encoding(this); - __ evmovw($rtmp$$Register, $src$$XMMRegister); - __ evpbroadcastw($dst$$XMMRegister, $rtmp$$Register, vlen_enc); + __ vpbroadcastw($dst$$XMMRegister, $src$$XMMRegister, vlen_enc); %} ins_pipe( pipe_slow ); %} diff --git a/test/micro/org/openjdk/bench/jdk/incubator/vector/ReplHFRegBenchmark.java b/test/micro/org/openjdk/bench/jdk/incubator/vector/ReplHFRegBenchmark.java new file mode 100644 index 000000000000..becc1be2fd0b --- /dev/null +++ b/test/micro/org/openjdk/bench/jdk/incubator/vector/ReplHFRegBenchmark.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.openjdk.bench.jdk.incubator.vector; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; + +@BenchmarkMode(Mode.Throughput) +@OutputTimeUnit(TimeUnit.MILLISECONDS) +@State(Scope.Thread) +public class ReplHFRegBenchmark { + static final int LEN = 32; + + static final int N = 16; + + static final int K = 512; + + short[] src = new short[LEN]; + short[] dst = new short[LEN]; + + float[] vals = new float[K + N]; + + @Setup + public void setup() { + Arrays.fill(src, Float.floatToFloat16(1.0f)); + for (int i = 0; i < vals.length; i++) { + // Small magnitudes so the adds stay in FP16 range. + vals[i] = 0.125f + (i % 7) * 0.0625f; + } + } + + private static short hfAdd(short a, float b) { + return Float.floatToFloat16(Float.float16ToFloat(a) + + Float.float16ToFloat(Float.floatToFloat16(b))); + } + + @Benchmark + public int manyBroadcasts() { + short[] d = dst; + short[] s = src; + float[] v = vals; + for (int k = 0; k < K; k++) { + float v0 = v[k], v1 = v[k + 1], v2 = v[k + 2], v3 = v[k + 3]; + float v4 = v[k + 4], v5 = v[k + 5], v6 = v[k + 6], v7 = v[k + 7]; + float v8 = v[k + 8], v9 = v[k + 9], v10 = v[k + 10], v11 = v[k + 11]; + float v12 = v[k + 12], v13 = v[k + 13], v14 = v[k + 14], v15 = v[k + 15]; + for (int i = 0; i < LEN; i++) { + short a = s[i]; + int t = hfAdd(a, v0) ^ hfAdd(a, v1) ^ hfAdd(a, v2) ^ hfAdd(a, v3) + ^ hfAdd(a, v4) ^ hfAdd(a, v5) ^ hfAdd(a, v6) ^ hfAdd(a, v7) + ^ hfAdd(a, v8) ^ hfAdd(a, v9) ^ hfAdd(a, v10) ^ hfAdd(a, v11) + ^ hfAdd(a, v12) ^ hfAdd(a, v13) ^ hfAdd(a, v14) ^ hfAdd(a, v15); + d[i] = (short) t; + } + } + return d[0] & 0xffff; + } +} From d0cc2cd35c1d3afdba8155ed8a4af2bad336f73b Mon Sep 17 00:00:00 2001 From: Marc Chevalier Date: Mon, 19 Oct 2026 06:58:16 +0000 Subject: [PATCH 091/169] 8393019: [Valhalla] C2: assert(_outcnt==1) failed: not unique Reviewed-by: chagedorn, qamai --- src/hotspot/share/opto/loopopts.cpp | 6 +- src/hotspot/share/opto/macro.cpp | 60 ++++++++++++------- .../TestFlatArrayCheckExpansion.java | 51 +++++++++++++++- 3 files changed, 93 insertions(+), 24 deletions(-) diff --git a/src/hotspot/share/opto/loopopts.cpp b/src/hotspot/share/opto/loopopts.cpp index d5332dbef541..c2699cd4afb9 100644 --- a/src/hotspot/share/opto/loopopts.cpp +++ b/src/hotspot/share/opto/loopopts.cpp @@ -1168,8 +1168,10 @@ void PhaseIdealLoop::move_flat_array_check_out_of_loop(Node* n) { // Replace memory input and re-compute ctrl to move the check out of the loop _igvn.replace_input_of(n, 1, mem); set_ctrl_and_loop(n, get_early_ctrl(n)); - Node* bol = n->unique_out(); - set_ctrl_and_loop(bol, get_early_ctrl(bol)); + for (DUIterator_Fast imax, i = n->fast_outs(imax); i < imax; i++) { + Node* bol = n->fast_out(i); + set_ctrl_and_loop(bol, get_early_ctrl(bol)); + } } } diff --git a/src/hotspot/share/opto/macro.cpp b/src/hotspot/share/opto/macro.cpp index 5e5ca35bcdab..1f873cf882f3 100644 --- a/src/hotspot/share/opto/macro.cpp +++ b/src/hotspot/share/opto/macro.cpp @@ -3089,12 +3089,18 @@ void PhaseMacroExpand::expand_subtypecheck_node(SubTypeCheckNode *check) { // } void PhaseMacroExpand::expand_flatarraycheck_node(FlatArrayCheckNode* check) { bool use_mark_word = _igvn.type(check->in(FlatArrayCheckNode::ArrayOrKlass))->isa_oopptr() != nullptr; - Node* bol = check->unique_out(); - for (DUIterator_Fast imax, i = bol->fast_outs(imax); i < imax; i++) { - if (!bol->fast_out(i)->is_If()) { - // No control input, fall back to layout helper check - use_mark_word = false; - break; + Unique_Node_List outputs; + + for (DUIterator_Fast imax, i = check->fast_outs(imax); i < imax; i++) { + Node* bol = check->fast_out(i); + assert(bol->is_Bool(), "expected Bool node, but got: %s", bol->Name()); + outputs.push(bol); + for (DUIterator_Fast jmax, j = bol->fast_outs(jmax); j < jmax; j++) { + if (!bol->fast_out(j)->is_If()) { + // No control input, fall back to layout helper check + use_mark_word = false; + break; + } } } @@ -3112,16 +3118,26 @@ void PhaseMacroExpand::expand_flatarraycheck_node(FlatArrayCheckNode* check) { } assert(!mark->is_Con(), "Should have been optimized out"); - // Replace the bool node - assert(bol->is_Bool() && bol->as_Bool()->_test._test == BoolTest::ne, "unexpected condition"); - // Check if flat array bits are set Node* mask = MakeConX(markWord::flat_array_bit_in_place); Node* masked = _igvn.transform(new AndXNode(mark, mask)); Node* cmp = _igvn.transform(new CmpXNode(masked, MakeConX(0))); - Node* is_not_flat = _igvn.transform(new BoolNode(cmp, BoolTest::eq)); - _igvn.replace_node(bol, is_not_flat); + // Replace the bool nodes + for (uint i = 0; i < outputs.size(); ++i) { + Node* bol = outputs.at(i); + assert(bol->as_Bool()->_test._test == BoolTest::ne || bol->as_Bool()->_test._test == BoolTest::eq, "unexpected condition"); + + // FlatArrayCheckNode returns true if one of the inputs is flat. + // - Bool(FlatArrayCheckNode, !=) is testing that the arrays are not flat, + // which means that none of the mark words has the flatness bit on, + // that is `mark & mask == 0`, so we need to build Bool(Cmp(mark & mask, 0), ==). + // - Similarly, Bool(FlatArrayCheckNode, ==) is testing that at least one + // array is flat, so we need to build Bool(Cmp(mark & mask, 0), !=). + // Hence, the .negate() thereunder. + Node* flatness_test = _igvn.transform(new BoolNode(cmp, bol->as_Bool()->_test.negate())); + _igvn.replace_node(bol, flatness_test); + } _igvn.replace_node(check, C->top()); } else { // Fall back to layout helper check @@ -3144,16 +3160,18 @@ void PhaseMacroExpand::expand_flatarraycheck_node(FlatArrayCheckNode* check) { } Node* masked = transform_later(new AndINode(lhs, intcon(Klass::_lh_array_tag_flat_value_bit_inplace))); Node* cmp = transform_later(new CmpINode(masked, intcon(0))); - Node* new_bol = transform_later(new BoolNode(cmp, BoolTest::eq)); - Node* m2b = transform_later(new Conv2BNode(masked)); - // The matcher expects the input to If/CMove nodes to be produced by a Bool(CmpI..) - // pattern, but the input to other potential users (e.g. Phi) to be some - // other pattern (e.g. a Conv2B node, possibly idealized as a CMoveI). - for (DUIterator_Last imin, i = bol->last_outs(imin); i >= imin; --i) { - Node* user = bol->last_out(i); - for (uint j = 0; j < user->req(); j++) { - if (user->in(j) == bol) { - _igvn.replace_input_of(user, j, (user->is_If() || user->is_CMove()) ? new_bol : m2b); + + for (uint i = 0; i < outputs.size(); ++i) { + Node* bol = outputs.at(i); + for (DUIterator_Last jmin, j = bol->last_outs(jmin); j >= jmin; --j) { + Node* user = bol->last_out(j); + // See comment above for the logic of the .negate(). + Node* new_bol = transform_later(new BoolNode(cmp, bol->as_Bool()->_test.negate())); + assert(user->is_If() || user->is_CMove(), "must be a legit user of Bool node"); + for (uint k = 0; k < user->req(); k++) { + if (user->in(k) == bol) { + _igvn.replace_input_of(user, k, new_bol); + } } } } diff --git a/test/hotspot/jtreg/compiler/valhalla/valuetypes/TestFlatArrayCheckExpansion.java b/test/hotspot/jtreg/compiler/valhalla/valuetypes/TestFlatArrayCheckExpansion.java index f1a2fa8c4146..5eeb7435cb66 100644 --- a/test/hotspot/jtreg/compiler/valhalla/valuetypes/TestFlatArrayCheckExpansion.java +++ b/test/hotspot/jtreg/compiler/valhalla/valuetypes/TestFlatArrayCheckExpansion.java @@ -28,8 +28,10 @@ /** * @test - * @bug 8391280 + * @bug 8391280 8393019 * @summary Test macro expansion of merged flat-array checks with array and klass inputs + * @key stress randomness + * @requires vm.compiler2.enabled * @enablePreview * @library /test/lib * @modules java.base/jdk.internal.value @@ -37,6 +39,18 @@ * @run main/othervm -Xbatch * -XX:CompileCommand=compileonly,${test.main.class}::test* * ${test.main.class} + * @run main/othervm -Xbatch -XX:-TieredCompilation + * -XX:+UnlockDiagnosticVMOptions + * -XX:ConditionalMoveLimit=4 + * -XX:+StressIGVN -XX:StressSeed=4 + * -XX:CompileCommand=compileonly,${test.main.class}::test* + * ${test.main.class} + * @run main/othervm -Xbatch -XX:-TieredCompilation + * -XX:+UnlockDiagnosticVMOptions + * -XX:ConditionalMoveLimit=4 + * -XX:+StressIGVN + * -XX:CompileCommand=compileonly,${test.main.class}::test* + * ${test.main.class} */ public class TestFlatArrayCheckExpansion { @@ -103,6 +117,35 @@ static int test4(Object[] array1, Object[] array2) { return res; } + // Forcing two outputs to the FlatArrayCheck: + // Naively, notFlat would be + // CMove(Bool(FlatArrayCheck, !=), false=0, true=1) + // But CMove doesn't like having 0 on the false-case (with true-case being constant), + // so it is tranformed into + // CMove(Bool(FlatArrayCheck, ==), false=1, true=0) + // On the other hand, (notFlat ? 1 : 2) + // CMove(Bool(FlatArrayCheck, !=), false=2, true=1) + // So we end up with a FlatArrayCheck with two Bool output: + // one with condition "==", and one "!=". This test makes sure we support FlatArrayCheck + // with multiple outputs. + static int test5(Object[] array, int i) { + boolean notFlat = !ValueClass.isFlatArray(array); + Object value = array[i]; + return (notFlat ? 1 : 2) + (value == null ? 4 : 8); + } + + // Pretty much the same as above, but in the situation we want to move the flatness check + // above the loop. + static int test6(Object[] array) { + int res = 0; + for (int i = 0; i < 4; i++) { + boolean notFlat = !ValueClass.isFlatArray(array); + Object value = array[i]; + res += (notFlat ? 1 : 2) + (value == null ? 4 : 8); + } + return res; + } + public static void main(String[] args) { Object[] refArray = {1, 2, 3, 4}; Integer[] flatArray = {1, 2, 3, 4}; @@ -124,6 +167,12 @@ public static void main(String[] args) { Asserts.assertEQ(test4(refArray, flatArray), 18); Asserts.assertEQ(test4(flatArray, refArray), isFlat ? 14 : 18); Asserts.assertEQ(test4(flatArray, flatArray), isFlat ? 14 : 18); + + Asserts.assertEQ(test5(refArray, 0), 9); + Asserts.assertEQ(test5(flatArray, 0), isFlat ? 10 : 9); + + Asserts.assertEQ(test6(refArray), 36); + Asserts.assertEQ(test6(flatArray), isFlat ? 40 : 36); } } } From 80df161c41b637806ce950d4aa1f7ceb40ae3db7 Mon Sep 17 00:00:00 2001 From: Albert Mingkun Yang Date: Mon, 19 Oct 2026 07:16:49 +0000 Subject: [PATCH 092/169] 8394791: RISC-V: IR Framework Add "zbb" to verified CPU features Reviewed-by: fyang, gcao, dzhang --- .../compiler/lib/ir_framework/test/ApplicableIRRulesPrinter.java | 1 + 1 file changed, 1 insertion(+) diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/test/ApplicableIRRulesPrinter.java b/test/hotspot/jtreg/compiler/lib/ir_framework/test/ApplicableIRRulesPrinter.java index 2db7975b3e2e..94d1b039f212 100644 --- a/test/hotspot/jtreg/compiler/lib/ir_framework/test/ApplicableIRRulesPrinter.java +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/test/ApplicableIRRulesPrinter.java @@ -122,6 +122,7 @@ public class ApplicableIRRulesPrinter { "asimdhp", // RISCV64 "rvv", + "zbb", "zbkb", "zfh", "zvbb", From 4b4a691daf3744cc569d206d654070a9c477ada1 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Mon, 19 Oct 2026 07:21:46 +0000 Subject: [PATCH 093/169] 8392938: G1: Wrong young gen bounds calculation on huge heaps Reviewed-by: tschatzl, ayang --- src/hotspot/share/gc/g1/g1YoungGenSizer.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/hotspot/share/gc/g1/g1YoungGenSizer.cpp b/src/hotspot/share/gc/g1/g1YoungGenSizer.cpp index df8838bcd974..3f7632d6aa11 100644 --- a/src/hotspot/share/gc/g1/g1YoungGenSizer.cpp +++ b/src/hotspot/share/gc/g1/g1YoungGenSizer.cpp @@ -28,6 +28,7 @@ #include "gc/shared/gc_globals.hpp" #include "logging/log.hpp" #include "runtime/globals_extension.hpp" +#include "utilities/checkedCast.hpp" G1YoungGenSizer::G1YoungGenSizer() : _sizer_kind(SizerDefaults), _use_adaptive_sizing(true), _min_desired_num_regions(), _max_desired_num_regions(0) { @@ -119,12 +120,12 @@ G1YoungGenSizer::G1YoungGenSizer() : _sizer_kind(SizerDefaults), } uint G1YoungGenSizer::calculate_default_min_num_regions(uint new_num_regions) { - uint default_value = (new_num_regions * G1NewSizePercent) / 100; + uint default_value = checked_cast(((uint64_t)new_num_regions * G1NewSizePercent) / 100); return MAX2(1U, default_value); } uint G1YoungGenSizer::calculate_default_max_num_regions(uint new_num_regions) { - uint default_value = (new_num_regions * G1MaxNewSizePercent) / 100; + uint default_value = checked_cast(((uint64_t)new_num_regions * G1MaxNewSizePercent) / 100); return MAX2(1U, default_value); } From 760dbf418bfbedecca2d5765189962fcaff3f9f4 Mon Sep 17 00:00:00 2001 From: Roland Westrelin Date: Mon, 19 Oct 2026 07:31:07 +0000 Subject: [PATCH 094/169] 8392784: [Valhalla] Infinite loop in PushValueTypeDown Reviewed-by: thartmann, qamai --- src/hotspot/share/opto/cfgnode.cpp | 43 ++++++++++---- .../TestInfinitePushValueTypesDown.java | 56 +++++++++++++++++++ 2 files changed, 89 insertions(+), 10 deletions(-) create mode 100644 test/hotspot/jtreg/compiler/valhalla/valuetypes/TestInfinitePushValueTypesDown.java diff --git a/src/hotspot/share/opto/cfgnode.cpp b/src/hotspot/share/opto/cfgnode.cpp index c560041d1d2d..1e5e3ccc3434 100644 --- a/src/hotspot/share/opto/cfgnode.cpp +++ b/src/hotspot/share/opto/cfgnode.cpp @@ -2941,10 +2941,17 @@ class PushValueTypeDown { _nodes_from_phi.push(in); } } else if (n->is_ValueType()) { - Node* buf = n->as_ValueType()->get_oop(); - if (buf != nullptr) { - _nodes_from_phi.push(buf); - _subgraph_to_clone.push(n); + ValueTypeNode* value_type = n->as_ValueType(); + Node* buf = value_type->get_oop(); + _nodes_from_phi.push(buf); + _subgraph_to_clone.push(n); + // We may have a cycle through one the ValueType's field so follow those inputs + for (uint j = 0; j < value_type->field_count(); ++j) { + Node* field = value_type->field_value(j); + const Type* field_type = _phase->type(field); + if (field_type->isa_instptr() && !field_type->is_valueklassptr() && field_type->is_instptr()->can_be_value_type()) { + _nodes_from_phi.push(field); + } } } } @@ -3012,11 +3019,21 @@ class PushValueTypeDown { assert(in_clone != nullptr, "must be cloned"); n_clone->set_req(1, in_clone); } else if (n->is_ValueType()) { - Node* in = n->as_ValueType()->get_oop(); + ValueTypeNode* value_type = n->as_ValueType(); + Node* in = value_type->get_oop(); Node* in_clone = get_clone(in); if (in_clone != nullptr) { _phase->is_IterGVN()->rehash_node_delayed(n); - n->as_ValueType()->set_oop(*_phase, in_clone); + value_type->set_oop(*_phase, in_clone); + } + // If the ValueType is reached from one of its field's input, make sure we update that input to not have a cycle + for (uint j = 0; j < value_type->field_count(); ++j) { + Node* field = value_type->field_value(j); + Node* field_clone = get_clone(field); + if (field_clone != nullptr) { + _phase->is_IterGVN()->rehash_node_delayed(n); + value_type->set_field_value(j, field_clone); + } } } } @@ -3043,7 +3060,7 @@ class PushValueTypeDown { before_encode_decode++; } else if (n->is_ValueType()) { Node* buf = n->as_ValueType()->get_oop(); - if (buf != nullptr && i >= _init_nodes) { + if (i >= _init_nodes) { vts_to_skip++; } } @@ -3101,9 +3118,15 @@ class PushValueTypeDown { } } else if (n->is_ValueType()) { assert(i < init_nodes, ""); - Node* buf = n->as_ValueType()->get_oop(); - if (buf != nullptr) { - after.push(buf); + ValueTypeNode* value_type = n->as_ValueType(); + Node* buf = value_type->get_oop(); + after.push(buf); + for (uint j = 0; j < value_type->field_count(); ++j) { + Node* field = value_type->field_value(j); + const Type* field_type = _phase->type(field); + if (field_type->isa_instptr() && !field_type->is_valueklassptr() && field_type->is_instptr()->can_be_value_type()) { + after.push(field); + } } } } diff --git a/test/hotspot/jtreg/compiler/valhalla/valuetypes/TestInfinitePushValueTypesDown.java b/test/hotspot/jtreg/compiler/valhalla/valuetypes/TestInfinitePushValueTypesDown.java new file mode 100644 index 000000000000..563c88231b25 --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/valuetypes/TestInfinitePushValueTypesDown.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @bug 8392784 + * @enablePreview + * @run main/othervm -Xbatch -XX:CompileCommand=compileonly,${test.main.class}::test ${test.main.class} + * @run main ${test.main.class} + */ + +public class TestInfinitePushValueTypesDown { + + static value class MyValue { + Object other; + + MyValue(Object other) { + this.other = other; + } + } + + static Object test() { + Object other = null; + for (int i = 0; i < 1; i++) { + other = new MyValue(other); + } + return other; + } + + public static void main(String[] args) { + for (int i = 0; i < 50_000; i++) { + test(); + } + } +} + From 90dfb8cfeae718617ff977ffb9300b490c65a8c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hannes=20Walln=C3=B6fer?= Date: Mon, 19 Oct 2026 07:53:38 +0000 Subject: [PATCH 095/169] 8393742: Multiple @apiNote tags in a doc comment are rendered poorly Reviewed-by: nbenalla --- .../formats/html/taglets/SimpleTaglet.java | 55 +++++++---- .../formats/html/taglets/TagletManager.java | 2 +- .../doclet/testSimpleTag/TestSimpleTag.java | 91 ++++++++++++++++++- 3 files changed, 125 insertions(+), 23 deletions(-) diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/SimpleTaglet.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/SimpleTaglet.java index adf979697c5b..6f6b7e68f0c4 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/SimpleTaglet.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/SimpleTaglet.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -60,21 +60,6 @@ public class SimpleTaglet extends BaseTaglet implements InheritableTaglet { private final boolean enabled; - /** - * Constructs a {@code SimpleTaglet}. - * - * @param tagName the name of this tag - * @param header the header to output - * @param locations the possible locations that this tag can appear in - * The string can contain 'p' for package, 't' for type, - * 'm' for method, 'c' for constructor and 'f' for field. - * See {@link #getLocations(String) getLocations} for the - * complete list. - */ - SimpleTaglet(HtmlConfiguration config, String tagName, String header, String locations) { - this(config, tagName, header, getLocations(locations), isEnabled(locations)); - } - /** * Constructs a {@code SimpleTaglet}. * @@ -125,6 +110,39 @@ protected SimpleTaglet(HtmlConfiguration config, DocTree.Kind tagKind, String he this.enabled = enabled; } + /** + * Constructs a {@code SimpleTaglet} to represent a custom tag. The returned taglet + * overrides block tag output to render multiple block tags as individual {@code

      } + * elements instead of as a comma-separated list. + * + * @param config the configuration + * @param tagName the name of this tag + * @param header the header to output + * @param locations the possible locations that this tag can appear in + * The string can contain 'p' for package, 't' for type, + * 'm' for method, 'c' for constructor and 'f' for field. + * See {@link #getLocations(String) getLocations} for the + * complete list. + */ + public static SimpleTaglet createCustomTaglet(HtmlConfiguration config, String tagName, + String header, String locations) { + return new SimpleTaglet(config, tagName, header, getLocations(locations), isEnabled(locations)) { + @Override + protected Content simpleBlockTagOutput(Element element, List simpleTags, String header) { + var ch = utils.getCommentHelper(element); + var context = tagletWriter.context; + var htmlWriter = tagletWriter.htmlWriter; + + ContentBuilder output = new ContentBuilder(HtmlTree.DT(RawHtml.of(header))); + for (DocTree simpleTag : simpleTags) { + output.add(HtmlTree.DD(htmlWriter.commentTagsToContent(element, ch.getBody(simpleTag), + context.within(simpleTag)))); + } + return output; + } + }; + } + /** * Constructs a {@code SimpleTaglet} that will look for tags on enclosing type elements * if there are no relevant tags on a nested (member) type element. @@ -134,7 +152,8 @@ protected SimpleTaglet(HtmlConfiguration config, DocTree.Kind tagKind, String he * @param locations the possible locations that this tag can appear in * @param enabled whether this tag is enabled */ - static SimpleTaglet createWithDefaultForNested(HtmlConfiguration config, DocTree.Kind tagKind, String header, Set locations, boolean enabled) { + static SimpleTaglet createWithDefaultForNested(HtmlConfiguration config, DocTree.Kind tagKind, String header, + Set locations, boolean enabled) { return new SimpleTaglet(config, tagKind, header, locations, enabled) { @Override protected List getDefaultBlockTags(Element e, Predicate accepts) { @@ -237,7 +256,7 @@ protected List getDefaultBlockTags(Element e, Predicate< * * @return the output */ - private Content simpleBlockTagOutput(Element element, + protected Content simpleBlockTagOutput(Element element, List simpleTags, String header) { var ch = utils.getCommentHelper(element); diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/TagletManager.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/TagletManager.java index 0e7b29590287..0237c08f5629 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/TagletManager.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/TagletManager.java @@ -303,7 +303,7 @@ public void addNewSimpleCustomTag(String tagName, String header, String location // remove + put in both branches below move the tag to the back of the map's ordering Taglet tag = allTaglets.remove(tagName); if (tag == null || header != null) { - allTaglets.put(tagName, new SimpleTaglet(config, tagName, header, locations)); + allTaglets.put(tagName, SimpleTaglet.createCustomTaglet(config, tagName, header, locations)); if (Utils.toLowerCase(locations).indexOf('x') == -1) { checkTagName(tagName); } diff --git a/test/langtools/jdk/javadoc/doclet/testSimpleTag/TestSimpleTag.java b/test/langtools/jdk/javadoc/doclet/testSimpleTag/TestSimpleTag.java index 47f5bd832a96..98c8f89e4b58 100644 --- a/test/langtools/jdk/javadoc/doclet/testSimpleTag/TestSimpleTag.java +++ b/test/langtools/jdk/javadoc/doclet/testSimpleTag/TestSimpleTag.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2002, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,21 +23,27 @@ /* * @test - * @bug 4695326 4750173 4920381 8078320 8071982 8239804 + * @bug 4695326 4750173 4920381 8078320 8071982 8239804 8393742 * @summary Test the declaration of simple tags using -tag. Verify that * "-tag name" is a shortcut for "-tag name:a:Name:". Also verity that * you can escape the ":" character with a back slash so that it is not * considered a separator when parsing the simple tag argument. - * @library ../../lib + * @library /tools/lib ../../lib * @modules jdk.javadoc/jdk.javadoc.internal.tool - * @build javadoc.tester.* + * @build toolbox.ToolBox javadoc.tester.* * @run main TestSimpleTag */ import javadoc.tester.JavadocTester; +import toolbox.ToolBox; + +import java.io.IOException; +import java.nio.file.Path; public class TestSimpleTag extends JavadocTester { + private final ToolBox tb = new ToolBox(); + public static void main(String... args) throws Exception { var tester = new TestSimpleTag(); tester.runTests(); @@ -65,4 +71,81 @@ public void test() {
      Parameters:
      arg - this is an int argument.
      """); } + + + // Multiple custom tags should be encoded as individual
      elements, + // while preserving comma-separated list format for other simple tags. + @Test + public void testMultipleTags(Path base) throws IOException { + Path src = base.resolve("src"); + + tb.writeJavaFiles(src, """ + package test; + /** + * Test class with multiple tags. + * + * @warning + * First warning. + * + * @version 1.0 + * @author Duke + * @author Leo + * @author Lisa + * @version 1.0.1 + * + * @note This is a note. + * + * @note Second note with multiple paragraphs. + *

      Second paragraph of second note.

      + *

      Third paragraph of second note.

      + * + * @note Third note. + * + * @warning Another warning. + *
        + *
      • First list item. + *
      • Second list item. + *
      + */ + public class MultiTag { + } + """); + + javadoc("-d", base.resolve("out").toString(), + "-author", "-version", + "-tag", "warning:a:Warning:", + "-tag", "note:a:Note:", + "-tag", "author", + "-tag", "version", + "-sourcepath", src.toString(), + "test"); + + checkExit(Exit.OK); + + checkOutput("test/MultiTag.html", true, """ +
      +
      Warning:
      +
      First warning.
      +
      Another warning. +
        +
      • First list item. +
      • Second list item. +
      + """, + """ +
      Note:
      +
      This is a note.
      +
      Second note with multiple paragraphs. +

      Second paragraph of second note.

      +

      Third paragraph of second note.

      +
      Third note.
      + """, + """ +
      Author:
      +
      Duke, Leo, Lisa
      +
      Version:
      +
      1.0, 1.0.1
      +
      + """); + } } From 39331686589aa71aa13f83f7bc353d94d2b46edd Mon Sep 17 00:00:00 2001 From: Serguei Spitsyn Date: Mon, 19 Oct 2026 08:12:06 +0000 Subject: [PATCH 096/169] 8394716: SetExtensionEventCallback should not enable the event Reviewed-by: lmesnik, cjplummer --- src/hotspot/share/prims/jvmti.xml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/hotspot/share/prims/jvmti.xml b/src/hotspot/share/prims/jvmti.xml index b7ede129cf5e..9fa08096688b 100644 --- a/src/hotspot/share/prims/jvmti.xml +++ b/src/hotspot/share/prims/jvmti.xml @@ -10316,10 +10316,10 @@ myInit() { Set Extension Event Callback - Sets the callback function for an extension event and - enables the event. Or, if the callback is null, disables - the event. Note that unlike standard events, setting - the callback and enabling the event are a single operation. + Sets the callback function for an extension event. + If is null the callback is removed. + Use with + to enable or disable notification. new @@ -10338,12 +10338,12 @@ myInit() { jvmtiExtensionEvent - disable the event + remove the callback + If is null the callback is removed. If callback is non-null, - set callback to be the event callback function - and enable the event. + set callback to be the event callback function. From 7bb1ed212d4f3f52be02a86857a8190bb66d2f09 Mon Sep 17 00:00:00 2001 From: Aleksey Shipilev Date: Mon, 19 Oct 2026 08:23:43 +0000 Subject: [PATCH 097/169] 8392844: Compile normal version when OSR compilation is not possible at back branch Reviewed-by: thartmann, iveresov, phh --- src/hotspot/share/compiler/compilationPolicy.cpp | 11 +++++++++-- .../valhalla/valuetypes/TestBasicFunctionality.java | 11 +++++++---- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/hotspot/share/compiler/compilationPolicy.cpp b/src/hotspot/share/compiler/compilationPolicy.cpp index 47ad8e650100..38e0df7fa468 100644 --- a/src/hotspot/share/compiler/compilationPolicy.cpp +++ b/src/hotspot/share/compiler/compilationPolicy.cpp @@ -1582,9 +1582,16 @@ void CompilationPolicy::method_back_branch_event(const methodHandle& mh, const m if (is_compilation_enabled()) { CompLevel next_osr_level = loop_event(imh, level, THREAD); CompLevel max_osr_level = (CompLevel)imh->highest_osr_comp_level(); - // At the very least compile the OSR version + + // Try to compile the OSR version, so that it can be switched at this back branch. + // If OSR compilation is not possible, compile normal version instead, and hope + // the back branch bearing method would be re-entered soon. if (!CompileBroker::compilation_is_in_queue(imh) && (next_osr_level != level)) { - compile(imh, bci, next_osr_level, CHECK); + if (can_be_osr_compiled(imh, CompLevel_any)) { + compile(imh, bci, next_osr_level, CHECK); + } else if (comp_level(imh()) < next_osr_level) { + compile(imh, InvocationEntryBci, next_osr_level, CHECK); + } } // Use loop event as an opportunity to also check if there's been diff --git a/test/hotspot/jtreg/compiler/valhalla/valuetypes/TestBasicFunctionality.java b/test/hotspot/jtreg/compiler/valhalla/valuetypes/TestBasicFunctionality.java index 0c8c449eaa07..17d7eb0fa985 100644 --- a/test/hotspot/jtreg/compiler/valhalla/valuetypes/TestBasicFunctionality.java +++ b/test/hotspot/jtreg/compiler/valhalla/valuetypes/TestBasicFunctionality.java @@ -377,7 +377,7 @@ public void test11_verifier() { } // Test loop with uncommon trap referencing a value object - @Test + @Test(allowNotCompilable = true) @IR(applyIf = {"UseArrayFlattening", "true"}, failOn = LOAD_OF_ANY_KLASS, counts = {SCOPE_OBJECT, ">= 1"}) @@ -405,10 +405,13 @@ public long test12(boolean b) { @Run(test = "test12") public void test12_verifier(RunInfo info) { - // Disable OSR compilation prevents the method from getting recompiled because the IR rules - // expect all loads moved into the uncommon trap, which is not the case when the method get - // recompiled and the path that was unreached before is now compiled + // IR rules expect all loads moved into the uncommon trap, which is not the case when the method get + // recompiled and the path that was unreached before is now compiled. To avoid this, we need to block + // stray OSR compilations and recompiles after warmup. WhiteBox.getWhiteBox().makeMethodNotCompilable(info.getTest(), CompLevel.C2.getValue(), true); + if (info.isTestC2Compiled()) { + WhiteBox.getWhiteBox().makeMethodNotCompilable(info.getTest(), CompLevel.C2.getValue(), false); + } long result = test12(info.isWarmUp()); Asserts.assertEQ(result, info.isWarmUp() ? rL + (1000 * rI) : ((Math.abs(rI) % 10) + 1) * hash()); } From 837606c26766759f82c61fc1d6570fbba751e46a Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 19 Oct 2026 08:43:54 +0000 Subject: [PATCH 098/169] 8394743: Remove redundant private methods from MutableBigInteger Reviewed-by: darcy --- .../classes/java/math/MutableBigInteger.java | 32 +------------------ 1 file changed, 1 insertion(+), 31 deletions(-) diff --git a/src/java.base/share/classes/java/math/MutableBigInteger.java b/src/java.base/share/classes/java/math/MutableBigInteger.java index 1ede4cf32f8c..afee2aeeac74 100644 --- a/src/java.base/share/classes/java/math/MutableBigInteger.java +++ b/src/java.base/share/classes/java/math/MutableBigInteger.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -383,24 +383,6 @@ private final int getLowestSetBit() { return ((intLen-1-j)<<5) + Integer.numberOfTrailingZeros(b); } - /** - * Return the int in use in this MutableBigInteger at the specified - * index. This method is not used because it is not inlined on all - * platforms. - */ - private final int getInt(int index) { - return value[offset+index]; - } - - /** - * Return a long which is equal to the unsigned value of the int in - * use in this MutableBigInteger at the specified index. This method is - * not used because it is not inlined on all platforms. - */ - private final long getLong(int index) { - return value[offset+index] & LONG_MASK; - } - /** * Ensure that the MutableBigInteger is in normal form, specifically * making sure that there are no leading zeros, and that if the @@ -426,18 +408,6 @@ final void normalize() { offset = (intLen == 0 ? 0 : offset+numZeros); } - /** - * If this MutableBigInteger cannot hold len words, increase the size - * of the value array to len words. - */ - private final void ensureCapacity(int len) { - if (value.length < len) { - value = new int[len]; - offset = 0; - intLen = len; - } - } - /** * Convert this MutableBigInteger into an int array with no leading * zeros, of a length that is equal to this MutableBigInteger's intLen. From 5fd84fbbf6ffd4bf7c589a5502df5bfe25b1fb67 Mon Sep 17 00:00:00 2001 From: Thomas Schatzl Date: Mon, 19 Oct 2026 09:35:59 +0000 Subject: [PATCH 099/169] 8393711: G1: Coarsening can inadvertedly add 2^32 cards to card set occupancy counters Reviewed-by: ayang, aboldtch --- src/hotspot/share/gc/g1/g1CardSet.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/hotspot/share/gc/g1/g1CardSet.cpp b/src/hotspot/share/gc/g1/g1CardSet.cpp index 8f84bf59387e..5da3c8096757 100644 --- a/src/hotspot/share/gc/g1/g1CardSet.cpp +++ b/src/hotspot/share/gc/g1/g1CardSet.cpp @@ -686,7 +686,10 @@ void G1CardSet::transfer_cards(G1CardSetHashTableValue* table_entry, ContainerPt assert(container_type(source_container) == ContainerHowl, "must be"); // Need to correct for that the Full remembered set occupies more cards than the // AoCS before. - _num_occupied.add_then_fetch(_config->max_cards_in_region() - table_entry->_num_occupied.load_relaxed(), memory_order_relaxed); + // Use int64_t to represent the difference because due to races this difference can be negative. + const int64_t diff = (int64_t)_config->max_cards_in_region() - table_entry->_num_occupied.load_relaxed(); + + _num_occupied.add_then_fetch((size_t)diff, memory_order_relaxed); } } @@ -702,7 +705,8 @@ void G1CardSet::transfer_cards_in_howl(ContainerPtr parent_container, G1TransferCard iter(this, card_region); iterate_cards_during_transfer(source_container, iter); } else { - uint diff = _config->max_cards_in_howl_bitmap() - container_ptr(source_container)->num_bits_set(); + // Use int64_t to represent the difference because due to races this difference can be negative. + int64_t diff = (int64_t)_config->max_cards_in_howl_bitmap() - container_ptr(source_container)->num_bits_set(); // Need to correct for that the Full remembered set occupies more cards than the // bitmap before. @@ -712,14 +716,14 @@ void G1CardSet::transfer_cards_in_howl(ContainerPtr parent_container, diff -= 1; G1CardSetHowl* howling_array = container_ptr(parent_container); - howling_array->_num_entries.add_then_fetch(diff, memory_order_relaxed); + howling_array->_num_entries.add_then_fetch((uint)diff, memory_order_relaxed); G1CardSetHashTableValue* table_entry = get_container(card_region); assert(table_entry != nullptr, "Table entry not found for transferred cards"); - table_entry->_num_occupied.add_then_fetch(diff, memory_order_relaxed); + table_entry->_num_occupied.add_then_fetch((uint)diff, memory_order_relaxed); - _num_occupied.add_then_fetch(diff, memory_order_relaxed); + _num_occupied.add_then_fetch((size_t)diff, memory_order_relaxed); } } From 56c0820181969d2830b93da8aeba6e79480b13c4 Mon Sep 17 00:00:00 2001 From: Jan Lahoda Date: Mon, 19 Oct 2026 09:42:08 +0000 Subject: [PATCH 100/169] 8389737: javac incorrectly accepts super.field in this() invocation when subclass hides the field Reviewed-by: mcimadamore, vromero --- .../com/sun/tools/javac/comp/Resolve.java | 14 +- .../javac/constructors/SuperFieldAccess.java | 276 ++++++++++++++++++ 2 files changed, 287 insertions(+), 3 deletions(-) create mode 100644 test/langtools/tools/javac/constructors/SuperFieldAccess.java diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Resolve.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Resolve.java index e560a9db9f29..f27708342699 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Resolve.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Resolve.java @@ -4024,9 +4024,17 @@ private boolean isSimpleEarlyFieldRefAllowed(DiagnosticPosition pos, // If unrelated this/super, ignore return field.owner != context.owner(); } - if (field.isStatic() || - !field.isMemberOf(context.owner(), types)) { - // If unqualified static field, or unrelated instance field, ignore + if (field.isStatic()) { + // If unqualified static field, ignore + return true; + } + boolean declaredInSuper = + types.closure(context.owner().type) + .stream() + .map(t -> t.tsym) + .anyMatch(ts -> field.owner == ts); + if (!declaredInSuper) { + // If unrelated instance field, ignore return true; } // We have now ruled out all cases where the check should not apply. Let's follow 6.5.6.1 diff --git a/test/langtools/tools/javac/constructors/SuperFieldAccess.java b/test/langtools/tools/javac/constructors/SuperFieldAccess.java new file mode 100644 index 000000000000..9c7677913c95 --- /dev/null +++ b/test/langtools/tools/javac/constructors/SuperFieldAccess.java @@ -0,0 +1,276 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8389737 + * @summary Verify super field access is checked properly. + * @library /tools/lib + * @modules + * jdk.compiler/com.sun.tools.javac.api + * jdk.compiler/com.sun.tools.javac.main + * @build toolbox.ToolBox toolbox.JavacTask + * @run junit SuperFieldAccess + */ + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; + +import toolbox.JavaTask; +import toolbox.JavacTask; +import toolbox.ToolBox; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; +import toolbox.Task; + +import static org.junit.jupiter.api.Assertions.*; + +public class SuperFieldAccess { + Path base; + ToolBox tb = new ToolBox(); + + @Test + void testIllegalSuperAccessInEarlyContext() throws Exception { + //cases illegal per JLS 15.11.2 + failingCase(""" + class Super { + int field; + } + + class Sub extends Super { + + int field; + + Sub(int ignored) { + } + + Sub() { + int v = super.field; + this(v); + } + } + """, + "Super.java:13:22: compiler.err.cant.ref.before.ctor.called: field", + "1 error"); + failingCase(""" + class Super { + int field; + } + + class T2 { + class Nested extends Super { + Nested() { + super.field = 0; //illegal per JLS 15.11.2? + super(); + } + } + } + """, + "Super.java:8:18: compiler.err.cant.ref.before.ctor.called: field", + "1 error"); + + failingCase(""" + class NotInheritedTest2 { + private int field; + class Nested extends NotInheritedTest2 { + Nested() { + super.field = 0; + super(); + } + } + } + """, + "NotInheritedTest2.java:5:18: compiler.err.cant.ref.before.ctor.called: field", + "1 error"); + failingCase(""" + class NotInheritedTest2 { + private int field; + class Nested extends NotInheritedTest2 { + private int field; + Nested() { + super.field = 0; + super(); + } + } + } + """, + "NotInheritedTest2.java:6:18: compiler.err.cant.ref.before.ctor.called: field", + "1 error"); + failingCase(""" + class NotInheritedTest2 { + int field; + class Nested extends NotInheritedTest2 { + Nested() { + super.field = 0; + super(); + } + } + } + """, + "NotInheritedTest2.java:5:18: compiler.err.cant.ref.before.ctor.called: field", + "1 error"); + failingCase(""" + class Super { + int field; + } + class T3 { + class Nested extends Super { + Nested() { + Nested.super.field = 0; + super(); + } + } + } + """, + "Super.java:7:25: compiler.err.cant.ref.before.ctor.called: field", + "1 error"); + failingCase(""" + class Super { + int field; + } + + class T1 extends Super { + T1() { + class Nested { + Nested() { + super(); + T1.super.field = 0; + } + } + super(); + } + } + """, + "Super.java:10:19: compiler.err.cant.ref.before.ctor.called: super", + "1 error"); + failingCase(""" + class NotInheritedTest { + private int field; + class Intermediate extends NotInheritedTest {} + class Nested extends Intermediate { + Nested() { + super.field = 0; + super(); + } + } + } + """, + "NotInheritedTest.java:6:18: compiler.err.report.access: field, private, NotInheritedTest", + "1 error"); + failingCase(""" + class NotInheritedTest { + int field; + class Intermediate extends NotInheritedTest {} + class Nested extends Intermediate { + int field; + Nested() { + super.field = 0; + super(); + } + } + } + """, + "NotInheritedTest.java:7:18: compiler.err.cant.ref.before.ctor.called: field", + "1 error"); + } + + private void failingCase(String code, String... errors) throws IOException { + Path classes = base.resolve("classes"); + Files.createDirectories(classes); + List log = new JavacTask(tb) + .outdir(classes) + .options("-XDrawDiagnostics") + .sources(code) + .run(Task.Expect.FAIL) + .writeAll() + .getOutputLines(Task.OutputKind.DIRECT); + List expected = List.of(errors); + + assertEquals(expected, log); + } + + @Test + void testLegalSuperAccessInEarlyContext() throws Exception { + passingCase(""" + class Test { + class Super { + int field1; + int field2; + } + + class T1 extends Super { + class Nested { + Nested() { + T1.super.field1 = 1; + field2 = 2; + super(); + } + } + } + void main() { + T1 t1 = new T1(); + t1.new Nested(); + System.out.println(t1.field1); + System.out.println(t1.field2); + } + } + """, + "1", + "2"); + } + + private void passingCase(String code, String... output) throws IOException { + Path classes = base.resolve("classes"); + Files.createDirectories(classes); + new JavacTask(tb) + .outdir(classes) + .options("-XDrawDiagnostics") + .sources(code) + .run(Task.Expect.SUCCESS) + .writeAll(); + + List log = new JavaTask(tb) + .classpath(classes.toString()) + .className("Test") + .run() + .writeAll() + .getOutputLines(Task.OutputKind.STDOUT); + + List expected = List.of(output); + + assertEquals(expected, log); + } + + @BeforeEach + public void setUp(TestInfo info) { + base = Paths.get(".") + .resolve(info.getTestMethod() + .orElseThrow() + .getName()); + } +} From f4b39ffb0f6f733cc0c4ea435ab45eb7be1fdaba Mon Sep 17 00:00:00 2001 From: Andreas Chmielewski Date: Mon, 19 Oct 2026 09:46:12 +0000 Subject: [PATCH 101/169] 8389135: Support TLS 1.2 Extended Master Secret (EMS) derivation in SunPKCS11 Reviewed-by: valeriep --- .../com/sun/crypto/provider/SunJCE.java | 2 +- .../sun/security/ssl/SSLConfiguration.java | 8 +- .../security/ssl/SSLMasterKeyDerivation.java | 6 +- ...P11Tls12ExtendedMasterSecretGenerator.java | 173 +++++++++++++ .../sun/security/pkcs11/SunPKCS11.java | 9 + .../security/pkcs11/wrapper/CK_MECHANISM.java | 9 +- ...S12_EXTENDED_MASTER_KEY_DERIVE_PARAMS.java | 69 +++++ .../security/pkcs11/wrapper/Functions.java | 9 +- .../pkcs11/wrapper/PKCS11Constants.java | 8 +- .../share/native/libj2pkcs11/p11_convert.c | 84 +++++- .../share/native/libj2pkcs11/p11_keymgmt.c | 43 ++-- .../share/native/libj2pkcs11/p11_util.c | 13 +- .../share/native/libj2pkcs11/pkcs11wrapper.h | 9 +- .../TestExtendedMasterSecretHandshake.java | 241 ++++++++++++++++++ .../security/pkcs11/tls/fips/FipsModeTLS.java | 7 +- 15 files changed, 638 insertions(+), 52 deletions(-) create mode 100644 src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11Tls12ExtendedMasterSecretGenerator.java create mode 100644 src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/wrapper/CK_TLS12_EXTENDED_MASTER_KEY_DERIVE_PARAMS.java create mode 100644 test/jdk/sun/security/pkcs11/tls/TestExtendedMasterSecretHandshake.java diff --git a/src/java.base/share/classes/com/sun/crypto/provider/SunJCE.java b/src/java.base/share/classes/com/sun/crypto/provider/SunJCE.java index 4b38bd55809e..c9e7191df33d 100644 --- a/src/java.base/share/classes/com/sun/crypto/provider/SunJCE.java +++ b/src/java.base/share/classes/com/sun/crypto/provider/SunJCE.java @@ -792,7 +792,7 @@ void putEntries() { ps("KeyGenerator", "SunTlsMasterSecret", "com.sun.crypto.provider.TlsMasterSecretGenerator", - List.of("SunTls12MasterSecret", "SunTlsExtendedMasterSecret"), + List.of("SunTls12MasterSecret", "SunTlsExtendedMasterSecret", "SunTls12ExtendedMasterSecret"), null); ps("KeyGenerator", "SunTlsKeyMaterial", diff --git a/src/java.base/share/classes/sun/security/ssl/SSLConfiguration.java b/src/java.base/share/classes/sun/security/ssl/SSLConfiguration.java index 1cc29e29477a..c40c9bc432fc 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLConfiguration.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLConfiguration.java @@ -126,8 +126,12 @@ final class SSLConfiguration implements Cloneable { if (supportExtendedMasterSecret) { try { KeyGenerator.getInstance("SunTlsExtendedMasterSecret"); - } catch (NoSuchAlgorithmException nae) { - supportExtendedMasterSecret = false; + } catch (NoSuchAlgorithmException _) { + try { + KeyGenerator.getInstance("SunTls12ExtendedMasterSecret"); + } catch (NoSuchAlgorithmException _) { + supportExtendedMasterSecret = false; + } } } useExtendedMasterSecret = supportExtendedMasterSecret; diff --git a/src/java.base/share/classes/sun/security/ssl/SSLMasterKeyDerivation.java b/src/java.base/share/classes/sun/security/ssl/SSLMasterKeyDerivation.java index 533b7f9a52de..829c35f5155f 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLMasterKeyDerivation.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLMasterKeyDerivation.java @@ -121,7 +121,11 @@ public SecretKey deriveKey(String typeNotUsed) throws IOException { TlsMasterSecretParameterSpec spec; if (context.handshakeSession.useExtendedMasterSecret) { // reset to use the extended master secret algorithm - masterAlg = "SunTlsExtendedMasterSecret"; + if (protocolVersion.id == ProtocolVersion.TLS12.id) { + masterAlg = "SunTls12ExtendedMasterSecret"; + } else { + masterAlg = "SunTlsExtendedMasterSecret"; + } // For the session hash, use the handshake messages up to and // including the ClientKeyExchange message. diff --git a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11Tls12ExtendedMasterSecretGenerator.java b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11Tls12ExtendedMasterSecretGenerator.java new file mode 100644 index 000000000000..054baca520b7 --- /dev/null +++ b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11Tls12ExtendedMasterSecretGenerator.java @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2026, IBM Corporation. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package sun.security.pkcs11; + +import sun.security.internal.spec.TlsMasterSecretParameterSpec; +import sun.security.pkcs11.wrapper.*; + +import javax.crypto.KeyGeneratorSpi; +import javax.crypto.SecretKey; +import java.security.*; +import java.security.spec.AlgorithmParameterSpec; + +import static sun.security.pkcs11.TemplateManager.O_GENERATE; +import static sun.security.pkcs11.wrapper.PKCS11Constants.*; + +/** + * KeyGenerator for the TLS extended master secret. + * See RFC 7627 for further details. + * + * @since x.x + */ +public final class P11Tls12ExtendedMasterSecretGenerator extends KeyGeneratorSpi { + + // See RFC 5246, section 8.1 Computing the Master Secret + private static final int TLS_MASTER_SECRET_LEN = 48; + + private static final String MSG = "TlsExtendedMasterSecretGenerator must be " + + "initialized using a TlsMasterSecretParameterSpec"; + + private final Token token; + + CK_VERSION ckVersion; + private long mechanism; + private byte[] sessionHash; + @SuppressWarnings("deprecation") + private TlsMasterSecretParameterSpec spec; + private P11Key p11Key; + + P11Tls12ExtendedMasterSecretGenerator(Token token, String algorithm, long mechanism) + throws PKCS11Exception { + super(); + this.token = token; + this.mechanism = mechanism; + } + + protected void engineInit(SecureRandom random) { + throw new InvalidParameterException(MSG); + } + + @SuppressWarnings("deprecation") + protected void engineInit(AlgorithmParameterSpec params, SecureRandom random) + throws InvalidAlgorithmParameterException { + if (!(params instanceof TlsMasterSecretParameterSpec spec)) { + throw new InvalidAlgorithmParameterException(MSG); + } + + int tlsVersion = (spec.getMajorVersion() << 8) | spec.getMinorVersion(); + if (tlsVersion != 0x0303) { + // RFC 7627 defines EMS for earlier TLS versions as well, but + // this implementation only exposes PKCS#11 TLS 1.2 EMS mechanisms. + throw new InvalidAlgorithmParameterException( + "Extended Master Secret is only supported for TLS 1.2"); + } + + try { + p11Key = P11SecretKeyFactory.convertKey(token, + spec.getPremasterSecret(), null); + } catch (InvalidKeyException e) { + throw new InvalidAlgorithmParameterException("init() failed", e); + } + + this.spec = spec; + + String alg = p11Key.getAlgorithm(); + boolean isTlsRsaPremasterSecret = "TlsRsaPremasterSecret".equals(alg); + if (!isTlsRsaPremasterSecret && !"TlsPremasterSecret".equals(alg)) { + throw new InvalidAlgorithmParameterException( + "Unsupported premaster secret algorithm: " + alg); + } + + mechanism = isTlsRsaPremasterSecret + ? CKM_TLS12_EXTENDED_MASTER_KEY_DERIVE + : CKM_TLS12_EXTENDED_MASTER_KEY_DERIVE_DH; + + if (isTlsRsaPremasterSecret) { + ckVersion = new CK_VERSION(0, 0); + } else { + // PKCS#11 defines separate EMS derivation mechanisms for RSA + // premaster secrets and all other premaster secret types. + // The non-RSA mechanism is used for DH, ECDH, ECDHE, Kerberos, + // and other key-exchange methods whose premaster secret does + // not contain the embedded protocol version present in RSA + // premaster secrets. + ckVersion = null; + } + + sessionHash = spec.getExtendedMasterSecretSessionHash(); + if (sessionHash.length == 0) { + throw new InvalidAlgorithmParameterException( + "Extended Master Secret session hash missing"); + } + } + + protected void engineInit(int keysize, SecureRandom random) { + throw new InvalidParameterException(MSG); + } + + protected SecretKey engineGenerateKey() { + if (spec == null) { + throw new IllegalStateException( + "TlsExtendedMasterSecretGenerator must be initialized"); + } + + CK_MECHANISM ckMechanism = new CK_MECHANISM( + mechanism, new CK_TLS12_EXTENDED_MASTER_KEY_DERIVE_PARAMS( + Functions.getHashMechId(spec.getPRFHashAlg()), + sessionHash, ckVersion)); + + Session session = null; + long p11KeyID = p11Key.getKeyID(); + + try { + session = token.getObjSession(); + + CK_ATTRIBUTE[] attributes = new CK_ATTRIBUTE[]{ + new CK_ATTRIBUTE(CKA_CLASS, CKO_SECRET_KEY), + new CK_ATTRIBUTE(CKA_KEY_TYPE, CKK_GENERIC_SECRET), + new CK_ATTRIBUTE(CKA_VALUE_LEN, TLS_MASTER_SECRET_LEN) + }; + + attributes = token.getAttributes( + O_GENERATE, + CKO_SECRET_KEY, + CKK_GENERIC_SECRET, + attributes); + + long keyID = token.p11.C_DeriveKey(session.id(), ckMechanism, + p11KeyID, attributes); + + int major = (ckVersion == null) ? -1 : ckVersion.major; + int minor = (ckVersion == null) ? -1 : ckVersion.minor; + + return P11Key.masterSecretKey(session, keyID, "TlsMasterSecret", + TLS_MASTER_SECRET_LEN << 3, attributes, major, minor); + } catch (PKCS11Exception e) { + throw new ProviderException("Could not generate extended master secret", e); + } finally { + p11Key.releaseKeyID(); + token.releaseSession(session); + } + } +} diff --git a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/SunPKCS11.java b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/SunPKCS11.java index 00d8559e6966..276f9e14a814 100644 --- a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/SunPKCS11.java +++ b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/SunPKCS11.java @@ -1,5 +1,6 @@ /* * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2026, IBM Corporation. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -1031,6 +1032,10 @@ private static void register(Descriptor d) { d(KG, "SunTls12MasterSecret", "sun.security.pkcs11.P11TlsMasterSecretGenerator", m(CKM_TLS12_MASTER_KEY_DERIVE, CKM_TLS12_MASTER_KEY_DERIVE_DH)); + d(KG, "SunTls12ExtendedMasterSecret", + "sun.security.pkcs11.P11TlsExtendedMasterSecretGenerator", + m(CKM_TLS12_EXTENDED_MASTER_KEY_DERIVE, + CKM_TLS12_EXTENDED_MASTER_KEY_DERIVE_DH)); d(KG, "SunTlsKeyMaterial", "sun.security.pkcs11.P11TlsKeyMaterialGenerator", m(CKM_SSL3_KEY_AND_MAC_DERIVE, CKM_TLS_KEY_AND_MAC_DERIVE)); @@ -1408,6 +1413,7 @@ public Object newInstance0(Object param) throws PKCS11Exception, NoSuchAlgorithmException { String algorithm = getAlgorithm(); String type = getType(); + if (type == MD) { return new P11Digest(token, algorithm, mechanism); } else if (type == CIP) { @@ -1455,6 +1461,9 @@ public Object newInstance0(Object param) throws || algorithm == "SunTls12MasterSecret") { return new P11TlsMasterSecretGenerator( token, algorithm, mechanism); + } else if (algorithm == "SunTls12ExtendedMasterSecret") { + return new P11Tls12ExtendedMasterSecretGenerator( + token, algorithm, mechanism); } else if (algorithm == "SunTlsKeyMaterial" || algorithm == "SunTls12KeyMaterial") { return new P11TlsKeyMaterialGenerator( diff --git a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/wrapper/CK_MECHANISM.java b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/wrapper/CK_MECHANISM.java index 4d518b7a167a..4051f4cc07f7 100644 --- a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/wrapper/CK_MECHANISM.java +++ b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/wrapper/CK_MECHANISM.java @@ -1,8 +1,7 @@ /* * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved. - */ - -/* Copyright (c) 2002 Graz University of Technology. All rights reserved. + * Copyright (c) 2026, IBM Corporation. All rights reserved. + * Copyright (c) 2002 Graz University of Technology. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -111,6 +110,10 @@ public CK_MECHANISM(long mechanism, CK_TLS12_MASTER_KEY_DERIVE_PARAMS params) { init(mechanism, params); } + public CK_MECHANISM(long mechanism, CK_TLS12_EXTENDED_MASTER_KEY_DERIVE_PARAMS params) { + init(mechanism, params); + } + public CK_MECHANISM(long mechanism, CK_SSL3_KEY_MAT_PARAMS params) { init(mechanism, params); } diff --git a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/wrapper/CK_TLS12_EXTENDED_MASTER_KEY_DERIVE_PARAMS.java b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/wrapper/CK_TLS12_EXTENDED_MASTER_KEY_DERIVE_PARAMS.java new file mode 100644 index 000000000000..174aad5f6e8f --- /dev/null +++ b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/wrapper/CK_TLS12_EXTENDED_MASTER_KEY_DERIVE_PARAMS.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2026, IBM Corporation. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package sun.security.pkcs11.wrapper; + +/** + * CK_TLS12_EXTENDED_MASTER_KEY_DERIVE_PARAMS from PKCS#11 v3.20. + */ +public class CK_TLS12_EXTENDED_MASTER_KEY_DERIVE_PARAMS { + + /** + * PKCS#11: + * + *
      +     * CK_MECHANISM_TYPE prfHashMechanism;
      +     * 
      + */ + public long prfHashMechanism; + + /** + * PKCS#11: + * + *
      +     * CK_BYTE_PTR pSessionHash;
      +     * CK_ULONG ulSessionHashLen;
      +     * 
      + */ + public byte[] pSessionHash; + + /** + * PKCS#11: + * + *
      +     * CK_VERSION_PTR pVersion;
      +     * 
      + */ + public CK_VERSION pVersion; + + public CK_TLS12_EXTENDED_MASTER_KEY_DERIVE_PARAMS( + long prfHashMechanism, + byte[] sessionHash, + CK_VERSION version) { + this.prfHashMechanism = prfHashMechanism; + this.pSessionHash = sessionHash; + this.pVersion = version; + } +} diff --git a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/wrapper/Functions.java b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/wrapper/Functions.java index 723235b7c36f..0b217b09492f 100644 --- a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/wrapper/Functions.java +++ b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/wrapper/Functions.java @@ -1,8 +1,7 @@ /* * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved. - */ - -/* Copyright (c) 2002 Graz University of Technology. All rights reserved. + * Copyright (c) 2026, IBM Corporation. All rights reserved. + * Copyright (c) 2002 Graz University of Technology. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -1162,6 +1161,10 @@ private static void addParamSources(long id, String name) { addMech(CKM_SP800_108_FEEDBACK_KDF, "CKM_SP800_108_FEEDBACK_KDF"); addMech(CKM_SP800_108_DOUBLE_PIPELINE_KDF, "CKM_SP800_108_DOUBLE_PIPELINE_KDF"); + addMech(CKM_TLS12_EXTENDED_MASTER_KEY_DERIVE, + "CKM_TLS12_EXTENDED_MASTER_KEY_DERIVE"); + addMech(CKM_TLS12_EXTENDED_MASTER_KEY_DERIVE_DH, + "CKM_TLS12_EXTENDED_MASTER_KEY_DERIVE_DH"); addMech(CKM_VENDOR_DEFINED, "CKM_VENDOR_DEFINED"); diff --git a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/wrapper/PKCS11Constants.java b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/wrapper/PKCS11Constants.java index 924bfbcc2267..2721664d8d3d 100644 --- a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/wrapper/PKCS11Constants.java +++ b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/wrapper/PKCS11Constants.java @@ -1,8 +1,7 @@ /* * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved. - */ - -/* Copyright (c) 2002 Graz University of Technology. All rights reserved. + * Copyright (c) 2026, IBM Corporation. All rights reserved. + * Copyright (c) 2002 Graz University of Technology. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -989,6 +988,9 @@ public interface PKCS11Constants { public static final long CKM_SP800_108_FEEDBACK_KDF = 0x000003adL; public static final long CKM_SP800_108_DOUBLE_PIPELINE_KDF = 0x000003aeL; + public static final long CKM_TLS12_EXTENDED_MASTER_KEY_DERIVE = 0x00000056L; + public static final long CKM_TLS12_EXTENDED_MASTER_KEY_DERIVE_DH = 0x00000057L; + public static final long CKM_VENDOR_DEFINED = 0x80000000L; // NSS private diff --git a/src/jdk.crypto.cryptoki/share/native/libj2pkcs11/p11_convert.c b/src/jdk.crypto.cryptoki/share/native/libj2pkcs11/p11_convert.c index 37404db10248..b4ef9c33f374 100644 --- a/src/jdk.crypto.cryptoki/share/native/libj2pkcs11/p11_convert.c +++ b/src/jdk.crypto.cryptoki/share/native/libj2pkcs11/p11_convert.c @@ -1,8 +1,7 @@ /* * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved. - */ - -/* Copyright (c) 2002 Graz University of Technology. All rights reserved. + * Copyright (c) 2026, IBM Corporation. All rights reserved. + * Copyright (c) 2002 Graz University of Technology. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -731,6 +730,79 @@ jTlsMacParamsToCKTlsMacParamPtr(JNIEnv *env, jobject jParam, CK_ULONG *pLength) return ckParamPtr; } +/* + * converts the Java CK_TLS12_EXTENDED_MASTER_KEY_DERIVE_PARAMS object to a + * CK_TLS12_EXTENDED_MASTER_KEY_DERIVE_PARAMS pointer + * + * @param env - used to call JNI functions to get the Java classes and objects + * @param jParam - the Java CK_TLS12_EXTENDED_MASTER_KEY_DERIVE_PARAMS object + * to convert + * @param pLength - length of the allocated memory of the returned pointer + * @return pointer to the new CK_TLS12_EXTENDED_MASTER_KEY_DERIVE_PARAMS + * structure + */ +CK_TLS12_EXTENDED_MASTER_KEY_DERIVE_PARAMS_PTR +jTls12ExtendedMasterKeyDeriveParamToCKTls12ExtendedMasterKeyDeriveParamPtr( + JNIEnv *env, jobject jParam, CK_ULONG *pLength) +{ + CK_TLS12_EXTENDED_MASTER_KEY_DERIVE_PARAMS_PTR ckParamPtr; + jclass clazz; + jfieldID fieldID; + + jlong prfHashMechanism; + jobject jVersion; + jobject jSessionHash; + + if (pLength != NULL) { + *pLength = 0L; + } + + clazz = (*env)->FindClass(env, CLASS_TLS12_EXTENDED_MASTER_KEY_DERIVE_PARAMS); + if (clazz == NULL) { return NULL; } + + fieldID = (*env)->GetFieldID(env, clazz, "prfHashMechanism", "J"); + if (fieldID == NULL) { return NULL;} + prfHashMechanism = (*env)->GetLongField(env, jParam, fieldID); + + fieldID = (*env)->GetFieldID(env, clazz, "pSessionHash", "[B"); + if (fieldID == NULL) { return NULL; } + jSessionHash = (*env)->GetObjectField(env, jParam, fieldID); + + fieldID = (*env)->GetFieldID(env, clazz, "pVersion", "Lsun/security/pkcs11/wrapper/CK_VERSION;"); + if (fieldID == NULL) { return NULL; } + jVersion = (*env)->GetObjectField(env, jParam, fieldID); + + ckParamPtr = calloc(1, sizeof(CK_TLS12_EXTENDED_MASTER_KEY_DERIVE_PARAMS)); + if (ckParamPtr == NULL) { + p11ThrowOutOfMemoryError(env, 0); + return NULL; + } + + ckParamPtr->prfHashMechanism = jLongToCKULong(prfHashMechanism); + + jByteArrayToCKByteArray(env, jSessionHash, &(ckParamPtr->pSessionHash), + &(ckParamPtr->ulSessionHashLen)); + if ((*env)->ExceptionCheck(env)) { + goto cleanup; + } + + ckParamPtr->pVersion = jVersionToCKVersionPtr(env, jVersion); + if ((*env)->ExceptionCheck(env)) { + goto cleanup; + } + + if (pLength != NULL) { + *pLength = sizeof(CK_TLS12_EXTENDED_MASTER_KEY_DERIVE_PARAMS); + } + + return ckParamPtr; +cleanup: + free(ckParamPtr->pSessionHash); + free(ckParamPtr->pVersion); + free(ckParamPtr); + return NULL; +} + /* * converts a Java CK_HKDF_PARAMS object to a CK_HKDF_PARAMS pointer * @@ -1631,6 +1703,12 @@ CK_VOID_PTR jMechParamToCKMechParamPtrSlow(JNIEnv *env, jobject jParam, ckpParamPtr = jTls12MasterKeyDeriveParamToCKTls12MasterKeyDeriveParamPtr(env, jParam, ckpLength); break; + case CKM_TLS12_EXTENDED_MASTER_KEY_DERIVE: + case CKM_TLS12_EXTENDED_MASTER_KEY_DERIVE_DH: + ckpParamPtr = + jTls12ExtendedMasterKeyDeriveParamToCKTls12ExtendedMasterKeyDeriveParamPtr( + env, jParam, ckpLength); + break; case CKM_TLS_PRF: case CKM_NSS_TLS_PRF_GENERAL: ckpParamPtr = jTlsPrfParamsToCKTlsPrfParamPtr(env, jParam, diff --git a/src/jdk.crypto.cryptoki/share/native/libj2pkcs11/p11_keymgmt.c b/src/jdk.crypto.cryptoki/share/native/libj2pkcs11/p11_keymgmt.c index e540f3b5ed9f..3f06af317061 100644 --- a/src/jdk.crypto.cryptoki/share/native/libj2pkcs11/p11_keymgmt.c +++ b/src/jdk.crypto.cryptoki/share/native/libj2pkcs11/p11_keymgmt.c @@ -1,8 +1,7 @@ /* * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved. - */ - -/* Copyright (c) 2002 Graz University of Technology. All rights reserved. + * Copyright (c) 2026, IBM Corporation. All rights reserved. + * Copyright (c) 2002 Graz University of Technology. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -942,9 +941,22 @@ JNIEXPORT jlong JNICALL Java_sun_security_pkcs11_wrapper_PKCS11_C_1DeriveKey /* we must copy back the client version */ ssl3CopyBackClientVersion(env, ckpMechanism, jMechanism); break; - case CKM_TLS12_MASTER_KEY_DERIVE: - tls12CopyBackClientVersion(env, ckpMechanism, jMechanism); + case CKM_TLS12_MASTER_KEY_DERIVE: { + CK_TLS12_MASTER_KEY_DERIVE_PARAMS *p = + (CK_TLS12_MASTER_KEY_DERIVE_PARAMS *)ckpMechanism->pParameter; + + copyBackClientVersion(env, ckpMechanism, jMechanism, + p->pVersion, CLASS_TLS12_MASTER_KEY_DERIVE_PARAMS); break; + } + case CKM_TLS12_EXTENDED_MASTER_KEY_DERIVE: { + CK_TLS12_EXTENDED_MASTER_KEY_DERIVE_PARAMS *p = + (CK_TLS12_EXTENDED_MASTER_KEY_DERIVE_PARAMS *)ckpMechanism->pParameter; + + copyBackClientVersion(env, ckpMechanism, jMechanism, + p->pVersion, CLASS_TLS12_EXTENDED_MASTER_KEY_DERIVE_PARAMS); + break; + } case CKM_SSL3_KEY_AND_MAC_DERIVE: case CKM_TLS_KEY_AND_MAC_DERIVE: /* we must copy back the unwrapped key info to the jMechanism object */ @@ -973,7 +985,7 @@ JNIEXPORT jlong JNICALL Java_sun_security_pkcs11_wrapper_PKCS11_C_1DeriveKey return jKeyHandle ; } -static void copyBackClientVersion(JNIEnv *env, CK_MECHANISM_PTR ckpMechanism, jobject jMechanism, +void copyBackClientVersion(JNIEnv *env, CK_MECHANISM_PTR ckpMechanism, jobject jMechanism, CK_VERSION *ckVersion, const char *class_master_key_derive_params) { jclass jMasterKeyDeriveParamsClass, jMechanismClass, jVersionClass; @@ -1046,25 +1058,6 @@ void ssl3CopyBackClientVersion(JNIEnv *env, CK_MECHANISM_PTR ckpMechanism, } } -/* - * Copy back the client version information from the native - * structure to the Java object. This is only used for - * CKM_TLS12_MASTER_KEY_DERIVE mechanism when used for deriving a key. - * - */ -void tls12CopyBackClientVersion(JNIEnv *env, CK_MECHANISM_PTR ckpMechanism, - jobject jMechanism) -{ - CK_TLS12_MASTER_KEY_DERIVE_PARAMS *ckTLS12MasterKeyDeriveParams; - ckTLS12MasterKeyDeriveParams = - (CK_TLS12_MASTER_KEY_DERIVE_PARAMS *)ckpMechanism->pParameter; - if (ckTLS12MasterKeyDeriveParams != NULL_PTR) { - copyBackClientVersion(env, ckpMechanism, jMechanism, - ckTLS12MasterKeyDeriveParams->pVersion, - CLASS_TLS12_MASTER_KEY_DERIVE_PARAMS); - } -} - static void copyBackKeyMatParams(JNIEnv *env, CK_MECHANISM_PTR ckpMechanism, jobject jMechanism, CK_SSL3_RANDOM_DATA *RandomInfo, CK_SSL3_KEY_MAT_OUT_PTR ckSSL3KeyMatOut, const char *class_key_mat_params) diff --git a/src/jdk.crypto.cryptoki/share/native/libj2pkcs11/p11_util.c b/src/jdk.crypto.cryptoki/share/native/libj2pkcs11/p11_util.c index 463818626dd7..d222c4f139c8 100644 --- a/src/jdk.crypto.cryptoki/share/native/libj2pkcs11/p11_util.c +++ b/src/jdk.crypto.cryptoki/share/native/libj2pkcs11/p11_util.c @@ -1,8 +1,7 @@ /* * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved. - */ - -/* Copyright (c) 2002 Graz University of Technology. All rights reserved. + * Copyright (c) 2026, IBM Corporation. All rights reserved. + * Copyright (c) 2002 Graz University of Technology. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -318,6 +317,7 @@ void freeCKMechanismPtr(CK_MECHANISM_PTR mechPtr) { CK_SSL3_MASTER_KEY_DERIVE_PARAMS *sslMkdTmp; CK_SSL3_KEY_MAT_PARAMS* sslKmTmp; CK_TLS12_MASTER_KEY_DERIVE_PARAMS *tlsMkdTmp; + CK_TLS12_EXTENDED_MASTER_KEY_DERIVE_PARAMS *tlsExtMkdTmp; CK_TLS12_KEY_MAT_PARAMS* tlsKmTmp; if (mechPtr != NULL) { @@ -395,6 +395,13 @@ void freeCKMechanismPtr(CK_MECHANISM_PTR mechPtr) { free(tlsMkdTmp->RandomInfo.pServerRandom); free(tlsMkdTmp->pVersion); break; + case CKM_TLS12_EXTENDED_MASTER_KEY_DERIVE: + case CKM_TLS12_EXTENDED_MASTER_KEY_DERIVE_DH: + tlsExtMkdTmp = tmp; + TRACE0("[ CK_TLS12_EXTENDED_MASTER_KEY_DERIVE_PARAMS ]\n"); + free(tlsExtMkdTmp->pSessionHash); + free(tlsExtMkdTmp->pVersion); + break; case CKM_TLS12_KEY_AND_MAC_DERIVE: tlsKmTmp = tmp; TRACE0("[ CK_TLS12_KEY_MAT_PARAMS ]\n"); diff --git a/src/jdk.crypto.cryptoki/share/native/libj2pkcs11/pkcs11wrapper.h b/src/jdk.crypto.cryptoki/share/native/libj2pkcs11/pkcs11wrapper.h index 347ef00c93a1..1a2bab653289 100644 --- a/src/jdk.crypto.cryptoki/share/native/libj2pkcs11/pkcs11wrapper.h +++ b/src/jdk.crypto.cryptoki/share/native/libj2pkcs11/pkcs11wrapper.h @@ -1,8 +1,7 @@ /* * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved. - */ - -/* Copyright (c) 2002 Graz University of Technology. All rights reserved. + * Copyright (c) 2026, IBM Corporation. All rights reserved. + * Copyright (c) 2002 Graz University of Technology. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -306,6 +305,7 @@ void printDebug(const char *format, ...); // CLASS_SSL3_KEY_MAT_OUT is used by CLASS_SSL3_KEY_MAT_PARAMS and CK_TLS12_KEY_MAT_PARAMS #define CLASS_SSL3_MASTER_KEY_DERIVE_PARAMS "sun/security/pkcs11/wrapper/CK_SSL3_MASTER_KEY_DERIVE_PARAMS" #define CLASS_TLS12_MASTER_KEY_DERIVE_PARAMS "sun/security/pkcs11/wrapper/CK_TLS12_MASTER_KEY_DERIVE_PARAMS" +#define CLASS_TLS12_EXTENDED_MASTER_KEY_DERIVE_PARAMS "sun/security/pkcs11/wrapper/CK_TLS12_EXTENDED_MASTER_KEY_DERIVE_PARAMS" #define CLASS_SSL3_KEY_MAT_PARAMS "sun/security/pkcs11/wrapper/CK_SSL3_KEY_MAT_PARAMS" #define CLASS_TLS12_KEY_MAT_PARAMS "sun/security/pkcs11/wrapper/CK_TLS12_KEY_MAT_PARAMS" #define CLASS_TLS_PRF_PARAMS "sun/security/pkcs11/wrapper/CK_TLS_PRF_PARAMS" @@ -443,7 +443,8 @@ typedef struct { void copyBackPBEInitializationVector(JNIEnv *env, CK_MECHANISM *ckMechanism, jobject jMechanism); void copyBackSetUnwrappedKey(JNIEnv *env, CK_MECHANISM *ckMechanism, jobject jMechanism); void ssl3CopyBackClientVersion(JNIEnv *env, CK_MECHANISM *ckMechanism, jobject jMechanism); -void tls12CopyBackClientVersion(JNIEnv *env, CK_MECHANISM *ckMechanism, jobject jMechanism); +void copyBackClientVersion(JNIEnv *env, CK_MECHANISM_PTR ckpMechanism, jobject jMechanism, + CK_VERSION_PTR pVersion, const char *mechanismClass); void ssl3CopyBackKeyMatParams(JNIEnv *env, CK_MECHANISM *ckMechanism, jobject jMechanism); void tls12CopyBackKeyMatParams(JNIEnv *env, CK_MECHANISM *ckMechanism, jobject jMechanism); diff --git a/test/jdk/sun/security/pkcs11/tls/TestExtendedMasterSecretHandshake.java b/test/jdk/sun/security/pkcs11/tls/TestExtendedMasterSecretHandshake.java new file mode 100644 index 000000000000..bedac5341ab6 --- /dev/null +++ b/test/jdk/sun/security/pkcs11/tls/TestExtendedMasterSecretHandshake.java @@ -0,0 +1,241 @@ +/* + * Copyright (c) 2026, IBM Corporation. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8389135 + * @summary Verifies TLS 1.2 Extended Master Secret negotiation using SunPKCS11 + * @library /test/lib .. + * @modules java.base/sun.security.internal.spec + * + * @run main/othervm + * -Djdk.tls.useExtendedMasterSecret=true + * TestExtendedMasterSecretHandshake true + * + * @run main/othervm + * -Djdk.tls.useExtendedMasterSecret=false + * TestExtendedMasterSecretHandshake false + */ + +import jdk.test.lib.security.SSLSocketTest; +import jtreg.SkippedException; +import sun.security.internal.spec.TlsMasterSecretParameterSpec; + +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import javax.net.ssl.SSLServerSocket; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocket; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintStream; +import java.security.Provider; +import java.security.Security; + +public class TestExtendedMasterSecretHandshake extends PKCS11Test { + + private static boolean useExtendedMasterSecret; + + public static void main(String[] args) throws Exception { + useExtendedMasterSecret = Boolean.parseBoolean(args[0]); + + main(new TestExtendedMasterSecretHandshake(), args); + } + + @Override + public void main(Provider p) throws Exception { + initialize(p); + + if (!shouldRun(p)) { + throw new SkippedException( + "Test skipped: SunTls12ExtendedMasterSecret not supported by provider " + p.getName()); + } + + if (useExtendedMasterSecret) { + verifyDirectEMSGeneration(p); + } + + System.setProperty("javax.net.debug", "ssl,handshake"); + + String log = runHandshake(); + + if (!log.contains("TLS handshake and application data exchange succeeded")) { + throw new RuntimeException("Handshake failed"); + } + + if (useExtendedMasterSecret) { + if (countOccurrences(log, "Consumed extension: extended_master_secret") != 2 || + countOccurrences(log, "Ignore unavailable extension: extended_master_secret") != 0) { + throw new RuntimeException("EMS extension not negotiated on both sides"); + } + + if (!log.contains("TLS handshake and application data exchange succeeded")) { + throw new RuntimeException("Handshake did not complete"); + } + + System.out.println("Verified EMS derivation through PKCS11 enabled"); + } else { + if (countOccurrences(log, "Consumed extension: extended_master_secret") != 0 || + countOccurrences(log, "Ignore unavailable extension: extended_master_secret") < 2) { + throw new RuntimeException("EMS extension negotiated although jdk.tls.useExtendedMasterSecret=false"); + } + + System.out.println("Verified EMS derivation through PKCS11 disabled"); + } + } + + private static void initialize(Provider p) { + Security.insertProviderAt(p, 1); + } + + private static boolean shouldRun(Provider p) { + return p.getService("KeyGenerator", "SunTls12ExtendedMasterSecret") != null; + } + + private static void verifyDirectEMSGeneration(Provider p) throws Exception { + KeyGenerator kg = KeyGenerator.getInstance("SunTls12ExtendedMasterSecret", p); + if (kg.getProvider() != p) { + throw new RuntimeException("Unexpected provider: " + kg.getProvider().getName()); + } + + byte[] premaster = new byte[48]; + byte[] sessionHash = new byte[32]; + SecretKey premasterKey = new SecretKeySpec(premaster, "TlsPremasterSecret"); + + TlsMasterSecretParameterSpec spec = new TlsMasterSecretParameterSpec( + premasterKey, + 3, 3, // TLS 1.2 + sessionHash, + "SHA-256", + 32, + 64); + kg.init(spec); + SecretKey key = kg.generateKey(); + + if (key == null) { + throw new RuntimeException("Generated EMS key is null"); + } + + if (!"TlsMasterSecret".equals(key.getAlgorithm())) { + throw new RuntimeException("Unexpected algorithm: " + key.getAlgorithm()); + } + } + + private static int countOccurrences(String text, String needle) { + int count = 0; + int index = 0; + while ((index = text.indexOf(needle, index)) >= 0) { + count++; + index += needle.length(); + } + return count; + } + + private static String runHandshake() throws Exception { + ByteArrayOutputStream byteLogOutput = new ByteArrayOutputStream(); + + PrintStream ps = new PrintStream(byteLogOutput, true); + + PrintStream oldOut = System.out; + PrintStream oldErr = System.err; + + try { + System.setOut(ps); + System.setErr(ps); + + new HandshakeTest().run(); + + } finally { + System.setOut(oldOut); + System.setErr(oldErr); + } + + return byteLogOutput.toString(); + } + + private static class HandshakeTest extends SSLSocketTest { + private SSLSession clientSession; + private SSLSession serverSession; + + @Override + protected void configureServerSocket(SSLServerSocket socket) { + socket.setEnabledProtocols(new String[]{"TLSv1.2"}); + } + + @Override + protected void runServerApplication(SSLSocket socket) throws Exception { + InputStream in = socket.getInputStream(); + OutputStream out = socket.getOutputStream(); + + int value = in.read(); + if (value != 42) { + throw new RuntimeException("Unexpected client value: " + value); + } + + out.write(99); + out.flush(); + + serverSession = socket.getSession(); + } + + @Override + protected void runClientApplication(SSLSocket socket) throws Exception { + OutputStream out = socket.getOutputStream(); + InputStream in = socket.getInputStream(); + + out.write(42); + out.flush(); + + int response = in.read(); + if (response != 99) { + throw new RuntimeException("Unexpected server value: " + response); + } + + clientSession = socket.getSession(); + } + + @Override + public void run() throws Exception { + super.run(); + + if (clientSession == null) { + throw new RuntimeException("Client session missing"); + } + if (serverSession == null) { + throw new RuntimeException("Server session missing"); + } + + if (!"TLSv1.2".equals(clientSession.getProtocol())) { + throw new RuntimeException("Unexpected protocol: " + clientSession.getProtocol()); + } + + if (!"TLSv1.2".equals(serverSession.getProtocol())) { + throw new RuntimeException("Unexpected protocol: " + serverSession.getProtocol()); + } + + System.out.println("TLS handshake and application data exchange succeeded"); + } + } +} \ No newline at end of file diff --git a/test/jdk/sun/security/pkcs11/tls/fips/FipsModeTLS.java b/test/jdk/sun/security/pkcs11/tls/fips/FipsModeTLS.java index a54bb501f786..d978db72781d 100644 --- a/test/jdk/sun/security/pkcs11/tls/fips/FipsModeTLS.java +++ b/test/jdk/sun/security/pkcs11/tls/fips/FipsModeTLS.java @@ -31,11 +31,9 @@ * java.base/com.sun.crypto.provider * @library /test/lib ../.. * @run main/othervm/timeout=120 -Djdk.tls.client.protocols=TLSv1.2 - * -Djdk.tls.useExtendedMasterSecret=false * -Djdk.tls.client.enableSessionTicketExtension=false FipsModeTLS - * @comment SunPKCS11 does not support (TLS1.2) SunTlsExtendedMasterSecret yet. - * Stateless resumption doesn't currently work with NSS-FIPS, see JDK-8368669. - * NSS-FIPS does not support ML-KEM, so configures the list of named groups. + * @comment Stateless resumption doesn't currently work with NSS-FIPS, see JDK-8368669. + * NSS-FIPS does not support ML-KEM, so configures the list of named groups. * @run main/othervm/timeout=120 * -Djdk.tls.client.protocols=TLSv1.3 * -Djdk.tls.namedGroups=x25519,secp256r1,secp384r1,secp521r1,x448,ffdhe2048,ffdhe3072,ffdhe4096,ffdhe6144,ffdhe8192 @@ -135,6 +133,7 @@ private static boolean shouldRun() { KeyGenerator.getInstance( "SunTls12RsaPremasterSecret", sunPKCS11NSSProvider); KeyGenerator.getInstance("SunTls12Prf", sunPKCS11NSSProvider); + KeyGenerator.getInstance("SunTls12ExtendedMasterSecret", sunPKCS11NSSProvider); } } catch (NoSuchAlgorithmException e) { return false; From 3bc15ee71b1523f30a13e7d17fde360cfd9ea537 Mon Sep 17 00:00:00 2001 From: Jaikiran Pai Date: Mon, 19 Oct 2026 10:28:56 +0000 Subject: [PATCH 102/169] 8394828: Problemlist java/net/httpclient/ConnectTimeoutTest and java/nio/channels/AsyncCloseAndInterrupt.java on macos 27 Reviewed-by: dfuchs --- test/jdk/ProblemList.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/jdk/ProblemList.txt b/test/jdk/ProblemList.txt index d25cea8165cc..50a24e959075 100644 --- a/test/jdk/ProblemList.txt +++ b/test/jdk/ProblemList.txt @@ -396,6 +396,10 @@ java/net/MulticastSocket/SetGetNetworkInterfaceTest.java 8308807 aix-ppc6 java/net/MulticastSocket/SetLoopbackMode.java 7122846,8308807 macosx-all,aix-ppc64 java/net/MulticastSocket/SetOutgoingIf.java 8308807 aix-ppc64 java/net/MulticastSocket/Test.java 7145658 macosx-all +java/net/httpclient/ConnectTimeoutTest.java#sync 8393427 macosx-27.0 +java/net/httpclient/ConnectTimeoutTest.java#sync-proxy 8393427 macosx-27.0 +java/net/httpclient/ConnectTimeoutTest.java#async 8393427 macosx-27.0 +java/net/httpclient/ConnectTimeoutTest.java#async-proxy 8393427 macosx-27.0 ############################################################################ @@ -410,6 +414,7 @@ java/nio/channels/DatagramChannel/MulticastSendReceiveTests.java 8144003 macosx- java/nio/channels/DatagramChannel/Promiscuous.java 8144003 macosx-all java/nio/channels/SocketChannel/CloseDuringConnect.java 8375658 macosx-all java/nio/channels/SocketChannel/OpenLeak.java 8375658 macosx-all +java/nio/channels/AsyncCloseAndInterrupt.java 8393427 macosx-27.0 ############################################################################ From 78aedeaa39ea85a9c2116f787346c0cf4c89f3e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=AE=81=E5=B2=B3?= Date: Mon, 19 Oct 2026 11:12:49 +0000 Subject: [PATCH 103/169] 8392387: RISC-V: Use Zvbb rotate for ChaCha20 Reviewed-by: gli, fyang --- src/hotspot/cpu/riscv/macroAssembler_riscv.hpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/hotspot/cpu/riscv/macroAssembler_riscv.hpp b/src/hotspot/cpu/riscv/macroAssembler_riscv.hpp index 838491511921..09835da442b1 100644 --- a/src/hotspot/cpu/riscv/macroAssembler_riscv.hpp +++ b/src/hotspot/cpu/riscv/macroAssembler_riscv.hpp @@ -1598,11 +1598,15 @@ INSN(sd_release, sd, sd_rl); } // vector pseudo instructions - // rotate vector register left with shift bits, 32-bit version + // Rotate 32-bit vector elements left. The caller must configure SEW=32. inline void vrole32_vi(VectorRegister vd, uint32_t shift, VectorRegister tmp_vr) { - vsrl_vi(tmp_vr, vd, 32 - shift); - vsll_vi(vd, vd, shift); - vor_vv(vd, vd, tmp_vr); + if (UseZvbb) { + vror_vi(vd, vd, 32 - shift); + } else { + vsrl_vi(tmp_vr, vd, 32 - shift); + vsll_vi(vd, vd, shift); + vor_vv(vd, vd, tmp_vr); + } } inline void vl1r_v(VectorRegister vd, Register rs) { From 776ffc524e5ea825b73dd7e6b315fbc33765010b Mon Sep 17 00:00:00 2001 From: Sean Mullan Date: Mon, 19 Oct 2026 12:04:32 +0000 Subject: [PATCH 104/169] 8377819: Security properties jlink plugin Reviewed-by: alanb --- .../plugins/SecurityPropertiesPlugin.java | 310 +++++++++++++++++ .../tools/jlink/resources/plugins.properties | 28 ++ src/jdk.jlink/share/classes/module-info.java | 3 +- src/jdk.jlink/share/man/jlink.md | 20 ++ .../plugins/SecurityPropertiesPluginTest.java | 329 ++++++++++++++++++ test/jdk/tools/lib/tests/JImageGenerator.java | 6 +- 6 files changed, 694 insertions(+), 2 deletions(-) create mode 100644 src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/SecurityPropertiesPlugin.java create mode 100644 test/jdk/tools/jlink/plugins/SecurityPropertiesPluginTest.java diff --git a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/SecurityPropertiesPlugin.java b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/SecurityPropertiesPlugin.java new file mode 100644 index 000000000000..708410179aea --- /dev/null +++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/SecurityPropertiesPlugin.java @@ -0,0 +1,310 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.tools.jlink.internal.plugins; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.FileInputStream; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.HexFormat; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; + +import jdk.tools.jlink.internal.ResourcePoolEntryFactory; +import jdk.tools.jlink.plugin.PluginException; +import jdk.tools.jlink.plugin.ResourcePool; +import jdk.tools.jlink.plugin.ResourcePoolBuilder; +import jdk.tools.jlink.plugin.ResourcePoolEntry; + +/** + * Security properties plugin. + * + * Creates the java.security configuration file in the output image and + * adds or overrides properties from the specified file. + */ +public class SecurityPropertiesPlugin extends AbstractPlugin { + + private static final String RES = "/java.base/conf/security/java.security"; + + // holds additional properties to be added or overridden + private Properties extraProps; + + public SecurityPropertiesPlugin() { + super("security-properties"); + } + + @Override + public boolean hasArguments() { + return true; + } + + @Override + public boolean hasRawArgument() { + return true; + } + + @Override + public void configure(Map config) { + String propsFile = config.get(getName()); + if (propsFile == null) { + throw new AssertionError(); + } + + extraProps = new Properties(); + try (FileInputStream fis = new FileInputStream(propsFile)) { + extraProps.load(fis); + } catch (IOException ioe) { + throw new IllegalArgumentException(ioe); + } + if (extraProps.isEmpty()) { + throw new IllegalArgumentException("No properties in " + propsFile); + } + } + + @Override + public ResourcePool transform(ResourcePool in, ResourcePoolBuilder out) { + in.transformAndCopy(res -> { + if (res.type().equals(ResourcePoolEntry.Type.CONFIG) && + res.path().equals(RES)) { + byte[] props = processProperties(res.content()); + return ResourcePoolEntryFactory.create(res, props); + } + return res; + }, out); + return out.build(); + } + + private byte[] processProperties(InputStream content) { + + List lines = new ArrayList<>(); + Set keys = new HashSet<>(); + + // Read in contents of java.security file into separate list, + // replacing values of overridden properties as we go. + // Use ISO_8859_1 to be consistent with java.security file loading. + try (InputStreamReader isr = new InputStreamReader(content, + StandardCharsets.ISO_8859_1); + BufferedReader br = new BufferedReader(isr)) { + String line = br.readLine(); + while (line != null) { + // add comments and blank lines as-is + if (isBlankOrComment(line)) { + lines.add(line); + line = br.readLine(); + continue; + } + + StringBuilder sb = new StringBuilder(line); + while (lineContinues(line)) { + // multi-lined value, add lines until end of value + line = br.readLine(); + if (line == null) { + // end of stream + break; + } + sb.append("\n").append(line); + } + + // use Properties.load() to ensure property is parsed correctly + ByteArrayInputStream bais = new ByteArrayInputStream( + sb.toString().getBytes(StandardCharsets.ISO_8859_1)); + Properties props = new Properties(); + props.load(bais); + Set propNames = props.stringPropertyNames(); + // should only be one property + if (propNames.size() != 1) { + throw new PluginException("Parsing error: " + propNames); + } + + // check for duplicate properties and include statements + String propName = propNames.iterator().next(); + if (keys.contains(propName)) { + throw new PluginException("Parsing error, " + + "duplicate property: " + propName); + } + if (propName.equals("include")) { + throw new PluginException("Parsing error, " + + "include statement disallowed"); + } + keys.add(propName); + + // check if property should be overridden + String propValue = (String) extraProps.remove(propName); + if (propValue != null) { + // override value + lines.add(line(propName, propValue)); + } else { + lines.add(line(propName, props.getProperty(propName))); + } + line = br.readLine(); + } + } catch (Exception e) { + throw new PluginException(e); + } + + // extract and remove include property if it exists + String includeValue = (String) extraProps.remove("include"); + + // add user-defined properties at end + for (String propName : extraProps.stringPropertyNames()) { + lines.add(line(propName, extraProps.getProperty(propName))); + }; + + // add include property at end if it has been specified and use + // space character as delimiter + if (includeValue != null) { + lines.add("include " + convert(includeValue, false)); + } + + // Write contents of list to byte array. Use ISO_8859_1 to be + // consistent with java.security file loading. + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try (OutputStreamWriter osw = new OutputStreamWriter(baos, + StandardCharsets.ISO_8859_1); + BufferedWriter bw = new BufferedWriter(osw)) { + for (CharSequence line: lines) { + bw.append(line); + bw.newLine(); + } + bw.flush(); + } catch (Exception e) { + throw new PluginException(e); + } + return baos.toByteArray(); + } + + /** + * Returns true if line is empty, only contains whitespace, or has an + * ASCII '#' or '!' as its first non-whitespace character. Whitespace + * characters are as specified by java.util.Properties. + */ + private static boolean isBlankOrComment(String line) { + for (int i = 0; i < line.length(); i++) { + char c = line.charAt(i); + if (c == '#' || c == '!') { + return true; + } else if (c == ' ' || c == '\t' || c == '\f') { + continue; + } else if (c == '\n' || c == '\r') { + return true; + } else { + return false; + } + } + return true; + } + + /** + * Returns true if line ends in an odd number of contiguous backslashes. + */ + private static boolean lineContinues(String line) { + int backslashes = 0; + for (int i = 0; i < line.length(); i++) { + char c = line.charAt(i); + if (c == '\\') { + backslashes++; + } else { + backslashes = 0; + } + } + return (backslashes % 2 == 0) ? false : true; + } + + /** + * Create line to add to properties file. + */ + private static String line(String name, String value) { + return convert(name, true) + "=" + convert(value, false); + } + + /** + * Converts unicodes to encoded \uxxxx and escapes + * special characters with a preceding slash. + * + * This method was copied from java.util.Properties.saveConvert(). + */ + private static String convert(String theString, + boolean escapeSpace) { + int len = theString.length(); + int bufLen = len * 2; + if (bufLen < 0) { + bufLen = Integer.MAX_VALUE; + } + StringBuilder outBuffer = new StringBuilder(bufLen); + HexFormat hex = HexFormat.of().withUpperCase(); + for(int x=0; x 61) && (aChar < 127)) { + if (aChar == '\\') { + outBuffer.append('\\'); outBuffer.append('\\'); + continue; + } + outBuffer.append(aChar); + continue; + } + switch(aChar) { + case ' ': + if (x == 0 || escapeSpace) + outBuffer.append('\\'); + outBuffer.append(' '); + break; + case '\t':outBuffer.append('\\'); outBuffer.append('t'); + break; + case '\n':outBuffer.append('\\'); outBuffer.append('n'); + break; + case '\r':outBuffer.append('\\'); outBuffer.append('r'); + break; + case '\f':outBuffer.append('\\'); outBuffer.append('f'); + break; + case '=': // Fall through + case ':': // Fall through + case '#': // Fall through + case '!': + outBuffer.append('\\'); outBuffer.append(aChar); + break; + default: + if (((aChar < 0x0020) || (aChar > 0x007e))) { + outBuffer.append("\\u"); + outBuffer.append(hex.toHexDigits(aChar)); + } else { + outBuffer.append(aChar); + } + } + } + return outBuffer.toString(); + } +} diff --git a/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/plugins.properties b/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/plugins.properties index 60e9fe64d36a..4b1048299839 100644 --- a/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/plugins.properties +++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/plugins.properties @@ -163,6 +163,34 @@ generate-jli-classes.usage=\ \ correctness add ignore-version=true\n\ \ to override this. +security-properties.argument= + +security-properties.description=\ +Override the security properties in the\n\ +conf/security/java.security configuration file with the properties in the\n\ +specified filename. Security properties with the same name are overridden\n\ +and security properties not present are added to the end of the\n\ +conf/security/java.security configuration file. Duplicate properties\n\ +(properties with the same name) and include statements in the original\n\ +java.security file are disallowed and treated as an error. Comments in the\n\ +properties file are ignored and not copied. If the properties file contains\n\ +an include statement, it is always added as the last line of the\n\ +conf/security/java.security configuration file, after any new security\n\ +properties that may have been added. The include filename does not need to\n\ +exist at jlink time. If the properties file contains duplicate properties,\n\ +the value of the last property is used. + + +security-properties.usage=\ +\ --security-properties \n\ +\ Override the security properties in the\n\ +\ conf/security/java.security configuration file\n\ +\ with the properties in the specified filename.\n\ +\ Security properties with the same name are\n\ +\ overridden and security properties not present\n\ +\ are added to the end of the\n\ +\ conf/security/java.security configuration file. + system-modules.argument=batch-size= sets the batch size of module descriptors\n\ \ to avoid exceeding the method length limit. The default\n\ \ batch size is 75. diff --git a/src/jdk.jlink/share/classes/module-info.java b/src/jdk.jlink/share/classes/module-info.java index 0adc1ce6d370..abd348f10d36 100644 --- a/src/jdk.jlink/share/classes/module-info.java +++ b/src/jdk.jlink/share/classes/module-info.java @@ -82,5 +82,6 @@ jdk.tools.jlink.internal.plugins.VendorVersionPlugin, jdk.tools.jlink.internal.plugins.CDSPlugin, jdk.tools.jlink.internal.plugins.SaveJlinkArgfilesPlugin, - jdk.tools.jlink.internal.plugins.CACertsPlugin; + jdk.tools.jlink.internal.plugins.CACertsPlugin, + jdk.tools.jlink.internal.plugins.SecurityPropertiesPlugin; } diff --git a/src/jdk.jlink/share/man/jlink.md b/src/jdk.jlink/share/man/jlink.md index 865cdff999a9..256ef2d140b9 100644 --- a/src/jdk.jlink/share/man/jlink.md +++ b/src/jdk.jlink/share/man/jlink.md @@ -250,6 +250,26 @@ Options Description : Generate CDS archive if the runtime image supports the CDS feature. +### Plugin `security-properties` + +Options +: `--security-properties=`*filename* + +Description +: Override the security properties in the `conf/security/java.security` + configuration file with the properties in the specified *filename*. + Security properties with the same name are overridden and security + properties not present are added to the end of the + `conf/security/java.security` configuration file. Duplicate properties + (properties with the same name) and include statements in the + original `java.security` file are disallowed and treated as an error. + Comments in the properties file are ignored and not copied. If the + properties file contains an include statement, it is always added as the + last line of the `conf/security/java.security` configuration file, + after any new security properties that may have been added. The include + filename does not need to exist at jlink time. If the properties file + contains duplicate properties, the value of the last property is used. + ### Plugin `cacerts` Options diff --git a/test/jdk/tools/jlink/plugins/SecurityPropertiesPluginTest.java b/test/jdk/tools/jlink/plugins/SecurityPropertiesPluginTest.java new file mode 100644 index 000000000000..d550abb149c4 --- /dev/null +++ b/test/jdk/tools/jlink/plugins/SecurityPropertiesPluginTest.java @@ -0,0 +1,329 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.io.ByteArrayInputStream; +import java.io.FileInputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.List; +import java.util.Properties; +import java.util.Random; + +import jdk.test.lib.Asserts; +import jdk.test.lib.util.FileUtils; +import jdk.tools.jlink.internal.LinkableRuntimeImage; +import jtreg.SkippedException; +import tests.Helper; +import tests.JImageGenerator; +import tests.JImageGenerator.JLinkTask; +import tests.JImageGenerator.JModTask; +import tests.Result; + +/* @test + * @bug 8377819 + * @summary Test the --security-properties plugin + * @library ../../lib /test/lib + * @modules java.base/jdk.internal.jimage + * jdk.jlink/jdk.tools.jimage + * jdk.jlink/jdk.tools.jlink.internal + * @build tests.* + * @run main/othervm SecurityPropertiesPluginTest + */ + +public class SecurityPropertiesPluginTest { + + private static Helper helper; + + private static final String SECPROPS_PATH = "conf/security/java.security"; + private static final String TEST_DIR = System.getProperty("test.dir", "."); + private static final boolean LINKABLE_RUNTIME = + LinkableRuntimeImage.isLinkableRuntime(); + private static final String DELIMS = "=: "; + + // Replacement for java.security file + private static final String JAVA_SECURITY_REPLACEMENT = + """ + # Normal comment + # Comment with leading spaces + ! Comment with '!' + \t# Comment with leading tab + \f# Comment with leading form-feed + # Next 3 are blank lines + + \n + \r\n + # Normal property + foo=bar + # JDK property + jdk.certpath.disabledAlgorithms=MD4 + # Property with leading spaces + bar=foo + # Property with delimiter ("=") in name, must be escaped + ba\\=z=foo + # Multi-lined property with space delimiter + fruits apple, banana, pear, \\ + cantaloupe, watermelon, \\ + kiwi, mango + # Property with ':' delimiter + Truth:Beauty + # Property with EOF immediately after continuation character + sport=baseball\\ + """; + + /** + * Properties to add or override. + * Each entry contains the key, value, the expected key and the expected + * value after loading/parsing. + */ + private static final String[][] EXTRA_PROPS = new String[][] { + // overrides a current property + {"keystore.type", "bogus", "keystore.type", "bogus"}, + // simple user-defined property + {"foo", "bar", "foo", "bar"}, + // two include properties (it should only use the 2nd one) + {"include", "doNotUse", "include", "use"}, + {"include", "use", "include", "use"}, + // overrides a multi-lined value property + {"jdk.certpath.disabledAlgorithms", "MD2", + "jdk.certpath.disabledAlgorithms", "MD2"}, + // value with a char that is encoded differently in ISO-8859-1 vs. UTF-8 + {"iso_8859_1_char", "é", "iso_8859_1_char", "é"}, + // value with an empty string + {"empty", "", "empty", ""}, + // value with an equal sign + {"equalSign", "\\=", "equalSign", "="}, + // value with a unicode escaped euro character + {"aaa", "euro_\\u20AC_value", "aaa", "euro_\u20AC_value"}, + // key with a unicode escaped euro character + {"euro_\\u20AC_key", "111", "euro_\u20AC_key", "111"}, + // key with an escaped space character + {"spaced\\ key", "333", "spaced key", "333"}, + // value with an escaped slash character + {"abb", "slash_\\\\_value", "abb", "slash_\\_value"}, + // key with an escaped slash character + {"slash_\\\\_key", "222", "slash_\\_key", "222"}, + // value with indented spaces + {"zyy", "\\ indented value", "zyy", " indented value"}, + // multi-line value with newline character + {"zzz", "multi-line\\nvalue", "zzz", "multi-line\nvalue"} + }; + + // Invalid java.security file containing duplicate properties + private static final String DUPLICATE_PROPS = + """ + foo=123 + bar=789 + foo=456 + """; + + // Invalid java.security file containing include statement + private static final String INCLUDE_STATEMENT = + """ + include notAllowed + """; + + public static void main(String[] args) throws Throwable { + + helper = Helper.newHelper(LINKABLE_RUNTIME); + if (helper == null) { + throw new SkippedException("Test not run: no linkable runtime"); + } + + writePropsToFile("test.security"); + + testWithDefaultJDKImage(); + testWithCustomJDKImage(); + + // test illegal/bad options + testBadOptions(); + } + + /** + * Create image using current java.security file. Test that properties + * are overridden and compliant with java.util.Properties specification. + */ + private static void testWithDefaultJDKImage() throws Exception { + + helper.generateDefaultJModule("defaultModule"); + Path image = helper.generateDefaultImage( + new String[] { "--security-properties", "test.security" }, + "defaultModule").assertSuccess(); + helper.checkImage(image, "defaultModule", null, null, + new String[] { SECPROPS_PATH }); + + testImage(image); + } + + /** + * Create image using custom java.security file. Test that properties + * are overridden and compliant with java.util.Properties specification. + */ + private static void testWithCustomJDKImage() throws Exception { + + if (!helper.jdkHasPackagedModules()) { + System.out.println("Skipping testWithCustomJDKImage test case, " + + "no jmods in image"); + return; + } + + // Copy JDK's jmods directory + Path jdkJmodsPath = Path.of(System.getProperty("test.jdk"), "jmods"); + Path jdkJmodsCopyPath = Path.of(TEST_DIR, "jmodsCopy"); + FileUtils.copyDirectory(jdkJmodsPath, jdkJmodsCopyPath); + + // Extract contents of jmods copy + JModTask jmodTask = JImageGenerator.getJModTask() + .jmod(jdkJmodsCopyPath.resolve("java.base.jmod")) + .option("--dir") + .option("java.base.jmod.extracted"); + jmodTask.extract().assertSuccess(); + + // Create a new java.base.jmod with replacement java.security file + createReplacementJmod(JAVA_SECURITY_REPLACEMENT); + + // Create default module for testing + Path customModule = + helper.generateDefaultJModule("customModule").assertSuccess(); + + // Create second image using custom module and new java.base.jmod + Path customImage = Path.of(TEST_DIR, "images/customModule1.image"); + createSecondImage(customImage).assertSuccess(); + + testImage(customImage); + + Files.delete(Path.of(TEST_DIR, "jmods/java.base.jmod")); + createReplacementJmod(DUPLICATE_PROPS); + + // Create second image using custom module and new java.base.jmod + // Expect failure because java.security file has duplicate properties. + customImage = Path.of(TEST_DIR, "images/customModule2.image"); + createSecondImage(customImage) + .assertFailure("Parsing error, duplicate property"); + + Files.delete(Path.of(TEST_DIR, "jmods/java.base.jmod")); + createReplacementJmod(INCLUDE_STATEMENT); + + // Create second image using custom module and new java.base.jmod + // Expect failure because java.security file has an include statement. + customImage = Path.of(TEST_DIR, "images/customModule3.image"); + createSecondImage(customImage) + .assertFailure("Parsing error, include statement disallowed"); + } + + private static void createReplacementJmod(String jsProps) throws IOException{ + // Replace JDK's java.security file with test version. First, sanity + // check syntax of replacement by loading it in Properties object. + Properties props = new Properties(); + props.load(new ByteArrayInputStream( + jsProps.getBytes(StandardCharsets.ISO_8859_1))); + + // Now replace the extracted JDK's java.security file + Path extractedPath = Path.of(TEST_DIR, "java.base.jmod.extracted"); + Files.copy(new ByteArrayInputStream( + jsProps.getBytes(StandardCharsets.ISO_8859_1)), + extractedPath.resolve(SECPROPS_PATH), + StandardCopyOption.REPLACE_EXISTING); + + // Create a new java.base.jmod with replacement java.security file + JModTask jmodTask = JImageGenerator.getJModTask() + .addClassPath(extractedPath.resolve("classes")) + .addCmds(extractedPath.resolve("bin")) + .addConfig(extractedPath.resolve("conf")) + .addNativeLibraries(extractedPath.resolve("lib")) + .jmod(Path.of(TEST_DIR, "jmods/java.base.jmod")); + jmodTask.create().assertSuccess(); + } + + private static Result createSecondImage(Path customImage) { + JLinkTask jLinkTask = JImageGenerator.getJLinkTask() + .modulePath(Path.of(TEST_DIR, "jmods").toString()) + .output(customImage) + .addMods("customModule") + .limitMods("customModule") + .option("--security-properties") + .option("test.security"); + return jLinkTask.call(); + } + + private static void testImage(Path image) throws Exception { + + Path secPropsPath = image.resolve(SECPROPS_PATH); + Properties javasecProps = new Properties(); + try (FileInputStream fis = + new FileInputStream(secPropsPath.toFile())) { + javasecProps.load(fis); + } + + for (String[] prop : EXTRA_PROPS) { + Asserts.assertEquals(prop[3], javasecProps.getProperty(prop[2])); + Asserts.assertTrue(javasecProps.containsKey(prop[2])); + } + + // check include is last line + List lines = Files.readAllLines(secPropsPath, + StandardCharsets.ISO_8859_1); + Asserts.assertEquals(lines.getLast(), "include use"); + + // Make sure override of "jdk.certpath.disabledAlgorithms" + // is in the right place + Asserts.assertLessThan( + lines.indexOf("jdk.certpath.disabledAlgorithms=MD2"), + lines.size() - EXTRA_PROPS.length); + + // Check property with EOF after \ was parsed correctly + if (image.endsWith("customModule1.image")) { + Asserts.assertEquals("baseball", javasecProps.getProperty("sport")); + } + } + + private static void testBadOptions() throws Exception { + + // non-existent props file + String module = "testBad"; + helper.generateDefaultJModule(module); + helper.generateDefaultImage(new String[] + { "--security-properties", "nonexistent-file" }, module) + .assertFailure("java.io.FileNotFoundException: " + + "nonexistent-file"); + } + + private static void writePropsToFile(String filename) throws Exception { + Random r = new Random(); + Path p = Path.of(TEST_DIR, filename); + // write some comments in both formats ('#' and '!') and blank line + StringBuilder sb = new StringBuilder(); + sb.append("# Test properties file\n") + .append(" # Test properties file\n") + .append("! Test properties file\n") + .append(" "); + // use random delimiter + for (String[] prop : EXTRA_PROPS) { + sb.append(prop[0] + DELIMS.charAt(r.nextInt(DELIMS.length())) + + prop[1] + System.lineSeparator()); + } + Files.writeString(p, sb, StandardCharsets.ISO_8859_1); + } +} diff --git a/test/jdk/tools/lib/tests/JImageGenerator.java b/test/jdk/tools/lib/tests/JImageGenerator.java index 5e006459101c..602b4db6266d 100644 --- a/test/jdk/tools/lib/tests/JImageGenerator.java +++ b/test/jdk/tools/lib/tests/JImageGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -486,6 +486,10 @@ public Result call() { return cmd(""); } + public Result extract() { + return cmd("extract"); + } + private Result cmd(String cmd) { String[] args = optionsJMod(cmd); System.err.println("jmod options: " + optionsPrettyPrint(args)); From 7a5681d6d186994e3d10aa1762be585f0ef49a04 Mon Sep 17 00:00:00 2001 From: Albert Mingkun Yang Date: Mon, 19 Oct 2026 12:37:33 +0000 Subject: [PATCH 105/169] 8393500: Parallel: Simplify Full GC compaction state transitions Reviewed-by: tschatzl, gli --- .../share/gc/parallel/psCompactionManager.cpp | 10 +- .../parallel/psCompactionManager.inline.hpp | 4 +- .../share/gc/parallel/psParallelCompact.cpp | 63 ++--- .../share/gc/parallel/psParallelCompact.hpp | 260 +++++++++++------- 4 files changed, 179 insertions(+), 158 deletions(-) diff --git a/src/hotspot/share/gc/parallel/psCompactionManager.cpp b/src/hotspot/share/gc/parallel/psCompactionManager.cpp index 9b96db0e5a68..6ca80152401b 100644 --- a/src/hotspot/share/gc/parallel/psCompactionManager.cpp +++ b/src/hotspot/share/gc/parallel/psCompactionManager.cpp @@ -172,15 +172,13 @@ void ParCompactionManager::drain_region_stacks() { size_t ParCompactionManager::pop_shadow_region_mt_safe(PSParallelCompact::RegionData* region_ptr) { MonitorLocker ml(_shadow_region_monitor, Mutex::_no_safepoint_check_flag); while (true) { + // If the heap region has no remaining destination dependencies, use it directly. + if (region_ptr->available()) { + return InvalidShadow; + } if (!_shadow_region_array->is_empty()) { return _shadow_region_array->pop(); } - // Check if the corresponding heap region is available now. - // If so, we don't need to get a shadow region anymore, and - // we return InvalidShadow to indicate such a case. - if (region_ptr->claimed()) { - return InvalidShadow; - } ml.wait(1); } } diff --git a/src/hotspot/share/gc/parallel/psCompactionManager.inline.hpp b/src/hotspot/share/gc/parallel/psCompactionManager.inline.hpp index dc2663f38ffd..d88511d81736 100644 --- a/src/hotspot/share/gc/parallel/psCompactionManager.inline.hpp +++ b/src/hotspot/share/gc/parallel/psCompactionManager.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -61,7 +61,7 @@ void ParCompactionManager::push_region(size_t index) #ifdef ASSERT const ParallelCompactData& sd = PSParallelCompact::summary_data(); ParallelCompactData::RegionData* const region_ptr = sd.region(index); - assert(region_ptr->claimed(), "must be claimed"); + assert(region_ptr->is_normal(), "must be normal fill work"); assert(region_ptr->_pushed++ == 0, "should only be pushed once"); #endif region_stack()->push(index); diff --git a/src/hotspot/share/gc/parallel/psParallelCompact.cpp b/src/hotspot/share/gc/parallel/psParallelCompact.cpp index 8913c90a4bd3..33b7a62a53c3 100644 --- a/src/hotspot/share/gc/parallel/psParallelCompact.cpp +++ b/src/hotspot/share/gc/parallel/psParallelCompact.cpp @@ -111,31 +111,13 @@ const size_t ParallelCompactData::RegionSizeOffsetMask = RegionSize - 1; const size_t ParallelCompactData::RegionAddrOffsetMask = RegionSizeBytes - 1; const size_t ParallelCompactData::RegionAddrMask = ~RegionAddrOffsetMask; -const ParallelCompactData::RegionData::region_sz_t -ParallelCompactData::RegionData::dc_shift = 27; - -const ParallelCompactData::RegionData::region_sz_t -ParallelCompactData::RegionData::dc_mask = ~0U << dc_shift; - -const ParallelCompactData::RegionData::region_sz_t -ParallelCompactData::RegionData::dc_one = 0x1U << dc_shift; - -const ParallelCompactData::RegionData::region_sz_t -ParallelCompactData::RegionData::los_mask = ~dc_mask; - -const ParallelCompactData::RegionData::region_sz_t -ParallelCompactData::RegionData::dc_claimed = 0x8U << dc_shift; - -const ParallelCompactData::RegionData::region_sz_t -ParallelCompactData::RegionData::dc_completed = 0xcU << dc_shift; - bool ParallelCompactData::RegionData::is_clear() { return (_destination == nullptr) && (_source_region == 0) && (_partial_obj_addr == nullptr) && (_partial_obj_size == 0) && - (dc_and_los() == 0) && - (shadow_state() == 0); + (_live_obj_size.load_relaxed() == 0) && + (state() == 0); } #ifdef ASSERT @@ -144,8 +126,8 @@ void ParallelCompactData::RegionData::verify_clear() { assert(_source_region == 0, "inv"); assert(_partial_obj_addr == nullptr, "inv"); assert(_partial_obj_size == 0, "inv"); - assert(dc_and_los() == 0, "inv"); - assert(shadow_state() == 0, "inv"); + assert(_live_obj_size.load_relaxed() == 0, "inv"); + assert(state() == 0, "inv"); } #endif @@ -1508,10 +1490,8 @@ void PSParallelCompact::prepare_region_draining_tasks(uint parallel_gc_threads) // Populate with [beg_region, end_region) for (size_t cur = end_region - 1; cur + 1 > beg_region; --cur) { - if (sd.region(cur)->claim_unsafe()) { + if (sd.region(cur)->available_to_normal()) { ParCompactionManager* cm = ParCompactionManager::gc_thread_compaction_manager(worker_id); - bool result = sd.region(cur)->mark_normal(); - assert(result, "Must succeed at this point."); cm->push_region(cur); region_logger.handle(cur); // Assign regions to tasks in round-robin fashion. @@ -1724,15 +1704,15 @@ void PSParallelCompact::verify_regions_after_compaction() { // [beg_region, new_top_region) must be completed (as compaction targets) for (cur_region = beg_region; cur_region < new_top_region; ++cur_region) { const RegionData* const c = sd.region(cur_region); - assert(c->completed(), "region %zu not filled: destination_count=%u", - cur_region, c->destination_count()); + assert(c->completed(), "region %zu not filled: destination_count=%u status=%s", + cur_region, c->destination_count(), c->status_name()); } // [new_top_region, old_top_region) must be empty. for (cur_region = new_top_region; cur_region < old_top_region; ++cur_region) { const RegionData* const c = sd.region(cur_region); - assert(c->available(), "region %zu not empty: destination_count=%u", - cur_region, c->destination_count()); + assert(c->available() && c->is_init(), "region %zu not empty: destination_count=%u status=%s", + cur_region, c->destination_count(), c->status_name()); } } { @@ -1748,8 +1728,8 @@ void PSParallelCompact::verify_regions_after_compaction() { for (size_t cur_region = beg_region; cur_region < end_region; ++cur_region) { const RegionData* const c = sd.region(cur_region); - assert(c->available(), "region %zu not empty: destination_count=%u", - cur_region, c->destination_count()); + assert(c->available() && c->is_init(), "region %zu not empty: destination_count=%u status=%s", + cur_region, c->destination_count(), c->status_name()); } } } @@ -1861,17 +1841,16 @@ void PSParallelCompact::decrement_destination_counts(ParCompactionManager* cm, for (RegionData* cur = beg; cur < end; ++cur) { assert(cur->data_size() > 0, "region must have live data"); cur->decrement_destination_count(); - if (cur < enqueue_end && cur->available() && cur->claim()) { - if (cur->mark_normal()) { + if (cur < enqueue_end && cur->available()) { + if (cur->try_mark_normal()) { cm->push_region(sd.region(cur)); - } else if (cur->mark_copied()) { + } else if (cur->try_complete_filled_shadow()) { // Try to copy the content of the shadow region back to its corresponding // heap region if the shadow region is filled. Otherwise, the GC thread // fills the shadow region will copy the data back (see // MoveAndUpdateShadowClosure::complete_region). copy_back(sd.region_to_addr(cur->shadow_region()), sd.region_to_addr(cur)); ParCompactionManager::push_shadow_region_mt_safe(cur->shadow_region()); - cur->set_completed(); } } } @@ -2144,7 +2123,7 @@ bool PSParallelCompact::steal_unavailable_region(ParCompactionManager* cm, size_ uint active_gc_threads = ParallelScavengeHeap::heap()->workers().active_workers(); while (next < old_new_top) { - if (sd.region(next)->mark_shadow()) { + if (sd.region(next)->try_mark_shadow()) { region_idx = next; return true; } @@ -2205,7 +2184,7 @@ void MoveAndUpdateClosure::copy_partial_obj(size_t partial_obj_size) } void MoveAndUpdateClosure::complete_region(HeapWord* dest_addr, PSParallelCompact::RegionData* region_ptr) { - assert(region_ptr->shadow_state() == ParallelCompactData::RegionData::NormalRegion, "Region should be finished"); + assert(region_ptr->is_normal(), "Region should be normal"); region_ptr->set_completed(); } @@ -2235,18 +2214,16 @@ void MoveAndUpdateClosure::do_addr(HeapWord* addr, size_t words, markWord mark) } void MoveAndUpdateShadowClosure::complete_region(HeapWord* dest_addr, PSParallelCompact::RegionData* region_ptr) { - assert(region_ptr->shadow_state() == ParallelCompactData::RegionData::ShadowRegion, "Region should be shadow"); + assert(region_ptr->is_shadow(), "Region should be shadow"); // Record the shadow region index region_ptr->set_shadow_region(_shadow); - // Mark the shadow region as filled to indicate the data is ready to be - // copied back - region_ptr->mark_filled(); + // Finish shadow filling and determine whether this worker must copy back immediately. + bool copy_now = region_ptr->finish_shadow_fill(); // Try to copy the content of the shadow region back to its corresponding // heap region if available; the GC thread that decreases the destination // count to zero will do the copying otherwise (see // PSParallelCompact::decrement_destination_counts). - if (((region_ptr->available() && region_ptr->claim()) || region_ptr->claimed()) && region_ptr->mark_copied()) { - region_ptr->set_completed(); + if (copy_now) { PSParallelCompact::copy_back(PSParallelCompact::summary_data().region_to_addr(_shadow), dest_addr); ParCompactionManager::push_shadow_region_mt_safe(_shadow); } diff --git a/src/hotspot/share/gc/parallel/psParallelCompact.hpp b/src/hotspot/share/gc/parallel/psParallelCompact.hpp index 3eb3691c8c0f..807bafff6942 100644 --- a/src/hotspot/share/gc/parallel/psParallelCompact.hpp +++ b/src/hotspot/share/gc/parallel/psParallelCompact.hpp @@ -107,7 +107,7 @@ class ParallelCompactData // in this region (words). This does not include the partial object // extending onto the region (if any), or the part of an object that extends // onto the next region (if any). - size_t live_obj_size() const { return dc_and_los() & los_mask; } + size_t live_obj_size() const { return _live_obj_size.load_relaxed(); } // Total live data that lies within the region (words). size_t data_size() const { return partial_obj_size() + live_obj_size(); } @@ -117,31 +117,29 @@ class ParallelCompactData // values of destination_count are // // 0 - data from the region will be compacted completely into itself, or the - // region is empty. The region can be claimed and then filled. + // region is empty. The region can be filled immediately. // 1 - data from the region will be compacted into 1 other region; some // data from the region may also be compacted into the region itself. // 2 - data from the region will be copied to 2 other regions. // // During compaction as regions are emptied, the destination_count is - // decremented (atomically) and when it reaches 0, it can be claimed and - // then filled. - // - // A region is claimed for processing by atomically changing the - // destination_count to the claimed value (dc_claimed). After a region has - // been filled, the destination_count should be set to the completed value - // (dc_completed). - inline uint destination_count() const; - inline uint destination_count_raw() const; - - // Whether this region is available to be claimed, has been claimed, or has - // been completed. - // - // Minor subtlety: claimed() returns true if the region is marked - // completed(), which is desirable since a region must be claimed before it - // can be completed. - bool available() const { return dc_and_los() < dc_one; } - bool claimed() const { return dc_and_los() >= dc_claimed; } - bool completed() const { return dc_and_los() >= dc_completed; } + // decremented atomically. When it reaches 0, the region has no remaining + // destination dependencies. + uint destination_count() const { return state_dest_count(state()); } + + // Whether this region has no remaining destination dependencies. + bool available() const { return destination_count() == 0; } + bool completed() const { return state_status(state()) == status_completed; } + const char* status_name() const { + switch (state_status(state())) { + case status_init: return "init"; + case status_normal: return "normal"; + case status_shadow: return "shadow"; + case status_filled_shadow: return "filled_shadow"; + case status_completed: return "completed"; + default: return "unknown"; + } + } // These are not atomic. void set_destination(HeapWord* addr) { _destination = addr; } @@ -155,37 +153,26 @@ class ParallelCompactData inline void set_destination_count(uint count); inline void set_completed(); - inline bool claim_unsafe(); + inline bool available_to_normal(); // These are atomic. - inline void add_live_obj(size_t words); + void add_live_obj(size_t words) { + assert(words <= RegionSize - live_obj_size(), "overflow"); + _live_obj_size.add_then_fetch(static_cast(words)); + } inline void decrement_destination_count(); - inline bool claim(); - - // Possible values of _shadow_state, and transition is as follows - // Normal Path: - // UnusedRegion -> mark_normal() -> NormalRegion - // Shadow Path: - // UnusedRegion -> mark_shadow() -> ShadowRegion -> - // mark_filled() -> FilledShadow -> mark_copied() -> CopiedShadow - static const int UnusedRegion = 0; // The region is not collected yet - static const int ShadowRegion = 1; // Stolen by an idle thread, and a shadow region is created for it - static const int FilledShadow = 2; // Its shadow region has been filled and ready to be copied back - static const int CopiedShadow = 3; // The data of the shadow region has been copied back - static const int NormalRegion = 4; // The region will be collected by the original parallel algorithm - - // Mark the current region as normal or shadow to enter different processing paths - inline bool mark_normal(); - inline bool mark_shadow(); - // Mark the shadow region as filled and ready to be copied back - inline void mark_filled(); - // Mark the shadow region as copied back to avoid double copying. - inline bool mark_copied(); - // Special case: see the comment in PSParallelCompact::fill_and_update_shadow_region. - // Return to the normal path here + inline bool try_mark_normal(); + inline bool try_complete_filled_shadow(); + + // Mark the current region as shadow to enter the shadow processing path. + inline bool try_mark_shadow(); + // Finish shadow filling. Returns true if the caller must copy back immediately. + inline bool finish_shadow_fill(); + // Special case: see PSParallelCompact::fill_and_update_shadow_region. inline void shadow_to_normal(); - - int shadow_state() { return _shadow_state.load_relaxed(); } + bool is_init() const { return state_status(state()) == status_init; } + bool is_normal() const { return state_status(state()) == status_normal; } + bool is_shadow() const { return state_status(state()) == status_shadow; } bool is_clear(); @@ -194,25 +181,76 @@ class ParallelCompactData private: // The type used to represent object sizes within a region. typedef uint region_sz_t; + // The type used to represent destination count and compaction state. + typedef uint state_t; - // Constants for manipulating the _dc_and_los field, which holds both the - // destination count and live obj size. The live obj size lives at the - // least significant end so no masking is necessary when adding. - static const region_sz_t dc_shift; // Shift amount. - static const region_sz_t dc_mask; // Mask for destination count. - static const region_sz_t dc_one; // 1, shifted appropriately. - static const region_sz_t dc_claimed; // Region has been claimed. - static const region_sz_t dc_completed; // Region has been completed. - static const region_sz_t los_mask; // Mask for live obj size. + // Layout of _state: + // + // 31-5 4-2 1-0 + // +--------+---------+----------+ + // unused status dest_count + // +--------+---------+----------+ + // | | | | + // | | |* 1-0 dest_count: destination regions still to be copied + // | | | from this source region (0-2) + // | |* 2-4 status: compaction path progress, see StateBits + // |* 5-31 unused + // + // Complete status routes: + // 1. Normal fill, initially or later available: + // status_init -> status_normal -> status_completed + // The first transition happens during initial task setup or when dest_count reaches 0. + // + // 2. Shadow selected, but no shadow region is available when dest_count reaches 0: + // status_init -> status_shadow -> status_normal -> status_completed + // status_shadow with dest_count 0 can fall back to normal filling. + // + // 3. Shadow fill completes before dest_count reaches 0: + // status_init -> status_shadow -> status_filled_shadow -> status_completed + // The last transition claims copy-back when dest_count reaches 0. + // + // 4. Dest_count reaches 0 before shadow fill completes: + // status_init -> status_shadow -> status_completed + // The last transition claims immediate copy-back when shadow fill completes. + enum StateBits : state_t { + max_destination_count = 2, + dest_count_mask = 0x3, + status_shift = 2, + status_mask = 0x7 << status_shift, + + status_init = 0 << status_shift, // Not yet selected for filling. + status_normal = 1 << status_shift, // Selected for direct filling. + status_shadow = 2 << status_shift, // Selected for filling via a shadow region. + status_filled_shadow = 3 << status_shift, // Shadow filled, waiting for destination dependencies. + status_completed = 4 << status_shift // Direct fill done, or shadow copy-back exclusively claimed. + }; + + // The destination count occupies the low status_shift bits, so decrementing the whole + // word in decrement_destination_count() decrements the count alone. + static_assert(dest_count_mask == ((1U << status_shift) - 1), + "dest_count field width must equal status_shift"); + static_assert(max_destination_count <= dest_count_mask, + "max_destination_count must fit in the dest_count field"); HeapWord* _destination; size_t _source_region; HeapWord* _partial_obj_addr; region_sz_t _partial_obj_size; - Atomic _dc_and_los; - Atomic _shadow_state; + Atomic _live_obj_size; + Atomic _state; - region_sz_t dc_and_los() const { return _dc_and_los.load_relaxed(); } + state_t state() const { return _state.load_relaxed(); } + static state_t make_state(state_t dest_count, state_t status) { + return dest_count | status; + } + static state_t state_dest_count(state_t state) { return state & dest_count_mask; } + static state_t state_status(state_t state) { return state & status_mask; } + static state_t with_dest_count(state_t state, state_t dest_count) { + return (state & ~dest_count_mask) | dest_count; + } + static state_t with_status(state_t state, state_t status) { + return (state & ~status_mask) | status; + } #ifdef ASSERT public: uint _pushed; // 0 until region is pushed onto a stack @@ -269,83 +307,91 @@ class ParallelCompactData size_t _region_count; }; -inline uint -ParallelCompactData::RegionData::destination_count_raw() const -{ - return dc_and_los() & dc_mask; -} - -inline uint -ParallelCompactData::RegionData::destination_count() const -{ - return destination_count_raw() >> dc_shift; -} - inline void ParallelCompactData::RegionData::set_destination_count(uint count) { - assert(count <= (dc_completed >> dc_shift), "count too large"); - const region_sz_t live_sz = (region_sz_t) live_obj_size(); - _dc_and_los.store_relaxed((count << dc_shift) | live_sz); + assert(count <= max_destination_count, "count too large"); + const state_t old_state = state(); + assert(state_status(old_state) == status_init, "cannot reset processed region"); + _state.store_relaxed(with_dest_count(old_state, static_cast(count))); } inline void ParallelCompactData::RegionData::decrement_destination_count() { - assert(dc_and_los() < dc_claimed, "already claimed"); - assert(dc_and_los() >= dc_one, "count would go negative"); - _dc_and_los.add_then_fetch(dc_mask); + state_t old_state = _state.fetch_then_sub(static_cast(1)); + assert(state_dest_count(old_state) > 0, "count would go negative"); } inline void ParallelCompactData::RegionData::set_completed() { - assert(claimed(), "must be claimed first"); - _dc_and_los.store_relaxed(dc_completed | (region_sz_t) live_obj_size()); + const state_t old_state = state(); + assert(state_status(old_state) == status_normal, "can only complete normal regions"); + _state.store_relaxed(with_status(old_state, status_completed)); } -// MT-unsafe claiming of a region. Should only be used during single threaded -// execution. -inline bool ParallelCompactData::RegionData::claim_unsafe() +inline bool ParallelCompactData::RegionData::available_to_normal() { - if (available()) { - _dc_and_los.store_relaxed(dc_and_los() | dc_claimed); + const state_t old_state = state(); + assert(state_status(old_state) == status_init, "initial regions must be unprocessed"); + if (state_dest_count(old_state) == 0) { + _state.store_relaxed(make_state(0, status_normal)); return true; } return false; } -inline void ParallelCompactData::RegionData::add_live_obj(size_t words) +inline bool ParallelCompactData::RegionData::try_mark_normal() { - assert(words <= (size_t)los_mask - live_obj_size(), "overflow"); - _dc_and_los.add_then_fetch(static_cast(words)); + assert(available(), "region must have no remaining destination dependencies"); + return _state.compare_set(make_state(0, status_init), make_state(0, status_normal)); } -inline bool ParallelCompactData::RegionData::claim() +inline bool ParallelCompactData::RegionData::try_complete_filled_shadow() { - const region_sz_t los = static_cast(live_obj_size()); - return _dc_and_los.compare_set(los, dc_claimed | los); -} - -inline bool ParallelCompactData::RegionData::mark_normal() { - return _shadow_state.compare_set(UnusedRegion, NormalRegion); -} - -inline bool ParallelCompactData::RegionData::mark_shadow() { - if (shadow_state() != UnusedRegion) return false; - return _shadow_state.compare_set(UnusedRegion, ShadowRegion); + assert(available(), "region must have no remaining destination dependencies"); + return _state.compare_set(make_state(0, status_filled_shadow), make_state(0, status_completed)); } -inline void ParallelCompactData::RegionData::mark_filled() { - int old = _shadow_state.compare_exchange(ShadowRegion, FilledShadow); - assert(old == ShadowRegion, "Fail to mark the region as filled"); +inline bool ParallelCompactData::RegionData::try_mark_shadow() { + state_t old_state = state(); + while (true) { + if (state_dest_count(old_state) == 0 || + state_status(old_state) != status_init) { + return false; + } + const state_t new_state = with_status(old_state, status_shadow); + if (_state.compare_set(old_state, new_state)) { + return true; + } + old_state = state(); + } } -inline bool ParallelCompactData::RegionData::mark_copied() { - return _shadow_state.compare_set(FilledShadow, CopiedShadow); +inline bool ParallelCompactData::RegionData::finish_shadow_fill() { + state_t old_state = state(); + while (true) { + state_t new_state; + bool copy_now = false; + assert(state_status(old_state) == status_shadow, "must be shadow"); + if (state_dest_count(old_state) == 0) { + new_state = with_status(old_state, status_completed); + copy_now = true; + } else { + new_state = with_status(old_state, status_filled_shadow); + } + if (_state.compare_set(old_state, new_state)) { + return copy_now; + } + old_state = state(); + } } void ParallelCompactData::RegionData::shadow_to_normal() { - int old = _shadow_state.compare_exchange(ShadowRegion, NormalRegion); - assert(old == ShadowRegion, "Fail to mark the region as finish"); + assert(available(), "region must have no remaining destination dependencies"); + const state_t old_state = make_state(0, status_shadow); + const state_t new_state = make_state(0, status_normal); + bool result = _state.compare_set(old_state, new_state); + assert(result, "Fail to mark the region as normal"); } inline ParallelCompactData::RegionData* From f0157496a8de18f7ec8c413a98bc2c892d3f52d0 Mon Sep 17 00:00:00 2001 From: Frederic Parain Date: Mon, 19 Oct 2026 13:17:44 +0000 Subject: [PATCH 106/169] 8389595: [Valhalla] Add is_valhalla_enabled asserts to Valhalla specific methods Co-authored-by: Martin Doerr Co-authored-by: Amit Kumar Co-authored-by: Fei Yang Reviewed-by: kvn, mdoerr, thartmann --- .../cpu/aarch64/c1_LIRAssembler_aarch64.cpp | 18 +- .../cpu/aarch64/interp_masm_aarch64.cpp | 24 ++- .../cpu/aarch64/macroAssembler_aarch64.cpp | 16 ++ .../cpu/aarch64/stubGenerator_aarch64.cpp | 4 +- .../cpu/aarch64/templateTable_aarch64.cpp | 121 ++++++++----- src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.cpp | 20 ++- src/hotspot/cpu/ppc/interp_masm_ppc_64.cpp | 23 ++- src/hotspot/cpu/ppc/macroAssembler_ppc.cpp | 12 ++ src/hotspot/cpu/ppc/stubGenerator_ppc.cpp | 19 +- src/hotspot/cpu/ppc/templateTable_ppc_64.cpp | 113 +++++++----- .../riscv/c1_LIRAssembler_arraycopy_riscv.cpp | 17 +- .../cpu/riscv/c1_LIRAssembler_riscv.cpp | 1 + src/hotspot/cpu/riscv/interp_masm_riscv.cpp | 24 ++- .../cpu/riscv/macroAssembler_riscv.cpp | 12 ++ src/hotspot/cpu/riscv/stubGenerator_riscv.cpp | 4 +- src/hotspot/cpu/riscv/templateTable_riscv.cpp | 117 +++++++----- src/hotspot/cpu/s390/c1_LIRAssembler_s390.cpp | 18 +- src/hotspot/cpu/s390/interp_masm_s390.cpp | 23 ++- src/hotspot/cpu/s390/macroAssembler_s390.cpp | 16 ++ src/hotspot/cpu/s390/templateTable_s390.cpp | 166 ++++++++++-------- src/hotspot/cpu/x86/c1_LIRAssembler_x86.cpp | 20 ++- src/hotspot/cpu/x86/interp_masm_x86.cpp | 23 ++- src/hotspot/cpu/x86/macroAssembler_x86.cpp | 16 ++ .../x86/stubGenerator_x86_64_arraycopy.cpp | 18 +- src/hotspot/cpu/x86/templateTable_x86.cpp | 112 +++++++----- src/hotspot/share/c1/c1_GraphBuilder.cpp | 2 +- src/hotspot/share/c1/c1_LIR.hpp | 4 +- src/hotspot/share/oops/methodData.cpp | 2 +- src/hotspot/share/opto/library_call.cpp | 33 +++- src/hotspot/share/opto/macro.cpp | 1 + src/hotspot/share/opto/macroArrayCopy.cpp | 9 +- src/hotspot/share/opto/subnode.hpp | 1 + src/hotspot/share/opto/valuetypenode.hpp | 3 + 33 files changed, 666 insertions(+), 346 deletions(-) diff --git a/src/hotspot/cpu/aarch64/c1_LIRAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/c1_LIRAssembler_aarch64.cpp index 745a3f58a7fc..c9d5d9b4dedb 100644 --- a/src/hotspot/cpu/aarch64/c1_LIRAssembler_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/c1_LIRAssembler_aarch64.cpp @@ -2379,13 +2379,14 @@ void LIR_Assembler::store_parameter(jobject o, int offset_from_rsp_in_words) { } void LIR_Assembler::arraycopy_valuetype_check(Register obj, Register tmp, CodeStub* slow_path, bool is_dest, bool null_check) { + assert(Arguments::is_valhalla_enabled(), "Value types must be enabled"); if (null_check) { __ cbz(obj, *slow_path->entry()); } if (is_dest) { __ test_null_free_array_oop(obj, tmp, *slow_path->entry()); - __ test_flat_array_oop(obj, tmp, *slow_path->entry()); - } else { + } + if (UseArrayFlattening) { __ test_flat_array_oop(obj, tmp, *slow_path->entry()); } } @@ -2467,11 +2468,13 @@ void LIR_Assembler::emit_arraycopy(LIR_OpArrayCopy* op) { } // Handle value type arrays - if (flags & LIR_OpArrayCopy::src_valuetype_check) { - arraycopy_valuetype_check(src, tmp, stub, false, (flags & LIR_OpArrayCopy::src_null_check)); - } - if (flags & LIR_OpArrayCopy::dst_valuetype_check) { - arraycopy_valuetype_check(dst, tmp, stub, true, (flags & LIR_OpArrayCopy::dst_null_check)); + if (Arguments::is_valhalla_enabled()) { + if (flags & LIR_OpArrayCopy::src_valuetype_check) { + arraycopy_valuetype_check(src, tmp, stub, false, (flags & LIR_OpArrayCopy::src_null_check)); + } + if (flags & LIR_OpArrayCopy::dst_valuetype_check) { + arraycopy_valuetype_check(dst, tmp, stub, true, (flags & LIR_OpArrayCopy::dst_null_check)); + } } assert(default_type != nullptr && default_type->is_array_klass() && default_type->is_loaded(), "must be true at this point"); @@ -2989,6 +2992,7 @@ void LIR_Assembler::emit_profile_type(LIR_OpProfileType* op) { } void LIR_Assembler::emit_profile_value_type(LIR_OpProfileValueType* op) { + assert(Arguments::is_valhalla_enabled(), "Should not be called without value types enabled"); Register obj = op->obj()->as_register(); Register tmp = op->tmp()->as_pointer_register(); bool not_null = op->not_null(); diff --git a/src/hotspot/cpu/aarch64/interp_masm_aarch64.cpp b/src/hotspot/cpu/aarch64/interp_masm_aarch64.cpp index c2bf73092cc5..caa75144893c 100644 --- a/src/hotspot/cpu/aarch64/interp_masm_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/interp_masm_aarch64.cpp @@ -44,6 +44,7 @@ #include "prims/jvmtiThreadState.hpp" #include "runtime/basicLock.hpp" #include "runtime/frame.inline.hpp" +#include "runtime/globals.hpp" #include "runtime/javaThread.hpp" #include "runtime/safepointMechanism.hpp" #include "runtime/sharedRuntime.hpp" @@ -211,10 +212,12 @@ void InterpreterMacroAssembler::get_method_counters(Register method, } void InterpreterMacroAssembler::read_flat_field(Register entry, Register obj) { + assert(UseFieldFlattening, "Should be used only if field flattening is enabled"); call_VM(obj, CAST_FROM_FN_PTR(address, InterpreterRuntime::read_flat_field), obj, entry); } void InterpreterMacroAssembler::write_flat_field(Register entry, Register tmp1, Register tmp2, Register obj) { + assert(UseFieldFlattening, "Should be used only if field flattening is enabled"); assert_different_registers(entry, tmp1, tmp2, obj); Label slow_path, done; @@ -1202,19 +1205,23 @@ template void InterpreterMacroAssembler::profile_array_type(Re mov(tmp, array); profile_obj_type(tmp, Address(mdp, in_bytes(ArrayData::array_offset()))); - Label not_flat; - test_non_flat_array_oop(array, tmp, not_flat); + if (Arguments::is_valhalla_enabled()) { + if (UseArrayFlattening) { + Label not_flat; + test_non_flat_array_oop(array, tmp, not_flat); - set_mdp_flag_at(mdp, ArrayData::flat_array_byte_constant()); + set_mdp_flag_at(mdp, ArrayData::flat_array_byte_constant()); - bind(not_flat); + bind(not_flat); + } - Label not_null_free; - test_non_null_free_array_oop(array, tmp, not_null_free); + Label not_null_free; + test_non_null_free_array_oop(array, tmp, not_null_free); - set_mdp_flag_at(mdp, ArrayData::null_free_array_byte_constant()); + set_mdp_flag_at(mdp, ArrayData::null_free_array_byte_constant()); - bind(not_null_free); + bind(not_null_free); + } bind(profile_continue); } @@ -1278,6 +1285,7 @@ void InterpreterMacroAssembler::profile_acmp(Register mdp, Register left, Register right, Register tmp) { + assert(Arguments::is_valhalla_enabled(), "Profiling acmp makes sense only for value types"); if (ProfileInterpreter) { Label profile_continue; diff --git a/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp index d083fd5b7610..0077a2daab5f 100644 --- a/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp @@ -2404,6 +2404,7 @@ void MacroAssembler::null_check(Register reg, int offset) { } void MacroAssembler::test_markword_is_value_type(Register markword, Label& is_value_type) { + assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled"); assert_different_registers(markword, rscratch2); mov(rscratch2, markWord::value_type_pattern_mask); andr(markword, markword, rscratch2); @@ -2413,6 +2414,7 @@ void MacroAssembler::test_markword_is_value_type(Register markword, Label& is_va } void MacroAssembler::test_oop_is_not_value_type(Register object, Register tmp, Label& not_value_type, bool can_be_null) { + assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled"); assert_different_registers(tmp, rscratch1); if (can_be_null) { cbz(object, not_value_type); @@ -2426,16 +2428,19 @@ void MacroAssembler::test_oop_is_not_value_type(Register object, Register tmp, L } void MacroAssembler::test_field_is_null_free_value_type(Register flags, Register temp_reg, Label& is_null_free_value_type) { + assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled"); assert(temp_reg == noreg, "not needed"); // keep signature uniform with x86 tbnz(flags, ResolvedFieldEntry::is_null_free_value_type_shift, is_null_free_value_type); } void MacroAssembler::test_field_is_not_null_free_value_type(Register flags, Register temp_reg, Label& not_null_free_value_type) { + assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled"); assert(temp_reg == noreg, "not needed"); // keep signature uniform with x86 tbz(flags, ResolvedFieldEntry::is_null_free_value_type_shift, not_null_free_value_type); } void MacroAssembler::test_field_is_flat(Register flags, Register temp_reg, Label& is_flat) { + assert(UseFieldFlattening, "should not be used without field flattening enabled"); assert(temp_reg == noreg, "not needed"); // keep signature uniform with x86 tbnz(flags, ResolvedFieldEntry::is_flat_shift, is_flat); } @@ -2452,23 +2457,28 @@ void MacroAssembler::test_oop_prototype_bit(Register oop, Register temp_reg, int } void MacroAssembler::test_flat_array_oop(Register oop, Register temp_reg, Label& is_flat_array) { + assert(UseArrayFlattening, "should not be used without array flattening enabled"); test_oop_prototype_bit(oop, temp_reg, markWord::flat_array_bit_in_place, true, is_flat_array); } void MacroAssembler::test_non_flat_array_oop(Register oop, Register temp_reg, Label&is_non_flat_array) { + assert(UseArrayFlattening, "should not be used without array flattening enabled"); test_oop_prototype_bit(oop, temp_reg, markWord::flat_array_bit_in_place, false, is_non_flat_array); } void MacroAssembler::test_null_free_array_oop(Register oop, Register temp_reg, Label& is_null_free_array) { + assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled"); test_oop_prototype_bit(oop, temp_reg, markWord::null_free_array_bit_in_place, true, is_null_free_array); } void MacroAssembler::test_non_null_free_array_oop(Register oop, Register temp_reg, Label&is_non_null_free_array) { + assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled"); test_oop_prototype_bit(oop, temp_reg, markWord::null_free_array_bit_in_place, false, is_non_null_free_array); } void MacroAssembler::test_flat_array_layout(Register lh, Label& is_flat_array) { + assert(UseArrayFlattening, "should not be used without array flattening enabled"); tst(lh, Klass::_lh_array_tag_flat_value_bit_inplace); br(Assembler::NE, is_flat_array); } @@ -5695,11 +5705,13 @@ void MacroAssembler::access_store_at(BasicType type, DecoratorSet decorators, } void MacroAssembler::payload_offset(Register value_klass, Register offset) { + assert(Arguments::is_valhalla_enabled(), "Must be"); ldr(offset, Address(value_klass, ValueKlass::adr_members_offset())); ldrw(offset, Address(offset, ValueKlass::payload_offset_offset())); } void MacroAssembler::payload_address(Register oop, Register data, Register value_klass) { + assert(Arguments::is_valhalla_enabled(), "Must be"); // ((address) (void*) o) + vk->payload_offset(); Register offset = (data == oop) ? rscratch1 : data; payload_offset(value_klass, offset); @@ -7156,6 +7168,7 @@ int MacroAssembler::store_value_type_fields_to_buf(ciValueKlass* vk, bool from_i // Move a value between registers/stack slots and update the reg_state bool MacroAssembler::move_helper(VMReg from, VMReg to, BasicType bt, RegState reg_state[]) { + assert(ValueTypePassFieldsAsArgs, "Must be"); assert(from->is_valid() && to->is_valid(), "source and destination must be valid"); if (reg_state[to->value()] == reg_written) { return true; // Already written @@ -7218,6 +7231,7 @@ bool MacroAssembler::move_helper(VMReg from, VMReg to, BasicType bt, RegState re // Calculate the extra stack space required for packing or unpacking value // args and adjust the stack pointer int MacroAssembler::extend_stack_for_value_args(int args_on_stack) { + assert(ValueTypePassFieldsAsArgs, "Must be"); int sp_inc = args_on_stack * VMRegImpl::stack_slot_size; sp_inc = align_up(sp_inc, StackAlignmentInBytes); assert(sp_inc > 0, "sanity"); @@ -7249,6 +7263,7 @@ int MacroAssembler::extend_stack_for_value_args(int args_on_stack) { bool MacroAssembler::unpack_value_helper(const GrowableArray* sig, int& sig_index, VMReg from, int& from_index, VMRegPair* to, int to_count, int& to_index, RegState reg_state[]) { + assert(ValueTypePassFieldsAsArgs, "Must be"); assert(sig->at(sig_index)._bt == T_VOID, "should be at end delimiter"); assert(from->is_valid(), "source must be valid"); bool progress = false; @@ -7395,6 +7410,7 @@ bool MacroAssembler::unpack_value_helper(const GrowableArray* sig, int bool MacroAssembler::pack_value_helper(const GrowableArray* sig, int& sig_index, int vtarg_index, VMRegPair* from, int from_count, int& from_index, VMReg to, RegState reg_state[], Register val_array) { + assert(ValueTypePassFieldsAsArgs, "Must be"); assert(sig->at(sig_index)._bt == T_METADATA, "should be at delimiter"); assert(to->is_valid(), "destination must be valid"); diff --git a/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp b/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp index 92f604c507e5..b59c23489bea 100644 --- a/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp @@ -2621,10 +2621,12 @@ class StubGenerator: public StubCodeGenerator { __ eor(rscratch2, rscratch2, scratch_src_klass); __ cbnz(rscratch2, L_failed); - if (Arguments::is_valhalla_enabled()) { + if (UseArrayFlattening) { // Check for flat value type array -> return -1 __ test_flat_array_oop(src, rscratch2, L_failed); + } + if (Arguments::is_valhalla_enabled()) { // Check for null-free (non-flat) value type array -> handle as object array __ test_null_free_array_oop(src, rscratch2, L_objArray); } diff --git a/src/hotspot/cpu/aarch64/templateTable_aarch64.cpp b/src/hotspot/cpu/aarch64/templateTable_aarch64.cpp index f1ab668d211d..0f8b21f47927 100644 --- a/src/hotspot/cpu/aarch64/templateTable_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/templateTable_aarch64.cpp @@ -29,9 +29,9 @@ #include "gc/shared/barrierSetAssembler.hpp" #include "gc/shared/collectedHeap.hpp" #include "gc/shared/tlab_globals.hpp" +#include "interpreter/interp_masm.hpp" #include "interpreter/interpreter.hpp" #include "interpreter/interpreterRuntime.hpp" -#include "interpreter/interp_masm.hpp" #include "interpreter/templateTable.hpp" #include "memory/universe.hpp" #include "oops/methodData.hpp" @@ -45,6 +45,7 @@ #include "prims/methodHandles.hpp" #include "runtime/arguments.hpp" #include "runtime/frame.inline.hpp" +#include "runtime/globals.hpp" #include "runtime/sharedRuntime.hpp" #include "runtime/stubRoutines.hpp" #include "runtime/synchronizer.hpp" @@ -2020,12 +2021,14 @@ void TemplateTable::if_acmp(Condition cc) { Label taken, not_taken; __ pop_ptr(r1); - __ profile_acmp(r2, r1, r0, r4); - Register is_value_type_mask = rscratch1; - __ mov(is_value_type_mask, markWord::value_type_pattern); if (Arguments::is_valhalla_enabled()) { + __ profile_acmp(r2, r1, r0, r4); + + Register is_value_type_mask = rscratch1; + __ mov(is_value_type_mask, markWord::value_type_pattern); + // The substitutability test is only necessary if r1 and r0 are not the same... __ cmp(r1, r0); __ br(Assembler::EQ, (cc == equal) ? taken : not_taken); @@ -2785,7 +2788,9 @@ void TemplateTable::getfield_or_static(int byte_no, bool is_static, RewriteContr __ b(Done); } else { Label is_flat; - __ test_field_is_flat(flags, noreg /* temp */, is_flat); + if (UseFieldFlattening) { + __ test_field_is_flat(flags, noreg /* temp */, is_flat); + } __ load_heap_oop(r0, field, rscratch1, rscratch2); __ push(atos); if (rc == may_rewrite) { @@ -2793,15 +2798,17 @@ void TemplateTable::getfield_or_static(int byte_no, bool is_static, RewriteContr } __ b(Done); __ bind(is_flat); - // field is flat (null-free or nullable with a null-marker) - __ mov(r0, obj); - __ read_flat_field(cache, r0); - __ verify_oop(r0); - __ push(atos); - if (rc == may_rewrite) { - patch_bytecode(Bytecodes::_fast_vgetfield, bc, r1); + if (UseFieldFlattening) { + // field is flat (null-free or nullable with a null-marker) + __ mov(r0, obj); + __ read_flat_field(cache, r0); + __ verify_oop(r0); + __ push(atos); + if (rc == may_rewrite) { + patch_bytecode(Bytecodes::_fast_vgetfield, bc, r1); + } + __ b(Done); } - __ b(Done); } } @@ -3046,7 +3053,9 @@ void TemplateTable::putfield_or_static(int byte_no, bool is_static, RewriteContr __ b(Done); } else { Label null_free_reference, is_flat, rewrite_value; - __ test_field_is_flat(flags, noreg /* temp */, is_flat); + if (UseFieldFlattening) { + __ test_field_is_flat(flags, noreg /* temp */, is_flat); + } __ test_field_is_null_free_value_type(flags, noreg /* temp */, null_free_reference); pop_and_check_object(obj); // Store into the field @@ -3065,8 +3074,10 @@ void TemplateTable::putfield_or_static(int byte_no, bool is_static, RewriteContr do_oop_store(_masm, field, r0, IN_HEAP); __ b(rewrite_value); __ bind(is_flat); - pop_and_check_object(r7); - __ write_flat_field(cache, index, flags, r7); + if (UseFieldFlattening) { + pop_and_check_object(r7); + __ write_flat_field(cache, index, flags, r7); + } __ bind(rewrite_value); if (rc == may_rewrite) { patch_bytecode(Bytecodes::_fast_vputfield, bc, r19, true, byte_no); @@ -3287,17 +3298,25 @@ void TemplateTable::fast_storefield(TosState state) switch (bytecode()) { case Bytecodes::_fast_vputfield: { - Label is_flat, done; - __ test_field_is_flat(r5, noreg /* temp */, is_flat); - __ null_check(r0); - do_oop_store(_masm, field, r0, IN_HEAP); - __ b(done); - __ bind(is_flat); - __ load_field_entry(r4, r5); - // Re-shuffle registers because of VM calls calling convention - __ mov(r7, r2); - __ write_flat_field(r4, r6, r8, r7); - __ bind(done); + if (Arguments::is_valhalla_enabled()) { + Label is_flat, done; + if (UseFieldFlattening) { + __ test_field_is_flat(r5, noreg /* temp */, is_flat); + } + __ null_check(r0); + do_oop_store(_masm, field, r0, IN_HEAP); + __ b(done); + __ bind(is_flat); + if (UseFieldFlattening) { + __ load_field_entry(r4, r5); + // Re-shuffle registers because of VM calls calling convention + __ mov(r7, r2); + __ write_flat_field(r4, r6, r8, r7); + } + __ bind(done); + } else { + __ should_not_reach_here(); + } } break; case Bytecodes::_fast_aputfield: @@ -3397,9 +3416,13 @@ void TemplateTable::fast_accessfield(TosState state) switch (bytecode()) { case Bytecodes::_fast_vgetfield: { - // field is flat - __ read_flat_field(r2, r0); - __ verify_oop(r0); + if (UseFieldFlattening) { + // field is flat + __ read_flat_field(r2, r0); + __ verify_oop(r0); + } else { + __ should_not_reach_here(); + } } break; case Bytecodes::_fast_agetfield: @@ -4104,8 +4127,10 @@ void TemplateTable::monitorenter() __ null_check(r0); Label is_value_type; - __ ldr(rscratch1, Address(r0, oopDesc::mark_offset_in_bytes())); - __ test_markword_is_value_type(rscratch1, is_value_type); + if (Arguments::is_valhalla_enabled()) { + __ ldr(rscratch1, Address(r0, oopDesc::mark_offset_in_bytes())); + __ test_markword_is_value_type(rscratch1, is_value_type); + } const Address monitor_block_top( rfp, frame::interpreter_frame_monitor_block_top_offset * wordSize); @@ -4210,9 +4235,11 @@ void TemplateTable::monitorenter() __ dispatch_next(vtos); __ bind(is_value_type); - __ call_VM(noreg, CAST_FROM_FN_PTR(address, - InterpreterRuntime::throw_identity_exception), r0); - __ should_not_reach_here(); + if (Arguments::is_valhalla_enabled()) { + __ call_VM(noreg, CAST_FROM_FN_PTR(address, + InterpreterRuntime::throw_identity_exception), r0); + __ should_not_reach_here(); + } } @@ -4223,17 +4250,19 @@ void TemplateTable::monitorexit() // check for null object __ null_check(r0); - const int is_value_type_mask = markWord::value_type_pattern; - Label has_identity; - __ ldr(rscratch1, Address(r0, oopDesc::mark_offset_in_bytes())); - __ mov(rscratch2, is_value_type_mask); - __ andr(rscratch1, rscratch1, rscratch2); - __ cmp(rscratch1, rscratch2); - __ br(Assembler::NE, has_identity); - __ call_VM(noreg, CAST_FROM_FN_PTR(address, - InterpreterRuntime::throw_illegal_monitor_state_exception)); - __ should_not_reach_here(); - __ bind(has_identity); + if (Arguments::is_valhalla_enabled()) { + const int is_value_type_mask = markWord::value_type_pattern; + Label has_identity; + __ ldr(rscratch1, Address(r0, oopDesc::mark_offset_in_bytes())); + __ mov(rscratch2, is_value_type_mask); + __ andr(rscratch1, rscratch1, rscratch2); + __ cmp(rscratch1, rscratch2); + __ br(Assembler::NE, has_identity); + __ call_VM(noreg, CAST_FROM_FN_PTR(address, + InterpreterRuntime::throw_illegal_monitor_state_exception)); + __ should_not_reach_here(); + __ bind(has_identity); + } const Address monitor_block_top( rfp, frame::interpreter_frame_monitor_block_top_offset * wordSize); diff --git a/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.cpp b/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.cpp index ca84fa198462..6834cf1a6870 100644 --- a/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.cpp +++ b/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.cpp @@ -1778,14 +1778,15 @@ void LIR_Assembler::unwind_op(LIR_Opr exceptionOop) { void LIR_Assembler::arraycopy_valuetype_check(Register obj, Register tmp, CodeStub* slow_path, bool is_dest, bool null_check) { + assert(Arguments::is_valhalla_enabled(), "Value types must be enabled"); if (null_check) { __ cmpdi(CR0, obj, 0); __ bc_far_optimized(Assembler::bcondCRbiIs1, __ bi0(CR0, Assembler::equal), *slow_path->entry()); } if (is_dest) { __ test_null_free_array_oop(obj, tmp, *slow_path->entry(), true); - __ test_flat_array_oop(obj, tmp, *slow_path->entry(), true); - } else { + } + if (UseArrayFlattening) { __ test_flat_array_oop(obj, tmp, *slow_path->entry(), true); } } @@ -1853,12 +1854,14 @@ void LIR_Assembler::emit_arraycopy(LIR_OpArrayCopy* op) { return; } - // Handle value type arrays - if (flags & LIR_OpArrayCopy::src_valuetype_check) { - arraycopy_valuetype_check(src, tmp, stub, false, (flags & LIR_OpArrayCopy::src_null_check)); - } - if (flags & LIR_OpArrayCopy::dst_valuetype_check) { - arraycopy_valuetype_check(dst, tmp, stub, true, (flags & LIR_OpArrayCopy::dst_null_check)); + if (Arguments::is_valhalla_enabled()) { + // Handle value type arrays + if (flags & LIR_OpArrayCopy::src_valuetype_check) { + arraycopy_valuetype_check(src, tmp, stub, false, (flags & LIR_OpArrayCopy::src_null_check)); + } + if (flags & LIR_OpArrayCopy::dst_valuetype_check) { + arraycopy_valuetype_check(dst, tmp, stub, true, (flags & LIR_OpArrayCopy::dst_null_check)); + } } assert(default_type != nullptr && default_type->is_array_klass() && default_type->is_loaded(), "must be true at this point"); @@ -3075,6 +3078,7 @@ void LIR_Assembler::emit_profile_type(LIR_OpProfileType* op) { } void LIR_Assembler::emit_profile_value_type(LIR_OpProfileValueType* op) { + assert(Arguments::is_valhalla_enabled(), "Should not be called without value types enabled"); Register obj = op->obj()->as_register(); //Register tmp = op->tmp()->as_pointer_register(); not needed! LIR_Address* mdo_addr = op->mdp()->as_address_ptr(); diff --git a/src/hotspot/cpu/ppc/interp_masm_ppc_64.cpp b/src/hotspot/cpu/ppc/interp_masm_ppc_64.cpp index 4226b3dbdbcd..57febf78fed2 100644 --- a/src/hotspot/cpu/ppc/interp_masm_ppc_64.cpp +++ b/src/hotspot/cpu/ppc/interp_masm_ppc_64.cpp @@ -1509,15 +1509,19 @@ template void InterpreterMacroAssembler::profile_array_type(Re profile_obj_type(array, R28_mdx, in_bytes(ArrayData::array_offset()), tmp1, tmp2); - Label not_flat; - test_non_flat_array_oop(array, tmp1, not_flat); - set_mdp_flag_at(ArrayData::flat_array_byte_constant(), tmp1); - bind(not_flat); + if (Arguments::is_valhalla_enabled()) { + if (UseArrayFlattening) { + Label not_flat; + test_non_flat_array_oop(array, tmp1, not_flat); + set_mdp_flag_at(ArrayData::flat_array_byte_constant(), tmp1); + bind(not_flat); + } - Label not_null_free; - test_non_null_free_array_oop(array, tmp1, not_null_free); - set_mdp_flag_at(ArrayData::null_free_array_byte_constant(), tmp1); - bind(not_null_free); + Label not_null_free; + test_non_null_free_array_oop(array, tmp1, not_null_free); + set_mdp_flag_at(ArrayData::null_free_array_byte_constant(), tmp1); + bind(not_null_free); + } bind(profile_continue); } @@ -1579,6 +1583,7 @@ void InterpreterMacroAssembler::profile_acmp(Register left, Register right, Register tmp1, Register tmp2) { + assert(Arguments::is_valhalla_enabled(), "Profiling acmp makes sense only for value types"); if (ProfileInterpreter) { Label profile_continue; assert_different_registers(left, right, tmp1, tmp2); @@ -2431,11 +2436,13 @@ void InterpreterMacroAssembler::notify_method_exit(bool is_native_method, TosSta } void InterpreterMacroAssembler::read_flat_field(Register entry, Register obj) { + assert(UseFieldFlattening, "Should be used only if field flattening is enabled"); call_VM(obj, CAST_FROM_FN_PTR(address, InterpreterRuntime::read_flat_field), obj, entry); } void InterpreterMacroAssembler::write_flat_field(Register entry, Register tmp1, Register tmp2, Register obj, Register value) { + assert(UseFieldFlattening, "Should be used only if field flattening is enabled"); assert_different_registers(entry, tmp1, tmp2, obj, value); Label slow_path, done; diff --git a/src/hotspot/cpu/ppc/macroAssembler_ppc.cpp b/src/hotspot/cpu/ppc/macroAssembler_ppc.cpp index e2151a6f9545..a40c618ee9c3 100644 --- a/src/hotspot/cpu/ppc/macroAssembler_ppc.cpp +++ b/src/hotspot/cpu/ppc/macroAssembler_ppc.cpp @@ -3286,6 +3286,7 @@ void MacroAssembler::load_method_holder(Register holder, Register method) { } void MacroAssembler::test_markword_is_value_type(Register markword, Label& is_value_type) { + assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled"); assert_different_registers(markword, R0); andi(R0, markword, markWord::value_type_pattern_mask); cmpwi(CR0, R0, markWord::value_type_pattern); @@ -3293,6 +3294,7 @@ void MacroAssembler::test_markword_is_value_type(Register markword, Label& is_va } void MacroAssembler::test_oop_is_not_value_type(Register object, Label& not_value_type, bool can_be_null) { + assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled"); if (can_be_null) { cmpdi(CR0, object, 0); beq(CR0, not_value_type); @@ -3304,16 +3306,19 @@ void MacroAssembler::test_oop_is_not_value_type(Register object, Label& not_valu } void MacroAssembler::test_field_is_null_free_value_type(Register flags, Label& is_null_free_value_type) { + assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled"); testbitdi(CR0, R0, flags, ResolvedFieldEntry::is_null_free_value_type_shift); bne(CR0, is_null_free_value_type); } void MacroAssembler::test_field_is_not_null_free_value_type(Register flags, Label& not_null_free_value_type) { + assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled"); testbitdi(CR0, R0, flags, ResolvedFieldEntry::is_null_free_value_type_shift); beq(CR0, not_null_free_value_type); } void MacroAssembler::test_field_is_flat(Register flags, Label& is_flat) { + assert(UseFieldFlattening, "should not be used without field flattening enabled"); testbitdi(CR0, R0, flags, ResolvedFieldEntry::is_flat_shift); bne(CR0, is_flat); } @@ -3336,22 +3341,27 @@ void MacroAssembler::test_oop_prototype_bit(Register oop, Register temp_reg, int } void MacroAssembler::test_flat_array_oop(Register oop, Register temp_reg, Label& is_flat_array, bool maybe_far) { + assert(UseArrayFlattening, "should not be used without array flattening enabled"); test_oop_prototype_bit(oop, temp_reg, markWord::flat_array_bit_in_place, true, is_flat_array, maybe_far); } void MacroAssembler::test_non_flat_array_oop(Register oop, Register temp_reg, Label& is_non_flat_array) { + assert(UseArrayFlattening, "should not be used without array flattening enabled"); test_oop_prototype_bit(oop, temp_reg, markWord::flat_array_bit_in_place, false, is_non_flat_array); } void MacroAssembler::test_null_free_array_oop(Register oop, Register temp_reg, Label& is_null_free_array, bool maybe_far) { + assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled"); test_oop_prototype_bit(oop, temp_reg, markWord::null_free_array_bit_in_place, true, is_null_free_array, maybe_far); } void MacroAssembler::test_non_null_free_array_oop(Register oop, Register temp_reg, Label& is_non_null_free_array) { + assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled"); test_oop_prototype_bit(oop, temp_reg, markWord::null_free_array_bit_in_place, false, is_non_null_free_array); } void MacroAssembler::test_flat_array_layout(Register lh, Label& is_flat_array) { + assert(UseArrayFlattening, "should not be used without array flattening enabled"); testbitdi(CR0, R0, lh, exact_log2(Klass::_lh_array_tag_flat_value_bit_inplace)); bne(CR0, is_flat_array); } @@ -3365,11 +3375,13 @@ void MacroAssembler::load_metadata(Register dst, Register src) { } void MacroAssembler::payload_offset(Register value_klass, Register offset) { + assert(Arguments::is_valhalla_enabled(), "Must be"); ld(offset, in_bytes(ValueKlass::adr_members_offset()), value_klass); lwz(offset, in_bytes(ValueKlass::payload_offset_offset()), offset); } void MacroAssembler::payload_address(Register oop, Register data, Register value_klass, Register t1) { + assert(Arguments::is_valhalla_enabled(), "Must be"); // ((address) (void*) o) + vk->payload_offset(); payload_offset(value_klass, t1); add(data, oop, t1); diff --git a/src/hotspot/cpu/ppc/stubGenerator_ppc.cpp b/src/hotspot/cpu/ppc/stubGenerator_ppc.cpp index cbb3af06c8a5..75c3a69c81f7 100644 --- a/src/hotspot/cpu/ppc/stubGenerator_ppc.cpp +++ b/src/hotspot/cpu/ppc/stubGenerator_ppc.cpp @@ -2594,10 +2594,14 @@ class StubGenerator: public StubCodeGenerator { __ bne(CR5, L_failed); // Check for flat value type array -> return -1 - __ test_flat_array_oop(src, temp, L_failed); + if (UseArrayFlattening) { + __ test_flat_array_oop(src, temp, L_failed); + } // Check for null-free (non-flat) value type array -> handle as object array - __ test_null_free_array_oop(src, temp, L_objArray); + if (Arguments::is_valhalla_enabled()) { + __ test_null_free_array_oop(src, temp, L_objArray); + } __ cmpwi(CR6, lh, Klass::_lh_neutral_value); // if (!src->is_Array()) return -1; __ bge(CR6, L_failed); @@ -2695,6 +2699,17 @@ class StubGenerator: public StubCodeGenerator { __ cmpw(CR0, lh, temp); __ bne(CR0, L_failed); +#ifdef ASSERT + if (Arguments::is_valhalla_enabled()) { + BLOCK_COMMENT("assert not null-free array {"); + Label L; + __ test_non_null_free_array_oop(dst, temp, L); + __ stop("unexpected null-free array"); + __ bind(L); + BLOCK_COMMENT("} assert not null-free array"); + } +#endif + // It is safe to examine both src.length and dst.length. arraycopy_range_checks(src, src_pos, dst, dst_pos, length, temp, lh, L_failed); diff --git a/src/hotspot/cpu/ppc/templateTable_ppc_64.cpp b/src/hotspot/cpu/ppc/templateTable_ppc_64.cpp index 5ee4e94afe99..2040b2f5a4ec 100644 --- a/src/hotspot/cpu/ppc/templateTable_ppc_64.cpp +++ b/src/hotspot/cpu/ppc/templateTable_ppc_64.cpp @@ -1866,10 +1866,10 @@ void TemplateTable::if_acmp(Condition cc) { __ pop_ptr(Rfirst); - __ profile_acmp(Rfirst, Rsecond, R11_scratch1, R12_scratch2); - - const int is_value_type_mask = markWord::value_type_pattern; if (Arguments::is_valhalla_enabled()) { + __ profile_acmp(Rfirst, Rsecond, R11_scratch1, R12_scratch2); + + const int is_value_type_mask = markWord::value_type_pattern; Label taken, not_taken; __ cmpd(CR0, Rfirst, Rsecond); __ beq(CR0, (cc == equal) ? taken : not_taken); @@ -2852,7 +2852,9 @@ void TemplateTable::getfield_or_static(int byte_no, bool is_static, RewriteContr __ push(atos); } else { Label is_flat; - __ test_field_is_flat(Rflags, is_flat); + if (UseFieldFlattening) { + __ test_field_is_flat(Rflags, is_flat); + } do_oop_load(_masm, Rclass_or_obj, Roffset, R17_tos, Rscratch, /* nv temp */ Rflags, IN_HEAP); __ verify_oop(R17_tos); __ push(atos); @@ -2863,13 +2865,15 @@ void TemplateTable::getfield_or_static(int byte_no, bool is_static, RewriteContr __ dispatch_epilog(vtos, Bytecodes::length_for(bytecode())); __ bind(is_flat); - // field is flat (null-free or nullable with a null-marker) - __ mr(R17_tos, Rclass_or_obj); - __ read_flat_field(Rcache, R17_tos); - __ verify_oop(R17_tos); - __ push(atos); - if (rc == may_rewrite) { - patch_bytecode(Bytecodes::_fast_vgetfield, Rbc, Rscratch); + if (UseFieldFlattening) { + // field is flat (null-free or nullable with a null-marker) + __ mr(R17_tos, Rclass_or_obj); + __ read_flat_field(Rcache, R17_tos); + __ verify_oop(R17_tos); + __ push(atos); + if (rc == may_rewrite) { + patch_bytecode(Bytecodes::_fast_vgetfield, Rbc, Rscratch); + } } } } @@ -3215,7 +3219,9 @@ void TemplateTable::putfield_or_static(int byte_no, bool is_static, RewriteContr do_oop_store(_masm, Rclass_or_obj, Roffset, R17_tos, Rscratch, Rscratch2, Rscratch3, IN_HEAP); } else { Label null_free_reference, is_flat, rewrite_value; - __ test_field_is_flat(Rflags, is_flat); + if (UseFieldFlattening) { + __ test_field_is_flat(Rflags, is_flat); + } __ test_field_is_null_free_value_type(Rflags, null_free_reference); pop_and_check_object(Rclass_or_obj); // Store into the field @@ -3237,8 +3243,10 @@ void TemplateTable::putfield_or_static(int byte_no, bool is_static, RewriteContr __ b(rewrite_value); __ bind(is_flat); - pop_and_check_object(Rclass_or_obj); - __ write_flat_field(Rcache, Rscratch, Rscratch2, Rclass_or_obj, R17_tos); + if (UseFieldFlattening) { + pop_and_check_object(Rclass_or_obj); + __ write_flat_field(Rcache, Rscratch, Rscratch2, Rclass_or_obj, R17_tos); + } __ bind(rewrite_value); if (rc == may_rewrite) { patch_bytecode(Bytecodes::_fast_vputfield, Rbc, Rscratch, true, byte_no); @@ -3316,14 +3324,22 @@ void TemplateTable::fast_storefield(TosState state) { switch(bytecode()) { case Bytecodes::_fast_vputfield: { - Label is_flat, done; - __ test_field_is_flat(Rflags, is_flat); - __ null_check_throw(R17_tos, -1, Rscratch); - do_oop_store(_masm, Rclass_or_obj, Roffset, R17_tos, Rscratch, Rscratch2, Rscratch3, IN_HEAP); - __ b(done); - __ bind(is_flat); - __ write_flat_field(Rcache, Rscratch, Rscratch2, Rclass_or_obj, R17_tos); - __ bind(done); + if (Arguments::is_valhalla_enabled()) { + Label is_flat, done; + if (UseFieldFlattening) { + __ test_field_is_flat(Rflags, is_flat); + } + __ null_check_throw(R17_tos, -1, Rscratch); + do_oop_store(_masm, Rclass_or_obj, Roffset, R17_tos, Rscratch, Rscratch2, Rscratch3, IN_HEAP); + __ b(done); + __ bind(is_flat); + if (UseFieldFlattening) { + __ write_flat_field(Rcache, Rscratch, Rscratch2, Rclass_or_obj, R17_tos); + } + __ bind(done); + } else { + __ should_not_reach_here(); + } break; } @@ -3404,17 +3420,22 @@ void TemplateTable::fast_accessfield(TosState state) { switch(bytecode()) { case Bytecodes::_fast_vgetfield: { - // field is flat - __ read_flat_field(Rcache, R17_tos); - __ verify_oop(R17_tos); - __ dispatch_epilog(state, Bytecodes::length_for(bytecode())); - - __ bind(LisVolatile); - if (support_IRIW_for_not_multiple_copy_atomic_cpu) { __ fence(); } - __ read_flat_field(Rcache, R17_tos); - __ verify_oop(R17_tos); - __ twi_0(R17_tos); - __ isync(); + if (UseFieldFlattening) { + // field is flat + __ read_flat_field(Rcache, R17_tos); + __ verify_oop(R17_tos); + __ dispatch_epilog(state, Bytecodes::length_for(bytecode())); + + __ bind(LisVolatile); + if (support_IRIW_for_not_multiple_copy_atomic_cpu) { __ fence(); } + __ read_flat_field(Rcache, R17_tos); + __ verify_oop(R17_tos); + __ twi_0(R17_tos); + __ isync(); + } else { + __ bind(LisVolatile); + __ should_not_reach_here(); + } break; } case Bytecodes::_fast_agetfield: @@ -4317,8 +4338,10 @@ void TemplateTable::monitorenter() { __ null_check_throw(Robj_to_lock, -1, Rscratch1); Label is_value_type; - __ ld(Rscratch1, oopDesc::mark_offset_in_bytes(), Robj_to_lock); - __ test_markword_is_value_type(Rscratch1, is_value_type); + if (Arguments::is_valhalla_enabled()) { + __ ld(Rscratch1, oopDesc::mark_offset_in_bytes(), Robj_to_lock); + __ test_markword_is_value_type(Rscratch1, is_value_type); + } // Check if any slot is present => short cut to allocation if not. __ cmpld(CR0, Rcurrent_monitor, Rbot); @@ -4378,9 +4401,11 @@ void TemplateTable::monitorenter() { __ dispatch_next(vtos); __ bind(is_value_type); - __ call_VM(noreg, CAST_FROM_FN_PTR(address, - InterpreterRuntime::throw_identity_exception), Robj_to_lock); - __ should_not_reach_here(); + if (Arguments::is_valhalla_enabled()) { + __ call_VM(noreg, CAST_FROM_FN_PTR(address, + InterpreterRuntime::throw_identity_exception), Robj_to_lock); + __ should_not_reach_here(); + } } void TemplateTable::monitorexit() { @@ -4404,11 +4429,13 @@ void TemplateTable::monitorexit() { // Null pointer check. __ null_check_throw(Robj_to_lock, -1, Rscratch); - const int is_value_type_mask = markWord::value_type_pattern; - __ ld(Rscratch, oopDesc::mark_offset_in_bytes(), Robj_to_lock); - __ andi(Rscratch, Rscratch, is_value_type_mask); - __ cmpwi(CR0, Rscratch, is_value_type_mask); - __ beq(CR0, Lillegal_monitor_state); + if (Arguments::is_valhalla_enabled()) { + const int is_value_type_mask = markWord::value_type_pattern; + __ ld(Rscratch, oopDesc::mark_offset_in_bytes(), Robj_to_lock); + __ andi(Rscratch, Rscratch, is_value_type_mask); + __ cmpwi(CR0, Rscratch, is_value_type_mask); + __ beq(CR0, Lillegal_monitor_state); + } // Check corner case: unbalanced monitorEnter / Exit. __ cmpld(CR0, Rcurrent_monitor, Rbot); diff --git a/src/hotspot/cpu/riscv/c1_LIRAssembler_arraycopy_riscv.cpp b/src/hotspot/cpu/riscv/c1_LIRAssembler_arraycopy_riscv.cpp index 97d049c428b1..70cccefb2ea5 100644 --- a/src/hotspot/cpu/riscv/c1_LIRAssembler_arraycopy_riscv.cpp +++ b/src/hotspot/cpu/riscv/c1_LIRAssembler_arraycopy_riscv.cpp @@ -241,13 +241,14 @@ void LIR_Assembler::arraycopy_type_check(Register src, Register src_pos, Registe } void LIR_Assembler::arraycopy_valuetype_check(Register obj, Register tmp, CodeStub* slow_path, bool is_dest, bool null_check) { + assert(Arguments::is_valhalla_enabled(), "Value types must be enabled"); if (null_check) { __ beqz(obj, *slow_path->entry(), /* is_far */ true); } if (is_dest) { __ test_null_free_array_oop(obj, tmp, *slow_path->entry()); - __ test_flat_array_oop(obj, tmp, *slow_path->entry()); - } else { + } + if (UseArrayFlattening) { __ test_flat_array_oop(obj, tmp, *slow_path->entry()); } } @@ -278,11 +279,13 @@ void LIR_Assembler::emit_arraycopy(LIR_OpArrayCopy* op) { return; } - if (flags & LIR_OpArrayCopy::src_valuetype_check) { - arraycopy_valuetype_check(src, tmp, stub, false, (flags & LIR_OpArrayCopy::src_null_check)); - } - if (flags & LIR_OpArrayCopy::dst_valuetype_check) { - arraycopy_valuetype_check(dst, tmp, stub, true, (flags & LIR_OpArrayCopy::dst_null_check)); + if (Arguments::is_valhalla_enabled()) { + if (flags & LIR_OpArrayCopy::src_valuetype_check) { + arraycopy_valuetype_check(src, tmp, stub, false, (flags & LIR_OpArrayCopy::src_null_check)); + } + if (flags & LIR_OpArrayCopy::dst_valuetype_check) { + arraycopy_valuetype_check(dst, tmp, stub, true, (flags & LIR_OpArrayCopy::dst_null_check)); + } } assert(default_type != nullptr && default_type->is_array_klass() && default_type->is_loaded(), diff --git a/src/hotspot/cpu/riscv/c1_LIRAssembler_riscv.cpp b/src/hotspot/cpu/riscv/c1_LIRAssembler_riscv.cpp index a105faf9137a..bba73d973192 100644 --- a/src/hotspot/cpu/riscv/c1_LIRAssembler_riscv.cpp +++ b/src/hotspot/cpu/riscv/c1_LIRAssembler_riscv.cpp @@ -1322,6 +1322,7 @@ void LIR_Assembler::emit_opSubstitutabilityCheck(LIR_OpSubstitutabilityCheck* op } void LIR_Assembler::emit_profile_value_type(LIR_OpProfileValueType* op) { + assert(Arguments::is_valhalla_enabled(), "Should not be called without value types enabled"); Register obj = op->obj()->as_register(); Register tmp = op->tmp()->as_pointer_register(); bool not_null = op->not_null(); diff --git a/src/hotspot/cpu/riscv/interp_masm_riscv.cpp b/src/hotspot/cpu/riscv/interp_masm_riscv.cpp index 3204f081db6f..9c9e636b5b23 100644 --- a/src/hotspot/cpu/riscv/interp_masm_riscv.cpp +++ b/src/hotspot/cpu/riscv/interp_masm_riscv.cpp @@ -44,6 +44,7 @@ #include "prims/jvmtiThreadState.hpp" #include "runtime/basicLock.hpp" #include "runtime/frame.inline.hpp" +#include "runtime/globals.hpp" #include "runtime/javaThread.hpp" #include "runtime/safepointMechanism.hpp" #include "runtime/sharedRuntime.hpp" @@ -1203,19 +1204,23 @@ template void InterpreterMacroAssembler::profile_array_type(Re mv(tmp, array); profile_obj_type(tmp, Address(mdp, in_bytes(ArrayData::array_offset())), t1); - Label not_flat; - test_non_flat_array_oop(array, tmp, not_flat); + if (Arguments::is_valhalla_enabled()) { + if (UseArrayFlattening) { + Label not_flat; + test_non_flat_array_oop(array, tmp, not_flat); - set_mdp_flag_at(mdp, ArrayData::flat_array_byte_constant()); + set_mdp_flag_at(mdp, ArrayData::flat_array_byte_constant()); - bind(not_flat); + bind(not_flat); + } - Label not_null_free; - test_non_null_free_array_oop(array, tmp, not_null_free); + Label not_null_free; + test_non_null_free_array_oop(array, tmp, not_null_free); - set_mdp_flag_at(mdp, ArrayData::null_free_array_byte_constant()); + set_mdp_flag_at(mdp, ArrayData::null_free_array_byte_constant()); - bind(not_null_free); + bind(not_null_free); + } bind(profile_continue); } @@ -1278,6 +1283,7 @@ void InterpreterMacroAssembler::profile_acmp(Register mdp, Register left, Register right, Register tmp) { + assert(Arguments::is_valhalla_enabled(), "Profiling acmp makes sense only for value types"); if (ProfileInterpreter) { Label profile_continue; @@ -1867,11 +1873,13 @@ void InterpreterMacroAssembler::get_method_counters(Register method, } void InterpreterMacroAssembler::read_flat_field(Register entry, Register obj) { + assert(UseFieldFlattening, "Should be used only if field flattening is enabled"); call_VM(obj, CAST_FROM_FN_PTR(address, InterpreterRuntime::read_flat_field), obj, entry); } void InterpreterMacroAssembler::write_flat_field(Register entry, Register tmp1, Register tmp2, Register obj) { + assert(UseFieldFlattening, "Should be used only if field flattening is enabled"); assert_different_registers(entry, tmp1, tmp2, obj); Label slow_path, done; diff --git a/src/hotspot/cpu/riscv/macroAssembler_riscv.cpp b/src/hotspot/cpu/riscv/macroAssembler_riscv.cpp index fbbfc87ae861..9f1261ce62a4 100644 --- a/src/hotspot/cpu/riscv/macroAssembler_riscv.cpp +++ b/src/hotspot/cpu/riscv/macroAssembler_riscv.cpp @@ -3829,11 +3829,13 @@ void MacroAssembler::mov_metadata(Register dst, Metadata* obj) { } void MacroAssembler::payload_offset(Register value_klass, Register offset) { + assert(Arguments::is_valhalla_enabled(), "Must be"); ld(offset, Address(value_klass, ValueKlass::adr_members_offset())); lwu(offset, Address(offset, ValueKlass::payload_offset_offset())); } void MacroAssembler::payload_address(Register oop, Register data, Register value_klass) { + assert(Arguments::is_valhalla_enabled(), "Must be"); assert_different_registers(data, t0); // ((address) (void*) o) + vk->payload_offset(); Register offset = (data == oop) ? t0 : data; @@ -3932,21 +3934,25 @@ void MacroAssembler::null_check(Register reg, int offset) { } void MacroAssembler::test_field_is_null_free_value_type(Register flags, Register temp_reg, Label& is_null_free_value_type) { + assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled"); test_bit(temp_reg, flags, ResolvedFieldEntry::is_null_free_value_type_shift); bnez(temp_reg, is_null_free_value_type); } void MacroAssembler::test_field_is_not_null_free_value_type(Register flags, Register temp_reg, Label& not_null_free_value_type) { + assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled"); test_bit(temp_reg, flags, ResolvedFieldEntry::is_null_free_value_type_shift); beqz(temp_reg, not_null_free_value_type); } void MacroAssembler::test_field_is_flat(Register flags, Register temp_reg, Label& is_flat) { + assert(UseFieldFlattening, "should not be used without field flattening enabled"); test_bit(temp_reg, flags, ResolvedFieldEntry::is_flat_shift); bnez(temp_reg, is_flat); } void MacroAssembler::test_markword_is_value_type(Register markword, Label& is_value_type) { + assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled"); assert_different_registers(markword, t1); mv(t1, markWord::value_type_pattern_mask); andr(markword, markword, t1); @@ -3955,6 +3961,7 @@ void MacroAssembler::test_markword_is_value_type(Register markword, Label& is_va } void MacroAssembler::test_oop_is_not_value_type(Register object, Register tmp, Label& not_value_type, bool can_be_null) { + assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled"); assert_different_registers(tmp, t0); if (can_be_null) { beqz(object, not_value_type); @@ -3979,22 +3986,27 @@ void MacroAssembler::test_oop_prototype_bit(Register oop, Register temp_reg, int } void MacroAssembler::test_flat_array_oop(Register oop, Register temp_reg, Label& is_flat_array) { + assert(UseArrayFlattening, "should not be used without array flattening enabled"); test_oop_prototype_bit(oop, temp_reg, markWord::flat_array_bit_in_place, true, is_flat_array); } void MacroAssembler::test_null_free_array_oop(Register oop, Register temp_reg, Label& is_null_free_array) { + assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled"); test_oop_prototype_bit(oop, temp_reg, markWord::null_free_array_bit_in_place, true, is_null_free_array); } void MacroAssembler::test_non_flat_array_oop(Register oop, Register temp_reg, Label&is_non_flat_array) { + assert(UseArrayFlattening, "should not be used without array flattening enabled"); test_oop_prototype_bit(oop, temp_reg, markWord::flat_array_bit_in_place, false, is_non_flat_array); } void MacroAssembler::test_non_null_free_array_oop(Register oop, Register temp_reg, Label&is_non_null_free_array) { + assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled"); test_oop_prototype_bit(oop, temp_reg, markWord::null_free_array_bit_in_place, false, is_non_null_free_array); } void MacroAssembler::test_flat_array_layout(Register lh, Label& is_flat_array) { + assert(UseArrayFlattening, "should not be used without array flattening enabled"); test_bit(t0, lh, exact_log2(Klass::_lh_array_tag_flat_value_bit_inplace)); bnez(t0, is_flat_array); } diff --git a/src/hotspot/cpu/riscv/stubGenerator_riscv.cpp b/src/hotspot/cpu/riscv/stubGenerator_riscv.cpp index 5fea2288b3c6..d2a5b98b79c9 100644 --- a/src/hotspot/cpu/riscv/stubGenerator_riscv.cpp +++ b/src/hotspot/cpu/riscv/stubGenerator_riscv.cpp @@ -2247,10 +2247,12 @@ class StubGenerator: public StubCodeGenerator { __ load_klass(t1, dst); __ bne(t1, scratch_src_klass, L_failed); - if (Arguments::is_valhalla_enabled()) { + if (UseArrayFlattening) { // Check for flat value type array -> return -1 __ test_flat_array_oop(src, t1, L_failed); + } + if (Arguments::is_valhalla_enabled()) { // Check for null-free (non-flat) value type array -> handle as object array __ test_null_free_array_oop(src, t1, L_objArray); } diff --git a/src/hotspot/cpu/riscv/templateTable_riscv.cpp b/src/hotspot/cpu/riscv/templateTable_riscv.cpp index 82b2f0eeb3d9..beb85c02e9fb 100644 --- a/src/hotspot/cpu/riscv/templateTable_riscv.cpp +++ b/src/hotspot/cpu/riscv/templateTable_riscv.cpp @@ -46,6 +46,7 @@ #include "prims/methodHandles.hpp" #include "runtime/arguments.hpp" #include "runtime/frame.inline.hpp" +#include "runtime/globals.hpp" #include "runtime/sharedRuntime.hpp" #include "runtime/stubRoutines.hpp" #include "runtime/synchronizer.hpp" @@ -1906,12 +1907,12 @@ void TemplateTable::if_acmp(Condition cc) { Label taken, not_taken; __ pop_ptr(x11); - __ profile_acmp(x12, x11, x10, x14); + if (Arguments::is_valhalla_enabled()) { + __ profile_acmp(x12, x11, x10, x14); - Register is_value_type_mask = t1; - __ mv(is_value_type_mask, markWord::value_type_pattern); + Register is_value_type_mask = t1; + __ mv(is_value_type_mask, markWord::value_type_pattern); - if (Arguments::is_valhalla_enabled()) { // The substitutability test is only necessary if x11 and x10 are not the same... if (cc == equal) { __ beq(x11, x10, taken); @@ -2721,7 +2722,9 @@ void TemplateTable::getfield_or_static(int byte_no, bool is_static, RewriteContr __ j(Done); } else { Label is_flat; - __ test_field_is_flat(flags, x28, is_flat); + if (UseFieldFlattening) { + __ test_field_is_flat(flags, x28, is_flat); + } __ load_heap_oop(x10, field, x28, x29); __ push(atos); if (rc == may_rewrite) { @@ -2729,15 +2732,17 @@ void TemplateTable::getfield_or_static(int byte_no, bool is_static, RewriteContr } __ j(Done); __ bind(is_flat); - // field is flat (null-free or nullable with a null-marker) - __ mv(x10, obj); - __ read_flat_field(cache, x10); - __ verify_oop(x10); - __ push(atos); - if (rc == may_rewrite) { - patch_bytecode(Bytecodes::_fast_vgetfield, bc, x11); + if (UseFieldFlattening) { + // field is flat (null-free or nullable with a null-marker) + __ mv(x10, obj); + __ read_flat_field(cache, x10); + __ verify_oop(x10); + __ push(atos); + if (rc == may_rewrite) { + patch_bytecode(Bytecodes::_fast_vgetfield, bc, x11); + } + __ j(Done); } - __ j(Done); } } @@ -2996,7 +3001,9 @@ void TemplateTable::putfield_or_static(int byte_no, bool is_static, RewriteContr __ j(Done); } else { Label null_free_reference, is_flat, rewrite_value; - __ test_field_is_flat(flags, x28, is_flat); + if (UseFieldFlattening) { + __ test_field_is_flat(flags, x28, is_flat); + } __ test_field_is_null_free_value_type(flags, x28, null_free_reference); pop_and_check_object(obj); { @@ -3022,8 +3029,10 @@ void TemplateTable::putfield_or_static(int byte_no, bool is_static, RewriteContr } __ j(rewrite_value); __ bind(is_flat); - pop_and_check_object(x17); - __ write_flat_field(cache, index, flags, x17); + if (UseFieldFlattening) { + pop_and_check_object(x17); + __ write_flat_field(cache, index, flags, x17); + } __ bind(rewrite_value); if (rc == may_rewrite) { patch_bytecode(Bytecodes::_fast_vputfield, bc, x9, true, byte_no); @@ -3275,17 +3284,25 @@ void TemplateTable::fast_storefield(TosState state) { switch (bytecode()) { case Bytecodes::_fast_vputfield: { - Label is_flat, done; - __ test_field_is_flat(x13, x28, is_flat); - __ null_check(x10); - __ store_heap_oop(field, x10, x28, x29, x15, IN_HEAP); - __ j(done); - __ bind(is_flat); - __ load_field_entry(x14, x13); - // Re-shuffle registers because of VM calls calling convention - __ mv(x17, x12); - __ write_flat_field(x14, x16, x18, x17); - __ bind(done); + if (Arguments::is_valhalla_enabled()) { + Label is_flat, done; + if (UseFieldFlattening) { + __ test_field_is_flat(x13, x28, is_flat); + } + __ null_check(x10); + __ store_heap_oop(field, x10, x28, x29, x15, IN_HEAP); + __ j(done); + __ bind(is_flat); + if (UseFieldFlattening) { + __ load_field_entry(x14, x13); + // Re-shuffle registers because of VM calls calling convention + __ mv(x17, x12); + __ write_flat_field(x14, x16, x18, x17); + } + __ bind(done); + } else { + __ should_not_reach_here(); + } } break; case Bytecodes::_fast_aputfield: @@ -3373,9 +3390,13 @@ void TemplateTable::fast_accessfield(TosState state) { switch (bytecode()) { case Bytecodes::_fast_vgetfield: { - // field is flat - __ read_flat_field(x12, x10); - __ verify_oop(x10); + if (UseFieldFlattening) { + // field is flat + __ read_flat_field(x12, x10); + __ verify_oop(x10); + } else { + __ should_not_reach_here(); + } } break; case Bytecodes::_fast_agetfield: @@ -4070,8 +4091,10 @@ void TemplateTable::monitorenter() { __ null_check(x10); Label is_value_type; - __ ld(t0, Address(x10, oopDesc::mark_offset_in_bytes())); - __ test_markword_is_value_type(t0, is_value_type); + if (Arguments::is_valhalla_enabled()) { + __ ld(t0, Address(x10, oopDesc::mark_offset_in_bytes())); + __ test_markword_is_value_type(t0, is_value_type); + } const Address monitor_block_top( fp, frame::interpreter_frame_monitor_block_top_offset * wordSize); @@ -4174,9 +4197,11 @@ void TemplateTable::monitorenter() { __ dispatch_next(vtos); __ bind(is_value_type); - __ call_VM(noreg, CAST_FROM_FN_PTR(address, - InterpreterRuntime::throw_identity_exception), x10); - __ should_not_reach_here(); + if (Arguments::is_valhalla_enabled()) { + __ call_VM(noreg, CAST_FROM_FN_PTR(address, + InterpreterRuntime::throw_identity_exception), x10); + __ should_not_reach_here(); + } } void TemplateTable::monitorexit() { @@ -4185,16 +4210,18 @@ void TemplateTable::monitorexit() { // check for null object __ null_check(x10); - const int is_value_type_mask = markWord::value_type_pattern; - Label has_identity; - __ ld(t0, Address(x10, oopDesc::mark_offset_in_bytes())); - __ mv(t1, is_value_type_mask); - __ andr(t0, t0, t1); - __ bne(t0, t1, has_identity); - __ call_VM(noreg, CAST_FROM_FN_PTR(address, - InterpreterRuntime::throw_illegal_monitor_state_exception)); - __ should_not_reach_here(); - __ bind(has_identity); + if (Arguments::is_valhalla_enabled()) { + const int is_value_type_mask = markWord::value_type_pattern; + Label has_identity; + __ ld(t0, Address(x10, oopDesc::mark_offset_in_bytes())); + __ mv(t1, is_value_type_mask); + __ andr(t0, t0, t1); + __ bne(t0, t1, has_identity); + __ call_VM(noreg, CAST_FROM_FN_PTR(address, + InterpreterRuntime::throw_illegal_monitor_state_exception)); + __ should_not_reach_here(); + __ bind(has_identity); + } const Address monitor_block_top( fp, frame::interpreter_frame_monitor_block_top_offset * wordSize); diff --git a/src/hotspot/cpu/s390/c1_LIRAssembler_s390.cpp b/src/hotspot/cpu/s390/c1_LIRAssembler_s390.cpp index d7799769ff83..d410668306a0 100644 --- a/src/hotspot/cpu/s390/c1_LIRAssembler_s390.cpp +++ b/src/hotspot/cpu/s390/c1_LIRAssembler_s390.cpp @@ -2063,11 +2063,13 @@ void LIR_Assembler::emit_arraycopy(LIR_OpArrayCopy* op) { } // Handle value type arrays - if (flags & LIR_OpArrayCopy::src_valuetype_check) { - arraycopy_valuetype_check(src, tmp, stub, false, (flags & LIR_OpArrayCopy::src_null_check)); - } - if (flags & LIR_OpArrayCopy::dst_valuetype_check) { - arraycopy_valuetype_check(dst, tmp, stub, true, (flags & LIR_OpArrayCopy::dst_null_check)); + if (Arguments::is_valhalla_enabled()) { + if (flags & LIR_OpArrayCopy::src_valuetype_check) { + arraycopy_valuetype_check(src, tmp, stub, false, (flags & LIR_OpArrayCopy::src_null_check)); + } + if (flags & LIR_OpArrayCopy::dst_valuetype_check) { + arraycopy_valuetype_check(dst, tmp, stub, true, (flags & LIR_OpArrayCopy::dst_null_check)); + } } assert(default_type != nullptr && default_type->is_array_klass() && default_type->is_loaded(), "must be true at this point"); @@ -3065,6 +3067,7 @@ void LIR_Assembler::emit_profile_type(LIR_OpProfileType* op) { } void LIR_Assembler::emit_profile_value_type(LIR_OpProfileValueType* op) { + assert(Arguments::is_valhalla_enabled(), "Should not be called without value types enabled"); Register obj = op->obj()->as_register(); Register tmp = op->tmp()->as_pointer_register(); LIR_Address* mdo_addr = op->mdp()->as_address_ptr(); @@ -3185,13 +3188,14 @@ void LIR_Assembler::emit_opSubstitutabilityCheck(LIR_OpSubstitutabilityCheck* op } void LIR_Assembler::arraycopy_valuetype_check(Register obj, Register tmp, CodeStub* slow_path, bool is_dest, bool null_check) { + assert(Arguments::is_valhalla_enabled(), "Value types must be enabled"); if (null_check) { __ compare64_and_branch(obj, (intptr_t)0, Assembler::bcondEqual, *slow_path->entry()); } if (is_dest) { __ test_null_free_array_oop(obj, tmp, *slow_path->entry()); - __ test_flat_array_oop(obj, tmp, *slow_path->entry()); - } else { + } + if (UseArrayFlattening) { __ test_flat_array_oop(obj, tmp, *slow_path->entry()); } } diff --git a/src/hotspot/cpu/s390/interp_masm_s390.cpp b/src/hotspot/cpu/s390/interp_masm_s390.cpp index c3775f8038b9..5f0db9eabaa1 100644 --- a/src/hotspot/cpu/s390/interp_masm_s390.cpp +++ b/src/hotspot/cpu/s390/interp_masm_s390.cpp @@ -1367,6 +1367,7 @@ void InterpreterMacroAssembler::profile_acmp(Register mdp, Register left, Register right, Register tmp) { + assert(Arguments::is_valhalla_enabled(), "Profiling acmp makes sense only for value types"); if (ProfileInterpreter) { assert_different_registers(mdp, left, right, tmp); Label profile_continue; @@ -1638,15 +1639,19 @@ template void InterpreterMacroAssembler::profile_array_type(Re profile_obj_type(array, Address(tmp1, in_bytes(ArrayData::array_offset())), tmp2); - Label not_flat; - test_non_flat_array_oop(array, tmp2, not_flat); - set_mdp_flag_at(tmp1, ArrayData::flat_array_byte_constant()); - bind(not_flat); + if (Arguments::is_valhalla_enabled()) { + if (UseArrayFlattening) { + Label not_flat; + test_non_flat_array_oop(array, tmp2, not_flat); + set_mdp_flag_at(tmp1, ArrayData::flat_array_byte_constant()); + bind(not_flat); + } - Label not_null_free; - test_non_null_free_array_oop(array, tmp2, not_null_free); - set_mdp_flag_at(tmp1, ArrayData::null_free_array_byte_constant()); - bind(not_null_free); + Label not_null_free; + test_non_null_free_array_oop(array, tmp2, not_null_free); + set_mdp_flag_at(tmp1, ArrayData::null_free_array_byte_constant()); + bind(not_null_free); + } bind(profile_continue); } @@ -2216,6 +2221,7 @@ void InterpreterMacroAssembler::pop_interpreter_frame(Register return_pc, Regist //------------------------------------- void InterpreterMacroAssembler::read_flat_field(Register entry, Register obj) { + assert(UseFieldFlattening, "Should be used only if field flattening is enabled"); assert_different_registers(entry, obj); assert_different_registers(entry, Z_ARG2); call_VM(obj, CAST_FROM_FN_PTR(address, InterpreterRuntime::read_flat_field), obj, entry); @@ -2223,6 +2229,7 @@ void InterpreterMacroAssembler::read_flat_field(Register entry, Register obj) { void InterpreterMacroAssembler::write_flat_field(Register entry, Register tmp1, Register tmp2, Register obj) { + assert(UseFieldFlattening, "Should be used only if field flattening is enabled"); assert_different_registers(entry, tmp1, tmp2, obj); Label slow_path, done; diff --git a/src/hotspot/cpu/s390/macroAssembler_s390.cpp b/src/hotspot/cpu/s390/macroAssembler_s390.cpp index c4688c0546b1..dd65e52b70a5 100644 --- a/src/hotspot/cpu/s390/macroAssembler_s390.cpp +++ b/src/hotspot/cpu/s390/macroAssembler_s390.cpp @@ -3939,6 +3939,7 @@ void MacroAssembler::null_check(Register reg, Register tmp, int64_t offset) { //------------------------------------- void MacroAssembler::test_markword_is_value_type(Register markword, Label& is_value_type) { + assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled"); static_assert(markWord::value_type_pattern <= 0x7FFF, "must fit in simm16 for z_chi"); z_nilf(markword, markWord::value_type_pattern_mask); z_chi(markword, markWord::value_type_pattern); @@ -3946,6 +3947,7 @@ void MacroAssembler::test_markword_is_value_type(Register markword, Label& is_va } void MacroAssembler::test_oop_is_not_value_type(Register object, Register tmp, Label& not_value_type, bool can_be_null) { + assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled"); if (can_be_null) { z_ltgr(object, object); branch_optimized(bcondEqual, not_value_type); @@ -3957,16 +3959,19 @@ void MacroAssembler::test_oop_is_not_value_type(Register object, Register tmp, L } void MacroAssembler::test_field_is_null_free_value_type(Register flags, Label& is_null_free) { + assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled"); testbit(flags, ResolvedFieldEntry::is_null_free_value_type_shift); z_brc(Assembler::bcondAllOne, is_null_free); } void MacroAssembler::test_field_is_not_null_free_value_type(Register flags, Label& not_null_free_value_type) { + assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled"); testbit(flags, ResolvedFieldEntry::is_null_free_value_type_shift); z_brc(Assembler::bcondAllZero, not_null_free_value_type); } void MacroAssembler::test_field_is_flat(Register flags, Label& is_flat) { + assert(UseFieldFlattening, "should not be used without field flattening enabled"); testbit(flags, ResolvedFieldEntry::is_flat_shift); z_brc(Assembler::bcondAllOne, is_flat); } @@ -4280,22 +4285,27 @@ void MacroAssembler::test_oop_prototype_bit(Register oop, Register temp_reg, int } void MacroAssembler::test_flat_array_oop(Register oop, Register temp_reg, Label& is_flat_array) { + assert(UseArrayFlattening, "should not be used without array flattening enabled"); test_oop_prototype_bit(oop, temp_reg, markWord::flat_array_bit_in_place, true, is_flat_array); } void MacroAssembler::test_non_flat_array_oop(Register oop, Register temp_reg, Label& is_non_flat_array) { + assert(UseArrayFlattening, "should not be used without array flattening enabled"); test_oop_prototype_bit(oop, temp_reg, markWord::flat_array_bit_in_place, false, is_non_flat_array); } void MacroAssembler::test_null_free_array_oop(Register oop, Register temp_reg, Label& is_null_free_array) { + assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled"); test_oop_prototype_bit(oop, temp_reg, markWord::null_free_array_bit_in_place, true, is_null_free_array); } void MacroAssembler::test_non_null_free_array_oop(Register oop, Register temp_reg, Label& is_non_null_free_array) { + assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled"); test_oop_prototype_bit(oop, temp_reg, markWord::null_free_array_bit_in_place, false, is_non_null_free_array); } void MacroAssembler::test_flat_array_layout(Register lh, Label& is_flat_array) { + assert(UseArrayFlattening, "should not be used without array flattening enabled"); testbit(lh, exact_log2(Klass::_lh_array_tag_flat_value_bit_inplace)); z_brnaz(is_flat_array); } @@ -4544,11 +4554,13 @@ void MacroAssembler::store_heap_oop(Register Roop, const Address &a, } void MacroAssembler::payload_offset(Register value_klass, Register offset) { + assert(Arguments::is_valhalla_enabled(), "Must be"); z_lg(offset, Address(value_klass, ValueKlass::adr_members_offset())); z_llgf(offset, Address(offset, ValueKlass::payload_offset_offset())); } void MacroAssembler::payload_addr(Register oop, Register data, Register value_klass) { + assert(Arguments::is_valhalla_enabled(), "Must be"); // ((address) (void*) o) + vk->payload_offset(); // // oop must differ from value_klass: payload_offset() overwrites value_klass @@ -7144,22 +7156,26 @@ int MacroAssembler::store_value_type_fields_to_buf(ciValueKlass* vk, bool from_i } bool MacroAssembler::move_helper(VMReg from, VMReg to, BasicType bt, RegState reg_state[]) { + assert(ValueTypePassFieldsAsArgs, "Must be"); Unimplemented(); } bool MacroAssembler::unpack_value_helper(const GrowableArray* sig, int& sig_index, VMReg from, int& from_index, VMRegPair* to, int to_count, int& to_index, RegState reg_state[]) { + assert(ValueTypePassFieldsAsArgs, "Must be"); Unimplemented(); } bool MacroAssembler::pack_value_helper(const GrowableArray* sig, int& sig_index, int vtarg_index, VMRegPair* from, int from_count, int& from_index, VMReg to, RegState reg_state[], Register val_array) { + assert(ValueTypePassFieldsAsArgs, "Must be"); Unimplemented(); } int MacroAssembler::extend_stack_for_value_args(int args_on_stack) { + assert(ValueTypePassFieldsAsArgs, "Must be"); Unimplemented(); } diff --git a/src/hotspot/cpu/s390/templateTable_s390.cpp b/src/hotspot/cpu/s390/templateTable_s390.cpp index dcb3fa4fc3f1..7234db81cd19 100644 --- a/src/hotspot/cpu/s390/templateTable_s390.cpp +++ b/src/hotspot/cpu/s390/templateTable_s390.cpp @@ -2116,9 +2116,9 @@ void TemplateTable::if_acmp(Condition cc) { Label taken, not_taken; __ pop_ptr(Z_ARG5); - __ profile_acmp(Z_tmp_1, Z_ARG5, Z_tos, Z_tmp_2); - if (Arguments::is_valhalla_enabled()) { + __ profile_acmp(Z_tmp_1, Z_ARG5, Z_tos, Z_tmp_2); + __ compareU64_and_branch(Z_tos, Z_ARG5, Assembler::bcondEqual, (cc == equal) ? taken : not_taken); // Use z_ltgr for efficient null checks @@ -2961,7 +2961,9 @@ void TemplateTable::getfield_or_static(int byte_no, bool is_static, RewriteContr // still holds the ResolvedFieldEntry* for read_flat_field below. __ load_sized_value(atos_flags, Address(atos_entry, in_bytes(ResolvedFieldEntry::flags_offset())), sizeof(u1), false); Label is_flat; - __ test_field_is_flat(atos_flags, is_flat); + if (UseFieldFlattening) { + __ test_field_is_flat(atos_flags, is_flat); + } __ load_heap_oop(Z_tos, field, oopLoad_tmp1, oopLoad_tmp2); __ push(atos); if (do_rewrite) { @@ -2969,14 +2971,17 @@ void TemplateTable::getfield_or_static(int byte_no, bool is_static, RewriteContr } __ z_bru(Done); __ bind(is_flat); - // field is flat (null-free or nullable with a null-marker). - // atos_entry (Z_ARG5) still holds the ResolvedFieldEntry* here. - __ z_lgr(Z_tos, obj); - __ read_flat_field(atos_entry, Z_tos); - __ verify_oop(Z_tos); - __ push(atos); - if (do_rewrite) { - patch_bytecode(Bytecodes::_fast_vgetfield, bc_reg, patch_tmp); + if (UseFieldFlattening) { + // field is flat (null-free or nullable with a null-marker). + // atos_entry (Z_ARG5) still holds the ResolvedFieldEntry* here. + __ z_lgr(Z_tos, obj); + __ read_flat_field(atos_entry, Z_tos); + __ verify_oop(Z_tos); + __ push(atos); + if (do_rewrite) { + patch_bytecode(Bytecodes::_fast_vgetfield, bc_reg, patch_tmp); + } + __ z_bru(Done); } } } @@ -3351,8 +3356,10 @@ void TemplateTable::putfield_or_static(int byte_no, bool is_static, RewriteContr oopStore_tmp1, oopStore_tmp2, oopStore_tmp3, IN_HEAP); } else { Label null_free_reference, is_flat, rewrite_value, done_valhalla; - __ z_tmll(flags, 1 << ResolvedFieldEntry::is_flat_shift); - __ branch_optimized(Assembler::bcondAllOne, is_flat); + if (UseFieldFlattening) { + __ z_tmll(flags, 1 << ResolvedFieldEntry::is_flat_shift); + __ branch_optimized(Assembler::bcondAllOne, is_flat); + } __ z_tmll(flags, 1 << ResolvedFieldEntry::is_null_free_value_type_shift); __ branch_optimized(Assembler::bcondAllOne, null_free_reference); pop_and_check_object(obj); @@ -3374,19 +3381,22 @@ void TemplateTable::putfield_or_static(int byte_no, bool is_static, RewriteContr oopStore_tmp1, oopStore_tmp2, oopStore_tmp3, IN_HEAP); __ z_bru(rewrite_value); __ bind(is_flat); - pop_and_check_object(oopStore_tmp1); - { - // cache (Z_ARG5) is dead here — overwritten by load_resolved_field_entry. - // tos_state (Z_ARG4) is dead here — its life ended after branch-table dispatch. - // Reload the ResolvedFieldEntry pointer into tos_state (Z_ARG4); use - // oopStore_tmp2 (Z_R1_scratch) as the index scratch. - // Pass oopStore_tmp3 (Z_ARG2) as tmp2 so that flags (Z_tmp_1) is not - // clobbered; flags must survive to the volatility check after Done. - Register flat_entry = tos_state; - Register flat_index = oopStore_tmp2; // Z_R1_scratch (index scratch, discarded after load) - __ load_field_entry(flat_entry, flat_index); - __ write_flat_field(flat_entry, flat_index, oopStore_tmp3, oopStore_tmp1); - }__ bind(rewrite_value); + if (UseFieldFlattening) { + pop_and_check_object(oopStore_tmp1); + { + // cache (Z_ARG5) is dead here — overwritten by load_resolved_field_entry. + // tos_state (Z_ARG4) is dead here — its life ended after branch-table dispatch. + // Reload the ResolvedFieldEntry pointer into tos_state (Z_ARG4); use + // oopStore_tmp2 (Z_R1_scratch) as the index scratch. + // Pass oopStore_tmp3 (Z_ARG2) as tmp2 so that flags (Z_tmp_1) is not + // clobbered; flags must survive to the volatility check after Done. + Register flat_entry = tos_state; + Register flat_index = oopStore_tmp2; // Z_R1_scratch (index scratch, discarded after load) + __ load_field_entry(flat_entry, flat_index); + __ write_flat_field(flat_entry, flat_index, oopStore_tmp3, oopStore_tmp1); + } + } + __ bind(rewrite_value); if (do_rewrite) { patch_bytecode(Bytecodes::_fast_vputfield, bc_reg, patch_tmp, true, byte_no); } @@ -3550,34 +3560,40 @@ void TemplateTable::fast_storefield(TosState state) { switch (bytecode()) { case Bytecodes::_fast_vputfield: { - // Register state at this point: - // obj/cache = Z_tmp_1 (R10) = target object (pop_and_check_object wrote obj here, - // overwriting the entry pointer that cache held earlier) - // off = Z_tmp_2 (R11) = field offset - // flags = Z_ARG5 (R6) = field flags - // Z_tos = Z_ARG1 (R2) = value oop being stored - // - // For write_flat_field we need the ResolvedFieldEntry pointer. - // Re-load it into Z_ARG3 (R4) using Z_ARG4 (R5) as the index scratch; - // both are volatile and not carrying live values at this point. - Label is_flat, done; - __ test_field_is_flat(flags, is_flat); - __ null_check(Z_tos); // Value being stored must not be null for null-free flat field. - do_oop_store(_masm, field, Z_tos, - Z_ARG2, Z_ARG3, Z_ARG4, IN_HEAP); - __ branch_optimized(Assembler::bcondAlways, done); - __ bind(is_flat); - { - // Reload the ResolvedFieldEntry pointer. pop_and_check_object above - // overwrote cache/Z_tmp_1 with the object. - Register flat_entry = Z_ARG4; // R5 — safe for call_VM arg_3 slot - Register flat_index = Z_ARG3; // R4 — discarded after load_field_entry - __ load_field_entry(flat_entry, flat_index); - // entry=R5, tmp1=Z_ARG2=R3, tmp2=flat_index=Z_ARG3=R4, obj=Z_tmp_1=R10 - // All four are distinct; obj(R10) != Z_tos(R2) so implementation is safe. - __ write_flat_field(flat_entry, Z_ARG2, flat_index, obj); + if (Arguments::is_valhalla_enabled()) { + // Register state at this point: + // obj/cache = Z_tmp_1 (R10) = target object (pop_and_check_object wrote obj here, + // overwriting the entry pointer that cache held earlier) + // off = Z_tmp_2 (R11) = field offset + // flags = Z_ARG5 (R6) = field flags + // Z_tos = Z_ARG1 (R2) = value oop being stored + // + // For write_flat_field we need the ResolvedFieldEntry pointer. + // Re-load it into Z_ARG3 (R4) using Z_ARG4 (R5) as the index scratch; + // both are volatile and not carrying live values at this point. + Label is_flat, done; + if (UseFieldFlattening) { + __ test_field_is_flat(flags, is_flat); + } + __ null_check(Z_tos); // Value being stored must not be null for null-free flat field. + do_oop_store(_masm, field, Z_tos, + Z_ARG2, Z_ARG3, Z_ARG4, IN_HEAP); + __ branch_optimized(Assembler::bcondAlways, done); + __ bind(is_flat); + if (UseFieldFlattening) { + // Reload the ResolvedFieldEntry pointer. pop_and_check_object above + // overwrote cache/Z_tmp_1 with the object. + Register flat_entry = Z_ARG4; // R5 — safe for call_VM arg_3 slot + Register flat_index = Z_ARG3; // R4 — discarded after load_field_entry + __ load_field_entry(flat_entry, flat_index); + // entry=R5, tmp1=Z_ARG2=R3, tmp2=flat_index=Z_ARG3=R4, obj=Z_tmp_1=R10 + // All four are distinct; obj(R10) != Z_tos(R2) so implementation is safe. + __ write_flat_field(flat_entry, Z_ARG2, flat_index, obj); + } + __ bind(done); + } else { + __ should_not_reach_here(); } - __ bind(done); } break; case Bytecodes::_fast_aputfield: @@ -3675,9 +3691,13 @@ void TemplateTable::fast_accessfield(TosState state) { // access field switch (bytecode()) { case Bytecodes::_fast_vgetfield: - // field is flat; cache (Z_tmp_1) holds ResolvedFieldEntry*, obj (Z_tos) is receiver/result - __ read_flat_field(cache, obj); - __ verify_oop(Z_tos); + if (UseFieldFlattening) { + // field is flat; cache (Z_tmp_1) holds ResolvedFieldEntry*, obj (Z_tos) is receiver/result + __ read_flat_field(cache, obj); + __ verify_oop(Z_tos); + } else { + __ stop("ShouldNotReachHere"); + } return; case Bytecodes::_fast_agetfield: do_oop_load(_masm, field, Z_tos, Z_tmp_1, Z_tmp_2, IN_HEAP); @@ -4511,8 +4531,10 @@ void TemplateTable::monitorenter() { // Check for value type (Valhalla feature) NearLabel is_value_type; - __ z_lg(Z_R1_scratch, Address(Z_tos, oopDesc::mark_offset_in_bytes())); - __ test_markword_is_value_type(Z_R1_scratch, is_value_type); + if (Arguments::is_valhalla_enabled()) { + __ z_lg(Z_R1_scratch, Address(Z_tos, oopDesc::mark_offset_in_bytes())); + __ test_markword_is_value_type(Z_R1_scratch, is_value_type); + } const int entry_size = frame::interpreter_frame_monitor_size_in_bytes(); NearLabel allocated; // Initialize entry pointer. @@ -4592,10 +4614,12 @@ void TemplateTable::monitorenter() { __ dispatch_next(vtos); // Handle value type exception (Valhalla feature) - __ bind(is_value_type); - __ call_VM(noreg, CAST_FROM_FN_PTR(address, - InterpreterRuntime::throw_identity_exception), Z_tos); - __ should_not_reach_here(); + if (Arguments::is_valhalla_enabled()) { + __ bind(is_value_type); + __ call_VM(noreg, CAST_FROM_FN_PTR(address, + InterpreterRuntime::throw_identity_exception), Z_tos); + __ should_not_reach_here(); + } BLOCK_COMMENT("} monitorenter"); } @@ -4611,14 +4635,16 @@ void TemplateTable::monitorexit() { // Check for value type (Valhalla feature) NearLabel is_value_type, has_identity; - __ z_lg(Z_R1_scratch, Address(Z_tos, oopDesc::mark_offset_in_bytes())); - __ test_markword_is_value_type(Z_R1_scratch, is_value_type); - __ z_bru(has_identity); - __ bind(is_value_type); - __ call_VM(noreg, CAST_FROM_FN_PTR(address, - InterpreterRuntime::throw_illegal_monitor_state_exception)); - __ should_not_reach_here(); - __ bind(has_identity); + if (Arguments::is_valhalla_enabled()) { + __ z_lg(Z_R1_scratch, Address(Z_tos, oopDesc::mark_offset_in_bytes())); + __ test_markword_is_value_type(Z_R1_scratch, is_value_type); + __ z_bru(has_identity); + __ bind(is_value_type); + __ call_VM(noreg, CAST_FROM_FN_PTR(address, + InterpreterRuntime::throw_illegal_monitor_state_exception)); + __ should_not_reach_here(); + __ bind(has_identity); + } NearLabel found, not_found; const Register Rcurr_monitor = Z_ARG2; diff --git a/src/hotspot/cpu/x86/c1_LIRAssembler_x86.cpp b/src/hotspot/cpu/x86/c1_LIRAssembler_x86.cpp index 96300e612089..2f470ab44558 100644 --- a/src/hotspot/cpu/x86/c1_LIRAssembler_x86.cpp +++ b/src/hotspot/cpu/x86/c1_LIRAssembler_x86.cpp @@ -2493,14 +2493,15 @@ void LIR_Assembler::store_parameter(Metadata* m, int offset_from_rsp_in_words) { void LIR_Assembler::arraycopy_valuetype_check(Register obj, Register tmp, CodeStub* slow_path, bool is_dest, bool null_check) { + assert(Arguments::is_valhalla_enabled(), "Value types must be enabled"); if (null_check) { __ testptr(obj, obj); __ jcc(Assembler::zero, *slow_path->entry()); } if (is_dest) { __ test_null_free_array_oop(obj, tmp, *slow_path->entry()); - __ test_flat_array_oop(obj, tmp, *slow_path->entry()); - } else { + } + if (UseArrayFlattening) { __ test_flat_array_oop(obj, tmp, *slow_path->entry()); } } @@ -2607,12 +2608,14 @@ void LIR_Assembler::emit_arraycopy(LIR_OpArrayCopy* op) { return; } - // Handle value type arrays - if (flags & LIR_OpArrayCopy::src_valuetype_check) { - arraycopy_valuetype_check(src, tmp, stub, false, (flags & LIR_OpArrayCopy::src_null_check)); - } - if (flags & LIR_OpArrayCopy::dst_valuetype_check) { - arraycopy_valuetype_check(dst, tmp, stub, true, (flags & LIR_OpArrayCopy::dst_null_check)); + if (Arguments::is_valhalla_enabled()) { + // Handle value type arrays + if (flags & LIR_OpArrayCopy::src_valuetype_check) { + arraycopy_valuetype_check(src, tmp, stub, false, (flags & LIR_OpArrayCopy::src_null_check)); + } + if (flags & LIR_OpArrayCopy::dst_valuetype_check) { + arraycopy_valuetype_check(dst, tmp, stub, true, (flags & LIR_OpArrayCopy::dst_null_check)); + } } assert(default_type != nullptr && default_type->is_array_klass() && default_type->is_loaded(), "must be true at this point"); @@ -3152,6 +3155,7 @@ void LIR_Assembler::emit_profile_type(LIR_OpProfileType* op) { } void LIR_Assembler::emit_profile_value_type(LIR_OpProfileValueType* op) { + assert(Arguments::is_valhalla_enabled(), "Should not be called without value types enabled"); Register obj = op->obj()->as_register(); Register tmp = op->tmp()->as_pointer_register(); Address mdo_addr = as_Address(op->mdp()->as_address_ptr()); diff --git a/src/hotspot/cpu/x86/interp_masm_x86.cpp b/src/hotspot/cpu/x86/interp_masm_x86.cpp index c2f5eb68c2bc..efafe816e155 100644 --- a/src/hotspot/cpu/x86/interp_masm_x86.cpp +++ b/src/hotspot/cpu/x86/interp_masm_x86.cpp @@ -1121,12 +1121,14 @@ void InterpreterMacroAssembler::get_method_counters(Register method, } void InterpreterMacroAssembler::read_flat_field(Register entry, Register obj) { + assert(UseFieldFlattening, "Should be used only if field flattening is enabled"); call_VM(obj, CAST_FROM_FN_PTR(address, InterpreterRuntime::read_flat_field), obj, entry); } void InterpreterMacroAssembler::write_flat_field(Register entry, Register tmp1, Register tmp2, Register obj, Register value) { + assert(UseFieldFlattening, "Should be used only if field flattening is enabled"); assert_different_registers(entry, tmp1, tmp2, obj, value); Label slow_path, done; @@ -1610,19 +1612,23 @@ template void InterpreterMacroAssembler::profile_array_type(Re mov(tmp, array); profile_obj_type(tmp, Address(mdp, in_bytes(ArrayData::array_offset()))); - Label not_flat; - test_non_flat_array_oop(array, tmp, not_flat); + if (Arguments::is_valhalla_enabled()) { + if (UseArrayFlattening) { + Label not_flat; + test_non_flat_array_oop(array, tmp, not_flat); - set_mdp_flag_at(mdp, ArrayData::flat_array_byte_constant()); + set_mdp_flag_at(mdp, ArrayData::flat_array_byte_constant()); - bind(not_flat); + bind(not_flat); + } - Label not_null_free; - test_non_null_free_array_oop(array, tmp, not_null_free); + Label not_null_free; + test_non_null_free_array_oop(array, tmp, not_null_free); - set_mdp_flag_at(mdp, ArrayData::null_free_array_byte_constant()); + set_mdp_flag_at(mdp, ArrayData::null_free_array_byte_constant()); - bind(not_null_free); + bind(not_null_free); + } bind(profile_continue); } @@ -1687,6 +1693,7 @@ void InterpreterMacroAssembler::profile_acmp(Register mdp, Register left, Register right, Register tmp) { + assert(Arguments::is_valhalla_enabled(), "Profiling acmp makes sense only for value types"); if (ProfileInterpreter) { Label profile_continue; diff --git a/src/hotspot/cpu/x86/macroAssembler_x86.cpp b/src/hotspot/cpu/x86/macroAssembler_x86.cpp index ae7304b354b2..216ed9f92e75 100644 --- a/src/hotspot/cpu/x86/macroAssembler_x86.cpp +++ b/src/hotspot/cpu/x86/macroAssembler_x86.cpp @@ -2406,12 +2406,14 @@ void MacroAssembler::null_check(Register reg, int offset) { } void MacroAssembler::test_markword_is_value_type(Register markword, Label& is_value_type) { + assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled"); andptr(markword, markWord::value_type_pattern_mask); cmpptr(markword, markWord::value_type_pattern); jcc(Assembler::equal, is_value_type); } void MacroAssembler::test_oop_is_not_value_type(Register object, Register tmp, Label& not_value_type, bool can_be_null) { + assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled"); if (can_be_null) { testptr(object, object); jcc(Assembler::zero, not_value_type); @@ -2424,18 +2426,21 @@ void MacroAssembler::test_oop_is_not_value_type(Register object, Register tmp, L } void MacroAssembler::test_field_is_null_free_value_type(Register flags, Register temp_reg, Label& is_null_free_value_type) { + assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled"); movl(temp_reg, flags); testl(temp_reg, 1 << ResolvedFieldEntry::is_null_free_value_type_shift); jcc(Assembler::notEqual, is_null_free_value_type); } void MacroAssembler::test_field_is_not_null_free_value_type(Register flags, Register temp_reg, Label& not_null_free_value_type) { + assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled"); movl(temp_reg, flags); testl(temp_reg, 1 << ResolvedFieldEntry::is_null_free_value_type_shift); jcc(Assembler::equal, not_null_free_value_type); } void MacroAssembler::test_field_is_flat(Register flags, Register temp_reg, Label& is_flat) { + assert(UseFieldFlattening, "should not be used without field flattening enabled"); movl(temp_reg, flags); testl(temp_reg, 1 << ResolvedFieldEntry::is_flat_shift); jcc(Assembler::notEqual, is_flat); @@ -2450,23 +2455,28 @@ void MacroAssembler::test_oop_prototype_bit(Register oop, Register temp_reg, int void MacroAssembler::test_flat_array_oop(Register oop, Register temp_reg, Label& is_flat_array) { + assert(UseArrayFlattening, "should not be used without array flattening enabled"); test_oop_prototype_bit(oop, temp_reg, markWord::flat_array_bit_in_place, true, is_flat_array); } void MacroAssembler::test_non_flat_array_oop(Register oop, Register temp_reg, Label& is_non_flat_array) { + assert(UseArrayFlattening, "should not be used without array flattening enabled"); test_oop_prototype_bit(oop, temp_reg, markWord::flat_array_bit_in_place, false, is_non_flat_array); } void MacroAssembler::test_null_free_array_oop(Register oop, Register temp_reg, Label&is_null_free_array) { + assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled"); test_oop_prototype_bit(oop, temp_reg, markWord::null_free_array_bit_in_place, true, is_null_free_array); } void MacroAssembler::test_non_null_free_array_oop(Register oop, Register temp_reg, Label&is_non_null_free_array) { + assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled"); test_oop_prototype_bit(oop, temp_reg, markWord::null_free_array_bit_in_place, false, is_non_null_free_array); } void MacroAssembler::test_flat_array_layout(Register lh, Label& is_flat_array) { + assert(UseArrayFlattening, "should not be used without array flattening enabled"); testl(lh, Klass::_lh_array_tag_flat_value_bit_inplace); jcc(Assembler::notZero, is_flat_array); } @@ -5622,11 +5632,13 @@ void MacroAssembler::access_store_at(BasicType type, DecoratorSet decorators, Ad } void MacroAssembler::payload_offset(Register value_klass, Register offset) { + assert(Arguments::is_valhalla_enabled(), "Must be"); movptr(offset, Address(value_klass, ValueKlass::adr_members_offset())); movl(offset, Address(offset, ValueKlass::payload_offset_offset())); } void MacroAssembler::payload_addr(Register oop, Register data, Register value_klass) { + assert(Arguments::is_valhalla_enabled(), "Must be"); // ((address) (void*) o) + vk->payload_offset(); Register offset = (data == oop) ? rscratch1 : data; payload_offset(value_klass, offset); @@ -6088,6 +6100,7 @@ int MacroAssembler::store_value_type_fields_to_buf(ciValueKlass* vk, bool from_i // Move a value between registers/stack slots and update the reg_state bool MacroAssembler::move_helper(VMReg from, VMReg to, BasicType bt, RegState reg_state[]) { + assert(ValueTypePassFieldsAsArgs, "Must be"); assert(from->is_valid() && to->is_valid(), "source and destination must be valid"); if (reg_state[to->value()] == reg_written) { return true; // Already written @@ -6151,6 +6164,7 @@ bool MacroAssembler::move_helper(VMReg from, VMReg to, BasicType bt, RegState re // Calculate the extra stack space required for packing or unpacking value // args and adjust the stack pointer (see MacroAssembler::remove_frame). int MacroAssembler::extend_stack_for_value_args(int args_on_stack) { + assert(ValueTypePassFieldsAsArgs, "Must be"); int sp_inc = args_on_stack * VMRegImpl::stack_slot_size; sp_inc = align_up(sp_inc, StackAlignmentInBytes); assert(sp_inc > 0, "sanity"); @@ -6170,6 +6184,7 @@ int MacroAssembler::extend_stack_for_value_args(int args_on_stack) { bool MacroAssembler::unpack_value_helper(const GrowableArray* sig, int& sig_index, VMReg from, int& from_index, VMRegPair* to, int to_count, int& to_index, RegState reg_state[]) { + assert(ValueTypePassFieldsAsArgs, "Must be"); assert(sig->at(sig_index)._bt == T_VOID, "should be at end delimiter"); assert(from->is_valid(), "source must be valid"); bool progress = false; @@ -6307,6 +6322,7 @@ bool MacroAssembler::unpack_value_helper(const GrowableArray* sig, int bool MacroAssembler::pack_value_helper(const GrowableArray* sig, int& sig_index, int vtarg_index, VMRegPair* from, int from_count, int& from_index, VMReg to, RegState reg_state[], Register val_array) { + assert(ValueTypePassFieldsAsArgs, "Must be"); assert(sig->at(sig_index)._bt == T_METADATA, "should be at delimiter"); assert(to->is_valid(), "destination must be valid"); diff --git a/src/hotspot/cpu/x86/stubGenerator_x86_64_arraycopy.cpp b/src/hotspot/cpu/x86/stubGenerator_x86_64_arraycopy.cpp index 2575d0e5007b..9c95408ad8ff 100644 --- a/src/hotspot/cpu/x86/stubGenerator_x86_64_arraycopy.cpp +++ b/src/hotspot/cpu/x86/stubGenerator_x86_64_arraycopy.cpp @@ -3601,10 +3601,12 @@ address StubGenerator::generate_generic_copy(address byte_copy_entry, address sh __ cmpq(r10_src_klass, rax); __ jcc(Assembler::notEqual, L_failed); - if (Arguments::is_valhalla_enabled()) { + if (UseArrayFlattening) { // Check for flat value type array -> return -1 __ test_flat_array_oop(src, rax, L_failed); + } + if (Arguments::is_valhalla_enabled()) { // Check for null-free (non-flat) value type array -> handle as object array __ test_null_free_array_oop(src, rax, L_objArray); } @@ -3738,12 +3740,14 @@ __ BIND(L_checkcast_copy); #ifdef ASSERT { - BLOCK_COMMENT("assert not null-free array {"); - Label L; - __ test_non_null_free_array_oop(dst, rklass_tmp, L); - __ stop("unexpected null-free array"); - __ bind(L); - BLOCK_COMMENT("} assert not null-free array"); + if (Arguments::is_valhalla_enabled()) { + BLOCK_COMMENT("assert not null-free array {"); + Label L; + __ test_non_null_free_array_oop(dst, rklass_tmp, L); + __ stop("unexpected null-free array"); + __ bind(L); + BLOCK_COMMENT("} assert not null-free array"); + } } #endif diff --git a/src/hotspot/cpu/x86/templateTable_x86.cpp b/src/hotspot/cpu/x86/templateTable_x86.cpp index 620498378e55..8961f621301c 100644 --- a/src/hotspot/cpu/x86/templateTable_x86.cpp +++ b/src/hotspot/cpu/x86/templateTable_x86.cpp @@ -1971,10 +1971,10 @@ void TemplateTable::if_acmp(Condition cc) { Label taken, not_taken; __ pop_ptr(rdx); - __ profile_acmp(rbx, rdx, rax, rcx); - - const int is_value_type_mask = markWord::value_type_pattern; if (Arguments::is_valhalla_enabled()) { + __ profile_acmp(rbx, rdx, rax, rcx); + + const int is_value_type_mask = markWord::value_type_pattern; __ cmpoop(rdx, rax); __ jcc(Assembler::equal, (cc == equal) ? taken : not_taken); @@ -2713,7 +2713,9 @@ void TemplateTable::getfield_or_static(int byte_no, bool is_static, RewriteContr __ jmp(Done); } else { Label is_flat; - __ test_field_is_flat(flags, rscratch1, is_flat); + if (UseFieldFlattening) { + __ test_field_is_flat(flags, rscratch1, is_flat); + } pop_and_check_object(obj); __ load_heap_oop(rax, field); __ push(atos); @@ -2722,13 +2724,15 @@ void TemplateTable::getfield_or_static(int byte_no, bool is_static, RewriteContr } __ jmp(Done); __ bind(is_flat); - // field is flat (null-free or nullable with a null-marker) - pop_and_check_object(rax); - __ read_flat_field(rcx, rax); - __ verify_oop(rax); - __ push(atos); - if (rc == may_rewrite) { - patch_bytecode(Bytecodes::_fast_vgetfield, bc, rbx); + if (UseFieldFlattening) { + // field is flat (null-free or nullable with a null-marker) + pop_and_check_object(rax); + __ read_flat_field(rcx, rax); + __ verify_oop(rax); + __ push(atos); + if (rc == may_rewrite) { + patch_bytecode(Bytecodes::_fast_vgetfield, bc, rbx); + } } __ jmp(Done); } @@ -2984,7 +2988,7 @@ void TemplateTable::putfield_or_static_helper(int byte_no, bool is_static, Rewri patch_bytecode(Bytecodes::_fast_aputfield, bc, rbx, true, byte_no); } __ jmp(Done); - } else { + } else { // Valhalla __ pop(atos); if (is_static) { Label is_nullable; @@ -2995,7 +2999,9 @@ void TemplateTable::putfield_or_static_helper(int byte_no, bool is_static, Rewri __ jmp(Done); } else { Label is_flat, null_free_reference, rewrite_value; - __ test_field_is_flat(flags, rscratch1, is_flat); + if (UseFieldFlattening) { + __ test_field_is_flat(flags, rscratch1, is_flat); + } __ test_field_is_null_free_value_type(flags, rscratch1, null_free_reference); pop_and_check_object(obj); // Store into the field @@ -3011,8 +3017,10 @@ void TemplateTable::putfield_or_static_helper(int byte_no, bool is_static, Rewri do_oop_store(_masm, field, rax); __ jmp(rewrite_value); __ bind(is_flat); - pop_and_check_object(rscratch2); - __ write_flat_field(rcx, r8, rscratch1, rscratch2, rax); + if (UseFieldFlattening) { + pop_and_check_object(rscratch2); + __ write_flat_field(rcx, r8, rscratch1, rscratch2, rax); + } __ bind(rewrite_value); if (rc == may_rewrite) { patch_bytecode(Bytecodes::_fast_vputfield, bc, rbx, true, byte_no); @@ -3240,17 +3248,25 @@ void TemplateTable::fast_storefield_helper(Address field, Register rax, Register switch (bytecode()) { case Bytecodes::_fast_vputfield: { - // Field is either flat (nullable or not) or non-flat and null-free - Label is_flat, done; - __ test_field_is_flat(flags, rscratch1, is_flat); - __ null_check(rax); // FIXME JDK-8341120 - do_oop_store(_masm, field, rax); - __ jmp(done); - __ bind(is_flat); - __ load_field_entry(r8, r9); - __ movptr(rscratch2, rcx); // re-shuffle registers because of VM call calling convention - __ write_flat_field(r8, rscratch1, r9, rscratch2, rax); - __ bind(done); + if (Arguments::is_valhalla_enabled()) { + // Field is either flat (nullable or not) or non-flat and null-free + Label is_flat, done; + if (UseFieldFlattening) { + __ test_field_is_flat(flags, rscratch1, is_flat); + } + __ null_check(rax); + do_oop_store(_masm, field, rax); + __ jmp(done); + __ bind(is_flat); + if (UseFieldFlattening) { + __ load_field_entry(r8, r9); + __ movptr(rscratch2, rcx); // re-shuffle registers because of VM call calling convention + __ write_flat_field(r8, rscratch1, r9, rscratch2, rax); + } + __ bind(done); + } else { + __ should_not_reach_here(); + } } break; case Bytecodes::_fast_aputfield: @@ -3322,8 +3338,12 @@ void TemplateTable::fast_accessfield(TosState state) { // access field switch (bytecode()) { case Bytecodes::_fast_vgetfield: - __ read_flat_field(rcx, rax); - __ verify_oop(rax); + if (UseFieldFlattening) { + __ read_flat_field(rcx, rax); + __ verify_oop(rax); + } else { + __ should_not_reach_here(); + } break; case Bytecodes::_fast_agetfield: do_oop_load(_masm, field, rax); @@ -4065,8 +4085,10 @@ void TemplateTable::monitorenter() { __ null_check(rax); Label is_value_type; - __ movptr(rbx, Address(rax, oopDesc::mark_offset_in_bytes())); - __ test_markword_is_value_type(rbx, is_value_type); + if (Arguments::is_valhalla_enabled()) { + __ movptr(rbx, Address(rax, oopDesc::mark_offset_in_bytes())); + __ test_markword_is_value_type(rbx, is_value_type); + } const Address monitor_block_top( rbp, frame::interpreter_frame_monitor_block_top_offset * wordSize); @@ -4162,9 +4184,11 @@ void TemplateTable::monitorenter() { __ dispatch_next(vtos); __ bind(is_value_type); - __ call_VM(noreg, CAST_FROM_FN_PTR(address, - InterpreterRuntime::throw_identity_exception), rax); - __ should_not_reach_here(); + if (Arguments::is_valhalla_enabled()) { + __ call_VM(noreg, CAST_FROM_FN_PTR(address, + InterpreterRuntime::throw_identity_exception), rax); + __ should_not_reach_here(); + } } void TemplateTable::monitorexit() { @@ -4173,16 +4197,18 @@ void TemplateTable::monitorexit() { // check for null object __ null_check(rax); - const int is_value_type_mask = markWord::value_type_pattern; - Label has_identity; - __ movptr(rbx, Address(rax, oopDesc::mark_offset_in_bytes())); - __ andptr(rbx, is_value_type_mask); - __ cmpl(rbx, is_value_type_mask); - __ jcc(Assembler::notEqual, has_identity); - __ call_VM(noreg, CAST_FROM_FN_PTR(address, - InterpreterRuntime::throw_illegal_monitor_state_exception)); - __ should_not_reach_here(); - __ bind(has_identity); + if (Arguments::is_valhalla_enabled()) { + const int is_value_type_mask = markWord::value_type_pattern; + Label has_identity; + __ movptr(rbx, Address(rax, oopDesc::mark_offset_in_bytes())); + __ andptr(rbx, is_value_type_mask); + __ cmpl(rbx, is_value_type_mask); + __ jcc(Assembler::notEqual, has_identity); + __ call_VM(noreg, CAST_FROM_FN_PTR(address, + InterpreterRuntime::throw_illegal_monitor_state_exception)); + __ should_not_reach_here(); + __ bind(has_identity); + } const Address monitor_block_top( rbp, frame::interpreter_frame_monitor_block_top_offset * wordSize); diff --git a/src/hotspot/share/c1/c1_GraphBuilder.cpp b/src/hotspot/share/c1/c1_GraphBuilder.cpp index 50bad1daef21..3bbdca1d1509 100644 --- a/src/hotspot/share/c1/c1_GraphBuilder.cpp +++ b/src/hotspot/share/c1/c1_GraphBuilder.cpp @@ -1389,7 +1389,7 @@ void GraphBuilder::if_node(Value x, If::Condition cond, Value y, ValueStack* sta } } } - if ((stream()->cur_bc() == Bytecodes::_if_acmpeq || stream()->cur_bc() == Bytecodes::_if_acmpne) && + if (Arguments::is_valhalla_enabled() && (stream()->cur_bc() == Bytecodes::_if_acmpeq || stream()->cur_bc() == Bytecodes::_if_acmpne) && profile_acmp()) { compilation()->set_would_profile(true); append(new ProfileACmpTypes(method(), bci(), x, y)); diff --git a/src/hotspot/share/c1/c1_LIR.hpp b/src/hotspot/share/c1/c1_LIR.hpp index 5f2a0bcbd843..55f7562c7e15 100644 --- a/src/hotspot/share/c1/c1_LIR.hpp +++ b/src/hotspot/share/c1/c1_LIR.hpp @@ -2133,7 +2133,9 @@ class LIR_OpProfileValueType : public LIR_Op { , _obj(obj) , _flag(flag) , _tmp(tmp) - , _not_null(not_null) { } + , _not_null(not_null) { + assert(Arguments::is_valhalla_enabled(), "Should not be called without value types enabled"); + } LIR_Opr mdp() const { return _mdp; } LIR_Opr obj() const { return _obj; } diff --git a/src/hotspot/share/oops/methodData.cpp b/src/hotspot/share/oops/methodData.cpp index f3bbacda4a82..a7b2cab16f09 100644 --- a/src/hotspot/share/oops/methodData.cpp +++ b/src/hotspot/share/oops/methodData.cpp @@ -1708,7 +1708,7 @@ bool MethodData::profile_array_accesses() { } bool MethodData::profile_acmp() { - return COMPILER2_PRESENT(UseACmpProfile ||) TypeProfileLevel > 0; + return COMPILER2_PRESENT(UseACmpProfile ||) (Arguments::is_valhalla_enabled() && TypeProfileLevel > 0); } bool MethodData::profile_all_parameters() { diff --git a/src/hotspot/share/opto/library_call.cpp b/src/hotspot/share/opto/library_call.cpp index dfa302ee9a85..c7da58aeb863 100644 --- a/src/hotspot/share/opto/library_call.cpp +++ b/src/hotspot/share/opto/library_call.cpp @@ -5000,17 +5000,34 @@ bool LibraryCallKit::inline_newArray(bool null_free, bool atomic) { // public static native boolean ValueClass::isNullRestrictedArray(Object array); // public static native boolean ValueClass::isAtomicArray(Object array); bool LibraryCallKit::inline_getArrayProperties(ArrayPropertiesCheck check) { + if (!Arguments::is_valhalla_enabled()) { + Node* res = gvn().transform(check == ArrayPropertiesCheck::IsAtomic ? intcon(1) : intcon(0)); + set_result(res); + return true; + } Node* array = argument(0); Node* bol; - switch(check) { + switch (check) { case IsFlat: - bol = flat_array_test(array); + if (UseArrayFlattening) { + bol = flat_array_test(array); + } else { + Node* res = gvn().transform(intcon(0)); + set_result(res); + return true; + } break; case IsNullRestricted: bol = null_free_array_test(array); break; case IsAtomic: { + if (!UseArrayFlattening) { + // Non-flat arrays are always atomic + Node* res = gvn().transform(intcon(1)); + set_result(res); + return true; + } // See conditions in JVM_IsAtomicArray // 1. If not flat, then atomic, or else... RegionNode* atomic_region = new RegionNode(1); @@ -5331,8 +5348,10 @@ bool LibraryCallKit::inline_array_copyOf(bool is_copyOfRange) { if (Arguments::is_valhalla_enabled()) { // Handle value type arrays // TODO 8251971 This is too strong - generate_fair_guard(flat_array_test(original), bailout); - generate_fair_guard(flat_array_test(refined_klass_node), bailout); + if (UseArrayFlattening) { + generate_fair_guard(flat_array_test(original), bailout); + generate_fair_guard(flat_array_test(refined_klass_node), bailout); + } generate_fair_guard(null_free_array_test(original), bailout); } @@ -7167,8 +7186,10 @@ bool LibraryCallKit::inline_arraycopy() { generate_fair_guard(tst, slow_region); // TODO 8251971 This is too strong - generate_fair_guard(flat_array_test(src), slow_region); - generate_fair_guard(flat_array_test(dest), slow_region); + if (UseArrayFlattening) { + generate_fair_guard(flat_array_test(src), slow_region); + generate_fair_guard(flat_array_test(dest), slow_region); + } } { diff --git a/src/hotspot/share/opto/macro.cpp b/src/hotspot/share/opto/macro.cpp index 1f873cf882f3..745b7a63c9d4 100644 --- a/src/hotspot/share/opto/macro.cpp +++ b/src/hotspot/share/opto/macro.cpp @@ -3088,6 +3088,7 @@ void PhaseMacroExpand::expand_subtypecheck_node(SubTypeCheckNode *check) { // ... // } void PhaseMacroExpand::expand_flatarraycheck_node(FlatArrayCheckNode* check) { + assert(UseArrayFlattening, "Should not be called if flat arrays are not enabled"); bool use_mark_word = _igvn.type(check->in(FlatArrayCheckNode::ArrayOrKlass))->isa_oopptr() != nullptr; Unique_Node_List outputs; diff --git a/src/hotspot/share/opto/macroArrayCopy.cpp b/src/hotspot/share/opto/macroArrayCopy.cpp index 8982bacbaa02..72cc6bccc804 100644 --- a/src/hotspot/share/opto/macroArrayCopy.cpp +++ b/src/hotspot/share/opto/macroArrayCopy.cpp @@ -306,10 +306,12 @@ Node* PhaseMacroExpand::mark_word_test(Node** ctrl, Node* obj, MergeMemNode* mem } Node* PhaseMacroExpand::generate_flat_array_guard(Node** ctrl, Node* array, MergeMemNode* mem, RegionNode* region) { + assert(UseArrayFlattening, "Should not be called if flat arrays are disabled"); return mark_word_test(ctrl, array, mem, markWord::flat_array_bit_in_place, region); } Node* PhaseMacroExpand::generate_null_free_array_guard(Node** ctrl, Node* array, MergeMemNode* mem, RegionNode* region) { + assert(Arguments::is_valhalla_enabled(), "Should not be called if value types are disabled"); return mark_word_test(ctrl, array, mem, markWord::null_free_array_bit_in_place, region); } @@ -1298,6 +1300,7 @@ void PhaseMacroExpand::generate_unchecked_arraycopy(Node** ctrl, MergeMemNode** const TypePtr* PhaseMacroExpand::adjust_for_flat_array(const TypeAryPtr* top_dest, Node*& src_offset, Node*& dest_offset, Node*& length, BasicType& dest_elem, Node*& dest_length) { + assert(UseArrayFlattening, "Should not be called if flat arrays are not enabled"); #ifdef ASSERT assert(top_dest->elem()->make_ptr()->is_instptr()->is_valueklassptr(), "must be concrete value klass"); BarrierSetC2* bs = BarrierSet::barrier_set()->barrier_set_c2(); @@ -1590,8 +1593,10 @@ void PhaseMacroExpand::expand_arraycopy_node(ArrayCopyNode *ac) { // We need to be careful here because 'adjust_for_flat_array' will adjust offsets/length etc. which then does not work anymore for the slow call to SharedRuntime::slow_arraycopy_C. assert(top_src->is_flat() == top_dest->is_flat(), "must have bailed out before"); if (Arguments::is_valhalla_enabled() && !flat_and_same_nullness) { - generate_flat_array_guard(&ctrl, src, merge_mem, slow_region); - generate_flat_array_guard(&ctrl, dest, merge_mem, slow_region); + if (UseArrayFlattening) { + generate_flat_array_guard(&ctrl, src, merge_mem, slow_region); + generate_flat_array_guard(&ctrl, dest, merge_mem, slow_region); + } generate_null_free_array_guard(&ctrl, dest, merge_mem, slow_region); } } diff --git a/src/hotspot/share/opto/subnode.hpp b/src/hotspot/share/opto/subnode.hpp index 862b34eebdbb..d4706affb796 100644 --- a/src/hotspot/share/opto/subnode.hpp +++ b/src/hotspot/share/opto/subnode.hpp @@ -321,6 +321,7 @@ class FlatArrayCheckNode : public CmpNode { ArrayOrKlass }; FlatArrayCheckNode(Compile* C, Node* mem, Node* array_or_klass) : CmpNode(mem, array_or_klass) { + assert(UseArrayFlattening, "Should not be used without flat arrays enabled"); init_class_id(Class_FlatArrayCheck); init_flags(Flag_is_macro); C->add_macro_node(this); diff --git a/src/hotspot/share/opto/valuetypenode.hpp b/src/hotspot/share/opto/valuetypenode.hpp index b70a335c94a2..c53fbba3bb7c 100644 --- a/src/hotspot/share/opto/valuetypenode.hpp +++ b/src/hotspot/share/opto/valuetypenode.hpp @@ -40,6 +40,7 @@ class ValueTypeNode : public TypeNode { private: ValueTypeNode(ciValueKlass* vk, Node* oop, bool null_free) : TypeNode(TypeInstPtr::make(null_free ? TypePtr::NotNull : TypePtr::BotPTR, vk), Values + vk->nof_declared_nonstatic_fields()) { + assert(Arguments::is_valhalla_enabled(), "should not be used without value types"); init_class_id(Class_ValueType); init_req(Oop, oop); Compile::current()->add_value_type(this); @@ -208,6 +209,7 @@ class LoadFlatNode final : public SafePointNode { private: LoadFlatNode(ciValueKlass* vk, const TypeTuple* type, bool null_free, DecoratorSet decorators) : SafePointNode(TypeFunc::Parms + 2, nullptr, TypePtr::BOTTOM), _vk(vk), _type(type), _null_free(null_free), _decorators(decorators) { + assert(UseArrayFlattening || UseFieldFlattening, "should not be used without flattening enabled"); init_class_id(Class_LoadFlat); Compile::current()->add_flat_access(this); } @@ -253,6 +255,7 @@ class StoreFlatNode final : public SafePointNode { private: StoreFlatNode(bool null_free, DecoratorSet decorators) : SafePointNode(TypeFunc::Parms + 3, nullptr, TypePtr::BOTTOM), _null_free(null_free), _decorators(decorators) { + assert(UseArrayFlattening || UseFieldFlattening, "should not be used without flattening enabled"); init_class_id(Class_StoreFlat); Compile::current()->add_flat_access(this); } From 55ea1d3b8089eb6bb354000deaceb110e9bc7cc0 Mon Sep 17 00:00:00 2001 From: Thomas Schatzl Date: Mon, 19 Oct 2026 14:26:49 +0000 Subject: [PATCH 107/169] 8393715: G1: Howl bucket assert can fire unnecessarily in debug mode in some configurations Reviewed-by: ayang --- .../share/gc/g1/g1CardSetContainers.hpp | 2 ++ .../gc/g1/g1CardSetContainers.inline.hpp | 3 +- test/hotspot/gtest/gc/g1/test_g1CardSet.cpp | 29 +++++++++++++++++++ 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/hotspot/share/gc/g1/g1CardSetContainers.hpp b/src/hotspot/share/gc/g1/g1CardSetContainers.hpp index 880c02a71ed9..2a5d332f55ce 100644 --- a/src/hotspot/share/gc/g1/g1CardSetContainers.hpp +++ b/src/hotspot/share/gc/g1/g1CardSetContainers.hpp @@ -252,6 +252,8 @@ class G1CardSetBitMap : public G1CardSetContainer { }; class G1CardSetHowl : public G1CardSetContainer { + DEBUG_ONLY(uint _num_buckets;) + public: typedef uint EntryCountType; using ContainerPtr = G1CardSet::ContainerPtr; diff --git a/src/hotspot/share/gc/g1/g1CardSetContainers.inline.hpp b/src/hotspot/share/gc/g1/g1CardSetContainers.inline.hpp index c9e7a046ea7a..d45ee59bccc0 100644 --- a/src/hotspot/share/gc/g1/g1CardSetContainers.inline.hpp +++ b/src/hotspot/share/gc/g1/g1CardSetContainers.inline.hpp @@ -276,7 +276,7 @@ inline size_t G1CardSetBitMap::header_size_in_bytes() { } inline Atomic const* G1CardSetHowl::container_addr(EntryCountType index) const { - assert(index < _num_entries.load_relaxed(), "precondition"); + precond(index < _num_buckets); return buckets() + index; } @@ -295,6 +295,7 @@ inline Atomic const* G1CardSetHowl::buckets() const inline G1CardSetHowl::G1CardSetHowl(EntryCountType card_in_region, G1CardSetConfiguration* config) : G1CardSetContainer(), + DEBUG_ONLY(_num_buckets(config->num_buckets_in_howl()) COMMA) _num_entries((config->max_cards_in_array() + 1)) /* Card Transfer will not increment _num_entries */ { EntryCountType num_buckets = config->num_buckets_in_howl(); EntryCountType bucket = config->howl_bucket_index(card_in_region); diff --git a/test/hotspot/gtest/gc/g1/test_g1CardSet.cpp b/test/hotspot/gtest/gc/g1/test_g1CardSet.cpp index 0cb663e1ade2..1ea81bdd1d2b 100644 --- a/test/hotspot/gtest/gc/g1/test_g1CardSet.cpp +++ b/test/hotspot/gtest/gc/g1/test_g1CardSet.cpp @@ -92,6 +92,8 @@ class G1CardSetTest : public ::testing::Test { static void card_set_basic_test(); static void card_set_mt_test(); + static void card_set_howl_bounds_test(); + static void add_cards(G1CardSet* card_set, uint cards_per_region, uint* cards, uint num_cards, G1AddCardResult* results); static void contains_cards(G1CardSet* card_set, uint cards_per_region, uint* cards, uint num_cards); @@ -492,6 +494,29 @@ void G1CardSetTest::card_set_mt_test() { ASSERT_TRUE(count_cards._num_cards <= cl.added()); } +void G1CardSetTest::card_set_howl_bounds_test() { + const uint CardsPerRegion = 65536; + const double CoarsenThreshold = 0.9; + + G1CardSetConfiguration config(1, + CoarsenThreshold, + 8, + CoarsenThreshold, + CardsPerRegion, + 0); + + G1CardSetFreePool free_pool(config.num_mem_object_types()); + G1CardSetMemoryManager mm(&config, &free_pool); + + G1CardSet card_set(&config, &mm); + + // Add a few cards so that we coarsen to howl in this configuration. + card_set.add_card(0, 1); + card_set.add_card(0, 2); + card_set.add_card(0, 3); + card_set.add_card(0, 4); +} + TEST_VM(G1CardSetTest, basic_card_set_test) { G1CardSetTest::card_set_basic_test(); } @@ -499,3 +524,7 @@ TEST_VM(G1CardSetTest, basic_card_set_test) { TEST_VM(G1CardSetTest, mt_card_set_test) { G1CardSetTest::card_set_mt_test(); } + +TEST_VM(G1CardSetTest, howl_container_bucket_bounds) { + G1CardSetTest::card_set_howl_bounds_test(); +} From 52c0e4b195f36623f8e8b585ccd7f7558155f1b9 Mon Sep 17 00:00:00 2001 From: Jaikiran Pai Date: Mon, 19 Oct 2026 15:30:54 +0000 Subject: [PATCH 108/169] 8394620: Update Zlib Data Compression Library's gzwrite.c Reviewed-by: alanb, lancea --- src/java.base/share/native/libzip/zlib/gzwrite.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/java.base/share/native/libzip/zlib/gzwrite.c b/src/java.base/share/native/libzip/zlib/gzwrite.c index b11c318d5432..5719bbffe00a 100644 --- a/src/java.base/share/native/libzip/zlib/gzwrite.c +++ b/src/java.base/share/native/libzip/zlib/gzwrite.c @@ -266,8 +266,11 @@ local z_size_t gz_write(gz_statep state, voidpc buf, z_size_t len) { n -= state->strm.avail_in; state->x.pos += n; len -= n; - if (ret == -1) + if (ret == -1) { + state->strm.avail_in = 0; + state->strm.next_in = state->in; return state->again ? put - len : 0; + } } while (len); } @@ -407,7 +410,8 @@ local int gz_vacate(gz_statep state) { z_streamp strm; strm = &(state->strm); - if (strm->next_in + strm->avail_in <= state->in + state->size) + if (strm->next_in == NULL || + strm->next_in + strm->avail_in <= state->in + state->size) return 0; (void)gz_comp(state, Z_NO_FLUSH); if (strm->avail_in == 0) { @@ -467,6 +471,7 @@ int ZEXPORTVA gzvprintf(gzFile file, const char *format, va_list va) { a Z_BUF_ERROR to let the application know that this gzprintf() needs to be retried. */ gz_error(state, Z_BUF_ERROR, "stalled write on gzprintf"); + return state->err; } if (!state->again) return state->err; From 728db6271c2b7ed017142308220fd395ea9fa426 Mon Sep 17 00:00:00 2001 From: Naoto Sato Date: Mon, 19 Oct 2026 16:28:41 +0000 Subject: [PATCH 109/169] 8394775: Rename the JSON test file containing # in its file name Reviewed-by: jlu --- ...ture_trailing_#.json => n_structure_trailing_NUMBER_SIGN.json} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/{n_structure_trailing_#.json => n_structure_trailing_NUMBER_SIGN.json} (100%) diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_trailing_#.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_trailing_NUMBER_SIGN.json similarity index 100% rename from test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_trailing_#.json rename to test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_trailing_NUMBER_SIGN.json From 795909c86ef82cc4dfad18860b6125bae7618bb1 Mon Sep 17 00:00:00 2001 From: Leonid Mesnik Date: Mon, 19 Oct 2026 17:08:02 +0000 Subject: [PATCH 110/169] 8389457: GC.class_histogram reports duplicate rows for refined array layouts Reviewed-by: dsimms, sspitsyn --- src/hotspot/share/memory/heapInspection.cpp | 27 ++++++- src/hotspot/share/memory/heapInspection.hpp | 3 +- .../gc/ClassHistogramFlattenedArrayTest.java | 77 +++++++++++++++++++ 3 files changed, 103 insertions(+), 4 deletions(-) create mode 100644 test/hotspot/jtreg/serviceability/dcmd/gc/ClassHistogramFlattenedArrayTest.java diff --git a/src/hotspot/share/memory/heapInspection.cpp b/src/hotspot/share/memory/heapInspection.cpp index bd0b0f4d88ea..0a8510c5eb83 100644 --- a/src/hotspot/share/memory/heapInspection.cpp +++ b/src/hotspot/share/memory/heapInspection.cpp @@ -99,24 +99,45 @@ const char* KlassInfoEntry::name() const { return name; } +const char* KlassInfoEntry::layout() const { + if (_klass->is_flatArray_klass()) { + switch (FlatArrayKlass::cast(_klass)->layout_kind()) { + case LayoutKind::NULL_FREE_NON_ATOMIC_FLAT: + return " [flat, null-restricted, non-atomic]"; + case LayoutKind::NULL_FREE_ATOMIC_FLAT: + return " [flat, null-restricted, atomic]"; + case LayoutKind::NULLABLE_NON_ATOMIC_FLAT: + return " [flat, nullable, non-atomic]"; + case LayoutKind::NULLABLE_ATOMIC_FLAT: + return " [flat, nullable, atomic]"; + default: + return " [unknown layout]"; + } + } + return ""; +} + + void KlassInfoEntry::print_on(outputStream* st) const { ResourceMark rm; // simplify the formatting (ILP32 vs LP64) - always cast the numbers to 64-bit ModuleEntry* module = _klass->module(); if (module->is_named()) { - st->print_cr(INT64_FORMAT_W(13) " " UINT64_FORMAT_W(13) " %s (%s%s%s)", + st->print_cr(INT64_FORMAT_W(13) " " UINT64_FORMAT_W(13) " %s%s (%s%s%s)", (int64_t)_instance_count, (uint64_t)_instance_words * HeapWordSize, name(), + layout(), module->name()->as_C_string(), module->version() != nullptr ? "@" : "", module->version() != nullptr ? module->version()->as_C_string() : ""); } else { - st->print_cr(INT64_FORMAT_W(13) " " UINT64_FORMAT_W(13) " %s", + st->print_cr(INT64_FORMAT_W(13) " " UINT64_FORMAT_W(13) " %s%s", (int64_t)_instance_count, (uint64_t)_instance_words * HeapWordSize, - name()); + name(), + layout()); } } diff --git a/src/hotspot/share/memory/heapInspection.hpp b/src/hotspot/share/memory/heapInspection.hpp index 646610e753d6..08095d3845a3 100644 --- a/src/hotspot/share/memory/heapInspection.hpp +++ b/src/hotspot/share/memory/heapInspection.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2002, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -82,6 +82,7 @@ class KlassInfoEntry: public CHeapObj { int compare(KlassInfoEntry* e1, KlassInfoEntry* e2); void print_on(outputStream* st) const; const char* name() const; + const char* layout() const; }; class KlassInfoClosure : public StackObj { diff --git a/test/hotspot/jtreg/serviceability/dcmd/gc/ClassHistogramFlattenedArrayTest.java b/test/hotspot/jtreg/serviceability/dcmd/gc/ClassHistogramFlattenedArrayTest.java new file mode 100644 index 000000000000..4d901521970a --- /dev/null +++ b/test/hotspot/jtreg/serviceability/dcmd/gc/ClassHistogramFlattenedArrayTest.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.util.regex.Pattern; + +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.dcmd.CommandExecutor; +import jdk.test.lib.dcmd.JMXExecutor; + +import jdk.internal.value.ValueClass; + + +/* + * @test + * @summary Test of diagnostic command GC.class_histogram for arrays with different layouts + * @enablePreview + * @library /test/lib + * @modules java.base/jdk.internal.misc + * java.compiler + * java.management + * java.base/jdk.internal.vm.annotation + * java.base/jdk.internal.value + * jdk.internal.jvmstat/sun.jvmstat.monitor + * @run main ${test.main.class} + */ +public class ClassHistogramFlattenedArrayTest { + + public static value record TestClassNA(short s1, short s2) {} + + public static value record TestClassNRA(short s1, short s2) {} + + @jdk.internal.vm.annotation.LooselyConsistentValue + public static value record TestClassNRNA(short s1, short s2) {} + + + public static Object[] arrays = { + ValueClass.newNullableAtomicArray(TestClassNA.class, 1024), + ValueClass.newNullRestrictedAtomicArray(TestClassNRA.class, 1024, new TestClassNRA((short)1, (short)1)), + ValueClass.newNullRestrictedNonAtomicArray(TestClassNRNA.class, 1024, new TestClassNRNA((short)1, (short)1)), + }; + + public static void main(String[] args) { + CommandExecutor executor = new JMXExecutor(); + OutputAnalyzer output = executor.execute("GC.class_histogram "); + + assertType(output, TestClassNA[].class, "flat, nullable, atomic"); + assertType(output, TestClassNRA[].class, "flat, null-restricted, atomic"); + assertType(output, TestClassNRNA[].class, "flat, null-restricted, non-atomic"); + } + + private static void assertType(OutputAnalyzer output, Class arrayClass, String type) { + output.shouldMatch("^\\s+\\d+:\\s+\\d+\\s+\\d+\\s+" + + Pattern.quote(arrayClass.getName()) + + "\\s+\\[" + Pattern.quote(type) + "\\](?:\\s+\\(.*\\))?\\s*$"); + } + +} From 5bb5a88f51ec8ff12493ba511572468b424da932 Mon Sep 17 00:00:00 2001 From: Mat Carter Date: Mon, 19 Oct 2026 17:19:43 +0000 Subject: [PATCH 111/169] 8386428: With -UseLSE the Starvation test on Windows ARM64 timeouts Reviewed-by: aph, qamai, vklang, gcao --- .../java/util/concurrent/LinkedTransferQueue.java | 8 +++++++- .../java/util/concurrent/SynchronousQueue.java | 2 +- .../java/lang/Thread/virtual/stress/PingPong.java | 14 ++++++++++++++ .../java/lang/Thread/virtual/stress/Skynet.java | 8 ++++++++ .../BlockingQueue/ProducerConsumerLoops.java | 9 +++++++++ .../SingleProducerMultipleConsumerLoops.java | 9 +++++++++ 6 files changed, 48 insertions(+), 2 deletions(-) diff --git a/src/java.base/share/classes/java/util/concurrent/LinkedTransferQueue.java b/src/java.base/share/classes/java/util/concurrent/LinkedTransferQueue.java index 787d8d5fecd8..736b284aa2d4 100644 --- a/src/java.base/share/classes/java/util/concurrent/LinkedTransferQueue.java +++ b/src/java.base/share/classes/java/util/concurrent/LinkedTransferQueue.java @@ -388,6 +388,10 @@ final void selfLinkItem() { ITEM.set(this, this); } + final Thread getVolatileWaiter() { + return (Thread)WAITER.getVolatile(this); + } + /** The number of times to spin when eligible */ private static final int SPINS = 1 << 7; @@ -486,12 +490,14 @@ public final boolean block() { // VarHandle mechanics static final VarHandle ITEM; static final VarHandle NEXT; + static final VarHandle WAITER; static { try { Class tn = DualNode.class; MethodHandles.Lookup l = MethodHandles.lookup(); ITEM = l.findVarHandle(tn, "item", Object.class); NEXT = l.findVarHandle(tn, "next", tn); + WAITER = l.findVarHandle(tn, "waiter", Thread.class); } catch (ReflectiveOperationException e) { throw new ExceptionInInitializerError(e); } @@ -590,7 +596,7 @@ final Object xfer(Object e, long ns) { q = p.next; if (p.isData != haveData && haveData != (m != null)) { if (p.cmpExItem(m, e) == m) { - Thread w = p.waiter; // matched complementary node + Thread w = p.getVolatileWaiter(); // matched complementary node if (p != h && h == cmpExHead(h, (q == null) ? p : q)) h.next = h; // advance head; self-link old LockSupport.unpark(w); diff --git a/src/java.base/share/classes/java/util/concurrent/SynchronousQueue.java b/src/java.base/share/classes/java/util/concurrent/SynchronousQueue.java index 49efe5d5c2c0..230c5d2ec39c 100644 --- a/src/java.base/share/classes/java/util/concurrent/SynchronousQueue.java +++ b/src/java.base/share/classes/java/util/concurrent/SynchronousQueue.java @@ -177,7 +177,7 @@ final Object xferLifo(Object e, long ns) { else if (p.cmpExItem(m, e) != m) p = head; // missed; restart else { // matched complementary node - Thread w = p.waiter; + Thread w = p.getVolatileWaiter(); cmpExHead(p, p.next); LockSupport.unpark(w); break outer; diff --git a/test/jdk/java/lang/Thread/virtual/stress/PingPong.java b/test/jdk/java/lang/Thread/virtual/stress/PingPong.java index 2266d192fcaa..e9aea22e7a3a 100644 --- a/test/jdk/java/lang/Thread/virtual/stress/PingPong.java +++ b/test/jdk/java/lang/Thread/virtual/stress/PingPong.java @@ -35,6 +35,20 @@ * @run main PingPong LTQ 500000 */ +/* + * @test id=sq-no-lse + * @summary Stress test virtual threads with a SynchronousQueue without LSE + * @requires os.arch == "aarch64" & vm.debug != true + * @run main/othervm -XX:-UseLSE PingPong SQ 500000 + */ + +/* + * @test id=ltq-no-lse + * @summary Stress test virtual threads with a LinkedTransferQueue without LSE + * @requires os.arch == "aarch64" & vm.debug != true + * @run main/othervm -XX:-UseLSE PingPong LTQ 500000 + */ + import java.time.Duration; import java.time.Instant; import java.util.concurrent.SynchronousQueue; diff --git a/test/jdk/java/lang/Thread/virtual/stress/Skynet.java b/test/jdk/java/lang/Thread/virtual/stress/Skynet.java index 06f3b5ff235d..38cf00fbca09 100644 --- a/test/jdk/java/lang/Thread/virtual/stress/Skynet.java +++ b/test/jdk/java/lang/Thread/virtual/stress/Skynet.java @@ -37,6 +37,14 @@ * -XX:+ZVerifyOops -XX:ZCollectionInterval=0.01 -Xmx1500m Skynet */ +/* + * @test id=no-lse + * @summary Stress test virtual threads with a variation of the Skynet 1M benchmark without LSE + * @requires os.arch == "aarch64" & vm.continuations + * @requires !vm.debug | vm.gc != "Z" + * @run main/othervm/timeout=1600 -XX:-UseLSE -Xmx1500m Skynet 2 + */ + import java.util.concurrent.BlockingQueue; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.ThreadFactory; diff --git a/test/jdk/java/util/concurrent/BlockingQueue/ProducerConsumerLoops.java b/test/jdk/java/util/concurrent/BlockingQueue/ProducerConsumerLoops.java index 9ea23980740c..c7096de05dbb 100644 --- a/test/jdk/java/util/concurrent/BlockingQueue/ProducerConsumerLoops.java +++ b/test/jdk/java/util/concurrent/BlockingQueue/ProducerConsumerLoops.java @@ -38,6 +38,15 @@ * @library /test/lib */ +/* + * @test id=no-lse + * @bug 4486658 + * @summary multiple producers and consumers using blocking queues without LSE + * @requires os.arch == "aarch64" + * @library /test/lib + * @run main/othervm -XX:-UseLSE ProducerConsumerLoops + */ + import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.NANOSECONDS; diff --git a/test/jdk/java/util/concurrent/BlockingQueue/SingleProducerMultipleConsumerLoops.java b/test/jdk/java/util/concurrent/BlockingQueue/SingleProducerMultipleConsumerLoops.java index 6f672ef7181a..44ee737a7d3f 100644 --- a/test/jdk/java/util/concurrent/BlockingQueue/SingleProducerMultipleConsumerLoops.java +++ b/test/jdk/java/util/concurrent/BlockingQueue/SingleProducerMultipleConsumerLoops.java @@ -38,6 +38,15 @@ * @library /test/lib */ +/* + * @test id=no-lse + * @bug 4486658 + * @summary check ordering for blocking queues with 1 producer and multiple consumers without LSE + * @requires os.arch == "aarch64" + * @library /test/lib + * @run main/othervm -XX:-UseLSE SingleProducerMultipleConsumerLoops + */ + import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.NANOSECONDS; From afc9aa82502d21eb986495d81197da9f8cf2596d Mon Sep 17 00:00:00 2001 From: Xin Liu Date: Mon, 19 Oct 2026 17:41:20 +0000 Subject: [PATCH 112/169] 8394810: Update hotspot-style to reflect renaming of Atomic Reviewed-by: stefank, jiangli --- doc/hotspot-style.html | 4 ++-- doc/hotspot-style.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/hotspot-style.html b/doc/hotspot-style.html index f0e07630a21a..54a7e42949a3 100644 --- a/doc/hotspot-style.html +++ b/doc/hotspot-style.html @@ -268,8 +268,8 @@

      Conventions for Lock-free synchronization context, such as a held mutex or at a safepoint. In such cases the variable should be declared volatile and it should NOT be accessed as a normal C++ lvalue. Rather, access should be -performed via functions from Atomic, such as -Atomic::load, Atomic::store, etc.

      +performed via functions from AtomicAccess, such as +AtomicAccess::load, AtomicAccess::store, etc.

      This special formulation makes it more clear to maintainers that the variable is accessed concurrently in a lock-free manner.

      Source Files

      diff --git a/doc/hotspot-style.md b/doc/hotspot-style.md index a1ee04036325..8cc18f329637 100644 --- a/doc/hotspot-style.md +++ b/doc/hotspot-style.md @@ -140,8 +140,8 @@ change should be done with a "setter" accessor matched to the simple Sometimes variables are accessed concurrently without appropriate synchronization context, such as a held mutex or at a safepoint. In such cases the variable should be declared `volatile` and it should NOT be accessed as a normal C++ lvalue. Rather, -access should be performed via functions from `Atomic`, such as `Atomic::load`, -`Atomic::store`, etc. +access should be performed via functions from `AtomicAccess`, such as `AtomicAccess::load`, +`AtomicAccess::store`, etc. This special formulation makes it more clear to maintainers that the variable is accessed concurrently in a lock-free manner. From e20083ac6ce571d58b6de01761a4ca8e6efefc59 Mon Sep 17 00:00:00 2001 From: Justin Lu Date: Mon, 19 Oct 2026 18:05:22 +0000 Subject: [PATCH 113/169] 8393920: CompactNumberFormat plural rules matching can be possessive Reviewed-by: naoto --- .../java/text/CompactNumberFormat.java | 6 +- .../PluralProcessingTest.java | 62 +++++++++++++++++++ 2 files changed, 65 insertions(+), 3 deletions(-) create mode 100644 test/jdk/java/text/Format/CompactNumberFormat/PluralProcessingTest.java diff --git a/src/java.base/share/classes/java/text/CompactNumberFormat.java b/src/java.base/share/classes/java/text/CompactNumberFormat.java index 1df338bcad72..be453e74ed63 100644 --- a/src/java.base/share/classes/java/text/CompactNumberFormat.java +++ b/src/java.base/share/classes/java/text/CompactNumberFormat.java @@ -1300,17 +1300,17 @@ private Map buildPluralRulesMap() { } // Patterns for plurals syntax validation - private static final String EXPR = "([niftvwe])\\s*(([/%])\\s*(\\d+))*"; + private static final String EXPR = "([niftvwe])\\s*(([/%])\\s*(\\d+))*+"; private static final String RELATION = "(!?=)"; private static final String VALUE_RANGE = "((\\d+)\\.\\.(\\d+)|\\d+)"; private static final String CONDITION = EXPR + "\\s*" + RELATION + "\\s*" + VALUE_RANGE + "\\s*" + - "(,\\s*" + VALUE_RANGE + ")*"; + "(,\\s*" + VALUE_RANGE + ")*+"; private static final Pattern PLURALRULES_PATTERN = Pattern.compile("(zero|one|two|few|many):\\s*" + CONDITION + - "(\\s*(and|or)\\s*" + CONDITION + ")*"); + "(\\s*(and|or)\\s*" + CONDITION + ")*+"); /** * Validates a plural rule. diff --git a/test/jdk/java/text/Format/CompactNumberFormat/PluralProcessingTest.java b/test/jdk/java/text/Format/CompactNumberFormat/PluralProcessingTest.java new file mode 100644 index 000000000000..410a4cb65bca --- /dev/null +++ b/test/jdk/java/text/Format/CompactNumberFormat/PluralProcessingTest.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8393920 + * @summary Ensure that the plural processing in CompactNumberFormat can support its implementation + * limit of 2048 characters, even with a reduced stack size. + * @run junit/othervm -Xss1m -Xint PluralProcessingTest + */ + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.FieldSource; + +import java.text.CompactNumberFormat; +import java.text.DecimalFormatSymbols; +import java.util.List; +import java.util.Locale; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +public class PluralProcessingTest { + + // Plural rules that are excessively long + private static final List LONG_PLURAL_RULES = List.of( + Arguments.argumentSet("range_list", + "one:n=1" + ",1".repeat(1_000)), + Arguments.argumentSet("and_condition", + "one:n=1" + "andn=1".repeat(340)), + Arguments.argumentSet("expr", + "one:n" + "/1".repeat(1_000) + "=1") + ); + + @FieldSource("LONG_PLURAL_RULES") + @ParameterizedTest + void longPluralRulesTest(String rule) { + assertDoesNotThrow(() -> + new CompactNumberFormat("#", DecimalFormatSymbols.getInstance(Locale.ROOT), + new String[]{""}, rule)); + } +} From 52e860fb13696a081bcc80c92f55cfed9d9acba6 Mon Sep 17 00:00:00 2001 From: William Kemper Date: Mon, 19 Oct 2026 18:37:33 +0000 Subject: [PATCH 114/169] 8387790: Shenandoah: Remove degenerated cycles 8338534: Shenandoah: Handle alloc failure differently when immediate garbage is available Reviewed-by: kdnilsen, shade, rkennke --- .../shenandoahAdaptiveHeuristics.cpp | 24 +- .../shenandoahAdaptiveHeuristics.hpp | 9 +- .../heuristics/shenandoahGlobalHeuristics.hpp | 6 +- .../heuristics/shenandoahHeuristics.cpp | 92 ++- .../heuristics/shenandoahHeuristics.hpp | 101 ++-- .../heuristics/shenandoahOldHeuristics.cpp | 14 +- .../heuristics/shenandoahOldHeuristics.hpp | 6 +- .../shenandoahPassiveHeuristics.cpp | 35 +- .../shenandoahPassiveHeuristics.hpp | 4 +- .../shenandoah/mode/shenandoahPassiveMode.cpp | 14 +- .../gc/shenandoah/shenandoahArguments.cpp | 4 +- .../gc/shenandoah/shenandoahBarrierSet.cpp | 3 +- .../shenandoahBarrierSet.inline.hpp | 25 +- .../gc/shenandoah/shenandoahClosures.hpp | 29 +- .../shenandoah/shenandoahClosures.inline.hpp | 43 +- .../gc/shenandoah/shenandoahCollectionSet.cpp | 10 + .../gc/shenandoah/shenandoahCollectionSet.hpp | 3 + .../shenandoah/shenandoahCollectorPolicy.cpp | 168 ++++-- .../shenandoah/shenandoahCollectorPolicy.hpp | 76 ++- .../gc/shenandoah/shenandoahConcurrentGC.cpp | 122 ++-- .../gc/shenandoah/shenandoahConcurrentGC.hpp | 17 +- .../gc/shenandoah/shenandoahControlThread.cpp | 257 +++------ .../gc/shenandoah/shenandoahControlThread.hpp | 30 +- .../gc/shenandoah/shenandoahController.cpp | 164 +++++- .../gc/shenandoah/shenandoahController.hpp | 113 +++- .../gc/shenandoah/shenandoahDegeneratedGC.cpp | 526 ------------------ .../gc/shenandoah/shenandoahDegeneratedGC.hpp | 73 --- .../share/gc/shenandoah/shenandoahFullGC.cpp | 168 +++--- .../share/gc/shenandoah/shenandoahFullGC.hpp | 19 +- .../share/gc/shenandoah/shenandoahGC.cpp | 108 ---- .../share/gc/shenandoah/shenandoahGC.hpp | 28 +- .../gc/shenandoah/shenandoahGeneration.cpp | 52 +- .../gc/shenandoah/shenandoahGeneration.hpp | 9 +- .../shenandoahGenerationalControlThread.cpp | 500 ++++++----------- .../shenandoahGenerationalControlThread.hpp | 53 +- .../shenandoahGenerationalEvacuationTask.cpp | 46 +- .../shenandoahGenerationalEvacuationTask.hpp | 8 +- .../shenandoahGenerationalFullGC.cpp | 5 +- .../shenandoah/shenandoahGenerationalHeap.cpp | 115 ++-- .../shenandoah/shenandoahGenerationalHeap.hpp | 11 +- .../share/gc/shenandoah/shenandoahHeap.cpp | 479 ++++++---------- .../share/gc/shenandoah/shenandoahHeap.hpp | 96 ++-- .../gc/shenandoah/shenandoahHeap.inline.hpp | 105 ++-- .../gc/shenandoah/shenandoahHeapRegion.cpp | 127 ++++- .../gc/shenandoah/shenandoahHeapRegion.hpp | 9 +- .../shenandoahHeapRegionCounters.cpp | 5 +- .../shenandoahHeapRegionCounters.hpp | 2 +- .../shenandoah/shenandoahInPlacePromoter.cpp | 3 - .../share/gc/shenandoah/shenandoahMark.cpp | 33 +- .../share/gc/shenandoah/shenandoahMark.hpp | 2 +- .../gc/shenandoah/shenandoahMark.inline.hpp | 4 +- .../gc/shenandoah/shenandoahMmuTracker.cpp | 12 +- .../gc/shenandoah/shenandoahMmuTracker.hpp | 11 +- .../share/gc/shenandoah/shenandoahNMethod.cpp | 4 +- .../share/gc/shenandoah/shenandoahOldGC.cpp | 11 +- .../share/gc/shenandoah/shenandoahOldGC.hpp | 3 +- .../gc/shenandoah/shenandoahOldGeneration.cpp | 47 +- .../gc/shenandoah/shenandoahOldGeneration.hpp | 30 +- .../share/gc/shenandoah/shenandoahPLAB.cpp | 12 +- .../gc/shenandoah/shenandoahPhaseTimings.cpp | 1 - .../gc/shenandoah/shenandoahPhaseTimings.hpp | 36 +- .../shenandoahReferenceProcessor.cpp | 6 +- .../shenandoah/shenandoahRegulatorThread.cpp | 29 +- .../shenandoah/shenandoahRegulatorThread.hpp | 1 - .../gc/shenandoah/shenandoahRootProcessor.cpp | 9 - .../gc/shenandoah/shenandoahRootProcessor.hpp | 16 - .../shenandoahRootProcessor.inline.hpp | 15 - .../share/gc/shenandoah/shenandoahSTWMark.cpp | 17 +- .../share/gc/shenandoah/shenandoahSTWMark.hpp | 4 +- .../shenandoah/shenandoahScanRemembered.cpp | 10 - .../shenandoah/shenandoahScanRemembered.hpp | 4 +- .../shenandoahScanRemembered.inline.hpp | 3 +- .../shenandoah/shenandoahThreadLocalData.cpp | 1 + .../shenandoah/shenandoahThreadLocalData.hpp | 13 + .../share/gc/shenandoah/shenandoahUtils.cpp | 22 +- .../share/gc/shenandoah/shenandoahUtils.hpp | 15 +- .../gc/shenandoah/shenandoahVMOperations.cpp | 11 - .../gc/shenandoah/shenandoahVMOperations.hpp | 11 - .../gc/shenandoah/shenandoahVerifier.cpp | 39 +- .../gc/shenandoah/shenandoahVerifier.hpp | 1 - .../gc/shenandoah/shenandoahWorkerPolicy.cpp | 25 +- .../gc/shenandoah/shenandoahWorkerPolicy.hpp | 6 +- .../gc/shenandoah/shenandoah_globals.hpp | 21 +- src/hotspot/share/runtime/arguments.cpp | 1 + src/hotspot/share/runtime/vmOperation.hpp | 1 - .../test_shenandoahCollectorPolicy.cpp | 62 +-- .../jtreg/gc/TestAllocHumongousFragment.java | 15 +- .../gc/shenandoah/TestAllocIntArrays.java | 13 +- .../gc/shenandoah/TestAllocObjectArrays.java | 12 +- .../jtreg/gc/shenandoah/TestAllocObjects.java | 13 +- .../TestDynamicSoftMaxHeapSize.java | 11 - .../jtreg/gc/shenandoah/TestHeapUncommit.java | 13 +- .../jtreg/gc/shenandoah/TestJcmdHeapDump.java | 6 - .../jtreg/gc/shenandoah/TestLotsOfCycles.java | 7 - .../gc/shenandoah/TestObjItrWithHeapDump.java | 1 - .../gc/shenandoah/TestRegionSampling.java | 6 - .../jtreg/gc/shenandoah/TestResizeTLAB.java | 16 - .../gc/shenandoah/TestRetainObjects.java | 13 +- .../jtreg/gc/shenandoah/TestSieveObjects.java | 13 +- .../TestSoftMaxHeapSizeAvailableCalc.java | 2 - .../jtreg/gc/shenandoah/TestStringDedup.java | 7 +- .../gc/shenandoah/TestStringDedupStress.java | 7 - .../shenandoah/TestStringInternCleanup.java | 13 +- .../gc/shenandoah/TestVerifyJCStress.java | 3 +- ...=> TestHumongousAllocFailureRecovery.java} | 59 +- ...tPromoteInPlaceDuringAbbreviatedCycle.java | 4 +- .../gc/shenandoah/jni/TestPinnedGarbage.java | 7 +- .../mxbeans/TestChurnNotifications.java | 7 +- .../mxbeans/TestPauseNotifications.java | 9 +- .../options/TestHumongousMoves.java | 6 +- .../gcbasher/TestGCBasherWithShenandoah.java | 15 +- .../stress/gcold/TestGCOldWithShenandoah.java | 57 +- 112 files changed, 1721 insertions(+), 3079 deletions(-) delete mode 100644 src/hotspot/share/gc/shenandoah/shenandoahDegeneratedGC.cpp delete mode 100644 src/hotspot/share/gc/shenandoah/shenandoahDegeneratedGC.hpp delete mode 100644 src/hotspot/share/gc/shenandoah/shenandoahGC.cpp rename test/hotspot/jtreg/gc/shenandoah/generational/{TestHumongousAllocFailureDegeneration.java => TestHumongousAllocFailureRecovery.java} (65%) diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahAdaptiveHeuristics.cpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahAdaptiveHeuristics.cpp index 55a582cd54c2..43277c11eda4 100644 --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahAdaptiveHeuristics.cpp +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahAdaptiveHeuristics.cpp @@ -77,7 +77,7 @@ void ShenandoahAdaptiveHeuristics::post_initialize() { } void ShenandoahAdaptiveHeuristics::compute_headroom_adjustment() { - // The trigger threshold represents mutator available - "head room". + // The trigger threshold represents mutator available - "headroom". // We plan for GC to finish before the amount of allocated memory exceeds trigger threshold. This is the same as saying we // intend to finish GC before the amount of available memory is less than the allocation headroom. Headroom is the planned // safety buffer to allow a small amount of additional allocation to take place in case we were overly optimistic in delaying @@ -152,15 +152,8 @@ void ShenandoahAdaptiveHeuristics::choose_collection_set_from_regiondata(Shenand } } -void ShenandoahAdaptiveHeuristics::add_degenerated_gc_time(double time_at_start, double gc_time) { - // Conservatively add sample into linear model, if this time is above the predicted concurrent gc time - if (_cycles.predict_duration(time_at_start, _margin_of_error_sd) < gc_time) { - _cycles.record_duration(time_at_start, gc_time); - } -} - -void ShenandoahAdaptiveHeuristics::record_success_concurrent() { - ShenandoahHeuristics::record_success_concurrent(); +void ShenandoahAdaptiveHeuristics::record_concurrent_completion() { + ShenandoahHeuristics::record_concurrent_completion(); // We add this time even if it is a shortened cycle. There is a risk that this pulls // the gc time trend down, but it is still a more accurate view than excluding times @@ -213,13 +206,6 @@ void ShenandoahAdaptiveHeuristics::record_success_concurrent() { } } -void ShenandoahAdaptiveHeuristics::record_degenerated(bool is_generational_global) { - ShenandoahHeuristics::record_degenerated(is_generational_global); - if (!is_generational_global) { - add_degenerated_gc_time(_precursor_cycle_start, elapsed_degenerated_cycle_time()); - } -} - bool ShenandoahAdaptiveHeuristics::should_start_gc() { const size_t capacity = ShenandoahHeap::heap()->soft_max_capacity(); const size_t available = _space_info->soft_mutator_available(); @@ -227,7 +213,7 @@ bool ShenandoahAdaptiveHeuristics::should_start_gc() { log_debug(gc, ergo)("should_start_gc calculation: available: " PROPERFMT ", soft_max_capacity: " PROPERFMT, PROPERFMTARGS(available), PROPERFMTARGS(capacity)); - if (_start_gc_is_pending) { + if (_start_gc_is_pending.load_relaxed()) { log_info(gc, ergo)("GC start is already pending"); return true; } @@ -308,7 +294,7 @@ bool ShenandoahAdaptiveHeuristics::trigger_average_allocation_rate(const Shenand // The following trace represents an actual workload, with allocation rates sampled at 10 Hz, the default behavior before // introduction of accelerated allocation rate detection. Though the allocation rate is seen to be increasing at times // 101.907 and 102.007 and 102.108, the newly sampled allocation rate is not enough to trigger GC because the headroom is -// still quite large. In fact, GC is not triggered until time 102.409s, and this GC degenerates. +// still quite large. In fact, GC is not triggered until time 102.409s, and this GC stalls. // // Sample Time (s) Allocation Rate (MB/s) Headroom (GB) // 101.807 0.0 26.93 diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahAdaptiveHeuristics.hpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahAdaptiveHeuristics.hpp index b571f81140c4..8705bf86a54a 100644 --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahAdaptiveHeuristics.hpp +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahAdaptiveHeuristics.hpp @@ -29,7 +29,6 @@ #include "gc/shenandoah/heuristics/shenandoahHeuristics.hpp" #include "gc/shenandoah/shenandoahAllocRate.hpp" #include "gc/shenandoah/shenandoahCycleDuration.hpp" -#include "gc/shenandoah/shenandoahPhaseTimings.hpp" #include "utilities/numberSeq.hpp" @@ -37,8 +36,7 @@ * The adaptive heuristic tracks the allocation behavior and average cycle * time of the application. It attempts to start a cycle with enough time * to complete before the available memory is exhausted. It errors on the - * side of starting cycles early to avoid allocation failures (degenerated - * cycles). + * side of starting cycles early to avoid allocation failures. * * This heuristic limits the number of regions for evacuation such that the * evacuation reserve is respected. This helps it avoid allocation failures @@ -56,8 +54,7 @@ class ShenandoahAdaptiveHeuristics : public ShenandoahHeuristics { // before we need to start the next GC. void start_idle_span() override; - void record_success_concurrent() override; - void record_degenerated(bool is_generational_global) override; + void record_concurrent_completion() override; bool should_start_gc() override; @@ -113,8 +110,6 @@ class ShenandoahAdaptiveHeuristics : public ShenandoahHeuristics { // bytes of headroom at which we should trigger GC size_t _headroom_adjustment; - void add_degenerated_gc_time(double timestamp_at_start, double duration); - // A conservative minimum threshold of free space that we'll try to maintain when possible. // For example, we might trigger a concurrent gc if we are likely to drop below // this threshold, or we might consider this when dynamically resizing generations diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGlobalHeuristics.hpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGlobalHeuristics.hpp index 1e96a665704b..da3998f58543 100644 --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGlobalHeuristics.hpp +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGlobalHeuristics.hpp @@ -159,15 +159,17 @@ class ShenandoahGlobalCSetBudget { class ShenandoahGlobalHeuristics : public ShenandoahGenerationalHeuristics { public: - ShenandoahGlobalHeuristics(ShenandoahGlobalGeneration* generation); + explicit ShenandoahGlobalHeuristics(ShenandoahGlobalGeneration* generation); + +protected: void select_collection_set_regions(ShenandoahCollectionSet* cset, RegionData* data, size_t size, size_t actual_free) override; private: void choose_global_collection_set(ShenandoahCollectionSet* cset, - const ShenandoahHeuristics::RegionData* data, + const RegionData* data, size_t size, size_t actual_free, size_t cur_young_garbage) const; }; diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahHeuristics.cpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahHeuristics.cpp index d0a5d0e0cc10..aa2cb70e0442 100644 --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahHeuristics.cpp +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahHeuristics.cpp @@ -55,12 +55,10 @@ ShenandoahHeuristics::ShenandoahHeuristics(ShenandoahSpaceInfo* space_info) : _most_recent_planned_sleep_interval(0.0), _start_gc_is_pending(false), _declined_trigger_count(0), - _most_recent_declined_trigger_count(0), _space_info(space_info), _region_data(nullptr), _guaranteed_gc_interval(0), - _precursor_cycle_start(os::elapsedTime()), - _cycle_start(_precursor_cycle_start), + _cycle_start(os::elapsedTime()), _last_cycle_end(0), _gc_times_learned(0), _gc_time_penalties(0), @@ -164,17 +162,10 @@ void ShenandoahHeuristics::start_idle_span() { // do nothing } -void ShenandoahHeuristics::record_degenerated_cycle_start(bool out_of_cycle) { - if (out_of_cycle) { - _precursor_cycle_start = _cycle_start = os::elapsedTime(); - } else { - _precursor_cycle_start = _cycle_start; - _cycle_start = os::elapsedTime(); - } -} - void ShenandoahHeuristics::record_cycle_start() { _cycle_start = os::elapsedTime(); + cancel_trigger_request(); + log_debug(gc, ergo)("Declined trigger count at start: %zu", _declined_trigger_count.load_relaxed()); } void ShenandoahHeuristics::record_cycle_end() { @@ -188,7 +179,7 @@ void ShenandoahHeuristics::record_cycle_end() { } bool ShenandoahHeuristics::should_start_gc() { - if (_start_gc_is_pending) { + if (_start_gc_is_pending.load_relaxed()) { log_info(gc, ergo)("GC start is already pending"); return true; } @@ -213,33 +204,15 @@ bool ShenandoahHeuristics::should_start_gc() { return false; } -bool ShenandoahHeuristics::should_degenerate_cycle() { - return ShenandoahHeap::heap()->shenandoah_policy()->consecutive_degenerated_gc_count() <= ShenandoahFullGCThreshold; -} - void ShenandoahHeuristics::adjust_penalty(intx step) { - assert(0 <= _gc_time_penalties && _gc_time_penalties <= 100, - "In range before adjustment: %zd", _gc_time_penalties); - if ((_most_recent_declined_trigger_count <= Penalty_Free_Declinations) && (step > 0)) { - // Don't penalize if heuristics are not responsible for a negative outcome. Allow Penalty_Free_Declinations following - // previous GC for self calibration without penalty. - step = 0; - } - - intx new_val = _gc_time_penalties + step; - if (new_val < 0) { - new_val = 0; + const intx adjusted = clamp(_gc_time_penalties + step, Min_Penalty, Max_Penalty); + if (adjusted != _gc_time_penalties) { + _gc_time_penalties = adjusted; + log_info(gc, ergo)("Adjusted heuristic penalty: %zd", _gc_time_penalties); } - if (new_val > 100) { - new_val = 100; - } - _gc_time_penalties = new_val; - - assert(0 <= _gc_time_penalties && _gc_time_penalties <= 100, - "In range after adjustment: %zd", _gc_time_penalties); } -void ShenandoahHeuristics::log_trigger(const char* fmt, ...) { +void ShenandoahHeuristics::log_trigger(const char* fmt, ...) const { LogTarget(Info, gc) lt; if (lt.is_enabled()) { ResourceMark rm; @@ -257,30 +230,36 @@ void ShenandoahHeuristics::log_trigger(const char* fmt, ...) { } } -void ShenandoahHeuristics::record_success_concurrent() { - _gc_times_learned++; - - adjust_penalty(Concurrent_Adjust); +ShenandoahHeuristics::PenaltyData ShenandoahHeuristics::consume_penalty_data() { + const size_t declines = _declined_trigger_count.exchange(0, memory_order_relaxed); + const bool stalls = _allocation_stalls.exchange(false, memory_order_relaxed); + log_debug(gc, ergo)("Declined trigger count at end: %zu", declines); + return { declines, stalls }; } -void ShenandoahHeuristics::record_degenerated(bool is_generational_global) { - - if (!is_generational_global) { - // We don't penalize generational GC heuristics for global GC because heuristics predict based on assumption of young GC. - _most_recent_declined_trigger_count = _declined_trigger_count; - _declined_trigger_count = 0; - } else { - _most_recent_declined_trigger_count = _declined_trigger_count = 0; +void ShenandoahHeuristics::record_concurrent_completion() { + _gc_times_learned++; + const PenaltyData data = consume_penalty_data(); + if (!data.stalls) { + adjust_penalty(Concurrent_Adjust); + } else if (data.declined_triggers > Penalty_Free_Declinations) { + // There were stalls _and_ the trigger was lazy, penalize it. + adjust_penalty(Stall_Penalty); } - adjust_penalty(Degenerated_Penalty); } -void ShenandoahHeuristics::record_success_full() { - adjust_penalty(Full_Penalty); +void ShenandoahHeuristics::record_allocation_stall() { + _allocation_stalls.store_relaxed(true); } -void ShenandoahHeuristics::record_allocation_failure_gc() { - // Do nothing. +void ShenandoahHeuristics::record_full_gc(GCCause::Cause cause) { + const PenaltyData data = consume_penalty_data(); + if (cause == GCCause::_shenandoah_upgrade_to_full_gc) { + adjust_penalty(Full_Penalty); + } else if (data.stalls && data.declined_triggers > Penalty_Free_Declinations) { + // There were stalls _and_ the trigger was lazy, penalize it. + adjust_penalty(Stall_Penalty); + } } void ShenandoahHeuristics::record_requested_gc() { @@ -309,13 +288,6 @@ double ShenandoahHeuristics::elapsed_cycle_time() const { return os::elapsedTime() - _cycle_start; } - -// Includes the time spent in abandoned concurrent GC cycle that may have triggered this degenerated cycle. -double ShenandoahHeuristics::elapsed_degenerated_cycle_time() const { - double now = os::elapsedTime(); - return now - _precursor_cycle_start; -} - #ifdef ASSERT void ShenandoahHeuristics::assert_humongous_mark_consistency(ShenandoahHeapRegion* region) { assert(region->is_humongous(), "Region %zu must be humongous", region->index()); diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahHeuristics.hpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahHeuristics.hpp index 1deb98c3f8a9..21bad3058166 100644 --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahHeuristics.hpp +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahHeuristics.hpp @@ -26,11 +26,11 @@ #ifndef SHARE_GC_SHENANDOAH_HEURISTICS_SHENANDOAHHEURISTICS_HPP #define SHARE_GC_SHENANDOAH_HEURISTICS_SHENANDOAHHEURISTICS_HPP +#include "gc/shared/gcCause.hpp" #include "gc/shenandoah/heuristics/shenandoahSpaceInfo.hpp" #include "gc/shenandoah/shenandoahSharedVariables.hpp" #include "memory/allocation.hpp" #include "runtime/globals_extension.hpp" -#include "utilities/numberSeq.hpp" #define SHENANDOAH_ERGO_DISABLE_FLAG(name) \ do { \ @@ -66,12 +66,14 @@ class ShenandoahHeapRegion; * cycle. */ class ShenandoahHeuristics : public CHeapObj { - static const intx Concurrent_Adjust = -1; // recover from penalties - static const intx Degenerated_Penalty = 10; // how much to penalize average GC duration history on Degenerated GC - static const intx Full_Penalty = 20; // how much to penalize average GC duration history on Full GC + static constexpr intx Concurrent_Adjust = -1; // recover from penalties + static constexpr intx Stall_Penalty = 10; // how much to penalize average GC duration history on allocation stall + static constexpr intx Full_Penalty = 20; // how much to penalize average GC duration history on Full GC + static constexpr intx Min_Penalty = 0; + static constexpr intx Max_Penalty = 100; // How many times can I decline a trigger opportunity without being penalized for excessive idle span before trigger? - static const size_t Penalty_Free_Declinations = 16; + static constexpr size_t Penalty_Free_Declinations = 16; #ifdef ASSERT enum UnionTag { @@ -79,24 +81,34 @@ class ShenandoahHeuristics : public CHeapObj { }; #endif -private: double _most_recent_trigger_evaluation_time; double _most_recent_planned_sleep_interval; protected: static constexpr uint Moving_Average_Samples = 10; // Number of samples to store in moving averages - bool _start_gc_is_pending; // True denotes that GC has been triggered, so no need to trigger again. - size_t _declined_trigger_count; // This counts how many times since previous GC finished that this - // heuristic has answered false to should_start_gc(). - size_t _most_recent_declined_trigger_count; - // This represents the value of _declined_trigger_count as captured at the - // moment the most recent GC effort was triggered. In case the most recent - // concurrent GC effort degenerates, the value of this variable allows us to - // differentiate between degeneration because heuristic was overly optimistic - // in delaying the trigger vs. degeneration for other reasons (such as the - // most recent GC triggered "immediately" after previous GC finished, but the - // free headroom has already been depleted). + // True denotes that GC has been triggered, so no need to trigger again. + shenandoah_padding(0); + Atomic _start_gc_is_pending; + + // This counts how many times since previous GC finished that this heuristic has answered false to should_start_gc(). + // If allocations stall during a concurrent gc and this is above Penalty_Free_Declinations at the end of the cycle, + // the heuristics will be 'penalized' in a way that will make them start the next concurrent cycle sooner. Also + // note that once the trigger has been accepted, _start_gc_is_pending will be set and subsequent attempts to evaluate + // the trigger conditions will return early and will not increase _declined_trigger_count. This is written to + // by both the regulator and control thread, read by control thread. + shenandoah_padding(1); + Atomic _declined_trigger_count; + shenandoah_padding(2); + Atomic _allocation_stalls; + shenandoah_padding(3); + + // Snapshot declined trigger count and alloc stalls to compute appropriate penalties (if warranted) + struct PenaltyData { + size_t declined_triggers; + bool stalls; + }; + class RegionData { private: ShenandoahHeapRegion* _region; @@ -110,7 +122,7 @@ class ShenandoahHeuristics : public CHeapObj { public: - inline void clear() { + void clear() { _region = nullptr; _region_union._garbage = 0; #ifdef ASSERT @@ -118,7 +130,7 @@ class ShenandoahHeuristics : public CHeapObj { #endif } - inline void set_region_and_garbage(ShenandoahHeapRegion* region, size_t garbage) { + void set_region_and_garbage(ShenandoahHeapRegion* region, size_t garbage) { _region = region; _region_union._garbage = garbage; #ifdef ASSERT @@ -126,7 +138,7 @@ class ShenandoahHeuristics : public CHeapObj { #endif } - inline void set_region_and_livedata(ShenandoahHeapRegion* region, size_t live) { + void set_region_and_livedata(ShenandoahHeapRegion* region, size_t live) { _region = region; _region_union._live_data = live; #ifdef ASSERT @@ -134,24 +146,24 @@ class ShenandoahHeuristics : public CHeapObj { #endif } - inline void update_livedata(size_t live) { + void update_livedata(size_t live) { _region_union._live_data = live; #ifdef ASSERT _union_tag = is_live_data; #endif } - inline ShenandoahHeapRegion* get_region() const { + ShenandoahHeapRegion* get_region() const { assert(_union_tag != is_uninitialized, "Cannot fetch region from uninitialized RegionData"); return _region; } - inline size_t get_garbage() const { + size_t get_garbage() const { assert(_union_tag == is_garbage, "Invalid union fetch"); return _region_union._garbage; } - inline size_t get_livedata() const { + size_t get_livedata() const { assert(_union_tag == is_live_data, "Invalid union fetch"); return _region_union._live_data; } @@ -176,7 +188,6 @@ class ShenandoahHeuristics : public CHeapObj { size_t _guaranteed_gc_interval; - double _precursor_cycle_start; double _cycle_start; double _last_cycle_end; @@ -198,19 +209,15 @@ class ShenandoahHeuristics : public CHeapObj { virtual void adjust_penalty(intx step); - inline void accept_trigger() { - _start_gc_is_pending = true; - } - - inline void decline_trigger() { - _declined_trigger_count++; + void decline_trigger() { + _declined_trigger_count.add_then_fetch(1UL, memory_order_relaxed); } - inline double get_most_recent_wake_time() const { + double get_most_recent_wake_time() const { return _most_recent_trigger_evaluation_time; } - inline double get_planned_sleep_interval() const { + double get_planned_sleep_interval() const { return _most_recent_planned_sleep_interval; } @@ -236,9 +243,6 @@ class ShenandoahHeuristics : public CHeapObj { } virtual void record_cycle_start(); - - void record_degenerated_cycle_start(bool out_of_cycle); - virtual void record_cycle_end(); void update_should_start_query_times(double now, double planned_sleep_interval) { @@ -246,24 +250,24 @@ class ShenandoahHeuristics : public CHeapObj { _most_recent_planned_sleep_interval = planned_sleep_interval; } - virtual bool should_start_gc(); - - inline void cancel_trigger_request() { - _start_gc_is_pending = false; + void accept_trigger() { + _start_gc_is_pending.store_relaxed(true); } - virtual bool should_degenerate_cycle(); - - virtual void record_success_concurrent(); + void cancel_trigger_request() { + _start_gc_is_pending.store_relaxed(false); + } - virtual void record_degenerated(bool is_generational_global); + virtual bool should_start_gc(); - virtual void record_success_full(); + virtual void record_concurrent_completion(); - virtual void record_allocation_failure_gc(); + virtual void record_full_gc(GCCause::Cause cause); virtual void record_requested_gc(); + virtual void record_allocation_stall(); + // Choose the collection set, returning the number of regions that need to be transferred to the old reserve from the young // reserve in order to effectively evacuate the chosen collection set. In non-generational mode, the return value is 0. virtual void choose_collection_set(ShenandoahCollectionSet* collection_set); @@ -281,10 +285,11 @@ class ShenandoahHeuristics : public CHeapObj { virtual void post_initialize(); double elapsed_cycle_time() const; - double elapsed_degenerated_cycle_time() const; // Format prefix and emit log message indicating a GC cycle hs been triggered - void log_trigger(const char* fmt, ...) ATTRIBUTE_PRINTF(2, 3); + void log_trigger(const char* fmt, ...) const ATTRIBUTE_PRINTF(2, 3); + + PenaltyData consume_penalty_data(); DEBUG_ONLY(static void assert_humongous_mark_consistency(ShenandoahHeapRegion* region)); }; diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahOldHeuristics.cpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahOldHeuristics.cpp index 1b956b6994a3..6ce679555d8c 100644 --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahOldHeuristics.cpp +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahOldHeuristics.cpp @@ -848,22 +848,16 @@ void ShenandoahOldHeuristics::adjust_old_garbage_threshold() { } } -void ShenandoahOldHeuristics::record_success_concurrent() { +void ShenandoahOldHeuristics::record_concurrent_completion() { // Forget any triggers that occurred while OLD GC was ongoing. If we really need to start another, it will retrigger. clear_triggers(); - this->ShenandoahHeuristics::record_success_concurrent(); + this->ShenandoahHeuristics::record_concurrent_completion(); } -void ShenandoahOldHeuristics::record_degenerated(bool is_generational_global) { +void ShenandoahOldHeuristics::record_full_gc(GCCause::Cause cause) { // Forget any triggers that occurred while OLD GC was ongoing. If we really need to start another, it will retrigger. clear_triggers(); - this->ShenandoahHeuristics::record_degenerated(is_generational_global); -} - -void ShenandoahOldHeuristics::record_success_full() { - // Forget any triggers that occurred while OLD GC was ongoing. If we really need to start another, it will retrigger. - clear_triggers(); - this->ShenandoahHeuristics::record_success_full(); + this->ShenandoahHeuristics::record_full_gc(cause); } const char* ShenandoahOldHeuristics::name() { diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahOldHeuristics.hpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahOldHeuristics.hpp index 37d186a991f9..63a4cbed7069 100644 --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahOldHeuristics.hpp +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahOldHeuristics.hpp @@ -244,11 +244,9 @@ class ShenandoahOldHeuristics : public ShenandoahHeuristics { // Returns true if the old generation needs to prepare for marking, or continue marking. bool should_resume_old_cycle(); - void record_success_concurrent() override; + void record_concurrent_completion() override; - void record_degenerated(bool is_generational_global) override; - - void record_success_full() override; + void record_full_gc(GCCause::Cause cause) override; const char* name() override; diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahPassiveHeuristics.cpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahPassiveHeuristics.cpp index b5e9cc433ea6..f1619c2e3848 100644 --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahPassiveHeuristics.cpp +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahPassiveHeuristics.cpp @@ -1,6 +1,6 @@ /* * Copyright (c) 2018, 2019, Red Hat, Inc. All rights reserved. - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,11 +25,8 @@ #include "gc/shenandoah/heuristics/shenandoahPassiveHeuristics.hpp" -#include "gc/shenandoah/shenandoahCollectionSet.hpp" #include "gc/shenandoah/shenandoahHeap.inline.hpp" #include "gc/shenandoah/shenandoahHeapRegion.inline.hpp" -#include "logging/log.hpp" -#include "logging/logTag.hpp" ShenandoahPassiveHeuristics::ShenandoahPassiveHeuristics(ShenandoahSpaceInfo* space_info) : ShenandoahHeuristics(space_info) {} @@ -45,35 +42,9 @@ bool ShenandoahPassiveHeuristics::should_unload_classes() { return can_unload_classes(); } -bool ShenandoahPassiveHeuristics::should_degenerate_cycle() { - // Always fail to Degenerated GC, if enabled - return ShenandoahDegeneratedGC; -} - void ShenandoahPassiveHeuristics::choose_collection_set_from_regiondata(ShenandoahCollectionSet* cset, RegionData* data, size_t size, size_t actual_free) { - assert(ShenandoahDegeneratedGC, "This path is only taken for Degenerated GC"); - - // Do not select too large CSet that would overflow the available free space. - // Take at least the entire evacuation reserve, and be free to overflow to free space. - size_t max_capacity = _space_info->max_capacity(); - size_t available = MAX2(max_capacity / 100 * ShenandoahEvacReserve, actual_free); - size_t max_cset = (size_t)(available / ShenandoahEvacWaste); - - log_info(gc, ergo)("CSet Selection. Actual Free: %zu%s, Max CSet: %zu%s", - byte_size_in_proper_unit(actual_free), proper_unit_for_byte_size(actual_free), - byte_size_in_proper_unit(max_cset), proper_unit_for_byte_size(max_cset)); - - size_t threshold = ShenandoahHeapRegion::region_size_bytes() * ShenandoahGarbageThreshold / 100; - - size_t live_cset = 0; - for (size_t idx = 0; idx < size; idx++) { - ShenandoahHeapRegion* r = data[idx].get_region(); - size_t new_cset = live_cset + r->get_live_data_bytes(); - if (new_cset < max_cset && r->garbage() > threshold) { - live_cset = new_cset; - cset->add_region(r); - } - } + // Passive mode only runs full GCs. + ShouldNotReachHere(); } diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahPassiveHeuristics.hpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahPassiveHeuristics.hpp index 3cb85f5d05f0..809c8df60036 100644 --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahPassiveHeuristics.hpp +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahPassiveHeuristics.hpp @@ -29,7 +29,7 @@ /* * The passive heuristic is for use only with the passive mode. In - * the passive mode, Shenandoah only performs STW (i.e., degenerated) + * the passive mode, Shenandoah only performs STW (i.e., full) * collections. All the barriers are disabled and there are no concurrent * activities. Therefore, this heuristic _never_ triggers a cycle. It * will select regions for evacuation based on ShenandoahEvacReserve, @@ -44,8 +44,6 @@ class ShenandoahPassiveHeuristics : public ShenandoahHeuristics { bool should_unload_classes() override; - bool should_degenerate_cycle() override; - void choose_collection_set_from_regiondata(ShenandoahCollectionSet* set, RegionData* data, size_t data_size, size_t free) override; diff --git a/src/hotspot/share/gc/shenandoah/mode/shenandoahPassiveMode.cpp b/src/hotspot/share/gc/shenandoah/mode/shenandoahPassiveMode.cpp index a880baccdf7d..481e28e74272 100644 --- a/src/hotspot/share/gc/shenandoah/mode/shenandoahPassiveMode.cpp +++ b/src/hotspot/share/gc/shenandoah/mode/shenandoahPassiveMode.cpp @@ -22,26 +22,20 @@ * */ -#include "gc/shenandoah/heuristics/shenandoahHeuristics.hpp" #include "gc/shenandoah/heuristics/shenandoahPassiveHeuristics.hpp" #include "gc/shenandoah/heuristics/shenandoahSpaceInfo.hpp" #include "gc/shenandoah/mode/shenandoahPassiveMode.hpp" #include "gc/shenandoah/shenandoahHeap.inline.hpp" -#include "logging/log.hpp" -#include "logging/logTag.hpp" #include "runtime/globals_extension.hpp" -#include "runtime/java.hpp" void ShenandoahPassiveMode::initialize_flags() const { // Do not allow concurrent cycles. FLAG_SET_DEFAULT(ExplicitGCInvokesConcurrent, false); - // No need for evacuation reserve with Full GC, only for Degenerated GC. - if (!ShenandoahDegeneratedGC) { - if (FLAG_IS_DEFAULT(ShenandoahEvacReserve)) { - log_info(gc)("Heuristics sets -XX:ShenandoahEvacReserve=0"); - FLAG_SET_DEFAULT(ShenandoahEvacReserve, 0); - } + // No need for evacuation reserve with Full GC + if (FLAG_IS_DEFAULT(ShenandoahEvacReserve)) { + log_info(gc)("Heuristics sets -XX:ShenandoahEvacReserve=0"); + FLAG_SET_DEFAULT(ShenandoahEvacReserve, 0); } // Disable known barriers by default. diff --git a/src/hotspot/share/gc/shenandoah/shenandoahArguments.cpp b/src/hotspot/share/gc/shenandoah/shenandoahArguments.cpp index f980c1f88c88..87594c85a591 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahArguments.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahArguments.cpp @@ -243,8 +243,8 @@ void ShenandoahArguments::initialize_alignments() { CollectedHeap* ShenandoahArguments::create_heap() { if (strcmp(ShenandoahGCMode, "generational") != 0) { // Not generational - return new ShenandoahHeap(new ShenandoahCollectorPolicy()); + return new ShenandoahHeap(); } else { - return new ShenandoahGenerationalHeap(new ShenandoahCollectorPolicy()); + return new ShenandoahGenerationalHeap(); } } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.cpp b/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.cpp index f8eff1ddf34e..3d43167aeac3 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.cpp @@ -107,8 +107,7 @@ void ShenandoahBarrierSet::on_slowpath_allocation_exit(JavaThread* thread, oop n if (ReduceInitialCardMarks && ShenandoahCardBarrier && !ShenandoahHeap::heap()->is_in_young(new_obj)) { log_debug(gc)("Newly allocated object (" PTR_FORMAT ") is not in the young generation", p2i(new_obj)); // This can happen when an object is newly allocated, but we come to a safepoint before returning - // the object. If the safepoint runs a degenerated cycle that is upgraded to a full GC, this object - // will have survived two GC cycles. If the tenuring age is very low (1), this object may be promoted. + // the object. If the safepoint runs a full GC, and the tenuring age is very low (1), this object may be promoted. // In this case, we have an allocated object, but it has received no stores yet. If card marking barriers // have been elided, we could end up with an object in old holding pointers to young that won't be in // the remembered set. The solution here is conservative, but this problem should be rare, and it will diff --git a/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.inline.hpp b/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.inline.hpp index 33823ad1d8f4..acbb189b75e1 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.inline.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.inline.hpp @@ -34,6 +34,7 @@ #include "gc/shenandoah/shenandoahAsserts.hpp" #include "gc/shenandoah/shenandoahCardTable.hpp" #include "gc/shenandoah/shenandoahCollectionSet.inline.hpp" +#include "gc/shenandoah/shenandoahForwarding.hpp" #include "gc/shenandoah/shenandoahGeneration.hpp" #include "gc/shenandoah/shenandoahHeap.inline.hpp" #include "gc/shenandoah/shenandoahHeapRegion.hpp" @@ -155,7 +156,7 @@ template inline oop ShenandoahBarrierSet::oop_load_post(DecoratorSet decorators, oop value, T* addr) { assert((decorators & ON_UNKNOWN_OOP_REF) == 0, "Reference strength must be known"); - shenandoah_assert_not_in_cset_loc_except(addr, !is_heap_access(decorators) || _heap->cancelled_gc()); + shenandoah_assert_not_in_cset_loc_except(addr, !is_heap_access(decorators) || _heap->cancelled_gc() || _heap->has_self_forwarded_objects()); // Perform LRB to handle evacuation and possibly weak loads. value = load_reference_barrier(decorators, value, addr); @@ -172,9 +173,9 @@ template inline void ShenandoahBarrierSet::oop_store_pre(DecoratorSet decorators, T* addr, oop new_value) { assert((decorators & ON_UNKNOWN_OOP_REF) == 0, "Reference strength must be known"); - shenandoah_assert_not_in_cset_loc_except(addr, !is_heap_access(decorators) || _heap->cancelled_gc()); - shenandoah_assert_not_in_cset_except(nullptr, new_value, new_value == nullptr || _heap->cancelled_gc()); - shenandoah_assert_not_forwarded_except(nullptr, new_value, new_value == nullptr || _heap->cancelled_gc()); + shenandoah_assert_not_in_cset_loc_except(addr, !is_heap_access(decorators) || _heap->cancelled_gc() || _heap->has_self_forwarded_objects()); + shenandoah_assert_not_in_cset_except(nullptr, new_value, new_value == nullptr || ShenandoahForwarding::is_self_forwarded(new_value)); + shenandoah_assert_not_forwarded_except(nullptr, new_value, new_value == nullptr || ShenandoahForwarding::is_self_forwarded(new_value)); shenandoah_assert_marked_if(nullptr, new_value, !CompressedOops::is_null(new_value) && @@ -199,11 +200,11 @@ inline void ShenandoahBarrierSet::oop_cmpxchg_pre(DecoratorSet decorators, T* ad assert((decorators & ON_UNKNOWN_OOP_REF) == 0, "CAS should have resolved ref strength"); assert((decorators & ON_STRONG_OOP_REF) != 0, "CAS only for strong refs"); - shenandoah_assert_not_in_cset_loc_except(addr, !is_heap_access(decorators) || _heap->cancelled_gc()); - shenandoah_assert_not_in_cset_except(nullptr, compare_value, compare_value == nullptr || _heap->cancelled_gc()); - shenandoah_assert_not_in_cset_except(nullptr, new_value, new_value == nullptr || _heap->cancelled_gc()); - shenandoah_assert_not_forwarded_except(addr, compare_value, compare_value == nullptr || _heap->cancelled_gc()); - shenandoah_assert_not_forwarded_except(addr, new_value, new_value == nullptr || _heap->cancelled_gc()); + shenandoah_assert_not_in_cset_loc_except(addr, !is_heap_access(decorators) || _heap->cancelled_gc() || _heap->has_self_forwarded_objects()); + shenandoah_assert_not_in_cset_except(nullptr, compare_value, compare_value == nullptr || ShenandoahForwarding::is_self_forwarded(compare_value)); + shenandoah_assert_not_in_cset_except(nullptr, new_value, new_value == nullptr || ShenandoahForwarding::is_self_forwarded(new_value)); + shenandoah_assert_not_forwarded_except(addr, compare_value, compare_value == nullptr || ShenandoahForwarding::is_self_forwarded(compare_value)); + shenandoah_assert_not_forwarded_except(addr, new_value, new_value == nullptr || ShenandoahForwarding::is_self_forwarded(new_value)); // Handle the previous value through SATB, as we are about to perform the store. oop prev = RawAccess<>::oop_load(addr); @@ -221,9 +222,9 @@ inline void ShenandoahBarrierSet::oop_xchg_pre(DecoratorSet decorators, T* addr, assert((decorators & ON_UNKNOWN_OOP_REF) == 0, "XCHG should have resolved ref strength"); assert((decorators & ON_STRONG_OOP_REF) != 0, "XCHG only for strong refs"); - shenandoah_assert_not_in_cset_loc_except(addr, !is_heap_access(decorators) || _heap->cancelled_gc()); - shenandoah_assert_not_in_cset_except(nullptr, new_value, new_value == nullptr || _heap->cancelled_gc()); - shenandoah_assert_not_forwarded_except(addr, new_value, new_value == nullptr || _heap->cancelled_gc()); + shenandoah_assert_not_in_cset_loc_except(addr, !is_heap_access(decorators) || _heap->cancelled_gc() || _heap->has_self_forwarded_objects()); + shenandoah_assert_not_in_cset_except(nullptr, new_value, new_value == nullptr || ShenandoahForwarding::is_self_forwarded(new_value)); + shenandoah_assert_not_forwarded_except(addr, new_value, new_value == nullptr || ShenandoahForwarding::is_self_forwarded(new_value)); // Handle the previous value through SATB, as we are about to perform the store. oop prev = RawAccess<>::oop_load(addr); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahClosures.hpp b/src/hotspot/share/gc/shenandoah/shenandoahClosures.hpp index 58e47841819f..a9d3e5a3d9bf 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahClosures.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahClosures.hpp @@ -204,22 +204,8 @@ class ShenandoahCleanUpdateWeakOopsClosure : public OopClosure { // ========= Update References // -template -class ShenandoahMarkUpdateRefsClosure : public ShenandoahMarkRefsSuperClosure { -private: - template - inline void work(T* p); - -public: - ShenandoahMarkUpdateRefsClosure(ShenandoahObjToScanQueue* q, ShenandoahReferenceProcessor* rp, ShenandoahObjToScanQueue* old_q); - - virtual void do_oop(narrowOop* p) { work(p); } - virtual void do_oop(oop* p) { work(p); } -}; - -class ShenandoahUpdateRefsSuperClosure : public ShenandoahSuperClosure {}; - -class ShenandoahNonConcUpdateRefsClosure : public ShenandoahUpdateRefsSuperClosure { +template +class ShenandoahUpdateRefsClosure : public ShenandoahSuperClosure { private: template inline void work(T* p); @@ -229,17 +215,6 @@ class ShenandoahNonConcUpdateRefsClosure : public ShenandoahUpdateRefsSuperClosu virtual void do_oop(oop* p) { work(p); } }; -class ShenandoahConcUpdateRefsClosure : public ShenandoahUpdateRefsSuperClosure { -private: - template - inline void work(T* p); - -public: - virtual void do_oop(narrowOop* p) { work(p); } - virtual void do_oop(oop* p) { work(p); } -}; - - class ShenandoahFlushSATB : public ThreadClosure { private: SATBMarkQueueSet& _satb_qset; diff --git a/src/hotspot/share/gc/shenandoah/shenandoahClosures.inline.hpp b/src/hotspot/share/gc/shenandoah/shenandoahClosures.inline.hpp index 16280d35c5d5..0ff970ae9362 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahClosures.inline.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahClosures.inline.hpp @@ -201,33 +201,26 @@ void ShenandoahCleanUpdateWeakOopsClosure::do_oo // // ========= Update References // - -template -ShenandoahMarkUpdateRefsClosure::ShenandoahMarkUpdateRefsClosure(ShenandoahObjToScanQueue* q, - ShenandoahReferenceProcessor* rp, - ShenandoahObjToScanQueue* old_q) : - ShenandoahMarkRefsSuperClosure(q, rp, old_q) { - assert(_heap->is_stw_gc_in_progress(), "Can only be used for STW GC"); -} - -template -template -inline void ShenandoahMarkUpdateRefsClosure::work(T* p) { - // Update the location - _heap->non_conc_update_with_forwarded(p); - - // ...then do the usual thing - ShenandoahMarkRefsSuperClosure::work(p); -} - +template template -inline void ShenandoahNonConcUpdateRefsClosure::work(T* p) { - _heap->non_conc_update_with_forwarded(p); -} +void ShenandoahUpdateRefsClosure::work(T* p) { + T o = RawAccess<>::oop_load(p); + if (!CompressedOops::is_null(o)) { + oop obj = CompressedOops::decode_not_null(o); + if (_heap->in_collection_set(obj)) { + shenandoah_assert_forwarded(p, obj); + oop resolved = ShenandoahForwarding::forwardee(obj); + shenandoah_assert_not_in_cset_except(p, resolved, _heap->cancelled_gc() || _heap->has_self_forwarded_objects()); -template -inline void ShenandoahConcUpdateRefsClosure::work(T* p) { - _heap->conc_update_with_forwarded(p); + if (resolved != obj) { + if (CONCURRENT) { + _heap->atomic_update_oop(resolved, p, o); + } else { + RawAccess::oop_store(p, resolved); + } + } + } + } } inline void ShenandoahFlushSATB::do_thread(Thread* thread) { diff --git a/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.cpp b/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.cpp index d2a489a15bee..de816149faf2 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.cpp @@ -187,6 +187,16 @@ ShenandoahHeapRegion* ShenandoahCollectionSet::next() { return nullptr; } +bool ShenandoahCollectionSet::all_regions_claimed() const { + const size_t max = _heap->num_regions(); + for (size_t index = _current_index.load_relaxed(); index < max; index++) { + if (is_in(index)) { + return false; + } + } + return true; +} + void ShenandoahCollectionSet::print_on(outputStream* out) const { out->print_cr("Collection Set: Regions: " "%zu, Garbage: %zu%s, Live: %zu%s, Used: %zu%s", count(), diff --git a/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.hpp b/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.hpp index 7722423709d9..eec93f5b0ac2 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.hpp @@ -84,6 +84,9 @@ class ShenandoahCollectionSet : public CHeapObj { // Single-thread version ShenandoahHeapRegion* next(); + // Not MT safe, use to test if gc workers processed all regions + bool all_regions_claimed() const; + size_t count() const { return _region_count; } bool is_empty() const { return _region_count == 0; } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahCollectorPolicy.cpp b/src/hotspot/share/gc/shenandoah/shenandoahCollectorPolicy.cpp index 0eaf91f38fff..4d7d7992111c 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahCollectorPolicy.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahCollectorPolicy.cpp @@ -25,27 +25,29 @@ */ +#include "gc/shared/gc_globals.hpp" +#include "gc/shared/plab.hpp" +#include "gc/shenandoah/mode/shenandoahMode.hpp" #include "gc/shenandoah/shenandoahCollectorPolicy.hpp" -#include "gc/shenandoah/shenandoahGC.hpp" -#include "gc/shenandoah/shenandoahHeap.inline.hpp" -#include "runtime/os.hpp" - -ShenandoahCollectorPolicy::ShenandoahCollectorPolicy() : +#include "gc/shenandoah/shenandoahController.hpp" +#include "gc/shenandoah/shenandoahHeapRegion.inline.hpp" +#include "gc/shenandoah/shenandoahThreadLocalData.hpp" +#include "logging/log.hpp" +#include "utilities/copy.hpp" +#include "utilities/ostream.hpp" + +ShenandoahCollectorPolicy::ShenandoahCollectorPolicy(ShenandoahMode* mode) : _success_concurrent_gcs(0), _abbreviated_concurrent_gcs(0), - _success_degenerated_gcs(0), - _abbreviated_degenerated_gcs(0), + _last_whole_heap_gc_id(0), _success_full_gcs(0), - _consecutive_degenerated_gcs(0), - _consecutive_degenerated_gcs_without_progress(0), _consecutive_young_gcs(0), _mixed_gcs(0), _success_old_gcs(0), _interrupted_old_gcs(0), - _alloc_failure_degenerated(0), - _alloc_failure_degenerated_upgrade_to_full(0), - _alloc_failure_full(0) { - Copy::zero_to_bytes(_degen_point_counts, sizeof(size_t) * ShenandoahGC::_DEGENERATED_LIMIT); + _alloc_failure_full(0), + _mode(mode) { + Copy::zero_to_bytes(_stall_counts, sizeof(size_t) * ShenandoahController::PHASE_LIMIT); Copy::zero_to_bytes(_collection_cause_counts, sizeof(size_t) * GCCause::_last_gc_cause); _tracer = new ShenandoahTracer(); @@ -60,21 +62,19 @@ void ShenandoahCollectorPolicy::record_alloc_failure_to_full() { _alloc_failure_full++; } -void ShenandoahCollectorPolicy::record_alloc_failure_to_degenerated(ShenandoahGC::ShenandoahDegenPoint point) { - assert(point < ShenandoahGC::_DEGENERATED_LIMIT, "sanity"); - _alloc_failure_degenerated++; - _degen_point_counts[point]++; -} - -void ShenandoahCollectorPolicy::record_degenerated_upgrade_to_full() { - reset_consecutive_degenerated_gcs(); - _alloc_failure_degenerated_upgrade_to_full++; +void ShenandoahCollectorPolicy::record_allocation_stall(ShenandoahController::ShenandoahCollectorPhase phase) { + assert(phase < ShenandoahController::PHASE_LIMIT, "Invalid phase: %d", phase); + assert(ShenandoahController::UNSET <= phase, "Invalid phase: %d", phase); + _stall_counts[phase].add_then_fetch(1UL, memory_order_relaxed); } -void ShenandoahCollectorPolicy::record_success_concurrent(bool is_young, bool is_abbreviated) { +void ShenandoahCollectorPolicy::record_success_concurrent(size_t gc_id, bool is_young, bool is_abbreviated) { update_young(is_young); - reset_consecutive_degenerated_gcs(); + if (!is_young) { + _last_whole_heap_gc_id.store_relaxed(gc_id); + } + _success_concurrent_gcs++; if (is_abbreviated) { _abbreviated_concurrent_gcs++; @@ -95,23 +95,6 @@ void ShenandoahCollectorPolicy::record_interrupted_old() { _interrupted_old_gcs++; } -void ShenandoahCollectorPolicy::record_degenerated(bool is_young, bool is_abbreviated, bool progress) { - update_young(is_young); - - _success_degenerated_gcs++; - _consecutive_degenerated_gcs++; - - if (progress) { - _consecutive_degenerated_gcs_without_progress = 0; - } else { - _consecutive_degenerated_gcs_without_progress++; - } - - if (is_abbreviated) { - _abbreviated_degenerated_gcs++; - } -} - void ShenandoahCollectorPolicy::update_young(bool is_young) { if (is_young) { _consecutive_young_gcs++; @@ -120,10 +103,10 @@ void ShenandoahCollectorPolicy::update_young(bool is_young) { } } -void ShenandoahCollectorPolicy::record_success_full() { - reset_consecutive_degenerated_gcs(); +void ShenandoahCollectorPolicy::record_success_full(size_t gc_id) { _consecutive_young_gcs = 0; _success_full_gcs++; + _last_whole_heap_gc_id.store_relaxed(gc_id); } void ShenandoahCollectorPolicy::record_shutdown() { @@ -176,6 +159,11 @@ bool ShenandoahCollectorPolicy::should_run_full_gc(GCCause::Cause cause) { if (!ShenandoahHeap::heap()->mode()->is_concurrent()) { return true; } + + if (cause == GCCause::_shenandoah_upgrade_to_full_gc) { + return true; + } + return is_explicit_gc(cause) && !ExplicitGCInvokesConcurrent; } @@ -188,11 +176,75 @@ bool ShenandoahCollectorPolicy::should_handle_requested_gc(GCCause::Cause cause) return true; } +static PLAB* shenandoah_get_lab(ShenandoahHeapRegion* region) { + if (region->is_young()) { + return ShenandoahThreadLocalData::gclab(Thread::current()); + } + + assert(region->is_old(), "Region in cset(%zu) must be young or old", region->index()); + ShenandoahPLAB* shenandoah_plab = ShenandoahThreadLocalData::shenandoah_plab(Thread::current()); + assert(shenandoah_plab != nullptr, "Must have plab in generational mode"); + return shenandoah_plab->plab(); +} + +bool ShenandoahCollectorPolicy::should_abandon_evacuations(ShenandoahHeapRegion* region) { + if (region->has_self_forwards() && ShenandoahThreadLocalData::has_evac_failures()) { + // This region and this thread are lost. This thread has evacuated all it can. If + // we let it continue on to other regions, it will only fail those as well. We want + // to let other threads try the regions that this thread could not. This isn't as + // precise as it could be. In a mixed collection, we would ideally have a way to + // stop this thread from claiming old or young regions, depending on which lab is + // exhausted. + log_debug(gc, thread)("Region (%zu) has self-forwards and this thread has evacuation failures (LAB words remaining: %zu)", + region->index(), shenandoah_get_lab(region)->words_remaining()); + return true; + } + return false; +} + +// Some causes should not be allowed to preempt others. We must make sure that +// regulator requests and allocation failures do not preempt a shutdown request. +int ShenandoahCollectorPolicy::cause_priority(GCCause::Cause cause) { + if (is_explicit_gc(cause)) { + return 5; + } + + switch (cause) { + case GCCause::_shenandoah_stop_vm: + return 7; + case GCCause::_shenandoah_upgrade_to_full_gc: + return 6; + case GCCause::_shenandoah_humongous_allocation_failure: + return 4; + case GCCause::_shenandoah_allocation_failure_evac: + return 3; + case GCCause::_allocation_failure: + return 2; + case GCCause::_shenandoah_concurrent_gc: + return 1; + case GCCause::_no_gc: + return 0; + default: + // Unanticipated gc causes are treated as an allocation failure and cannot be + // preempted by regulator requests + return 2; + } +} + +template +size_t shenandoah_sum_array(T* a, size_t length) { + size_t sum = 0; + for (size_t i = 0; i < length; i++) { + sum += a[i].load_relaxed(); + } + return sum; +} + void ShenandoahCollectorPolicy::print_gc_stats(outputStream* out) const { - out->print_cr("Under allocation pressure, concurrent cycles may cancel, and either continue cycle"); - out->print_cr("under stop-the-world pause or result in stop-the-world Full GC. Increase heap size,"); - out->print_cr("tune GC heuristics, or lower allocation rate to avoid Degenerated and Full GC cycles."); - out->print_cr("Abbreviated cycles are those which found enough regions with no live objects to skip evacuation."); + out->print_cr("Under allocation pressure, concurrent cycles may stall allocating threads or escalate "); + out->print_cr("to stop-the-world Full GC. Increase heap size, tune GC heuristics, or lower allocation rate"); + out->print_cr("to avoid stalls and Full GC cycles. Abbreviated cycles are those which found enough"); + out->print_cr("regions with no live objects to skip evacuation."); out->cr(); size_t gc_attempts = 0; @@ -200,7 +252,7 @@ void ShenandoahCollectorPolicy::print_gc_stats(outputStream* out) const { gc_attempts += _collection_cause_counts[c]; } - size_t completed_gcs = _success_full_gcs + _success_degenerated_gcs + _success_concurrent_gcs + _success_old_gcs; + size_t completed_gcs = _success_full_gcs + _success_concurrent_gcs + _success_old_gcs; out->print_cr("%5zu GC attempts. %zu Completed GCs (%.2f%%).", gc_attempts, completed_gcs, percent_of(completed_gcs, gc_attempts)); @@ -218,27 +270,25 @@ void ShenandoahCollectorPolicy::print_gc_stats(outputStream* out) const { out->print_cr(" %5zu abbreviated (%.2f%%)", _abbreviated_concurrent_gcs, percent_of(_abbreviated_concurrent_gcs, _success_concurrent_gcs)); out->cr(); - if (ShenandoahHeap::heap()->mode()->is_generational()) { + if (_mode->is_generational()) { out->print_cr("%5zu Completed Old GCs (%.2f%%)", _success_old_gcs, percent_of(_success_old_gcs, completed_gcs)); out->print_cr(" %5zu mixed", _mixed_gcs); out->print_cr(" %5zu interruptions", _interrupted_old_gcs); out->cr(); } - size_t degenerated_gcs = _alloc_failure_degenerated_upgrade_to_full + _success_degenerated_gcs; - out->print_cr("%5zu Degenerated GCs (%.2f%%)", degenerated_gcs, percent_of(degenerated_gcs, completed_gcs)); - out->print_cr(" %5zu upgraded to Full GC (%.2f%%)", _alloc_failure_degenerated_upgrade_to_full, percent_of(_alloc_failure_degenerated_upgrade_to_full, degenerated_gcs)); - out->print_cr(" %5zu caused by allocation failure (%.2f%%)", _alloc_failure_degenerated, percent_of(_alloc_failure_degenerated, degenerated_gcs)); - out->print_cr(" %5zu abbreviated (%.2f%%)", _abbreviated_degenerated_gcs, percent_of(_abbreviated_degenerated_gcs, degenerated_gcs)); - for (int c = 0; c < ShenandoahGC::_DEGENERATED_LIMIT; c++) { - if (_degen_point_counts[c] > 0) { - const char* desc = ShenandoahGC::degen_point_to_string((ShenandoahGC::ShenandoahDegenPoint)c); - out->print_cr(" %5zu happened at %s", _degen_point_counts[c], desc); + const size_t total_stalls = shenandoah_sum_array(_stall_counts, ShenandoahController::PHASE_LIMIT); + out->print_cr("%5zu Stalls", total_stalls); + for (int c = 0; c < ShenandoahController::PHASE_LIMIT; c++) { + const size_t stall_count = _stall_counts[c].load_relaxed(); + if (stall_count > 0) { + const auto phase = static_cast(c); + const char* desc = ShenandoahController::collector_phase_to_string(phase); + out->print_cr(" %5zu happened at %s", stall_count, desc); } } out->cr(); out->print_cr("%5zu Full GCs (%.2f%%)", _success_full_gcs, percent_of(_success_full_gcs, completed_gcs)); out->print_cr(" %5zu caused by allocation failure (%.2f%%)", _alloc_failure_full, percent_of(_alloc_failure_full, _success_full_gcs)); - out->print_cr(" %5zu upgraded from Degenerated GC (%.2f%%)", _alloc_failure_degenerated_upgrade_to_full, percent_of(_alloc_failure_degenerated_upgrade_to_full, _success_full_gcs)); } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahCollectorPolicy.hpp b/src/hotspot/share/gc/shenandoah/shenandoahCollectorPolicy.hpp index e265da9d4134..a3931b6714eb 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahCollectorPolicy.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahCollectorPolicy.hpp @@ -27,48 +27,39 @@ #define SHARE_GC_SHENANDOAH_SHENANDOAHCOLLECTORPOLICY_HPP #include "gc/shared/gcTrace.hpp" -#include "gc/shenandoah/shenandoahGC.hpp" +#include "gc/shenandoah/shenandoahController.hpp" #include "gc/shenandoah/shenandoahSharedVariables.hpp" #include "gc/shenandoah/shenandoahTrace.hpp" #include "memory/allocation.hpp" -#include "utilities/ostream.hpp" + +class ShenandoahHeapRegion; +class ShenandoahMode; class ShenandoahCollectorPolicy : public CHeapObj { private: size_t _success_concurrent_gcs; size_t _abbreviated_concurrent_gcs; - size_t _success_degenerated_gcs; - size_t _abbreviated_degenerated_gcs; - // Written by control thread, read by mutators + + // Written by control thread, read by mutators (infrequently) + Atomic _last_whole_heap_gc_id; volatile size_t _success_full_gcs; - uint _consecutive_degenerated_gcs; - uint _consecutive_degenerated_gcs_without_progress; volatile size_t _consecutive_young_gcs; + size_t _mixed_gcs; size_t _success_old_gcs; size_t _interrupted_old_gcs; - size_t _alloc_failure_degenerated; - size_t _alloc_failure_degenerated_upgrade_to_full; size_t _alloc_failure_full; size_t _collection_cause_counts[GCCause::_last_gc_cause]; - size_t _degen_point_counts[ShenandoahGC::_DEGENERATED_LIMIT]; + + Atomic _stall_counts[ShenandoahController::PHASE_LIMIT]; ShenandoahSharedFlag _in_shutdown; ShenandoahTracer* _tracer; - - void reset_consecutive_degenerated_gcs() { - _consecutive_degenerated_gcs = 0; - _consecutive_degenerated_gcs_without_progress = 0; - } + ShenandoahMode* _mode; public: - // The most common scenario for lack of good progress following a degenerated GC is an accumulation of floating - // garbage during the most recently aborted concurrent GC effort. Usually, it is far more effective to - // reclaim this floating garbage with another degenerated cycle (which focuses on young generation and might require - // a pause of 200 ms) rather than a full GC cycle (which may require multiple seconds with a 10 GB old generation). - static constexpr size_t CONSECUTIVE_BAD_DEGEN_PROGRESS_THRESHOLD = 2; - ShenandoahCollectorPolicy(); + ShenandoahCollectorPolicy(ShenandoahMode* mode); void record_mixed_cycle(); void record_success_old(); @@ -77,19 +68,12 @@ class ShenandoahCollectorPolicy : public CHeapObj { // A collection cycle may be "abbreviated" if Shenandoah finds a sufficient percentage // of regions that contain no live objects (ShenandoahImmediateThreshold). These cycles // end after final mark, skipping the evacuation and reference-updating phases. Such - // cycles are very efficient and are worth tracking. Note that both degenerated and - // concurrent cycles can be abbreviated. - void record_success_concurrent(bool is_young, bool is_abbreviated); - - // Record that a degenerated cycle has been completed. Note that such a cycle may or - // may not make "progress". We separately track the total number of degenerated cycles, - // the number of consecutive degenerated cycles and the number of consecutive cycles that - // fail to make good progress. - void record_degenerated(bool is_young, bool is_abbreviated, bool progress); - void record_success_full(); - void record_alloc_failure_to_degenerated(ShenandoahGC::ShenandoahDegenPoint point); + // cycles are very efficient and are worth tracking. + void record_success_concurrent(size_t gc_id, bool is_young, bool is_abbreviated); + + void record_success_full(size_t gc_id); + void record_allocation_stall(ShenandoahController::ShenandoahCollectorPhase phase); void record_alloc_failure_to_full(); - void record_degenerated_upgrade_to_full(); void record_collection_cause(GCCause::Cause cause); void record_shutdown(); @@ -100,19 +84,11 @@ class ShenandoahCollectorPolicy : public CHeapObj { void print_gc_stats(outputStream* out) const; size_t full_gc_count() const { - return _success_full_gcs + _alloc_failure_degenerated_upgrade_to_full; + return _success_full_gcs; } - // If the heuristics find that the number of consecutive degenerated cycles is above - // ShenandoahFullGCThreshold, then they will initiate a Full GC upon an allocation - // failure. - size_t consecutive_degenerated_gc_count() const { - return _consecutive_degenerated_gcs; - } - - // Only upgrade to a full gc after the configured number of futile degenerated cycles. - bool should_upgrade_degenerated_gc() const { - return _consecutive_degenerated_gcs_without_progress >= CONSECUTIVE_BAD_DEGEN_PROGRESS_THRESHOLD; + size_t last_whole_heap_gc_id() const { + return _last_whole_heap_gc_id.load_relaxed(); } static bool is_allocation_failure(GCCause::Cause cause); @@ -121,11 +97,23 @@ class ShenandoahCollectorPolicy : public CHeapObj { static bool should_run_full_gc(GCCause::Cause cause); static bool should_handle_requested_gc(GCCause::Cause cause); + static bool is_higher_priority(GCCause::Cause lhs, GCCause::Cause rhs) { + return cause_priority(lhs) > cause_priority(rhs); + } + + // True if the region has self-forwarded objects and the calling thread has exhausted its LABs. + static bool should_abandon_evacuations(ShenandoahHeapRegion* region); + size_t consecutive_young_gc_count() const { return _consecutive_young_gcs; } + size_t reclaiming_gc_count() const { + return _success_concurrent_gcs + _success_full_gcs + _success_old_gcs; + } + private: + static int cause_priority(GCCause::Cause cause); void update_young(bool is_young); }; diff --git a/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp b/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp index f6a614586971..0b875fa1c2ca 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp @@ -28,12 +28,10 @@ #include "code/codeCache.hpp" #include "gc/shared/barrierSetNMethod.hpp" #include "gc/shared/collectorCounters.hpp" -#include "gc/shared/continuationGCSupport.inline.hpp" #include "gc/shenandoah/shenandoahBreakpoint.hpp" #include "gc/shenandoah/shenandoahClosures.inline.hpp" #include "gc/shenandoah/shenandoahCollectorPolicy.hpp" #include "gc/shenandoah/shenandoahConcurrentGC.hpp" -#include "gc/shenandoah/shenandoahForwarding.inline.hpp" #include "gc/shenandoah/shenandoahFreeSet.hpp" #include "gc/shenandoah/shenandoahGeneration.hpp" #include "gc/shenandoah/shenandoahGenerationalHeap.hpp" @@ -93,16 +91,17 @@ class ShenandoahBreakpointMarkScope : public StackObj { } }; -ShenandoahConcurrentGC::ShenandoahConcurrentGC(ShenandoahGeneration* generation, bool do_old_gc_bootstrap) : +ShenandoahConcurrentGC::ShenandoahConcurrentGC(ShenandoahController* controller, ShenandoahGeneration* generation, bool do_old_gc_bootstrap) : ShenandoahGC(generation), _mark(generation), - _degen_point(ShenandoahDegenPoint::_degenerated_unset), + _controller(controller), _abbreviated(false), _do_old_gc_bootstrap(do_old_gc_bootstrap) { + update_phase(ShenandoahController::INITIALIZING); } -ShenandoahGC::ShenandoahDegenPoint ShenandoahConcurrentGC::degen_point() const { - return _degen_point; +ShenandoahConcurrentGC::~ShenandoahConcurrentGC() { + update_phase(ShenandoahController::UNSET); } void ShenandoahConcurrentGC::entry_concurrent_update_refs_prepare(ShenandoahHeap* const heap) { @@ -163,13 +162,13 @@ bool ShenandoahConcurrentGC::collect(GCCause::Cause cause) { // Concurrent mark roots entry_mark_roots(); - if (check_cancellation_and_abort(ShenandoahDegenPoint::_degenerated_roots)) { + if (check_cancellation_and_abort()) { return false; } // Continue concurrent mark entry_mark(); - if (check_cancellation_and_abort(ShenandoahDegenPoint::_degenerated_mark)) { + if (check_cancellation_and_abort()) { return false; } } @@ -177,12 +176,8 @@ bool ShenandoahConcurrentGC::collect(GCCause::Cause cause) { // Complete marking under STW, and start evacuation vmop_entry_final_mark(); - // If the GC was cancelled before final mark, nothing happens on the safepoint. We are still - // in the marking phase and must resume the degenerated cycle from there. If the GC was cancelled - // after final mark, then we've entered the evacuation phase and must resume the degenerated cycle - // from that phase. if (_generation->is_concurrent_mark_in_progress()) { - bool cancelled = check_cancellation_and_abort(ShenandoahDegenPoint::_degenerated_mark); + bool cancelled = check_cancellation_and_abort(); assert(cancelled, "GC must have been cancelled between concurrent and final mark"); return false; } @@ -224,16 +219,29 @@ bool ShenandoahConcurrentGC::collect(GCCause::Cause cause) { // This may be skipped if there is nothing to evacuate. // If so, evac_in_progress would be unset by collection set preparation code. if (heap->is_evacuation_in_progress()) { + // Final mark may have reclaimed some immediate garbage that could satisfy allocation requests. + // We are proceeding with evacuation, so notify alloc waiters (if we are not evacuating, completion + // of the abbreviated cycle would also notify them). + _controller->notify_alloc_waiters(); + // Concurrently evacuate entry_evacuate(); - if (check_cancellation_and_abort(ShenandoahDegenPoint::_degenerated_evac)) { + if (check_cancellation_and_abort()) { return false; } + if (!heap->collection_set()->all_regions_claimed()) { + // Self forward objects stranded in the collection set + entry_self_forward_stranded_objects(); + if (check_cancellation_and_abort()) { + return false; + } + } + // Perform update-refs phase. entry_concurrent_update_refs_prepare(heap); - if (ShenandoahHeap::heap()->mode()->is_generational()) { + if (heap->mode()->is_generational()) { entry_update_card_table(); } @@ -242,13 +250,13 @@ bool ShenandoahConcurrentGC::collect(GCCause::Cause cause) { } entry_update_refs(); - if (check_cancellation_and_abort(ShenandoahDegenPoint::_degenerated_update_refs)) { + if (check_cancellation_and_abort()) { return false; } // Concurrent update thread roots entry_update_thread_roots(); - if (check_cancellation_and_abort(ShenandoahDegenPoint::_degenerated_update_refs)) { + if (check_cancellation_and_abort()) { return false; } @@ -262,12 +270,7 @@ bool ShenandoahConcurrentGC::collect(GCCause::Cause cause) { if (heap->mode()->is_generational()) { entry_complete_abbreviated_cycle(); - // If the promote-in-place operation was cancelled, we can have the degenerated - // cycle complete the operation. It will see that no evacuations are in progress, - // and that there are regions wanting promotion. The risk with not handling the - // cancellation would be failing to restore top for these regions and leaving - // them unable to serve allocations for the old generation. - if (check_cancellation_and_abort(ShenandoahDegenPoint::_degenerated_evac)) { + if (check_cancellation_and_abort()) { return false; } } @@ -287,7 +290,7 @@ bool ShenandoahConcurrentGC::collect(GCCause::Cause cause) { // Instead of always resetting immediately before the start of a new GC, we can often reset at the end of the // previous GC. This allows us to start the next GC cycle more quickly after a trigger condition is detected, - // reducing the likelihood that GC will degenerate. + // reducing the likelihood that mutators will experience allocation stalls. entry_reset_after_collect(); return true; @@ -313,11 +316,11 @@ void ShenandoahConcurrentGC::entry_complete_abbreviated_cycle() { if (heap->old_generation()->has_in_place_promotions()) { ShenandoahTimingsTracker timing(ShenandoahPhaseTimings::complete_abbreviated_promote_in_place); ShenandoahGCWorkerPhase worker_phase(ShenandoahPhaseTimings::complete_abbreviated_promote_in_place); - heap->promote_regions_in_place(_generation, true); + heap->promote_regions_in_place(_generation); } // At this point, the cycle is effectively complete. If the cycle has been cancelled here, - // the control thread will detect it on its next iteration and run a degenerated young cycle. + // the control thread will detect it on its next iteration. if (!heap->cancelled_gc() && !_generation->is_old()) { ShenandoahTimingsTracker tracker(ShenandoahPhaseTimings::complete_abbreviated_update_region_ages); heap->update_region_ages(_generation->complete_marking_context()); @@ -497,6 +500,7 @@ void ShenandoahConcurrentGC::entry_mark_roots() { ShenandoahWorkerPolicy::calc_workers_for_conc_marking(), "concurrent marking roots"); + update_phase(ShenandoahController::ROOTS); heap->try_inject_alloc_failure(); op_mark_roots(); } @@ -515,6 +519,7 @@ void ShenandoahConcurrentGC::entry_mark() { ShenandoahWorkerPolicy::calc_workers_for_conc_marking(), "concurrent marking"); + update_phase(ShenandoahController::MARK); heap->try_inject_alloc_failure(); op_mark(); heap->try_inject_pin(); @@ -613,9 +618,9 @@ void ShenandoahConcurrentGC::entry_cleanup_early() { op_cleanup_early(); if (!heap->is_evacuation_in_progress()) { // This is an abbreviated cycle. Rebuild the freeset in order to establish reserves for the next GC cycle. Doing - // the rebuild ASAP also expedites availability of immediate trash, reducing the likelihood that we will degenerate + // the rebuild ASAP also expedites availability of immediate trash, reducing the likelihood that mutators will stall // during promote-in-place processing. - heap->rebuild_free_set(true /*concurrent*/); + heap->rebuild_free_set(); } } @@ -630,11 +635,28 @@ void ShenandoahConcurrentGC::entry_evacuate() { ShenandoahWorkerPolicy::calc_workers_for_conc_evac(), "concurrent evacuation"); + update_phase(ShenandoahController::EVAC); heap->try_inject_alloc_failure(); heap->try_inject_pin(); op_evacuate(); } +void ShenandoahConcurrentGC::entry_self_forward_stranded_objects() { + ShenandoahHeap* const heap = ShenandoahHeap::heap(); + TraceCollectorStats tcs(heap->monitoring_support()->concurrent_collection_counters()); + SHENANDOAH_EVENT_MESSAGE(msg, _generation->type(), "Concurrent self forwarding", ""); + ShenandoahConcurrentSubphase gc_phase(msg, ShenandoahPhaseTimings::conc_self_forward_stranded); + EventMark em("%s", msg); + + ShenandoahWorkerScope scope(heap->workers(), + ShenandoahWorkerPolicy::calc_workers_for_conc_evac(), + "concurrent self forwarding"); + + heap->try_inject_alloc_failure(); + heap->try_inject_pin(); + op_self_forward_stranded_objects(); +} + void ShenandoahConcurrentGC::entry_update_thread_roots() { ShenandoahHeap* const heap = ShenandoahHeap::heap(); TraceCollectorStats tcs(heap->monitoring_support()->concurrent_collection_counters()); @@ -659,6 +681,7 @@ void ShenandoahConcurrentGC::entry_update_refs() { ShenandoahWorkerPolicy::calc_workers_for_conc_update_ref(), "concurrent reference update"); + update_phase(ShenandoahController::UPDATE_REFS); heap->try_inject_alloc_failure(); heap->try_inject_pin(); op_update_refs(); @@ -686,6 +709,12 @@ void ShenandoahConcurrentGC::entry_reset_after_collect() { op_reset_after_collect(); } +void ShenandoahConcurrentGC::update_phase(ShenandoahController::ShenandoahCollectorPhase phase) const { + if (_controller != nullptr) { + _controller->set_phase(phase); + } +} + void ShenandoahConcurrentGC::op_reset() { ShenandoahHeap* const heap = ShenandoahHeap::heap(); @@ -825,7 +854,7 @@ void ShenandoahConcurrentGC::op_final_mark() { // The collection set is chosen by prepare_regions_and_collection_set(). Additionally, certain parameters have been // established to govern the evacuation efforts that are about to begin. Refer to comments on reserve members in // ShenandoahGeneration and ShenandoahOldGeneration for more detail. - _generation->prepare_regions_and_collection_set(true /*concurrent*/); + _generation->prepare_regions_and_collection_set(); // Has to be done after cset selection heap->prepare_concurrent_roots(); @@ -1143,7 +1172,12 @@ void ShenandoahConcurrentGC::op_cleanup_early() { } void ShenandoahConcurrentGC::op_evacuate() { - ShenandoahHeap::heap()->evacuate_collection_set(_generation, true /*concurrent*/); + ShenandoahHeap* heap = ShenandoahHeap::heap(); + heap->evacuate_collection_set(_generation); +} + +void ShenandoahConcurrentGC::op_self_forward_stranded_objects() { + ShenandoahHeap::heap()->self_forward_stranded_objects(); } void ShenandoahConcurrentGC::op_init_update_refs() { @@ -1155,7 +1189,7 @@ void ShenandoahConcurrentGC::op_init_update_refs() { } void ShenandoahConcurrentGC::op_update_refs() { - ShenandoahHeap::heap()->update_heap_references(_generation, true /*concurrent*/); + ShenandoahHeap::heap()->update_heap_references(_generation); } class ShenandoahUpdateThreadHandshakeClosure : public HandshakeClosure { @@ -1163,7 +1197,7 @@ class ShenandoahUpdateThreadHandshakeClosure : public HandshakeClosure { // This closure runs when thread is stopped for handshake, which means // we can use non-concurrent closure here, as long as it only updates // locations modified by the thread itself, i.e. stack locations. - ShenandoahNonConcUpdateRefsClosure _cl; + ShenandoahUpdateRefsClosure _cl; public: ShenandoahUpdateThreadHandshakeClosure(); void do_thread(Thread* thread) override; @@ -1229,7 +1263,7 @@ void ShenandoahConcurrentGC::op_update_thread_roots() { void ShenandoahConcurrentGC::op_final_update_refs() { ShenandoahHeap* const heap = ShenandoahHeap::heap(); assert(ShenandoahSafepoint::is_at_shenandoah_safepoint(), "must be at safepoint"); - assert(!heap->_update_refs_iterator.has_next(), "Should have finished update references"); + assert(heap->is_update_refs_in_progress(), "Should have run update references first"); heap->finish_concurrent_roots(); @@ -1246,21 +1280,23 @@ void ShenandoahConcurrentGC::op_final_update_refs() { // If we are running in generational mode, this will also age active regions that // haven't been used for allocation. - heap->update_heap_region_states(true /*concurrent*/); + heap->update_heap_region_states(); heap->set_update_refs_in_progress(false); heap->set_has_forwarded_objects(false); + heap->unset_has_self_forwarded_objects(); + + if (VerifyAfterGC) { + Universe::verify(); + } + + heap->rebuild_free_set(); if (ShenandoahVerify) { ShenandoahTimingsTracker v(ShenandoahPhaseTimings::final_update_refs_verify); heap->verifier()->verify_after_update_refs(_generation); } - if (VerifyAfterGC) { - Universe::verify(); - } - - heap->rebuild_free_set(true /*concurrent*/); _generation->heuristics()->start_idle_span(); // Final pause: update GC barriers to idle state. @@ -1312,10 +1348,6 @@ void ShenandoahConcurrentGC::op_reset_after_collect() { } } -bool ShenandoahConcurrentGC::check_cancellation_and_abort(ShenandoahDegenPoint point) { - if (ShenandoahHeap::heap()->cancelled_gc()) { - _degen_point = point; - return true; - } - return false; +bool ShenandoahConcurrentGC::check_cancellation_and_abort() { + return ShenandoahHeap::heap()->cancelled_gc(); } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.hpp b/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.hpp index 7b49b6626e23..f3cdede2bc09 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.hpp @@ -28,11 +28,11 @@ #include "gc/shared/gcCause.hpp" #include "gc/shenandoah/shenandoahConcurrentMark.hpp" +#include "gc/shenandoah/shenandoahController.hpp" #include "gc/shenandoah/shenandoahGC.hpp" #include "gc/shenandoah/shenandoahHeap.hpp" class ShenandoahGeneration; - class VM_ShenandoahInitMark; class VM_ShenandoahFinalMarkStartEvac; class VM_ShenandoahInitUpdateRefs; @@ -50,15 +50,15 @@ class ShenandoahConcurrentGC : public ShenandoahGC { ShenandoahConcurrentMark _mark; private: - ShenandoahDegenPoint _degen_point; - bool _abbreviated; - const bool _do_old_gc_bootstrap; + ShenandoahController* _controller; + bool _abbreviated; + const bool _do_old_gc_bootstrap; public: - ShenandoahConcurrentGC(ShenandoahGeneration* generation, bool do_old_gc_bootstrap); + ShenandoahConcurrentGC(ShenandoahController* controller, ShenandoahGeneration* generation, bool do_old_gc_bootstrap); + ~ShenandoahConcurrentGC(); bool collect(GCCause::Cause cause) override; - ShenandoahDegenPoint degen_point() const; // Return true if this cycle found enough immediate garbage to skip evacuation bool abbreviated() const { return _abbreviated; } @@ -96,6 +96,7 @@ class ShenandoahConcurrentGC : public ShenandoahGC { void entry_cleanup_early(); void entry_complete_abbreviated_cycle(); void entry_evacuate(); + void entry_self_forward_stranded_objects(); void entry_update_thread_roots(); void entry_update_card_table(); void entry_concurrent_update_refs_prepare(ShenandoahHeap* heap); @@ -115,6 +116,7 @@ class ShenandoahConcurrentGC : public ShenandoahGC { void op_strong_roots(); void op_cleanup_early(); void op_evacuate(); + void op_self_forward_stranded_objects(); void op_init_update_refs(); void op_update_refs(); void op_update_thread_roots(); @@ -125,11 +127,12 @@ class ShenandoahConcurrentGC : public ShenandoahGC { void op_reset_after_collect(); // Check GC cancellation and abort concurrent GC - bool check_cancellation_and_abort(ShenandoahDegenPoint point); + bool check_cancellation_and_abort(); // Called when concurrent GC succeeds. void entry_reset_after_collect(); + void update_phase(ShenandoahController::ShenandoahCollectorPhase phase) const; private: void start_mark(); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp b/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp index 1eec38027752..3ba9e5e9fac7 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp @@ -23,12 +23,12 @@ * */ +#include "gc/shared/allocTracer.hpp" #include "gc/shenandoah/heuristics/shenandoahHeuristics.hpp" #include "gc/shenandoah/mode/shenandoahMode.hpp" #include "gc/shenandoah/shenandoahCollectorPolicy.hpp" #include "gc/shenandoah/shenandoahConcurrentGC.hpp" #include "gc/shenandoah/shenandoahControlThread.hpp" -#include "gc/shenandoah/shenandoahDegeneratedGC.hpp" #include "gc/shenandoah/shenandoahFreeSet.hpp" #include "gc/shenandoah/shenandoahFullGC.hpp" #include "gc/shenandoah/shenandoahGeneration.hpp" @@ -40,66 +40,40 @@ #include "memory/metaspaceStats.hpp" #include "memory/metaspaceUtils.hpp" -ShenandoahControlThread::ShenandoahControlThread() : - ShenandoahController(), - _requested_gc_cause(GCCause::_no_gc), - _degen_point(ShenandoahGC::_degenerated_outside_cycle), - _control_lock(CONTROL_LOCK_RANK, "ShenandoahControl_lock", true) { +ShenandoahControlThread::ShenandoahControlThread() { set_name("ShenControl"); create_and_start(); } void ShenandoahControlThread::run_service() { ShenandoahHeap* const heap = ShenandoahHeap::heap(); - const GCMode default_mode = concurrent_normal; - const GCCause::Cause default_cause = GCCause::_shenandoah_concurrent_gc; - int sleep = ShenandoahControlIntervalMin; + constexpr GCMode default_mode = concurrent_normal; + constexpr GCCause::Cause default_cause = GCCause::_shenandoah_concurrent_gc; + int sleep = ShenandoahControlIntervalMin; double last_sleep_adjust_time = os::elapsedTime(); + double most_recent_wake_time = last_sleep_adjust_time; - ShenandoahCollectorPolicy* const policy = heap->shenandoah_policy(); ShenandoahHeuristics* const heuristics = heap->heuristics(); - double most_recent_wake_time = os::elapsedTime(); while (!should_terminate()) { - const GCCause::Cause cancelled_cause = heap->cancelled_cause(); - if (cancelled_cause == GCCause::_shenandoah_stop_vm) { - break; - } + assert(!heap->cancelled_gc() || should_terminate(), "Can only be cancelled for shutdown"); // Figure out if we have pending requests. - const bool alloc_failure_pending = ShenandoahCollectorPolicy::is_allocation_failure(cancelled_cause); - const bool is_gc_requested = _gc_requested.try_unset(); - const GCCause::Cause requested_gc_cause = _requested_gc_cause; + GCCause::Cause cause; + { + MonitorLocker controller(&_control_lock, Mutex::_no_safepoint_check_flag); + cause = _requested_gc_cause; + _requested_gc_cause = GCCause::_no_gc; + } // Choose which GC mode to run in. The block below should select a single mode. GCMode mode = none; - GCCause::Cause cause = GCCause::_last_gc_cause; - ShenandoahGC::ShenandoahDegenPoint degen_point = ShenandoahGC::_degenerated_unset; - - if (alloc_failure_pending) { - // Allocation failure takes precedence: we have to deal with it first thing - heuristics->log_trigger("Handle Allocation Failure"); - - cause = GCCause::_allocation_failure; - - // Consume the degen point, and seed it with default value - degen_point = _degen_point; - _degen_point = ShenandoahGC::_degenerated_outside_cycle; - - if (ShenandoahDegeneratedGC && heuristics->should_degenerate_cycle()) { - heuristics->record_allocation_failure_gc(); - policy->record_alloc_failure_to_degenerated(degen_point); - mode = stw_degenerated; - } else { - heuristics->record_allocation_failure_gc(); - policy->record_alloc_failure_to_full(); - mode = stw_full; - } - } else if (is_gc_requested) { - cause = requested_gc_cause; + bool clear_soft_references = ShenandoahAlwaysClearSoftRefs; + if (cause != GCCause::_no_gc) { + // A cycle was requested, clear soft references heuristics->log_trigger("GC Request (%s)", GCCause::to_string(cause)); heuristics->record_requested_gc(); - + clear_soft_references = true; if (ShenandoahCollectorPolicy::should_run_full_gc(cause)) { mode = stw_full; } else { @@ -107,26 +81,19 @@ void ShenandoahControlThread::run_service() { // Unload and clean up everything heap->set_unload_classes(heuristics->can_unload_classes()); } - } else { - // Potential normal cycle: ask heuristics if it wants to act - if (heuristics->should_start_gc()) { - mode = default_mode; - cause = default_cause; - } - + } else if (heuristics->should_start_gc()) { + // Nobody requested a cycle, but heuristics want to run one + mode = default_mode; + cause = default_cause; // Ask policy if this cycle wants to process references or unload classes heap->set_unload_classes(heuristics->should_unload_classes()); } - if (alloc_failure_pending || is_gc_requested || ShenandoahAlwaysClearSoftRefs) { - // Blow away all soft references on this cycle - heap->global_generation()->ref_processor()->set_soft_reference_policy(true); - } + if (mode != none) { + assert(cause != GCCause::_no_gc, "GC cause should be set"); - const bool gc_requested = (mode != none); - assert (!gc_requested || cause != GCCause::_last_gc_cause, "GC cause should be set"); + heap->global_generation()->ref_processor()->set_soft_reference_policy(clear_soft_references); - if (gc_requested) { // Cannot uncommit bitmap slices during concurrent reset ShenandoahNoUncommitMark forbid_region_uncommit(heap); @@ -151,9 +118,6 @@ void ShenandoahControlThread::run_service() { case concurrent_normal: service_concurrent_normal_cycle(cause); break; - case stw_degenerated: - service_stw_degenerated_cycle(cause, degen_point); - break; case stw_full: service_stw_full_cycle(cause); break; @@ -162,15 +126,12 @@ void ShenandoahControlThread::run_service() { } heap->print_after_gc(); - // If this was the requested GC cycle, notify waiters about it - if (is_gc_requested) { - notify_gc_waiters(); - } + // Try to reduce concurrent worker count + decrease_concurrent_worker_count(); - // If this cycle completed without being cancelled, notify waiters about it - if (!heap->cancelled_gc()) { - notify_alloc_failure_waiters(); - } + // Notify waiters that a cycle is completed. They'll decide for themselves to continue waiting or not. + notify_gc_waiters(); + clear_allocation_failure_and_notify_waiters(); // Report current free set state at the end of cycle, whether // it is a normal completion, or the abort. @@ -212,7 +173,7 @@ void ShenandoahControlThread::run_service() { if (ShenandoahUncommit) { if (heap->check_soft_max_changed()) { heap->notify_soft_max_changed(); - } else if (is_gc_requested) { + } else if (ShenandoahCollectorPolicy::is_explicit_gc(cause)) { heap->notify_explicit_gc_requested(); } } @@ -231,59 +192,35 @@ void ShenandoahControlThread::run_service() { ml.wait(sleep); most_recent_wake_time = os::elapsedTime(); // Record a conservative estimate of the longest anticipated sleep duration until we sample again. - double planned_sleep_interval = MIN2(ShenandoahControlIntervalMax, MAX2(1, sleep * 2)) / 1000.0; + const double planned_sleep_interval = MIN2(ShenandoahControlIntervalMax, MAX2(1, sleep * 2)) / 1000.0; heuristics->update_should_start_query_times(most_recent_wake_time, planned_sleep_interval); if (LogTarget(Debug, gc, thread)::is_enabled()) { - double elapsed = most_recent_wake_time - before_sleep_time; - double hiccup = elapsed - double(sleep) / 1000.0; + const double elapsed = most_recent_wake_time - before_sleep_time; + const double hiccup = elapsed - static_cast(sleep) / 1000.0; if (hiccup > 0.001) { log_debug(gc, thread)("Control Thread hiccup time: %.3fs", hiccup); } } } + // In case any threads are waiting for a cycle to happen, notify them so they observe the shutdown. + notify_gc_waiters(); + notify_alloc_waiters(); + // We're done writing GC stats, so print them here. heap->print_gc_stats_at_exit(); } void ShenandoahControlThread::service_concurrent_normal_cycle(GCCause::Cause cause) { // Normal cycle goes via all concurrent phases. If allocation failure (af) happens during - // any of the concurrent phases, it first degrades to Degenerated GC and completes GC there. - // If second allocation failure happens during Degenerated GC cycle (for example, when GC - // tries to evac something and no memory is available), cycle degrades to Full GC. - // - // There are also a shortcut through the normal cycle: immediate garbage shortcut, when - // heuristics says there are no regions to compact, and all the collection comes from immediately - // reclaimable regions. - // - // ................................................................................................ - // - // (immediate garbage shortcut) Concurrent GC - // /-------------------------------------------\ - // | | - // | | - // | | - // | v - // [START] ----> Conc Mark ----o----> Conc Evac --o--> Conc Update-Refs ---o----> [END] - // | | | ^ - // | (af) | (af) | (af) | - // ..................|....................|.................|..............|....................... - // | | | | - // | | | | Degenerated GC - // v v v | - // STW Mark ----------> STW Evac ----> STW Update-Refs ----->o - // | | | ^ - // | (af) | (af) | (af) | - // ..................|....................|.................|..............|....................... - // | | | | - // | v | | Full GC - // \------------------->o<----------------/ | - // | | - // v | - // Full GC --------------------------/ + // any of the concurrent phases, the allocating thread will block until the concurrent + // cycle completes. // + // There is also a shortcut through the normal cycle: immediate garbage shortcut. When + // heuristics say there are no regions to compact, and all the collection comes from immediately + // reclaimable regions, Shenandoah can skip the evacuation phase. ShenandoahHeap* heap = ShenandoahHeap::heap(); - if (check_cancellation_or_degen(ShenandoahGC::_degenerated_outside_cycle)) { + if (heap->cancelled_gc()) { // Need to report at "gc" level to report GC ID proper. log_info(gc)("Cancelled before cycle started"); return; @@ -294,44 +231,22 @@ void ShenandoahControlThread::service_concurrent_normal_cycle(GCCause::Cause cau TraceCollectorStats tcs(heap->monitoring_support()->concurrent_collection_counters()); - ShenandoahConcurrentGC gc(heap->global_generation(), false); + ShenandoahConcurrentGC gc(this, heap->global_generation(), false); if (gc.collect(cause)) { - // Cycle is complete. There were no failed allocation requests and no degeneration, so count this as good progress. - heap->notify_gc_progress(); - heap->global_generation()->heuristics()->record_success_concurrent(); - heap->shenandoah_policy()->record_success_concurrent(false, gc.abbreviated()); + if (alloc_stall_count() == 0) { + heap->notify_gc_progress(); + } + heap->global_generation()->heuristics()->record_concurrent_completion(); + heap->shenandoah_policy()->record_success_concurrent(get_gc_id(), false, gc.abbreviated()); heap->log_heap_status("At end of GC"); } else { assert(heap->cancelled_gc(), "Must have been cancelled"); - check_cancellation_or_degen(gc.degen_point()); heap->log_heap_status("At end of cancelled GC"); } } -bool ShenandoahControlThread::check_cancellation_or_degen(ShenandoahGC::ShenandoahDegenPoint point) { - // Only read the cancellation cause once. Other threads may change it. - ShenandoahHeap* heap = ShenandoahHeap::heap(); - const GCCause::Cause cancelled_cause = heap->cancelled_cause(); - if (cancelled_cause == GCCause::_no_gc) { - return false; - } - - if (cancelled_cause == GCCause::_shenandoah_stop_vm) { - return true; - } - - if (ShenandoahCollectorPolicy::is_allocation_failure(cancelled_cause)) { - assert (_degen_point == ShenandoahGC::_degenerated_outside_cycle, - "Should not be set yet: %s", ShenandoahGC::degen_point_to_string(_degen_point)); - _degen_point = point; - return true; - } - - fatal("Unexpected reason for cancellation: %s", GCCause::to_string(cancelled_cause)); -} - void ShenandoahControlThread::stop_service() { - ShenandoahHeap::heap()->cancel_gc(GCCause::_shenandoah_stop_vm); + // Nothing to do here, worker threads will also check should_terminate, by way of shHeap::is_stopping. } void ShenandoahControlThread::service_stw_full_cycle(GCCause::Cause cause) { @@ -344,69 +259,25 @@ void ShenandoahControlThread::service_stw_full_cycle(GCCause::Cause cause) { gc.collect(cause); } -void ShenandoahControlThread::service_stw_degenerated_cycle(GCCause::Cause cause, ShenandoahGC::ShenandoahDegenPoint point) { - assert (point != ShenandoahGC::_degenerated_unset, "Degenerated point should be set"); - ShenandoahHeap* const heap = ShenandoahHeap::heap(); - ShenandoahGCSession session(cause, heap->global_generation(), true, - point == ShenandoahGC::ShenandoahDegenPoint::_degenerated_outside_cycle); - - heap->increment_total_collections(false); - - ShenandoahDegenGC gc(point, heap->global_generation()); - gc.collect(cause); -} - -void ShenandoahControlThread::request_gc(GCCause::Cause cause) { - if (ShenandoahCollectorPolicy::should_handle_requested_gc(cause)) { - handle_requested_gc(cause); +bool ShenandoahControlThread::notify_control_thread(GCCause::Cause cause, ShenandoahGeneration* ignored) { + MonitorLocker controller(&_control_lock, Mutex::_no_safepoint_check_flag); + if (ShenandoahCollectorPolicy::is_higher_priority(_requested_gc_cause, cause)) { + log_debug(gc, thread)("Not overwriting gc cause %s with %s", GCCause::to_string(_requested_gc_cause), GCCause::to_string(cause)); + return false; } -} -void ShenandoahControlThread::notify_control_thread(GCCause::Cause cause) { - // Although setting gc request is under _controller_lock, the read side (run_service()) - // does not take the lock. We need to enforce following order, so that read side sees - // latest requested gc cause when the flag is set. - MonitorLocker controller(&_control_lock, Mutex::_no_safepoint_check_flag); _requested_gc_cause = cause; - _gc_requested.set(); - controller.notify(); + controller.notify_all(); + return true; } -void ShenandoahControlThread::handle_requested_gc(GCCause::Cause cause) { - if (should_terminate()) { - log_info(gc, phases)("Control thread is terminating, no more GCs"); - return; - } - - // For normal requested GCs (System.gc) we want to block the caller. However, - // for whitebox requested GC, we want to initiate the GC and return immediately. - // The whitebox caller thread will arrange for itself to wait until the GC notifies - // it that has reached the requested breakpoint (phase in the GC). - if (cause == GCCause::_wb_breakpoint) { - notify_control_thread(cause); - return; - } +void ShenandoahControlThread::notify_alloc_stall(GCCause::Cause cause) { + ShenandoahHeap* heap = ShenandoahHeap::heap(); + ShenandoahGeneration* generation = heap->global_generation(); - // Make sure we have at least one complete GC cycle before unblocking - // from the explicit GC request. - // - // This is especially important for weak references cleanup and/or native - // resources (e.g. DirectByteBuffers) machinery: when explicit GC request - // comes very late in the already running cycle, it would miss lots of new - // opportunities for cleanup that were made available before the caller - // requested the GC. - - MonitorLocker ml(&_gc_waiters_lock); - size_t current_gc_id = get_gc_id(); - size_t required_gc_id = current_gc_id + 1; - while (current_gc_id < required_gc_id && !should_terminate()) { - notify_control_thread(cause); - ml.wait(); - current_gc_id = get_gc_id(); - } -} + // Inform global heuristics of allocation stall + generation->heuristics()->record_allocation_stall(); -void ShenandoahControlThread::notify_gc_waiters() { - MonitorLocker ml(&_gc_waiters_lock); - ml.notify_all(); + // Run a global cycle (no other kind for this control thread). + notify_control_thread(cause, generation); } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahControlThread.hpp b/src/hotspot/share/gc/shenandoah/shenandoahControlThread.hpp index c9bb64192013..ac9d6f365a8a 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahControlThread.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahControlThread.hpp @@ -25,51 +25,33 @@ #ifndef SHARE_GC_SHENANDOAH_SHENANDOAHCONTROLTHREAD_HPP #define SHARE_GC_SHENANDOAH_SHENANDOAHCONTROLTHREAD_HPP -#include "gc/shared/concurrentGCThread.hpp" #include "gc/shared/gcCause.hpp" #include "gc/shenandoah/shenandoahController.hpp" -#include "gc/shenandoah/shenandoahGC.hpp" -#include "gc/shenandoah/shenandoahPadding.hpp" #include "gc/shenandoah/shenandoahSharedVariables.hpp" +class ShenandoahGeneration; + class ShenandoahControlThread: public ShenandoahController { -private: typedef enum { none, concurrent_normal, - stw_degenerated, stw_full } GCMode; - ShenandoahSharedFlag _gc_requested; - GCCause::Cause _requested_gc_cause; - ShenandoahGC::ShenandoahDegenPoint _degen_point; - - // This lock is used to coordinate waking up the control thread - Monitor _control_lock; - public: ShenandoahControlThread(); +protected: void run_service() override; void stop_service() override; - void request_gc(GCCause::Cause cause) override; + // Sets the requested cause, flag and notifies the control thread + bool notify_control_thread(GCCause::Cause cause, ShenandoahGeneration* generation) override; + void notify_alloc_stall(GCCause::Cause cause) override; private: - // Sets the requested cause and flag and notifies the control thread - void notify_control_thread(GCCause::Cause cause); - - bool check_cancellation_or_degen(ShenandoahGC::ShenandoahDegenPoint point); void service_concurrent_normal_cycle(GCCause::Cause cause); void service_stw_full_cycle(GCCause::Cause cause); - void service_stw_degenerated_cycle(GCCause::Cause cause, ShenandoahGC::ShenandoahDegenPoint point); - - void notify_gc_waiters(); - - // Handle GC request. - // Blocks until GC is over. - void handle_requested_gc(GCCause::Cause cause); }; #endif // SHARE_GC_SHENANDOAH_SHENANDOAHCONTROLTHREAD_HPP diff --git a/src/hotspot/share/gc/shenandoah/shenandoahController.cpp b/src/hotspot/share/gc/shenandoah/shenandoahController.cpp index 0096aad2570d..e4eef635b6e6 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahController.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahController.cpp @@ -1,6 +1,6 @@ /* * Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,55 +23,173 @@ * */ -#include "gc/shared/allocTracer.hpp" #include "gc/shared/gc_globals.hpp" #include "gc/shenandoah/shenandoahCollectorPolicy.hpp" #include "gc/shenandoah/shenandoahController.hpp" #include "gc/shenandoah/shenandoahHeap.hpp" #include "gc/shenandoah/shenandoahHeapRegion.inline.hpp" +ShenandoahController::ShenandoahController(): + _gc_id(0), + _phase(UNSET), + _control_lock(CONTROL_LOCK_RANK, "ShenandoahControl_lock", true), + _gc_waiters_lock(WAITERS_LOCK_RANK, "ShenandoahGCWaiters_lock", true), + _alloc_waiters_lock(WAITERS_LOCK_RANK, "ShenandoahAllocWaiters_lock", true), + _requested_gc_cause(GCCause::_no_gc), + _alloc_stall_count(0), + _concurrent_worker_count(ConcGCThreads) { } + void ShenandoahController::update_gc_id() { - _gc_id.add_then_fetch((size_t)1); + _gc_id.add_then_fetch(1UL); } -size_t ShenandoahController::get_gc_id() { +size_t ShenandoahController::get_gc_id() const { return _gc_id.load_relaxed(); } -void ShenandoahController::handle_alloc_failure(const ShenandoahAllocRequest& req, bool block) { +void ShenandoahController::handle_alloc_failure(const ShenandoahAllocRequest &req) { assert(current()->is_Java_thread(), "expect Java thread here"); + _alloc_stall_count.add_then_fetch(1UL, memory_order_relaxed); + increase_concurrent_worker_count(); + ShenandoahHeap::heap()->shenandoah_policy()->record_allocation_stall(get_phase()); const bool is_humongous = ShenandoahHeapRegion::requires_humongous(req.size()); const GCCause::Cause cause = is_humongous ? GCCause::_shenandoah_humongous_allocation_failure : GCCause::_allocation_failure; + MonitorLocker ml(&_alloc_waiters_lock); + notify_alloc_stall(cause); + if (!should_terminate()) { + // This is the inner part of a larger retry loop, so just wait here + ml.wait(); + } +} + +void ShenandoahController::handle_alloc_failure_full() { + if (should_terminate()) { + log_info(gc)("Control thread is terminating, no more GCs"); + return; + } + + // Make sure we have at least one full GC cycle before unblocking from the explicit GC request. + ShenandoahHeap* heap = ShenandoahHeap::heap(); + const ShenandoahCollectorPolicy* policy = heap->shenandoah_policy(); + MonitorLocker ml(&_gc_waiters_lock); + size_t full_gc_count = policy->full_gc_count(); + const size_t required_count = full_gc_count + 1; + while (full_gc_count < required_count && !should_terminate()) { + notify_control_thread(GCCause::_shenandoah_upgrade_to_full_gc, heap->global_generation()); + ml.wait(); + full_gc_count = policy->full_gc_count(); + } +} - ShenandoahHeap* const heap = ShenandoahHeap::heap(); - size_t req_byte = req.size() * HeapWordSize; - if (heap->cancel_gc(cause)) { - log_info(gc)("Failed to allocate %s, " PROPERFMT, req.type_string(), PROPERFMTARGS(req_byte)); - request_gc(cause); +void ShenandoahController::request_gc(GCCause::Cause cause) { + if (ShenandoahCollectorPolicy::should_handle_requested_gc(cause)) { + handle_requested_gc(cause); } - AllocTracer::send_allocation_requiring_gc_event(req_byte, checked_cast(get_gc_id())); +} - if (block) { - MonitorLocker ml(&_alloc_failure_waiters_lock); - while (!should_terminate() && ShenandoahCollectorPolicy::is_allocation_failure(heap->cancelled_cause())) { - ml.wait(); +void ShenandoahController::handle_requested_gc(GCCause::Cause cause) { + // Requested gc's always operate on the entire heap + const ShenandoahHeap* heap = ShenandoahHeap::heap(); + const ShenandoahCollectorPolicy* policy = heap->shenandoah_policy(); + ShenandoahGeneration* global = heap->global_generation(); + + // For normal requested GCs (System.gc) we want to block the caller. However, + // for whitebox requested GC, we want to initiate the GC and return immediately. + // The whitebox caller thread will arrange for itself to wait until the GC notifies + // it that has reached the requested breakpoint (phase in the GC). + if (cause == GCCause::_wb_breakpoint) { + notify_control_thread(cause, global); + return; + } + + // Make sure we have at least one complete GC cycle before unblocking + // from the explicit GC request. + // + // This is especially important for weak references cleanup and/or native + // resources (e.g. DirectByteBuffers) machinery: when explicit GC request + // comes very late in the already running cycle, it would miss lots of new + // opportunities for cleanup that were made available before the caller + // requested the GC. + MonitorLocker ml(&_gc_waiters_lock); + const size_t current_gc_id = get_gc_id(); + while (policy->last_whole_heap_gc_id() <= current_gc_id && !should_terminate()) { + notify_control_thread(cause, global); + ml.wait(); + } +} + +void ShenandoahController::clear_allocation_failure_and_notify_waiters() { + MonitorLocker waiters(&_alloc_waiters_lock, Mutex::_no_safepoint_check_flag); + { + MonitorLocker ml(&_control_lock, Mutex::_no_safepoint_check_flag); + if (ShenandoahCollectorPolicy::is_allocation_failure(_requested_gc_cause)) { + // If an allocation failure occurred during this cycle, we'll have threads waiting + // for reclaimed memory. We'll wake them up, and they'll retry their allocation. + // If our waiters cannot allocate, they will signal the control thread again + // to start another cycle. If we didn't clear the request here, the control thread would + // immediately begin another allocation failure cycle. + _requested_gc_cause = GCCause::_no_gc; } } + waiters.notify_all(); } -void ShenandoahController::handle_alloc_failure_evac(size_t words) { +void ShenandoahController::increase_concurrent_worker_count() { + while (true) { + const size_t workers = _concurrent_worker_count.load_relaxed(); + if (workers == ParallelGCThreads) { + break; + } - ShenandoahHeap* const heap = ShenandoahHeap::heap(); - const bool is_humongous = ShenandoahHeapRegion::requires_humongous(words); - const GCCause::Cause cause = is_humongous ? GCCause::_shenandoah_humongous_allocation_failure : GCCause::_shenandoah_allocation_failure_evac; + const size_t new_value = MIN2(workers + 1, checked_cast(ParallelGCThreads)); + if (_concurrent_worker_count.compare_set(workers, new_value, memory_order_relaxed)) { + break; + } + } +} + +void ShenandoahController::decrease_concurrent_worker_count() { + const size_t alloc_stalls = _alloc_stall_count.exchange(0); + const int max_conc_gc_threads = checked_cast(ConcGCThreads); + if (alloc_stalls != 0) { + log_info(gc, ergo)("Allocation stalls: %zu, holding concurrent worker count at: %zu", + alloc_stalls, _concurrent_worker_count.load_relaxed()); + } else { + // There were no stalls during this cycle, try to reduce the concurrent gc workers + while (true) { + const int workers = _concurrent_worker_count.load_relaxed(); + if (workers == max_conc_gc_threads) { + break; + } - if (heap->cancel_gc(cause)) { - log_info(gc)("Failed to allocate " PROPERFMT " for evacuation", PROPERFMTARGS(words * HeapWordSize)); + const size_t new_value = MAX2(workers - 1, max_conc_gc_threads); + if (_concurrent_worker_count.compare_set(workers, new_value, memory_order_relaxed)) { + break; + } + } } } -void ShenandoahController::notify_alloc_failure_waiters() { - MonitorLocker ml(&_alloc_failure_waiters_lock); +void ShenandoahController::notify_gc_waiters() { + MonitorLocker ml(&_gc_waiters_lock); ml.notify_all(); } + +void ShenandoahController::notify_alloc_waiters() { + MonitorLocker ml(&_alloc_waiters_lock); + ml.notify_all(); +} + +const char* ShenandoahController::collector_phase_to_string(ShenandoahCollectorPhase phase) { + switch(phase) { + case UNSET: return "Outside of Cycle"; + case INITIALIZING: return "Initializing"; + case ROOTS: return "Roots"; + case MARK: return "Mark"; + case EVAC: return "Evacuation"; + case UPDATE_REFS: return "Update References"; + default: + ShouldNotReachHere(); + } +} diff --git a/src/hotspot/share/gc/shenandoah/shenandoahController.hpp b/src/hotspot/share/gc/shenandoah/shenandoahController.hpp index 60b41a5fe995..b7e22db9b04d 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahController.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahController.hpp @@ -28,56 +28,129 @@ #include "gc/shared/concurrentGCThread.hpp" #include "gc/shared/gcCause.hpp" #include "gc/shenandoah/shenandoahAllocRequest.hpp" -#include "gc/shenandoah/shenandoahSharedVariables.hpp" +#include "gc/shenandoah/shenandoahPadding.hpp" #include "runtime/atomic.hpp" +class ShenandoahGeneration; + /** * This interface exposes methods necessary for the heap to interact * with the threads responsible for driving the collection cycle. */ class ShenandoahController: public ConcurrentGCThread { +public: + enum ShenandoahCollectorPhase { + UNSET, + INITIALIZING, + ROOTS, + MARK, + EVAC, + UPDATE_REFS, + PHASE_LIMIT + }; + private: shenandoah_padding(0); // A monotonically increasing GC count. Atomic _gc_id; shenandoah_padding(1); + // Written by control thread, read by mutators + Atomic _phase; + protected: const Mutex::Rank WAITERS_LOCK_RANK = Mutex::safepoint - 5; const Mutex::Rank CONTROL_LOCK_RANK = Mutex::nosafepoint - 2; - // While we could have a single lock for these, it may risk unblocking - // GC waiters when alloc failure GC cycle finishes. We want instead - // to make complete explicit cycle for demanding customers. - Monitor _alloc_failure_waiters_lock; + // This lock is used to coordinate waking up the control thread and protecting _requested_gc_cause. + // The generational derived class also uses this lock to protect _gc_mode and _requested_generation + Monitor _control_lock; + + // Threads waiting for a complete gc cycle (full gc or concurrent global) will wait here. Monitor _gc_waiters_lock; + // Threads waiting for the gc to free memory will wait here. Note that immediate garbage may be reclaimed + // midcycle during final mark, so we want to notify alloc waiters independently of cycle waiters. Similarly, + // no threads should be notified when a concurrent old mark increment is interrupted. + Monitor _alloc_waiters_lock; + + // Represents a gc request + GCCause::Cause _requested_gc_cause; + + // The number of stalls experienced during the cycle. Incremented by mutators, reset by control thread. + shenandoah_padding(2); + Atomic _alloc_stall_count; + shenandoah_padding(3); + + // Written by control thread and mutators, clamped between ConcGCThreads and ParallelGCThreads. + Atomic _concurrent_worker_count; + shenandoah_padding(4); + // Increments the internal GC count. void update_gc_id(); + // Increase worker count when a stall is reported. Called from mutator thread. + void increase_concurrent_worker_count(); + + // Decrease worker count if no stalls were detected in a cycle. Called from control thread. + void decrease_concurrent_worker_count(); + + // Returns the total number of allocation stalls during a cycle + size_t alloc_stall_count() const { + return _alloc_stall_count.load_relaxed(); + } + + // Notify the control thread to run a cycle for the given generation + virtual bool notify_control_thread(GCCause::Cause cause, ShenandoahGeneration* generation) = 0; + + // Notify the control thread about an allocation stall specifically. Implementation + // will decide which generation to use and any other mode specific work that must be done. + virtual void notify_alloc_stall(GCCause::Cause cause) = 0; + public: - ShenandoahController(): - _gc_id(0), - _alloc_failure_waiters_lock(WAITERS_LOCK_RANK, "ShenandoahAllocFailureWaiters_lock", true), - _gc_waiters_lock(WAITERS_LOCK_RANK, "ShenandoahGCWaiters_lock", true) - { } + ShenandoahController(); // Request a collection cycle. This handles "explicit" gc requests // like System.gc and "implicit" gc requests, like metaspace oom. - virtual void request_gc(GCCause::Cause cause) = 0; + virtual void request_gc(GCCause::Cause cause); - // This cancels the collection cycle and has an option to block - // until another cycle completes successfully. - void handle_alloc_failure(const ShenandoahAllocRequest& req, bool block); + // Notify threads that the gc has completed a cycle, or it's exiting. + void notify_gc_waiters(); - // Invoked for allocation failures during evacuation. This cancels - // the collection cycle without blocking. - void handle_alloc_failure_evac(size_t words); + // Notify threads that the gc has recovered memory, or it's exiting. + void notify_alloc_waiters(); - // Notify threads waiting for GC to complete. - void notify_alloc_failure_waiters(); + // Inform the control thread that this allocation request failed. Caller will block until memory is reclaimed. + void handle_alloc_failure(const ShenandoahAllocRequest &req); + + // Run a full GC, callers will block until at least one full GC cycle is completed. + void handle_alloc_failure_full(); + + // Return suggested number of concurrent worker threads + size_t concurrent_worker_count() const { + return _concurrent_worker_count.load_relaxed(); + } // Return the value of a monotonic increasing GC count, maintained by the control thread. - size_t get_gc_id(); + size_t get_gc_id() const; + + ShenandoahCollectorPhase get_phase() const { + return _phase.load_relaxed(); + } + + // Record the current phase for the cycle. Used to track where allocation stalls occur. + void set_phase(ShenandoahCollectorPhase phase) { + assert(ShenandoahCollectorPhase::UNSET <= phase, "Phase out of bounds: %d", phase); + assert(phase < ShenandoahCollectorPhase::PHASE_LIMIT, "Phase out of bounds %d", phase); + _phase.store_relaxed(phase); + } + + static const char* collector_phase_to_string(ShenandoahCollectorPhase phase); + +protected: + void handle_requested_gc(GCCause::Cause cause); + + // Used at the end of a cycle to clear allocation failure and notify alloc waiters + virtual void clear_allocation_failure_and_notify_waiters(); }; #endif // SHARE_GC_SHENANDOAH_SHENANDOAHCONTROLLER_HPP diff --git a/src/hotspot/share/gc/shenandoah/shenandoahDegeneratedGC.cpp b/src/hotspot/share/gc/shenandoah/shenandoahDegeneratedGC.cpp deleted file mode 100644 index 071a06d823dd..000000000000 --- a/src/hotspot/share/gc/shenandoah/shenandoahDegeneratedGC.cpp +++ /dev/null @@ -1,526 +0,0 @@ -/* - * Copyright (c) 2021, Red Hat, Inc. All rights reserved. - * Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - * - */ - - -#include "code/codeCache.hpp" -#include "gc/shared/collectorCounters.hpp" -#include "gc/shenandoah/shenandoahClosures.inline.hpp" -#include "gc/shenandoah/shenandoahCollectorPolicy.hpp" -#include "gc/shenandoah/shenandoahConcurrentMark.hpp" -#include "gc/shenandoah/shenandoahDegeneratedGC.hpp" -#include "gc/shenandoah/shenandoahFullGC.hpp" -#include "gc/shenandoah/shenandoahGeneration.hpp" -#include "gc/shenandoah/shenandoahGenerationalHeap.hpp" -#include "gc/shenandoah/shenandoahHeap.inline.hpp" -#include "gc/shenandoah/shenandoahMetrics.hpp" -#include "gc/shenandoah/shenandoahMonitoringSupport.hpp" -#include "gc/shenandoah/shenandoahOldGeneration.hpp" -#include "gc/shenandoah/shenandoahRootProcessor.inline.hpp" -#include "gc/shenandoah/shenandoahSATBMarkQueueSet.hpp" -#include "gc/shenandoah/shenandoahSTWMark.hpp" -#include "gc/shenandoah/shenandoahUtils.hpp" -#include "gc/shenandoah/shenandoahVerifier.hpp" -#include "gc/shenandoah/shenandoahVMOperations.hpp" -#include "gc/shenandoah/shenandoahWorkerPolicy.hpp" -#include "gc/shenandoah/shenandoahYoungGeneration.hpp" -#include "runtime/threads.hpp" -#include "runtime/vmThread.hpp" -#include "utilities/events.hpp" - -ShenandoahDegenGC::ShenandoahDegenGC(ShenandoahDegenPoint degen_point, ShenandoahGeneration* generation, bool do_old_gc_bootstrap) : - ShenandoahGC(generation), - _degen_point(degen_point), - _abbreviated(false), - _do_old_gc_bootstrap(do_old_gc_bootstrap) { -} - -bool ShenandoahDegenGC::collect(GCCause::Cause cause) { - vmop_degenerated(); - ShenandoahHeap* heap = ShenandoahHeap::heap(); - if (heap->mode()->is_generational()) { - FormatBuffer<32> buf("Degenerated %s GC", _generation->name()); - const char* msg = _do_old_gc_bootstrap ? "Degenerated Bootstrap Old GC" : buf.buffer(); - heap->mmu_tracker()->record_degenerated(GCId::current(), msg); - heap->log_heap_status(FormatBuffer<64>("At end of %s", msg)); - } - return true; -} - -void ShenandoahDegenGC::vmop_degenerated() { - TraceCollectorStats tcs(ShenandoahHeap::heap()->monitoring_support()->full_stw_collection_counters()); - ShenandoahTimingsTracker timing(ShenandoahPhaseTimings::degen_gc_gross); - VM_ShenandoahDegeneratedGC degenerated_gc(this); - VMThread::execute(°enerated_gc); -} - -void ShenandoahDegenGC::entry_degenerated() { - const char* msg = degen_event_message(_degen_point); - ShenandoahPausePhase gc_phase(msg, ShenandoahPhaseTimings::degen_gc, true /* log_heap_usage */); - EventMark em("%s", msg); - ShenandoahHeap* const heap = ShenandoahHeap::heap(); - ShenandoahWorkerScope scope(heap->workers(), - ShenandoahWorkerPolicy::calc_workers_for_stw_degenerated(), - "stw degenerated gc"); - - heap->set_degenerated_gc_in_progress(true); - op_degenerated(); - heap->set_degenerated_gc_in_progress(false); - { - ShenandoahTimingsTracker timing(ShenandoahPhaseTimings::degen_gc_propagate_gc_state); - heap->propagate_gc_state_to_all_threads(); - } -} - -void ShenandoahDegenGC::op_degenerated() { - ShenandoahHeap* const heap = ShenandoahHeap::heap(); - heap->release_injected_pins(); - // Degenerated GC is STW, but it can also fail. Current mechanics communicates - // GC failure via cancelled_concgc() flag. So, if we detect the failure after - // some phase, we have to upgrade the Degenerate GC to Full GC. - heap->clear_cancelled_gc(); - - // If we degenerated from evacuation or update-refs, some objects in cset may - // have been self-forwarded by the failing thread. Clear those marks now so - // the remainder of this cycle (re-evac, update-refs, verification) sees a - // clean forwarding state. - if (_degen_point == ShenandoahDegenPoint::_degenerated_evac || - _degen_point == ShenandoahDegenPoint::_degenerated_update_refs) { - ShenandoahGCPhase phase(ShenandoahPhaseTimings::degen_gc_un_self_forward); - heap->un_self_forward_cset_regions(); - } - - // If it's passive mode with ShenandoahCardBarrier turned on: clean the write table - // without swapping the tables since no scan happens in passive mode anyway - if (ShenandoahCardBarrier && !heap->mode()->is_generational()) { - heap->old_generation()->card_scan()->mark_write_table_as_clean(); - } - - if (heap->mode()->is_generational()) { - const ShenandoahOldGeneration* old_generation = heap->old_generation(); - if (!heap->is_concurrent_old_mark_in_progress()) { - // If we are not marking the old generation, there should be nothing in the old mark queues - assert(old_generation->task_queues()->is_empty(), "Old gen task queues should be empty"); - } else { - // This is still necessary for degenerated cycles because the degeneration point may occur - // after final mark of the young generation. See ShenandoahConcurrentGC::op_final_update_refs for - // a more detailed explanation. - old_generation->transfer_pointers_from_satb(); - } - - if (_generation->is_global()) { - // If we are in a global cycle, the old generation should not be marking. It is, however, - // allowed to be holding regions for evacuation or coalescing. - assert(old_generation->is_idle() - || old_generation->is_doing_mixed_evacuations() - || old_generation->is_preparing_for_mark(), - "Old generation cannot be in state: %s", old_generation->state_name()); - } - } - - ShenandoahMetricsSnapshot metrics(heap->free_set()); - - switch (_degen_point) { - // The cases below form the Duff's-like device: it describes the actual GC cycle, - // but enters it at different points, depending on which concurrent phase had - // degenerated. - - case _degenerated_outside_cycle: - // We have degenerated from outside the cycle, which means something is bad with - // the heap, most probably heavy humongous fragmentation, or we are very low on free - // space. It makes little sense to wait for Full GC to reclaim as much as it can, when - // we can do the most aggressive degen cycle, which includes processing references and - // class unloading, unless those features are explicitly disabled. - - // Note that we can only do this for "outside-cycle" degens, otherwise we would risk - // changing the cycle parameters mid-cycle during concurrent -> degenerated handover. - heap->set_unload_classes(_generation->heuristics()->can_unload_classes() && - (!heap->mode()->is_generational() || _generation->is_global())); - - if (heap->mode()->is_generational()) { - // Clean the read table before swapping it. The end goal here is to have a clean - // write table, and to have the read table updated with the previous write table. - heap->old_generation()->card_scan()->mark_read_table_as_clean(); - - if (_generation->is_young()) { - // Swap remembered sets for young - _generation->swap_card_tables(); - } - } - - case _degenerated_roots: - // Degenerated from concurrent root mark, reset the flag for STW mark - if (!heap->mode()->is_generational()) { - if (heap->is_concurrent_mark_in_progress()) { - heap->cancel_concurrent_mark(); - } - } else { - if (_generation->is_concurrent_mark_in_progress()) { - // We want to allow old generation marking to be punctuated by young collections - // (even if they have degenerated). If this is a global cycle, we'd have cancelled - // the entire old gc before coming into this switch. Note that cancel_marking on - // the generation does NOT abandon incomplete SATB buffers as cancel_concurrent_mark does. - // We need to separate out the old pointers which is done below. - _generation->cancel_marking(); - } - - if (_degen_point == ShenandoahDegenPoint::_degenerated_roots) { - // We only need this if the concurrent cycle has already swapped the card tables. - // Marking will use the 'read' table, but interesting pointers may have been - // recorded in the 'write' table in the time between the cancelled concurrent cycle - // and this degenerated cycle. These pointers need to be included in the 'read' table - // used to scan the remembered set during the STW mark which follows here. - _generation->merge_write_table(); - } - } - - op_reset(); - - // STW mark - op_mark(); - - case _degenerated_mark: - // No fallthrough. Continue mark, handed over from concurrent mark if - // concurrent mark has yet completed - if (_degen_point == ShenandoahDegenPoint::_degenerated_mark && heap->is_concurrent_mark_in_progress()) { - assert(!ShenandoahBarrierSet::satb_mark_queue_set().get_filter_out_young(), - "Should not be filtering out young pointers when concurrent mark degenerates"); - op_finish_mark(); - } - assert(!heap->cancelled_gc(), "STW mark can not OOM"); - - /* Degen select Collection Set. etc. */ - op_prepare_evacuation(); - - op_cleanup_early(); - - case _degenerated_evac: - // If heuristics thinks we should do the cycle, this flag would be set, - // and we can do evacuation. Otherwise, it would be the shortcut cycle. - if (heap->is_evacuation_in_progress()) { - - if (_degen_point == _degenerated_evac) { - // Degeneration under oom-evac protocol allows the mutator LRB to expose - // references to from-space objects. This is okay, in theory, because we - // will come to the safepoint here to complete the evacuations and update - // the references. However, if the from-space reference is written to a - // region that was EC during final mark or was recycled after final mark - // it will not have TAMS or UWM updated. Such a region is effectively - // skipped during update references which can lead to crashes and corruption - // if the from-space reference is accessed. - if (UseTLAB) { - heap->labs_make_parsable(); - } - - for (size_t i = 0; i < heap->num_regions(); i++) { - ShenandoahHeapRegion* r = heap->get_region(i); - if (r->is_active() && r->top() > r->get_update_watermark()) { - r->set_update_watermark_at_safepoint(r->top()); - } - } - } - - // Degeneration under oom-evac protocol might have left some objects in - // collection set un-evacuated. Restart evacuation from the beginning to - // capture all objects. For all the objects that are already evacuated, - // it would be a simple check, which is supposed to be fast. This is also - // safe to do even without degeneration, as CSet iterator is at beginning - // in preparation for evacuation anyway. - // - // Before doing that, we need to make sure we never had any cset-pinned - // regions. This may happen if allocation failure happened when evacuating - // the about-to-be-pinned object, oom-evac protocol left the object in - // the collection set, and then the pin reached the cset region. If we continue - // the cycle here, we would trash the cset and alive objects in it. To avoid - // it, we fail degeneration right away and slide into Full GC to recover. - - { - heap->sync_pinned_region_status(); - heap->collection_set()->clear_current_index(); - ShenandoahHeapRegion* r; - while ((r = heap->collection_set()->next()) != nullptr) { - if (r->is_pinned()) { - op_degenerated_fail(); - return; - } - } - - heap->collection_set()->clear_current_index(); - } - op_evacuate(); - if (heap->cancelled_gc()) { - op_degenerated_fail(); - return; - } - } else if (has_in_place_promotions(heap)) { - // We have nothing to evacuate, but there are still regions to promote in place. - ShenandoahGCPhase phase(ShenandoahPhaseTimings::degen_gc_promote_regions); - ShenandoahGenerationalHeap::heap()->promote_regions_in_place(_generation, false /* concurrent*/); - } - - // Update collector state regardless of whether there are forwarded objects - heap->set_evacuation_in_progress(false); - heap->set_concurrent_weak_root_in_progress(false); - heap->set_concurrent_strong_root_in_progress(false); - - // If heuristics thinks we should do the cycle, this flag would be set, - // and we need to do update-refs. Otherwise, it would be the shortcut cycle. - if (heap->has_forwarded_objects()) { - op_init_update_refs(); - assert(!heap->cancelled_gc(), "STW reference update can not OOM"); - } else { - _abbreviated = true; - } - - // labs are retired, walk the old regions and update remembered set - if (ShenandoahHeap::heap()->mode()->is_generational()) { - ShenandoahGenerationalHeap::heap()->old_generation()->update_card_table(); - } - - case _degenerated_update_refs: - if (heap->has_forwarded_objects()) { - op_update_refs(); - op_update_roots(); - assert(!heap->cancelled_gc(), "STW reference update can not OOM"); - } - - op_cleanup_complete(); - - if (heap->mode()->is_generational()) { - ShenandoahGenerationalHeap::heap()->complete_degenerated_cycle(); - } - - break; - default: - ShouldNotReachHere(); - } - - DEBUG_ONLY(heap->assert_no_self_forwards()); - - // Leaving degenerated GC, we need to flip barriers back to idle. - CodeCache::arm_all_nmethods(); - - if (ShenandoahVerify) { - heap->verifier()->verify_after_degenerated(_generation); - } - - if (VerifyAfterGC) { - Universe::verify(); - } - - // Decide if this cycle made good progress, and, if not, should it upgrade to a full GC. - const bool progress = metrics.is_good_progress(); - ShenandoahCollectorPolicy* policy = heap->shenandoah_policy(); - policy->record_degenerated(_generation->is_young(), _abbreviated, progress); - if (progress) { - heap->notify_gc_progress(); - _generation->heuristics()->record_degenerated(heap->mode()->is_generational() && _generation->is_global()); - heap->start_idle_span(); - } else if (policy->should_upgrade_degenerated_gc()) { - // Upgrade to full GC, register full-GC impact on heuristics. - op_degenerated_futile(); - } else { - _generation->heuristics()->record_degenerated(heap->mode()->is_generational() && _generation->is_global()); - } -} - -void ShenandoahDegenGC::op_reset() { - _generation->prepare_gc(); -} - -void ShenandoahDegenGC::op_mark() { - assert(!_generation->is_concurrent_mark_in_progress(), "Should be reset"); - ShenandoahGCPhase phase(ShenandoahPhaseTimings::degen_gc_mark); - ShenandoahSTWMark mark(_generation, false /*full gc*/); - mark.mark(); -} - -void ShenandoahDegenGC::op_finish_mark() { - ShenandoahConcurrentMark mark(_generation); - mark.finish_mark(); -} - -void ShenandoahDegenGC::op_prepare_evacuation() { - ShenandoahHeap* const heap = ShenandoahHeap::heap(); - if (ShenandoahVerify) { - heap->verifier()->verify_roots_no_forwarded(_generation); - } - - // STW cleanup weak roots and unload classes - heap->parallel_cleaning(_generation, false /*full gc*/); - - // Prepare regions and collection set - _generation->prepare_regions_and_collection_set(false /*concurrent*/); - - // Retire the TLABs, which will force threads to reacquire their TLABs after the pause. - // This is needed for two reasons. Strong one: new allocations would be with new freeset, - // which would be outside the collection set, so no cset writes would happen there. - // Weaker one: new allocations would happen past update watermark, and so less work would - // be needed for reference updates (would update the large filler instead). - if (UseTLAB) { - ShenandoahGCPhase phase(ShenandoahPhaseTimings::degen_gc_final_manage_labs); - heap->tlabs_retire(false); - } - - if (!heap->collection_set()->is_empty()) { - if (ShenandoahVerify) { - heap->verifier()->verify_before_evacuation(_generation); - } - - heap->set_evacuation_in_progress(true); - heap->set_has_forwarded_objects(true); - } else { - if (ShenandoahVerify) { - if (has_in_place_promotions(heap)) { - heap->verifier()->verify_after_concmark_with_promotions(_generation); - } else { - heap->verifier()->verify_after_concmark(_generation); - } - } - - if (VerifyAfterGC) { - Universe::verify(); - } - } -} - -bool ShenandoahDegenGC::has_in_place_promotions(const ShenandoahHeap* heap) const { - return heap->mode()->is_generational() && heap->old_generation()->has_in_place_promotions(); -} - -void ShenandoahDegenGC::op_cleanup_early() { - ShenandoahHeap::heap()->recycle_trash(); -} - -void ShenandoahDegenGC::op_evacuate() { - ShenandoahGCPhase phase(ShenandoahPhaseTimings::degen_gc_evac); - ShenandoahHeap::heap()->evacuate_collection_set(_generation, false /* concurrent*/); -} - -void ShenandoahDegenGC::op_init_update_refs() { - // Evacuation has completed - ShenandoahHeap* const heap = ShenandoahHeap::heap(); - heap->prepare_update_heap_references(); - heap->set_update_refs_in_progress(true); -} - -void ShenandoahDegenGC::op_update_refs() { - ShenandoahHeap* const heap = ShenandoahHeap::heap(); - ShenandoahGCPhase phase(ShenandoahPhaseTimings::degen_gc_update_refs); - // Handed over from concurrent update references phase - heap->update_heap_references(_generation, false /*concurrent*/); - - heap->set_update_refs_in_progress(false); - heap->set_has_forwarded_objects(false); -} - -void ShenandoahDegenGC::op_update_roots() { - ShenandoahHeap* const heap = ShenandoahHeap::heap(); - - update_roots(false /*full_gc*/); - - heap->update_heap_region_states(false /*concurrent*/); - - if (ShenandoahVerify) { - heap->verifier()->verify_after_update_refs(_generation); - } - - if (VerifyAfterGC) { - Universe::verify(); - } - - heap->rebuild_free_set(false /*concurrent*/); -} - -void ShenandoahDegenGC::op_cleanup_complete() { - ShenandoahGCPhase phase(ShenandoahPhaseTimings::degen_gc_cleanup_complete); - - ShenandoahHeap* const heap = ShenandoahHeap::heap(); - - // If we are degenerating for old mark only, we need to check that there are no accidental - // young pointers in SATB queues. If so, we need to filter them before we perform cleanups, - // otherwise they would dangle. - ShenandoahSATBMarkQueueSet& satb_qs = ShenandoahBarrierSet::satb_mark_queue_set(); - if (heap->is_concurrent_old_mark_in_progress() && !heap->is_concurrent_young_mark_in_progress()) { - assert(satb_qs.get_filter_out_young(), "Must be"); - ShenandoahFlushSATB flush_satb(satb_qs); - Threads::threads_do(&flush_satb); - } else { - assert(!satb_qs.get_filter_out_young(), "Must not be"); - } - - heap->recycle_trash(); -} - -void ShenandoahDegenGC::op_degenerated_fail() { - upgrade_to_full(); -} - -void ShenandoahDegenGC::op_degenerated_futile() { - upgrade_to_full(); -} - -const char* ShenandoahDegenGC::degen_event_message(ShenandoahDegenPoint point) const { - switch (point) { - case _degenerated_unset: { - SHENANDOAH_EVENT_MESSAGE(msg, _generation->type(), "Pause Degenerated GC", " ()"); - return msg; - } - case _degenerated_outside_cycle: { - SHENANDOAH_EVENT_MESSAGE(msg, _generation->type(), "Pause Degenerated GC", " (Outside of Cycle)"); - return msg; - } - case _degenerated_roots: { - SHENANDOAH_EVENT_MESSAGE(msg, _generation->type(), "Pause Degenerated GC", " (Roots)"); - return msg; - } - case _degenerated_mark: { - SHENANDOAH_EVENT_MESSAGE(msg, _generation->type(), "Pause Degenerated GC", " (Mark)"); - return msg; - } - case _degenerated_evac: { - SHENANDOAH_EVENT_MESSAGE(msg, _generation->type(), "Pause Degenerated GC", " (Evacuation)"); - return msg; - } - case _degenerated_update_refs: { - SHENANDOAH_EVENT_MESSAGE(msg, _generation->type(), "Pause Degenerated GC", " (Update Refs)"); - return msg; - } - default: { - ShouldNotReachHere(); - SHENANDOAH_EVENT_MESSAGE(msg, _generation->type(), "Pause Degenerated GC", " (?)"); - return msg; - } - } -} - -void ShenandoahDegenGC::upgrade_to_full() { - log_info(gc, phases)("Degenerated GC upgrading to Full GC"); - ShenandoahHeap* heap = ShenandoahHeap::heap(); - heap->cancel_gc(GCCause::_shenandoah_upgrade_to_full_gc); - heap->increment_total_collections(true); - heap->shenandoah_policy()->record_degenerated_upgrade_to_full(); - ShenandoahFullGC full_gc; - full_gc.op_full(GCCause::_shenandoah_upgrade_to_full_gc); -} diff --git a/src/hotspot/share/gc/shenandoah/shenandoahDegeneratedGC.hpp b/src/hotspot/share/gc/shenandoah/shenandoahDegeneratedGC.hpp deleted file mode 100644 index f3d033af1288..000000000000 --- a/src/hotspot/share/gc/shenandoah/shenandoahDegeneratedGC.hpp +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (c) 2021, Red Hat, Inc. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - * - */ - -#ifndef SHARE_GC_SHENANDOAH_SHENANDOAHDEGENERATEDGC_HPP -#define SHARE_GC_SHENANDOAH_SHENANDOAHDEGENERATEDGC_HPP - -#include "gc/shenandoah/shenandoahGC.hpp" - -class VM_ShenandoahDegeneratedGC; -class ShenandoahGeneration; - -class ShenandoahDegenGC : public ShenandoahGC { - friend class VM_ShenandoahDegeneratedGC; -private: - const ShenandoahDegenPoint _degen_point; - bool _abbreviated; - const bool _do_old_gc_bootstrap; - -public: - ShenandoahDegenGC(ShenandoahDegenPoint degen_point, ShenandoahGeneration* generation, bool do_old_gc_bootstrap = false); - bool collect(GCCause::Cause cause) override; - -private: - void vmop_degenerated(); - void entry_degenerated(); - void op_degenerated(); - - void op_reset(); - void op_mark(); - void op_finish_mark(); - void op_prepare_evacuation(); - void op_cleanup_early(); - - void op_evacuate(); - void op_init_update_refs(); - void op_update_refs(); - void op_update_roots(); - void op_cleanup_complete(); - - // Fail handling - void op_degenerated_futile(); - void op_degenerated_fail(); - - // Turns this degenerated cycle into a full gc without leaving the safepoint - void upgrade_to_full(); - - const char* degen_event_message(ShenandoahDegenPoint point) const; - - bool has_in_place_promotions(const ShenandoahHeap* heap) const; -}; - -#endif // SHARE_GC_SHENANDOAH_SHENANDOAHDEGENERATEDGC_HPP diff --git a/src/hotspot/share/gc/shenandoah/shenandoahFullGC.cpp b/src/hotspot/share/gc/shenandoah/shenandoahFullGC.cpp index 11b98a10c12d..a7379170ad52 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahFullGC.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahFullGC.cpp @@ -24,9 +24,10 @@ * */ - +#include "classfile/systemDictionary.hpp" #include "code/codeCache.hpp" #include "compiler/oopMap.hpp" +#include "gc/shared/classUnloadingContext.hpp" #include "gc/shared/continuationGCSupport.hpp" #include "gc/shared/fullGCForwarding.inline.hpp" #include "gc/shared/gcTraceTime.inline.hpp" @@ -50,6 +51,7 @@ #include "gc/shenandoah/shenandoahMarkingContext.inline.hpp" #include "gc/shenandoah/shenandoahMetrics.hpp" #include "gc/shenandoah/shenandoahMonitoringSupport.hpp" +#include "gc/shenandoah/shenandoahParallelCleaning.inline.hpp" #include "gc/shenandoah/shenandoahPhaseTimings.hpp" #include "gc/shenandoah/shenandoahReferenceProcessor.hpp" #include "gc/shenandoah/shenandoahRootProcessor.inline.hpp" @@ -58,6 +60,7 @@ #include "gc/shenandoah/shenandoahVerifier.hpp" #include "gc/shenandoah/shenandoahVMOperations.hpp" #include "gc/shenandoah/shenandoahWorkerPolicy.hpp" +#include "gc/shenandoah/shenandoahYoungGeneration.hpp" #include "memory/metaspaceUtils.hpp" #include "memory/universe.hpp" #include "oops/compressedOops.inline.hpp" @@ -111,7 +114,7 @@ void ShenandoahFullGC::op_full(GCCause::Cause cause) { ShenandoahMetricsSnapshot metrics(heap->free_set()); // Perform full GC - do_it(cause); + do_it(); if (heap->mode()->is_generational()) { ShenandoahGenerationalFullGC::handle_completion(heap); @@ -125,9 +128,19 @@ void ShenandoahFullGC::op_full(GCCause::Cause cause) { heap->notify_gc_no_progress(); } - // Regardless if progress was made, we record that we completed a "successful" full GC. - _generation->heuristics()->record_success_full(); - heap->shenandoah_policy()->record_success_full(); + // Regardless if progress was made, we record that we completed a full GC. + if (heap->mode()->is_generational()) { + // In generational mode, the young heuristics are responsible for this failure + heap->young_generation()->heuristics()->record_full_gc(cause); + } else { + _generation->heuristics()->record_full_gc(cause); + } + + if (cause == GCCause::_shenandoah_upgrade_to_full_gc) { + heap->shenandoah_policy()->record_alloc_failure_to_full(); + } + + heap->shenandoah_policy()->record_success_full(heap->control_thread()->get_gc_id()); // Leaving full GC, we need to flip barriers back to idle. CodeCache::arm_all_nmethods(); @@ -138,23 +151,19 @@ void ShenandoahFullGC::op_full(GCCause::Cause cause) { } } -void ShenandoahFullGC::do_it(GCCause::Cause gc_cause) { +void ShenandoahFullGC::do_it() { ShenandoahHeap* heap = ShenandoahHeap::heap(); heap->release_injected_pins(); - // A full GC may be entered directly, or as an upgrade from a failed - // degenerated GC. In the latter case, self-forwarded objects may be - // present from the failed evacuation. Drain those marks before any phase - // (verify, update_roots, phase1_mark_heap) walks headers. - { - ShenandoahGCPhase phase(ShenandoahPhaseTimings::full_gc_un_self_forward); - heap->un_self_forward_cset_regions(); - } - if (heap->mode()->is_generational()) { ShenandoahGenerationalFullGC::prepare(); } + guarantee(heap->is_idle(), "Full GC should not be running from incomplete cycle"); + guarantee(!heap->has_forwarded_objects(), "Concurrent cycle should have finished update references"); + guarantee(!heap->is_concurrent_mark_in_progress(), "Cannot be marking now"); + guarantee(!heap->has_self_forwarded_objects(), "Self forwarded objects should be cleared by concurrent cycle."); + if (ShenandoahVerify) { heap->verifier()->verify_before_fullgc(_generation); } @@ -163,11 +172,6 @@ void ShenandoahFullGC::do_it(GCCause::Cause gc_cause) { Universe::verify(); } - // Degenerated GC may carry concurrent root flags when upgrading to - // full GC. We need to reset it before mutators resume. - heap->set_concurrent_strong_root_in_progress(false); - heap->set_concurrent_weak_root_in_progress(false); - heap->set_full_gc_in_progress(true); assert(ShenandoahSafepoint::is_at_shenandoah_safepoint(), "must be at a safepoint"); @@ -180,39 +184,8 @@ void ShenandoahFullGC::do_it(GCCause::Cause gc_cause) { { ShenandoahGCPhase prepare_phase(ShenandoahPhaseTimings::full_gc_prepare); - // Full GC is supposed to recover from any GC state: - - // a0. Remember if we have forwarded objects - bool has_forwarded_objects = heap->has_forwarded_objects(); - // a1. Cancel evacuation, if in progress - if (heap->is_evacuation_in_progress()) { - heap->set_evacuation_in_progress(false); - } - assert(!heap->is_evacuation_in_progress(), "sanity"); - - // a2. Cancel update-refs, if in progress - if (heap->is_update_refs_in_progress()) { - heap->set_update_refs_in_progress(false); - } - assert(!heap->is_update_refs_in_progress(), "sanity"); - - // b. Cancel all concurrent marks, if in progress - if (heap->is_concurrent_mark_in_progress()) { - heap->cancel_concurrent_mark(); - } - assert(!heap->is_concurrent_mark_in_progress(), "sanity"); - - // c. Update roots if this full GC is due to evac-oom, which may carry from-space pointers in roots. - if (has_forwarded_objects) { - update_roots(true /*full_gc*/); - } - - // d. Abandon reference discovery and clear all discovered references. - ShenandoahReferenceProcessor* rp = _generation->ref_processor(); - rp->abandon_partial_discovery(); - - // e. Sync pinned region status from the CP marks + // Sync pinned region status from the CP marks heap->sync_pinned_region_status(); if (heap->mode()->is_generational()) { @@ -221,8 +194,6 @@ void ShenandoahFullGC::do_it(GCCause::Cause gc_cause) { // The rest of prologue: _preserved_marks->init(heap->workers()->active_workers()); - - assert(heap->has_forwarded_objects() == has_forwarded_objects, "This should not change"); } if (UseTLAB) { @@ -235,11 +206,6 @@ void ShenandoahFullGC::do_it(GCCause::Cause gc_cause) { phase1_mark_heap(); - // Once marking is done, which may have fixed up forwarded objects, we can drop it. - // Coming out of Full GC, we would not have any forwarded objects. - // This also prevents resolves with fwdptr from kicking in while adjusting pointers in phase3. - heap->set_has_forwarded_objects(false); - heap->set_full_gc_move_in_progress(true); // Setup workers for the rest @@ -280,8 +246,6 @@ void ShenandoahFullGC::do_it(GCCause::Cause gc_cause) { heap->set_full_gc_move_in_progress(false); heap->set_full_gc_in_progress(false); - DEBUG_ONLY(heap->assert_no_self_forwards()); - if (ShenandoahVerify) { heap->verifier()->verify_after_fullgc(_generation); } @@ -312,15 +276,91 @@ void ShenandoahFullGC::phase1_mark_heap() { // enable ("weak") refs discovery rp->set_soft_reference_policy(true); // forcefully purge all soft references - ShenandoahSTWMark mark(_generation, true /*full_gc*/); + ShenandoahSTWMark mark(_generation); mark.mark(); - heap->parallel_cleaning(_generation, true /* full_gc */); + parallel_cleaning(_generation); - if (ShenandoahHeap::heap()->mode()->is_generational()) { + if (heap->mode()->is_generational()) { ShenandoahGenerationalFullGC::log_live_in_old(heap); } } +void ShenandoahFullGC::parallel_cleaning(ShenandoahGeneration* generation) const { + assert(SafepointSynchronize::is_at_safepoint(), "Must be at a safepoint"); + assert(ShenandoahHeap::heap()->is_stw_gc_in_progress(), "Only for Full GC"); + ShenandoahGCPhase phase(ShenandoahPhaseTimings::full_gc_purge); + stw_weak_refs(generation); + stw_process_weak_roots(); + stw_unload_classes(); +} + +void ShenandoahFullGC::stw_weak_refs(ShenandoahGeneration* generation) { + // Weak refs processing + ShenandoahPhaseTimings::Phase phase = ShenandoahPhaseTimings::full_gc_weakrefs; + ShenandoahTimingsTracker t(phase); + ShenandoahGCWorkerPhase worker_phase(phase); + generation->ref_processor()->process_references(phase, ShenandoahHeap::heap()->workers(), false /* concurrent */); +} + +// Weak roots are either pre-evacuated (final mark) or updated (final update refs), +// so they should not have forwarded oops. +// However, we do need to "null" dead oops in the roots, if it cannot be done +// in concurrent cycles. +void ShenandoahFullGC::stw_process_weak_roots() const { + ShenandoahHeap* heap = ShenandoahHeap::heap(); + uint num_workers = heap->workers()->active_workers(); + ShenandoahPhaseTimings::Phase timing_phase = ShenandoahPhaseTimings::full_gc_purge_weak_par; + ShenandoahGCPhase phase(timing_phase); + ShenandoahGCWorkerPhase worker_phase(timing_phase); + // Cleanup weak roots + ShenandoahIsAliveClosure is_alive; +#ifdef ASSERT + ShenandoahAssertNotForwardedClosure verify_cl; + ShenandoahParallelWeakRootsCleaningTask cleaning_task(timing_phase, &is_alive, &verify_cl, num_workers); +#else + ShenandoahParallelWeakRootsCleaningTask cleaning_task(timing_phase, &is_alive, &do_nothing_cl, num_workers); +#endif + heap->workers()->run_task(&cleaning_task); +} + +void ShenandoahFullGC::stw_unload_classes() const { + ShenandoahHeap* heap = ShenandoahHeap::heap(); + if (!heap->unload_classes()) return; + ClassUnloadingContext ctx(heap->workers()->active_workers(), + true /* unregister_nmethods_during_purge */, + false /* lock_nmethod_free_separately */); + + // Unload classes and purge SystemDictionary. + { + ShenandoahPhaseTimings::Phase phase = ShenandoahPhaseTimings::full_gc_purge_class_unload; + ShenandoahIsAliveSelector is_alive; + { + CodeCache::UnlinkingScope scope(is_alive.is_alive_closure()); + ShenandoahGCPhase gc_phase(phase); + ShenandoahGCWorkerPhase worker_phase(phase); + bool unloading_occurred = SystemDictionary::do_unloading(_gc_timer); + + ShenandoahClassUnloadingTask unlink_task(phase, unloading_occurred); + heap->workers()->run_task(&unlink_task); + } + // Release unloaded nmethods's memory. + ClassUnloadingContext::context()->purge_and_free_nmethods(); + } + + { + ShenandoahGCPhase phase(ShenandoahPhaseTimings::full_gc_purge_cldg); + ClassLoaderDataGraph::purge(true /* at_safepoint */); + } + // Resize and verify metaspace + MetaspaceGC::compute_new_size(); + + if (heap->mode()->is_generational()) { + heap->old_generation()->set_parsable(false); + } + + DEBUG_ONLY(MetaspaceUtils::verify();) +} + class ShenandoahPrepareForCompactionObjectClosure : public ObjectClosure { private: PreservedMarks* const _preserved_marks; diff --git a/src/hotspot/share/gc/shenandoah/shenandoahFullGC.hpp b/src/hotspot/share/gc/shenandoah/shenandoahFullGC.hpp index c69c8cec8fcb..def0b07c8a4e 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahFullGC.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahFullGC.hpp @@ -25,9 +25,7 @@ #ifndef SHARE_GC_SHENANDOAH_SHENANDOAHFULLGC_HPP #define SHARE_GC_SHENANDOAH_SHENANDOAHFULLGC_HPP -#include "gc/shared/gcTimer.hpp" #include "gc/shenandoah/shenandoahGC.hpp" -#include "gc/shenandoah/shenandoahGenerationalHeap.hpp" #include "gc/shenandoah/shenandoahHeapRegionSet.hpp" /** @@ -50,19 +48,16 @@ * Parallelization is handled by assigning each GC worker the slice of the heap (the set of regions) * where it does sliding compaction, without interfering with other threads. */ - +class GCTimer; class PreservedMarksSet; +class ShenandoahHeap; class VM_ShenandoahFullGC; -class ShenandoahDegenGC; class ShenandoahFullGC : public ShenandoahGC { friend class ShenandoahPrepareForCompactionObjectClosure; friend class VM_ShenandoahFullGC; - friend class ShenandoahDegenGC; -private: GCTimer* _gc_timer; - PreservedMarksSet* _preserved_marks; public: @@ -76,7 +71,7 @@ class ShenandoahFullGC : public ShenandoahGC { void entry_full(GCCause::Cause cause); void op_full(GCCause::Cause cause); - void do_it(GCCause::Cause gc_cause); + void do_it(); void phase1_mark_heap(); void phase2_calculate_target_addresses(ShenandoahHeapRegionSet** worker_slices); @@ -86,6 +81,14 @@ class ShenandoahFullGC : public ShenandoahGC { void distribute_slices(ShenandoahHeapRegionSet** worker_slices); void calculate_target_humongous_objects(); void compact_humongous_objects(); + + // Perform STW class unloading and weak root cleaning + void parallel_cleaning(ShenandoahGeneration* generation) const; + + void stw_unload_classes() const; + void stw_process_weak_roots() const; + + static void stw_weak_refs(ShenandoahGeneration* generation); }; #endif // SHARE_GC_SHENANDOAH_SHENANDOAHFULLGC_HPP diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGC.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGC.cpp deleted file mode 100644 index 6c6207e1618a..000000000000 --- a/src/hotspot/share/gc/shenandoah/shenandoahGC.cpp +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright (c) 2021, Red Hat, Inc. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - * - */ - - -#include "compiler/oopMap.hpp" -#include "gc/shared/workerThread.hpp" -#include "gc/shenandoah/shenandoahClosures.inline.hpp" -#include "gc/shenandoah/shenandoahGC.hpp" -#include "gc/shenandoah/shenandoahHeap.hpp" -#include "gc/shenandoah/shenandoahPhaseTimings.hpp" -#include "gc/shenandoah/shenandoahRootProcessor.inline.hpp" -#include "gc/shenandoah/shenandoahUtils.hpp" - -const char* ShenandoahGC::degen_point_to_string(ShenandoahDegenPoint point) { - switch(point) { - case _degenerated_unset: - return ""; - case _degenerated_outside_cycle: - return "Outside of Cycle"; - case _degenerated_roots: - return "Roots"; - case _degenerated_mark: - return "Mark"; - case _degenerated_evac: - return "Evacuation"; - case _degenerated_update_refs: - return "Update References"; - default: - ShouldNotReachHere(); - return "ERROR"; - } -} - -class ShenandoahUpdateRootsTask : public WorkerTask { -private: - ShenandoahRootUpdater* _root_updater; - bool _check_alive; -public: - ShenandoahUpdateRootsTask(ShenandoahRootUpdater* root_updater, bool check_alive) : - WorkerTask("Shenandoah Update Roots"), - _root_updater(root_updater), - _check_alive(check_alive){ - } - - void work(uint worker_id) { - assert(ShenandoahSafepoint::is_at_shenandoah_safepoint(), "Must be at a safepoint"); - ShenandoahParallelWorkerSession worker_session(worker_id); - - ShenandoahNonConcUpdateRefsClosure cl; - if (_check_alive) { - ShenandoahForwardedIsAliveClosure is_alive; - _root_updater->roots_do(worker_id, &is_alive, &cl); - } else { - AlwaysTrueClosure always_true; - _root_updater->roots_do(worker_id, &always_true, &cl); - } - } -}; - -void ShenandoahGC::update_roots(bool full_gc) { - assert(ShenandoahSafepoint::is_at_shenandoah_safepoint(), "Must be at a safepoint"); - assert(ShenandoahHeap::heap()->is_full_gc_in_progress() || - ShenandoahHeap::heap()->is_degenerated_gc_in_progress(), - "Only for degenerated GC and full GC"); - - bool check_alive = !full_gc; - ShenandoahPhaseTimings::Phase p = full_gc ? - ShenandoahPhaseTimings::full_gc_update_roots : - ShenandoahPhaseTimings::degen_gc_update_roots; - - ShenandoahGCPhase phase(p); -#ifdef COMPILER2 - DerivedPointerTable::clear(); -#endif // COMPILER2 - - ShenandoahHeap* const heap = ShenandoahHeap::heap(); - WorkerThreads* workers = heap->workers(); - uint nworkers = workers->active_workers(); - - ShenandoahRootUpdater root_updater(nworkers, p); - ShenandoahUpdateRootsTask update_roots(&root_updater, check_alive); - workers->run_task(&update_roots); - -#ifdef COMPILER2 - DerivedPointerTable::update_pointers(); -#endif // COMPILER2 -} diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGC.hpp b/src/hotspot/share/gc/shenandoah/shenandoahGC.hpp index 7182665f2e3a..670cf6dad5a0 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGC.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGC.hpp @@ -29,45 +29,27 @@ #include "memory/allocation.hpp" /* - * Base class of three Shenandoah GC modes + * Base class of three Shenandoah GC collections * * The relationship of the GCs: * * ("normal" mode) ----> Concurrent GC ----> (finish) - * | - * | - * v - * ("passive" mode) ---> Degenerated GC ---> (finish) - * | - * | - * v - * Full GC --------> (finish) + * ("old" mode) --> Concurrent GC (bootstrap) --> Old GC --> (finish) + * ("passive" mode) ---> Full GC --------> (finish) */ class ShenandoahGeneration; class ShenandoahGC : public StackObj { public: - // Fail point from concurrent GC - enum ShenandoahDegenPoint { - _degenerated_unset, - _degenerated_outside_cycle, - _degenerated_roots, - _degenerated_mark, - _degenerated_evac, - _degenerated_update_refs, - _DEGENERATED_LIMIT - }; - - explicit ShenandoahGC(ShenandoahGeneration* generation) : _generation(generation) {} + explicit ShenandoahGC(ShenandoahGeneration* generation) + : _generation(generation) { } // Returns false if the collection was cancelled, true otherwise. virtual bool collect(GCCause::Cause cause) = 0; - static const char* degen_point_to_string(ShenandoahDegenPoint point); ShenandoahGeneration* generation() const { return _generation; } protected: - static void update_roots(bool full_gc); ShenandoahGeneration* _generation; }; diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp index 6b1d94d64348..718b32ee7fc9 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp @@ -84,24 +84,6 @@ class ShenandoahResetBitmapClosure final : public ShenandoahHeapRegionClosure { bool is_thread_safe() override { return true; } }; -// Copy the write-version of the card-table into the read-version, clearing the -// write-copy. -class ShenandoahMergeWriteTable: public ShenandoahHeapRegionClosure { -private: - ShenandoahScanRemembered* _scanner; -public: - ShenandoahMergeWriteTable(ShenandoahScanRemembered* scanner) : _scanner(scanner) {} - - void heap_region_do(ShenandoahHeapRegion* r) override { - assert(r->is_old(), "Don't waste time doing this for non-old regions"); - _scanner->merge_write_table(r->bottom(), ShenandoahHeapRegion::region_size_words()); - } - - bool is_thread_safe() override { - return true; - } -}; - // Add [TAMS, top) volume over young regions. Used to correct age 0 cohort census // for adaptive tenuring when census is taken during marking. // In non-product builds, for the purposes of verification, we also collect the total @@ -229,20 +211,6 @@ void ShenandoahGeneration::swap_card_tables() { old_generation->card_scan()->swap_card_tables(); } -// Copy the write-version of the card-table into the read-version, clearing the -// write-version. The work is done at a safepoint and in parallel by the GC -// worker threads. -void ShenandoahGeneration::merge_write_table() { - // This should only happen for degenerated cycles - ShenandoahGenerationalHeap* heap = ShenandoahGenerationalHeap::heap(); - heap->assert_gc_workers(heap->workers()->active_workers()); - shenandoah_assert_safepoint(); - - ShenandoahOldGeneration* old_generation = heap->old_generation(); - ShenandoahMergeWriteTable task(old_generation->card_scan()); - old_generation->parallel_heap_region_iterate(&task); -} - void ShenandoahGeneration::prepare_gc() { reset_mark_bitmap(); } @@ -251,16 +219,15 @@ void ShenandoahGeneration::parallel_heap_region_iterate_free(ShenandoahHeapRegio ShenandoahHeap::heap()->parallel_heap_region_iterate(cl); } -void ShenandoahGeneration::prepare_regions_and_collection_set(bool concurrent) { +void ShenandoahGeneration::prepare_regions_and_collection_set() { ShenandoahHeap* heap = ShenandoahHeap::heap(); ShenandoahCollectionSet* collection_set = heap->collection_set(); bool is_generational = heap->mode()->is_generational(); - assert(!heap->is_full_gc_in_progress(), "Only for concurrent and degenerated GC"); + assert(!heap->is_full_gc_in_progress(), "Only for concurrent GC"); assert(!is_old(), "Only YOUNG and GLOBAL GC perform evacuations"); { - ShenandoahGCPhase phase(concurrent ? ShenandoahPhaseTimings::final_update_region_states : - ShenandoahPhaseTimings::degen_gc_final_update_region_states); + ShenandoahGCPhase phase(ShenandoahPhaseTimings::final_update_region_states); // Update region state for every active region, but only update the liveness data for // the generation we marked. We always need to update the watermark for old regions. // If there are mixed collections pending, we also need to synchronize the pinned status @@ -285,8 +252,7 @@ void ShenandoahGeneration::prepare_regions_and_collection_set(bool concurrent) { } { - ShenandoahGCPhase phase(concurrent ? ShenandoahPhaseTimings::choose_cset : - ShenandoahPhaseTimings::degen_gc_choose_cset); + ShenandoahGCPhase phase(ShenandoahPhaseTimings::choose_cset); collection_set->clear(); ShenandoahHeapLocker locker(heap->lock()); @@ -308,10 +274,9 @@ void ShenandoahGeneration::prepare_regions_and_collection_set(bool concurrent) { } { - ShenandoahGCPhase phase(concurrent ? ShenandoahPhaseTimings::final_rebuild_freeset : - ShenandoahPhaseTimings::degen_gc_final_rebuild_freeset); + ShenandoahGCPhase phase(ShenandoahPhaseTimings::final_rebuild_freeset); ShenandoahHeapLocker locker(heap->lock()); - // At start of evacation, we do NOT compute_old_generation_balance() + // At start of evacuation, we do NOT compute_old_generation_balance() size_t young_trashed_regions, old_trashed_regions, first_old, last_old, num_old; _free_set->prepare_to_rebuild(young_trashed_regions, old_trashed_regions, first_old, last_old, num_old); _free_set->finish_rebuild(young_trashed_regions, old_trashed_regions, num_old); @@ -427,8 +392,3 @@ size_t ShenandoahGeneration::available(size_t capacity) const { size_t result = in_use > capacity ? 0 : capacity - in_use; return result; } - -void ShenandoahGeneration::record_success_concurrent(bool abbreviated) { - heuristics()->record_success_concurrent(); - ShenandoahHeap::heap()->shenandoah_policy()->record_success_concurrent(is_young(), abbreviated); -} diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.hpp b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.hpp index 0183db7d1b3a..9bbba38d3638 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.hpp @@ -109,12 +109,9 @@ class ShenandoahGeneration : public CHeapObj, public ShenandoahSpaceInfo { template void reset_mark_bitmap(); - // Used by concurrent and degenerated GC to reset remembered set. + // Used by concurrent GC to reset remembered set. void swap_card_tables(); - // Update the read cards with the state of the write table (write table is not cleared). - void merge_write_table(); - // Called before init mark, expected to prepare regions for marking. virtual void prepare_gc(); @@ -135,7 +132,7 @@ class ShenandoahGeneration : public CHeapObj, public ShenandoahSpaceInfo { // been set aside to hold objects evacuated from the young-gen collection set. Conservatively, this value // equals the entire amount of live young-gen memory within the collection set, even though some of this memory // will likely be promoted. - virtual void prepare_regions_and_collection_set(bool concurrent); + virtual void prepare_regions_and_collection_set(); // Cancel marking (used by Full collect and when cancelling cycle). virtual void cancel_marking(); @@ -179,8 +176,6 @@ class ShenandoahGeneration : public CHeapObj, public ShenandoahSpaceInfo { virtual bool is_concurrent_mark_in_progress() = 0; void confirm_heuristics_mode(); - - virtual void record_success_concurrent(bool abbreviated); }; #endif // SHARE_GC_SHENANDOAH_SHENANDOAHGENERATION_HPP diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.cpp index d73ba05b44a9..42609cbce723 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.cpp @@ -28,7 +28,6 @@ #include "gc/shenandoah/shenandoahAsserts.hpp" #include "gc/shenandoah/shenandoahCollectorPolicy.hpp" #include "gc/shenandoah/shenandoahConcurrentGC.hpp" -#include "gc/shenandoah/shenandoahDegeneratedGC.hpp" #include "gc/shenandoah/shenandoahFreeSet.hpp" #include "gc/shenandoah/shenandoahFullGC.hpp" #include "gc/shenandoah/shenandoahGeneration.hpp" @@ -48,13 +47,9 @@ #include "utilities/events.hpp" ShenandoahGenerationalControlThread::ShenandoahGenerationalControlThread() : - _control_lock(CONTROL_LOCK_RANK, "ShenandoahGCRequest_lock", true), - _requested_gc_cause(GCCause::_no_gc), _requested_generation(nullptr), _gc_mode(none), - _degen_point(ShenandoahGC::_degenerated_unset), - _heap(ShenandoahGenerationalHeap::heap()), - _do_old_gc_bootstrap(false) { + _heap(ShenandoahGenerationalHeap::heap()) { shenandoah_assert_generational(); set_name("ShenControl"); create_and_start(); @@ -62,10 +57,7 @@ ShenandoahGenerationalControlThread::ShenandoahGenerationalControlThread() : void ShenandoahGenerationalControlThread::run_service() { - // This is the only instance of request. It is important that request.generation - // does not change between a concurrent cycle failure and the start of a degenerated - // cycle. We initialize it with the young generation to handle the pathological case - // where the very first cycle is degenerated (some tests exercise this path). + // This is the only instance of request. ShenandoahGCRequest request; request.generation = _heap->young_generation(); while (!should_terminate()) { @@ -85,6 +77,7 @@ void ShenandoahGenerationalControlThread::run_service() { // if there was no other cycle requested, cleanup and wait for the next request. MonitorLocker ml(&_control_lock, Mutex::_no_safepoint_check_flag); if (_requested_gc_cause == GCCause::_no_gc) { + _heap->clear_cancelled_gc(); set_gc_mode(ml, none); ml.wait(); } @@ -92,7 +85,7 @@ void ShenandoahGenerationalControlThread::run_service() { // In case any threads are waiting for a cycle to happen, notify them so they observe the shutdown. notify_gc_waiters(); - notify_alloc_failure_waiters(); + notify_alloc_waiters(); set_gc_mode(stopped); // We're done writing GC stats, so print them here. @@ -102,36 +95,41 @@ void ShenandoahGenerationalControlThread::run_service() { void ShenandoahGenerationalControlThread::stop_service() { log_debug(gc, thread)("Stopping control thread"); MonitorLocker ml(&_control_lock, Mutex::_no_safepoint_check_flag); - _heap->cancel_gc(GCCause::_shenandoah_stop_vm); notify_control_thread(ml, GCCause::_shenandoah_stop_vm); // We can't wait here because it may interfere with the active cycle's ability // to reach a safepoint (this runs on a java thread). } +void ShenandoahGenerationalControlThread::notify_alloc_stall(GCCause::Cause cause) { + ShenandoahGeneration* generation = _heap->young_generation(); + + // Tell the young generation heuristics about the stall. + generation->heuristics()->record_allocation_stall(); + + MonitorLocker ml(&_control_lock, Mutex::_no_safepoint_check_flag); + notify_control_thread(ml, cause, generation); +} + void ShenandoahGenerationalControlThread::check_for_request(ShenandoahGCRequest& request) { // Hold the lock while we read request cause and generation MonitorLocker ml(&_control_lock, Mutex::_no_safepoint_check_flag); - log_debug(gc, thread)("cancelled cause: %s, requested cause: %s", - GCCause::to_string(_heap->cancelled_cause()), GCCause::to_string(_requested_gc_cause)); + log_debug(gc, thread)("Requested cause: %s", GCCause::to_string(_requested_gc_cause)); request.cause = _requested_gc_cause; + request.generation = _requested_generation; + + // If the cause was an allocation failure or an explicit request, override the generation if (ShenandoahCollectorPolicy::is_allocation_failure(request.cause)) { - if (_degen_point == ShenandoahGC::_degenerated_unset) { + if (_heap->old_generation()->is_concurrent_mark_in_progress()) { + // We know we can't allocate in young, and we know we need to collect old. + // We don't really know how much longer it will take to clear out old (probably + // a long time if it keeps getting interrupted by allocation failures). At + // this point, let's run a global cycle. + request.generation = _heap->global_generation(); + } else if (_requested_generation == nullptr) { request.generation = _heap->young_generation(); - _degen_point = ShenandoahGC::_degenerated_outside_cycle; - } else { - assert(request.generation != nullptr, "Must know which generation to use for degenerated cycle"); } - } else { - if (request.cause == GCCause::_shenandoah_concurrent_gc || ShenandoahCollectorPolicy::is_explicit_gc(request.cause)) { - // This is a regulator request or an explicit gc request. Note that an explicit gc request is allowed to - // "upgrade" a regulator request. It is possible that the regulator "canceled" an old mark, so we must - // clear that cancellation here or the explicit gc cycle will erroneously detect it as a cancellation. - // This clear operation will only clear the cancellation if it was set by regulator request. - _heap->clear_cancellation(GCCause::_shenandoah_concurrent_gc); - } - request.generation = _requested_generation; } log_debug(gc, thread)("request.cause: %s, request.generation: %s", @@ -139,79 +137,68 @@ void ShenandoahGenerationalControlThread::check_for_request(ShenandoahGCRequest& _requested_gc_cause = GCCause::_no_gc; _requested_generation = nullptr; + _heap->clear_cancelled_gc(); if (request.cause == GCCause::_no_gc || request.cause == GCCause::_shenandoah_stop_vm) { return; } assert(request.generation != nullptr, "request.generation cannot be null, cause is: %s", GCCause::to_string(request.cause)); + GCMode mode; - if (ShenandoahCollectorPolicy::is_allocation_failure(request.cause)) { - mode = prepare_for_allocation_failure_gc(request); - } else if (ShenandoahCollectorPolicy::is_explicit_gc(request.cause)) { - mode = prepare_for_explicit_gc(request); + if (ShenandoahCollectorPolicy::should_run_full_gc(request.cause)) { + mode = prepare_for_full_gc(request); } else { mode = prepare_for_concurrent_gc(request); } set_gc_mode(ml, mode); } -ShenandoahGenerationalControlThread::GCMode ShenandoahGenerationalControlThread::prepare_for_allocation_failure_gc(ShenandoahGCRequest &request) { - // Important: not all paths update the request.generation. This is intentional. - // A degenerated cycle must use the same generation carried over from the previous request. - if (request.generation->is_old()) { - // This means we degenerated during the young bootstrap for the old generation - // cycle. The following degenerated cycle should therefore also be young. - request.generation = _heap->young_generation(); - } - - ShenandoahHeuristics* heuristics = request.generation->heuristics(); - bool old_gen_evacuation_failed = _heap->old_generation()->clear_failed_evacuation(); - - heuristics->log_trigger("Handle Allocation Failure"); - - // Do not bother with degenerated cycle if old generation evacuation failed - if (ShenandoahDegeneratedGC && heuristics->should_degenerate_cycle() && !old_gen_evacuation_failed) { - heuristics->record_allocation_failure_gc(); - _heap->shenandoah_policy()->record_alloc_failure_to_degenerated(_degen_point); - return stw_degenerated; - } else { - heuristics->record_allocation_failure_gc(); - _heap->shenandoah_policy()->record_alloc_failure_to_full(); - request.generation = _heap->global_generation(); +ShenandoahGenerationalControlThread::GCMode ShenandoahGenerationalControlThread::prepare_for_full_gc(ShenandoahGCRequest &request) const { + if (ShenandoahCollectorPolicy::is_explicit_gc(request.cause)) { + prepare_for_explicit_gc(request); return stw_full; } + + request.generation = _heap->global_generation(); + request.generation->heuristics()->log_trigger("Handle Full GC: %s", GCCause::to_string(request.cause)); + return stw_full; } -ShenandoahGenerationalControlThread::GCMode ShenandoahGenerationalControlThread::prepare_for_explicit_gc(ShenandoahGCRequest &request) const { - ShenandoahHeuristics* global_heuristics = _heap->global_generation()->heuristics(); +void ShenandoahGenerationalControlThread::prepare_for_explicit_gc(ShenandoahGCRequest &request) const { request.generation = _heap->global_generation(); - global_heuristics->log_trigger("GC Request (%s)", GCCause::to_string(request.cause)); - global_heuristics->record_requested_gc(); + request.generation->heuristics()->record_requested_gc(); + request.generation->heuristics()->log_trigger("GC Request (%s)", GCCause::to_string(request.cause)); + _heap->set_unload_classes(request.generation->heuristics()->can_unload_classes()); +} - if (ShenandoahCollectorPolicy::should_run_full_gc(request.cause)) { - return stw_full; - } else { - // Unload and clean up everything. Note that this is an _explicit_ request and so does not use - // the same `should_unload_classes` call as the regulator's concurrent gc request. - _heap->set_unload_classes(global_heuristics->can_unload_classes()); +ShenandoahGenerationalControlThread::GCMode ShenandoahGenerationalControlThread::prepare_for_concurrent_gc(ShenandoahGCRequest &request) const { + + if (ShenandoahCollectorPolicy::is_explicit_gc(request.cause)) { + prepare_for_explicit_gc(request); return concurrent_normal; } -} - -ShenandoahGenerationalControlThread::GCMode ShenandoahGenerationalControlThread::prepare_for_concurrent_gc(const ShenandoahGCRequest &request) const { - assert(!(request.generation->is_old() && _heap->old_generation()->is_doing_mixed_evacuations()), - "Old heuristic should not request cycles while it waits for mixed evacuations"); if (request.generation->is_global()) { - ShenandoahHeuristics* global_heuristics = _heap->global_generation()->heuristics(); - _heap->set_unload_classes(global_heuristics->should_unload_classes()); + _heap->set_unload_classes(request.generation->heuristics()->should_unload_classes()); } else { _heap->set_unload_classes(false); } + if (request.generation->is_old()) { + switch (_heap->old_generation()->state()) { + case ShenandoahOldGeneration::FILLING: + case ShenandoahOldGeneration::MARKING: + return servicing_old; + case ShenandoahOldGeneration::IDLE: + return bootstrapping_old; + default: + fatal("Should not be getting request for old collection in state: %s", _heap->old_generation()->state_name()); + } + } + // preemption was requested or this is a regular cycle - return request.generation->is_old() ? servicing_old : concurrent_normal; + return concurrent_normal; } void ShenandoahGenerationalControlThread::maybe_print_young_region_ages() const { @@ -234,6 +221,15 @@ void ShenandoahGenerationalControlThread::maybe_print_young_region_ages() const } } +void ShenandoahGenerationalControlThread::clear_allocation_failure_and_notify_waiters() { + ShenandoahController::clear_allocation_failure_and_notify_waiters(); + if (_heap->old_generation()->clear_failed_evacuation()) { + // Immediately run a full GC. + log_info(gc, ergo)("Requesting a full gc because of evacuation failures in old"); + notify_control_thread(GCCause::_shenandoah_upgrade_to_full_gc, _heap->global_generation()); + } +} + void ShenandoahGenerationalControlThread::run_gc_cycle(const ShenandoahGCRequest& request) { log_debug(gc, thread)("Starting GC (%s): %s, %s", gc_mode_name(gc_mode()), GCCause::to_string(request.cause), request.generation->name()); @@ -275,34 +271,25 @@ void ShenandoahGenerationalControlThread::run_gc_cycle(const ShenandoahGCRequest service_concurrent_normal_cycle(request); break; } - case stw_degenerated: { - service_stw_degenerated_cycle(request); + case bootstrapping_old: + case servicing_old: { + service_concurrent_old_cycle(request); break; } case stw_full: { service_stw_full_cycle(request.cause); break; } - case servicing_old: { - assert(request.generation->is_old(), "Expected old generation here"); - service_concurrent_old_cycle(request); - break; - } default: ShouldNotReachHere(); } _heap->print_after_gc(); } - // If this cycle completed successfully, notify threads waiting for gc - if (!_heap->cancelled_gc()) { - notify_gc_waiters(); - notify_alloc_failure_waiters(); - // Report current free set state at the end of cycle if normal completion. - // Do not report if cancelled, since we may not have rebuilt free set and content is unreliable. - _heap->free_set()->log_status_under_lock(); - } + // Try to reduce concurrent workers + decrease_concurrent_worker_count(); + _heap->free_set()->log_status_under_lock(); // Notify Universe about new heap usage. This has implications for // global soft refs policy, and we better report it every time heap @@ -345,41 +332,23 @@ void ShenandoahGenerationalControlThread::run_gc_cycle(const ShenandoahGCRequest } log_debug(gc, thread)("Completed GC (%s): %s, %s, cancelled: %s", - gc_mode_name(gc_mode()), GCCause::to_string(request.cause), request.generation->name(), GCCause::to_string(_heap->cancelled_cause())); + gc_mode_name(gc_mode()), GCCause::to_string(request.cause), request.generation->name(), BOOL_TO_STR(_heap->cancelled_gc())); } -// Young and old concurrent cycles are initiated by the regulator. GC requests are handled by the controller thread and -// always run a global cycle (which is concurrent by default, but may be overridden by command line options). Old cycles -// always degenerate to a global cycle. Young cycles are degenerated to complete the young cycle. Young and old degen -// may upgrade to Full GC. Full GC may also be triggered directly by a System.gc() invocation. -// -// -// +-----+ Idle +-----+-----------+---------------------+ -// | + | | | -// | | | | | -// | | v | | -// | | Bootstrap Old +-- | ------------+ | -// | | + | | | -// | | | | | | -// | v v v v | -// | Resume Old <----------+ Young +--> Young Degen | -// | + + ^ + + | -// v | | | | | | -// Global <-+ | +----------------------------+ | | -// + | | | -// | v v | -// +---> Global Degen +--------------------> Full <----+ -// +// Young and old concurrent cycles are initiated by the regulator. GC requests are +// handled by the controller thread and always run a global cycle (which is concurrent +// by default, but may be overridden by command line options to run on a safepoint). +// If there is an allocation failure during old marking, it will be cancelled. The +// next cycle will be global to complete collection of old and young together. Except for +// old marking, all concurrent cycles run to completion before returning to IDLE to choose +// the next action. void ShenandoahGenerationalControlThread::service_concurrent_normal_cycle(const ShenandoahGCRequest& request) { log_info(gc, ergo)("Start GC cycle (%s)", request.generation->name()); - if (request.generation->is_old()) { - service_concurrent_old_cycle(request); - } else { - service_concurrent_cycle(request.generation, request.cause, false); - } + service_concurrent_cycle(request.generation, request.cause, false); } void ShenandoahGenerationalControlThread::service_concurrent_old_cycle(const ShenandoahGCRequest& request) { + log_info(gc, ergo)("Start GC cycle (%s)", request.generation->name()); ShenandoahOldGeneration* old_generation = _heap->old_generation(); ShenandoahYoungGeneration* young_generation = _heap->young_generation(); ShenandoahOldGeneration::State original_state = old_generation->state(); @@ -389,74 +358,59 @@ void ShenandoahGenerationalControlThread::service_concurrent_old_cycle(const She _heap->increment_total_collections(false); switch (original_state) { - case ShenandoahOldGeneration::FILLING: { - ShenandoahGCSession session(request.cause, old_generation); - assert(gc_mode() == servicing_old, "Filling should be servicing old"); - _allow_old_preemption.set(); - old_generation->entry_coalesce_and_fill(); - _allow_old_preemption.unset(); - - // Before bootstrapping begins, we must acknowledge any cancellation request. - // If the gc has not been cancelled, this does nothing. If it has been cancelled, - // this will clear the cancellation request and exit before starting the bootstrap - // phase. This will allow the young GC cycle to proceed normally. If we do not - // acknowledge the cancellation request, the subsequent young cycle will observe - // the request and essentially cancel itself. - if (check_cancellation_or_degen(ShenandoahGC::_degenerated_outside_cycle)) { - // Need to report at "gc" level to report GC ID proper. - log_info(gc)("Preparation for old generation cycle was cancelled"); - return; - } - - // Coalescing threads completed and nothing was cancelled. it is safe to transition from this state. - old_generation->transition_to(ShenandoahOldGeneration::IDLE); - return; - } case ShenandoahOldGeneration::IDLE: old_generation->transition_to(ShenandoahOldGeneration::MARKING); // Configure the young generation's concurrent mark to put objects in // old regions into the concurrent mark queues associated with the old // generation. The young cycle will run as normal except that rather than // ignore old references it will mark and enqueue them in the old concurrent - // task queues but it will not traverse them. - set_gc_mode(bootstrapping_old); + // task queues, but it will not traverse them. + young_generation->set_old_gen_task_queues(old_generation->task_queues()); service_concurrent_cycle(young_generation, request.cause, true); _heap->process_gc_stats(); - if (_heap->cancelled_gc()) { - // Young generation bootstrap cycle has failed. Concurrent mark for old generation - // is going to resume after degenerated bootstrap cycle completes. - log_info(gc, phases)("Bootstrap cycle for old generation was cancelled"); - return; - } - - assert(_degen_point == ShenandoahGC::_degenerated_unset, "Degen point should not be set if gc wasn't cancelled"); - // From here we will 'resume' the old concurrent mark. This will skip reset - // and init mark for the concurrent mark. All of that work will have been - // done by the bootstrapping young cycle. - set_gc_mode(servicing_old); + // Here we return to the top of the control loop so that we can test + // if memory was exhausted before we start marking old. This will notify alloc + // waiters and give them a chance to resume before old marking begins. If our + // bootstrap cycle did not free enough memory, we will escalate to a global cycle + // and skip old marking. + break; case ShenandoahOldGeneration::MARKING: { ShenandoahGCSession session(request.cause, old_generation); - bool marking_complete = resume_concurrent_old_cycle(old_generation, request.cause); - if (marking_complete) { - assert(old_generation->state() != ShenandoahOldGeneration::MARKING, "Should not still be marking"); - if (original_state == ShenandoahOldGeneration::MARKING) { - _heap->mmu_tracker()->record_old_marking_increment(true); - _heap->log_heap_status("At end of Concurrent Old Marking finishing increment"); - } - } else if (original_state == ShenandoahOldGeneration::MARKING) { + resume_concurrent_old_cycle(old_generation, request.cause); + if (old_generation->state() != ShenandoahOldGeneration::MARKING) { + _heap->mmu_tracker()->record_old_marking_increment(true); + _heap->log_heap_status("At end of Concurrent Old Marking finishing increment"); + } else { _heap->mmu_tracker()->record_old_marking_increment(false); _heap->log_heap_status("At end of Concurrent Old Marking increment"); } break; } + case ShenandoahOldGeneration::FILLING: { + ShenandoahGCSession session(request.cause, old_generation); + _allow_old_preemption.set(); + const bool filling_complete = old_generation->entry_coalesce_and_fill(); + _allow_old_preemption.unset(); + + if (!filling_complete) { + assert(old_generation->state() == ShenandoahOldGeneration::FILLING, "Should still be filling"); + // Need to report at "gc" level to report GC ID proper. + log_info(gc)("Preparation for old generation cycle was cancelled"); + break; + } + + // Coalescing threads completed and nothing was cancelled. it is safe to transition from this state. + old_generation->transition_to(ShenandoahOldGeneration::IDLE); + break; + } default: fatal("Unexpected state for old GC: %s", ShenandoahOldGeneration::state_name(old_generation->state())); } } -bool ShenandoahGenerationalControlThread::resume_concurrent_old_cycle(ShenandoahOldGeneration* generation, GCCause::Cause cause) { +void ShenandoahGenerationalControlThread::resume_concurrent_old_cycle(ShenandoahOldGeneration* generation, GCCause::Cause cause) { assert(_heap->is_concurrent_old_mark_in_progress(), "Old mark should be in progress"); log_debug(gc)("Resuming old generation with " UINT32_FORMAT " marking tasks queued", generation->task_queues()->tasks()); @@ -465,61 +419,26 @@ bool ShenandoahGenerationalControlThread::resume_concurrent_old_cycle(Shenandoah // is allowed to cancel a GC. ShenandoahOldGC gc(generation, _allow_old_preemption); if (gc.collect(cause)) { - _heap->notify_gc_progress(); - generation->record_success_concurrent(false); - } - - if (_heap->cancelled_gc()) { - // It's possible the gc cycle was cancelled after the last time the collection checked for cancellation. In which - // case, the old gc cycle is still completed, and we have to deal with this cancellation. We set the degeneration - // point to be outside the cycle because if this is an allocation failure, that is what must be done (there is no - // degenerated old cycle). If the cancellation was due to a heuristic wanting to start a young cycle, then we are - // not actually going to a degenerated cycle, so don't set the degeneration point here. - if (ShenandoahCollectorPolicy::is_allocation_failure(cause)) { - check_cancellation_or_degen(ShenandoahGC::_degenerated_outside_cycle); - } else if (cause == GCCause::_shenandoah_concurrent_gc) { - _heap->shenandoah_policy()->record_interrupted_old(); + if (alloc_stall_count() == 0) { + _heap->notify_gc_progress(); } - return false; + generation->heuristics()->record_concurrent_completion(); + _heap->shenandoah_policy()->record_success_old(); + + // Old marking has completed, it will have rebuilt the free set with any immediate garbage. + // Notify alloc waiters. We don't notify cycle waiters because no threads should be waiting + // for an old cycle and because cycle waiters would have requested a global cycle that would + // have cancelled the old mark anyway. + clear_allocation_failure_and_notify_waiters(); + } else if (cause == GCCause::_shenandoah_concurrent_gc) { + _heap->shenandoah_policy()->record_interrupted_old(); } - return true; } -// Normal cycle goes via all concurrent phases. If allocation failure (af) happens during -// any of the concurrent phases, it first degrades to Degenerated GC and completes GC there. -// If second allocation failure happens during Degenerated GC cycle (for example, when GC -// tries to evac something and no memory is available), cycle degrades to Full GC. -// -// There are also a shortcut through the normal cycle: immediate garbage shortcut, when -// heuristics says there are no regions to compact, and all the collection comes from immediately -// reclaimable regions. -// -// ................................................................................................ -// -// (immediate garbage shortcut) Concurrent GC -// /-------------------------------------------\ -// | | -// | | -// | | -// | v -// [START] ----> Conc Mark ----o----> Conc Evac --o--> Conc Update-Refs ---o----> [END] -// | | | ^ -// | (af) | (af) | (af) | -// ..................|....................|.................|..............|....................... -// | | | | -// | | | | Degenerated GC -// v v v | -// STW Mark ----------> STW Evac ----> STW Update-Refs ----->o -// | | | ^ -// | (af) | (af) | (af) | -// ..................|....................|.................|..............|....................... -// | | | | -// | v | | Full GC -// \------------------->o<----------------/ | -// | | -// v | -// Full GC --------------------------/ -// +// Normal cycle goes via all concurrent phases. There is also a shortcut through the normal +// cycle: immediate garbage shortcut. When heuristics say there are no regions to compact, +// and all the collection comes from immediately reclaimable regions, Shenandoah can skip the +// evacuation phase. void ShenandoahGenerationalControlThread::service_concurrent_cycle(ShenandoahGeneration* generation, GCCause::Cause cause, bool do_old_gc_bootstrap) { @@ -531,17 +450,24 @@ void ShenandoahGenerationalControlThread::service_concurrent_cycle(ShenandoahGen assert(!generation->is_old(), "Old GC takes a different control path"); - _do_old_gc_bootstrap = do_old_gc_bootstrap; - ShenandoahConcurrentGC gc(generation, do_old_gc_bootstrap); + ShenandoahConcurrentGC gc(this, generation, do_old_gc_bootstrap); _heap->increment_total_collections(false); if (gc.collect(cause)) { // Cycle is complete - _heap->notify_gc_progress(); - generation->record_success_concurrent(gc.abbreviated()); - _do_old_gc_bootstrap = false; + if (alloc_stall_count() == 0) { + _heap->notify_gc_progress(); + } + + // In the generational mode, we don't use global heuristics to trigger global cycles, so + // direct heuristic signals to the young heuristics + _heap->young_generation()->heuristics()->record_concurrent_completion(); + _heap->shenandoah_policy()->record_success_concurrent(get_gc_id(), generation->is_young(), gc.abbreviated()); + + // Notify threads waiting for GC and/or memory when a young or global cycle completes + notify_gc_waiters(); + clear_allocation_failure_and_notify_waiters(); } else { assert(_heap->cancelled_gc(), "Must have been cancelled"); - check_cancellation_or_degen(gc.degen_point()); } const char* msg; @@ -576,67 +502,15 @@ void ShenandoahGenerationalControlThread::service_concurrent_cycle(ShenandoahGen _heap->log_heap_status(msg); } -bool ShenandoahGenerationalControlThread::check_cancellation_or_degen(ShenandoahGC::ShenandoahDegenPoint point) { - // Only read the cancellation cause once. Other threads may change it. - const GCCause::Cause cancelled_cause = _heap->cancelled_cause(); - if (cancelled_cause == GCCause::_no_gc) { - return false; - } - - if (cancelled_cause == GCCause::_shenandoah_stop_vm - || cancelled_cause == GCCause::_shenandoah_concurrent_gc) { - log_debug(gc, thread)("Cancellation detected, reason: %s", GCCause::to_string(cancelled_cause)); - return true; - } - - if (ShenandoahCollectorPolicy::is_allocation_failure(cancelled_cause)) { - assert(_degen_point == ShenandoahGC::_degenerated_unset, - "Should not be set yet: %s", ShenandoahGC::degen_point_to_string(_degen_point)); - MonitorLocker ml(&_control_lock, Mutex::_no_safepoint_check_flag); - _requested_gc_cause = cancelled_cause; - _degen_point = point; - log_debug(gc, thread)("Cancellation detected:, reason: %s, degen point: %s", - GCCause::to_string(cancelled_cause), - ShenandoahGC::degen_point_to_string(_degen_point)); - return true; - } - - fatal("Cancel GC either for alloc failure GC, or gracefully exiting, or to pause old generation marking"); -} - void ShenandoahGenerationalControlThread::service_stw_full_cycle(GCCause::Cause cause) { _heap->increment_total_collections(true); ShenandoahGCSession session(cause, _heap->global_generation()); ShenandoahFullGC gc; gc.collect(cause); - _degen_point = ShenandoahGC::_degenerated_unset; - _do_old_gc_bootstrap = false; -} - -void ShenandoahGenerationalControlThread::service_stw_degenerated_cycle(const ShenandoahGCRequest& request) { - assert(_degen_point != ShenandoahGC::_degenerated_unset, "Degenerated point should be set"); - _heap->increment_total_collections(false); - - ShenandoahGCSession session(request.cause, request.generation, true, - _degen_point == ShenandoahGC::ShenandoahDegenPoint::_degenerated_outside_cycle); - ShenandoahDegenGC gc(_degen_point, request.generation, _do_old_gc_bootstrap); - gc.collect(request.cause); - _degen_point = ShenandoahGC::_degenerated_unset; - _do_old_gc_bootstrap = false; - - assert(_heap->young_generation()->task_queues()->is_empty(), "Unexpected young generation marking tasks"); - if (request.generation->is_global()) { - assert(_heap->old_generation()->task_queues()->is_empty(), "Unexpected old generation marking tasks"); - assert(_heap->global_generation()->task_queues()->is_empty(), "Unexpected global generation marking tasks"); - } -} -void ShenandoahGenerationalControlThread::request_gc(GCCause::Cause cause) { - if (ShenandoahCollectorPolicy::is_allocation_failure(cause)) { - notify_control_thread(cause); - } else if (ShenandoahCollectorPolicy::should_handle_requested_gc(cause)) { - handle_requested_gc(cause); - } + // Notify threads waiting for GC and/or memory that a full cycle has completed + notify_gc_waiters(); + clear_allocation_failure_and_notify_waiters(); } bool ShenandoahGenerationalControlThread::request_concurrent_gc(ShenandoahGeneration* generation) { @@ -659,7 +533,6 @@ bool ShenandoahGenerationalControlThread::request_concurrent_gc(ShenandoahGenera // Cancel the old GC and wait for the control thread to start servicing the new request. log_info(gc, phases)("Preempting old generation mark to allow %s GC", generation->name()); while (gc_mode() == servicing_old) { - _heap->cancel_gc(GCCause::_shenandoah_concurrent_gc); notify_control_thread(ml, GCCause::_shenandoah_concurrent_gc, generation); ml.wait(); } @@ -687,40 +560,43 @@ bool ShenandoahGenerationalControlThread::request_concurrent_gc(ShenandoahGenera return false; } -void ShenandoahGenerationalControlThread::notify_control_thread(GCCause::Cause cause, ShenandoahGeneration* generation) { +bool ShenandoahGenerationalControlThread::notify_control_thread(GCCause::Cause cause, ShenandoahGeneration* generation) { MonitorLocker ml(&_control_lock, Mutex::_no_safepoint_check_flag); - notify_control_thread(ml, cause, generation); + return notify_control_thread(ml, cause, generation); } -void ShenandoahGenerationalControlThread::notify_control_thread(MonitorLocker& ml, GCCause::Cause cause, ShenandoahGeneration* generation) { - assert(_control_lock.is_locked(), "Request lock must be held here"); - if (ShenandoahCollectorPolicy::is_allocation_failure(_requested_gc_cause)) { - // We have already observed a request to handle an allocation failure. We cannot allow - // another request (System.gc or regulator) to subvert the degenerated cycle. +bool ShenandoahGenerationalControlThread::notify_control_thread(MonitorLocker& ml, GCCause::Cause cause, + ShenandoahGeneration* generation) { + assert(_control_lock.owned_by_self(), "Request lock must be held here"); + if (ShenandoahCollectorPolicy::is_higher_priority(_requested_gc_cause, cause)) { log_debug(gc, thread)("Not overwriting gc cause %s with %s", GCCause::to_string(_requested_gc_cause), GCCause::to_string(cause)); - } else { - log_debug(gc, thread)("Notify control (%s): %s, %s", gc_mode_name(gc_mode()), GCCause::to_string(cause), generation->name()); - _requested_gc_cause = cause; - _requested_generation = generation; - ml.notify(); + return false; } + + log_debug(gc, thread)("Notify control (%s): %s, %s", gc_mode_name(gc_mode()), GCCause::to_string(cause), generation->name()); + request_gc_and_interrupt_old(cause, generation); + ml.notify_all(); + return true; } -void ShenandoahGenerationalControlThread::notify_control_thread(GCCause::Cause cause) { - MonitorLocker ml(&_control_lock, Mutex::_no_safepoint_check_flag); - notify_control_thread(ml, cause); +void ShenandoahGenerationalControlThread::request_gc_and_interrupt_old(GCCause::Cause cause, + ShenandoahGeneration* generation) { + assert(_control_lock.owned_by_self(), "Request lock must be held here"); + _requested_gc_cause = cause; + _requested_generation = generation; + if (gc_mode() == servicing_old && !generation->is_old()) { + _heap->cancel_gc(cause); + } } void ShenandoahGenerationalControlThread::notify_control_thread(MonitorLocker& ml, GCCause::Cause cause) { - assert(_control_lock.is_locked(), "Request lock must be held here"); - if (ShenandoahCollectorPolicy::is_allocation_failure(_requested_gc_cause)) { - // We have already observed a request to handle an allocation failure. We cannot allow - // another request (System.gc or regulator) to subvert the degenerated cycle. + assert(_control_lock.owned_by_self(), "Request lock must be held here"); + if (ShenandoahCollectorPolicy::is_higher_priority(_requested_gc_cause, cause)) { log_debug(gc, thread)("Not overwriting gc cause %s with %s", GCCause::to_string(_requested_gc_cause), GCCause::to_string(cause)); } else { log_debug(gc, thread)("Notify control (%s): %s", gc_mode_name(gc_mode()), GCCause::to_string(cause)); _requested_gc_cause = cause; - ml.notify(); + ml.notify_all(); } } @@ -728,46 +604,10 @@ bool ShenandoahGenerationalControlThread::preempt_old_marking(ShenandoahGenerati return generation->is_young() && _allow_old_preemption.try_unset(); } -void ShenandoahGenerationalControlThread::handle_requested_gc(GCCause::Cause cause) { - // For normal requested GCs (System.gc) we want to block the caller. However, - // for whitebox requested GC, we want to initiate the GC and return immediately. - // The whitebox caller thread will arrange for itself to wait until the GC notifies - // it that has reached the requested breakpoint (phase in the GC). - if (cause == GCCause::_wb_breakpoint) { - notify_control_thread(cause, ShenandoahHeap::heap()->global_generation()); - return; - } - - // Make sure we have at least one complete GC cycle before unblocking - // from the explicit GC request. - // - // This is especially important for weak references cleanup and/or native - // resources (e.g. DirectByteBuffers) machinery: when explicit GC request - // comes very late in the already running cycle, it would miss lots of new - // opportunities for cleanup that were made available before the caller - // requested the GC. - - MonitorLocker ml(&_gc_waiters_lock); - size_t current_gc_id = get_gc_id(); - const size_t required_gc_id = current_gc_id + 1; - while (current_gc_id < required_gc_id && !should_terminate()) { - // Make requests to run a global cycle until at least one is completed - notify_control_thread(cause, ShenandoahHeap::heap()->global_generation()); - ml.wait(); - current_gc_id = get_gc_id(); - } -} - -void ShenandoahGenerationalControlThread::notify_gc_waiters() { - MonitorLocker ml(&_gc_waiters_lock); - ml.notify_all(); -} - const char* ShenandoahGenerationalControlThread::gc_mode_name(GCMode mode) { switch (mode) { case none: return "idle"; case concurrent_normal: return "normal"; - case stw_degenerated: return "degenerated"; case stw_full: return "full"; case servicing_old: return "old"; case bootstrapping_old: return "bootstrap"; diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.hpp b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.hpp index 358299ea4693..f59f7514cf62 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.hpp @@ -28,7 +28,6 @@ #include "gc/shared/gcCause.hpp" #include "gc/shenandoah/shenandoahController.hpp" -#include "gc/shenandoah/shenandoahGC.hpp" #include "gc/shenandoah/shenandoahSharedVariables.hpp" #include "runtime/mutexLocker.hpp" @@ -42,7 +41,6 @@ class ShenandoahGenerationalControlThread: public ShenandoahController { typedef enum { none, concurrent_normal, - stw_degenerated, stw_full, bootstrapping_old, servicing_old, @@ -57,14 +55,6 @@ class ShenandoahGenerationalControlThread: public ShenandoahController { }; private: - // This lock is used to coordinate setting the _requested_gc_cause, _requested generation - // and _gc_mode. It is important that these be changed together and have a consistent view. - Monitor _control_lock; - - // Represents a normal (non cancellation) gc request. This can be set by mutators (System.gc, - // whitebox gc, etc.) or by the regulator thread when the heuristics want to start a cycle. - GCCause::Cause _requested_gc_cause; - // This is the generation the request should operate on. ShenandoahGeneration* _requested_generation; @@ -73,10 +63,6 @@ class ShenandoahGenerationalControlThread: public ShenandoahController { // before making any state changes (double-checked locking idiom). volatile GCMode _gc_mode; - // Only the control thread knows the correct degeneration point. This is used to have the - // control thread resume a STW cycle from the point where the concurrent cycle was cancelled. - ShenandoahGC::ShenandoahDegenPoint _degen_point; - // A reference to the heap ShenandoahGenerationalHeap* _heap; @@ -84,17 +70,9 @@ class ShenandoahGenerationalControlThread: public ShenandoahController { // preparing for mark). ShenandoahSharedFlag _allow_old_preemption; - // True while the current cycle is the bootstrap of an old GC. - bool _do_old_gc_bootstrap; - public: ShenandoahGenerationalControlThread(); - void run_service() override; - void stop_service() override; - - void request_gc(GCCause::Cause cause) override; - // Return true if the request to start a concurrent GC for the given generation succeeded. bool request_concurrent_gc(ShenandoahGeneration* generation); @@ -102,28 +80,30 @@ class ShenandoahGenerationalControlThread: public ShenandoahController { GCMode gc_mode() const { return _gc_mode; } + +protected: + void run_service() override; + void stop_service() override; + + void notify_alloc_stall(GCCause::Cause cause) override; + bool notify_control_thread(GCCause::Cause cause, ShenandoahGeneration* generation) override; + + // Will also schedule an immediate full gc if old evacuations failed + void clear_allocation_failure_and_notify_waiters() override; private: - // Returns true if the cycle has been cancelled or degenerated. - bool check_cancellation_or_degen(ShenandoahGC::ShenandoahDegenPoint point); // Executes one GC cycle void run_gc_cycle(const ShenandoahGCRequest& request); // Returns true if the old generation marking completed (i.e., final mark executed for old generation). - bool resume_concurrent_old_cycle(ShenandoahOldGeneration* generation, GCCause::Cause cause); + void resume_concurrent_old_cycle(ShenandoahOldGeneration* generation, GCCause::Cause cause); // Various service methods handle different gc cycle types void service_concurrent_cycle(ShenandoahGeneration* generation, GCCause::Cause cause, bool reset_old_bitmap_specially); void service_stw_full_cycle(GCCause::Cause cause); - void service_stw_degenerated_cycle(const ShenandoahGCRequest& request); void service_concurrent_normal_cycle(const ShenandoahGCRequest& request); void service_concurrent_old_cycle(const ShenandoahGCRequest& request); - void notify_gc_waiters(); - - // Blocks until at least one global GC cycle is complete. - void handle_requested_gc(GCCause::Cause cause); - // Returns true if the old generation marking was interrupted to allow a young cycle. bool preempt_old_marking(ShenandoahGeneration* generation); @@ -137,18 +117,17 @@ class ShenandoahGenerationalControlThread: public ShenandoahController { // These notify the control thread after updating _requested_gc_cause and (optionally) _requested_generation. // Updating the requested generation is not necessary for allocation failures nor when stopping the thread. - void notify_control_thread(GCCause::Cause cause); void notify_control_thread(MonitorLocker& ml, GCCause::Cause cause); - void notify_control_thread(GCCause::Cause cause, ShenandoahGeneration* generation); - void notify_control_thread(MonitorLocker& ml, GCCause::Cause cause, ShenandoahGeneration* generation); + bool notify_control_thread(MonitorLocker& ml, GCCause::Cause cause, ShenandoahGeneration* generation); + void request_gc_and_interrupt_old(GCCause::Cause cause, ShenandoahGeneration* generation); // Take the _control_lock and check for a request to run a gc cycle. If a request is found, // the `prepare` methods are used to configure the heap and update heuristics accordingly. void check_for_request(ShenandoahGCRequest& request); - GCMode prepare_for_allocation_failure_gc(ShenandoahGCRequest &request); - GCMode prepare_for_explicit_gc(ShenandoahGCRequest &request) const; - GCMode prepare_for_concurrent_gc(const ShenandoahGCRequest &request) const; + GCMode prepare_for_concurrent_gc(ShenandoahGCRequest &request) const; + GCMode prepare_for_full_gc(ShenandoahGCRequest& request) const; + void prepare_for_explicit_gc(ShenandoahGCRequest &request) const; // Print table for young region ages if log is enabled void maybe_print_young_region_ages() const; diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalEvacuationTask.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalEvacuationTask.cpp index 2d58fbb63ae5..3bf7d8f1fe23 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalEvacuationTask.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalEvacuationTask.cpp @@ -1,6 +1,6 @@ /* * Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -24,6 +24,7 @@ */ #include "gc/shenandoah/shenandoahAsserts.hpp" +#include "gc/shenandoah/shenandoahCollectorPolicy.hpp" #include "gc/shenandoah/shenandoahForwarding.inline.hpp" #include "gc/shenandoah/shenandoahGeneration.hpp" #include "gc/shenandoah/shenandoahGenerationalEvacuationTask.hpp" @@ -50,28 +51,22 @@ class ShenandoahConcurrentEvacuator : public ObjectClosure { ShenandoahGenerationalEvacuationTask::ShenandoahGenerationalEvacuationTask(ShenandoahGenerationalHeap* heap, ShenandoahGeneration* generation, ShenandoahRegionIterator* iterator, - bool concurrent, bool only_promote_regions) : + bool only_promote_regions) : WorkerTask("Shenandoah Evacuation"), _heap(heap), _generation(generation), _regions(iterator), - _concurrent(concurrent), + _collection_set(_heap->collection_set()), _only_promote_regions(only_promote_regions) { shenandoah_assert_generational(); } void ShenandoahGenerationalEvacuationTask::work(uint worker_id) { - if (_concurrent) { - ShenandoahWorkerTimingsTracker timer(ShenandoahPhaseTimings::conc_evac, ShenandoahPhaseTimings::Work, worker_id, true); - ShenandoahConcurrentWorkerSession worker_session(worker_id); - SuspendibleThreadSetJoiner stsj; - do_work(); - } else { - ShenandoahWorkerTimingsTracker timer(ShenandoahPhaseTimings::degen_gc_evac, ShenandoahPhaseTimings::Work, worker_id, true); - ShenandoahParallelWorkerSession worker_session(worker_id); - do_work(); - } + ShenandoahWorkerTimingsTracker timer(ShenandoahPhaseTimings::conc_evac, ShenandoahPhaseTimings::Work, worker_id, true); + ShenandoahConcurrentWorkerSession worker_session(worker_id); + SuspendibleThreadSetJoiner stsj; + do_work(); } void ShenandoahGenerationalEvacuationTask::do_work() { @@ -104,7 +99,7 @@ void ShenandoahGenerationalEvacuationTask::promote_regions() { promoter.maybe_promote_region(r); - if (_heap->check_cancelled_gc_and_yield(_concurrent)) { + if (_heap->check_cancelled_gc_and_yield()) { break; } } @@ -113,25 +108,28 @@ void ShenandoahGenerationalEvacuationTask::promote_regions() { void ShenandoahGenerationalEvacuationTask::evacuate_and_promote_regions() { LogTarget(Debug, gc) lt; ShenandoahConcurrentEvacuator cl(_heap); - ShenandoahInPlacePromoter promoter(_heap); ShenandoahHeapRegion* r; - - while ((r = _regions->next()) != nullptr) { + while ((r = _collection_set->claim_next()) != nullptr) { if (lt.is_enabled()) { LogStream ls(lt); log_region(r, &ls); } - if (r->is_cset()) { - assert(r->has_live(), "Region %zu should have been reclaimed early", r->index()); - _heap->marked_object_iterate(r, &cl); - } else { - promoter.maybe_promote_region(r); - } + assert(r->has_live(), "Region %zu should have been reclaimed early", r->index()); + _heap->marked_object_iterate(r, &cl); - if (_heap->check_cancelled_gc_and_yield(_concurrent)) { + if (ShenandoahCollectorPolicy::should_abandon_evacuations(r)) { + // No more evacuations for this thread, but it may yet complete in-place promotions break; } + + if (_heap->check_cancelled_gc_and_yield()) { + // GC is cancelled (vm is stopping), no further work + assert(_heap->is_stopping(), "Can only stop evacuation for shutdown"); + return; + } } + + promote_regions(); } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalEvacuationTask.hpp b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalEvacuationTask.hpp index 1ff58b42e8cb..50a67869d6f2 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalEvacuationTask.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalEvacuationTask.hpp @@ -31,6 +31,7 @@ class ShenandoahGeneration; class ShenandoahGenerationalHeap; class ShenandoahHeapRegion; class ShenandoahRegionIterator; +class ShenandoahCollectionSet; // Unlike ShenandoahEvacuationTask, this iterates over all regions rather than just the collection set. // This is needed in order to promote humongous start regions if age() >= tenure threshold. @@ -39,22 +40,19 @@ class ShenandoahGenerationalEvacuationTask : public WorkerTask { ShenandoahGenerationalHeap* const _heap; ShenandoahGeneration* const _generation; ShenandoahRegionIterator* _regions; - bool _concurrent; + ShenandoahCollectionSet* _collection_set; bool _only_promote_regions; public: ShenandoahGenerationalEvacuationTask(ShenandoahGenerationalHeap* sh, ShenandoahGeneration* generation, ShenandoahRegionIterator* iterator, - bool concurrent, bool only_promote_regions); + bool only_promote_regions); void work(uint worker_id) override; private: void do_work(); void promote_regions(); void evacuate_and_promote_regions(); - void maybe_promote_region(ShenandoahHeapRegion* region); - void promote_in_place(ShenandoahHeapRegion* region); - void promote_humongous(ShenandoahHeapRegion* region); }; #endif //SHARE_GC_SHENANDOAH_SHENANDOAHGENERATIONALEVACUATIONTASK_HPP diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalFullGC.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalFullGC.cpp index 4d178f4b0494..5c3ebc47a7fc 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalFullGC.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalFullGC.cpp @@ -56,7 +56,7 @@ void assert_usage_not_more_than_regions_used(ShenandoahGeneration* generation) { void ShenandoahGenerationalFullGC::prepare() { auto heap = ShenandoahGenerationalHeap::heap(); - // Since we may arrive here from degenerated GC failure of either young or old, establish generation as GLOBAL. + // Full GC always operates on the GLOBAL generation. heap->set_active_generation(heap->global_generation()); // Full GC supersedes any marking or coalescing in old generation. @@ -70,6 +70,7 @@ void ShenandoahGenerationalFullGC::handle_completion(ShenandoahHeap* heap) { ShenandoahOldGeneration* old = gen_heap->old_generation(); young->heuristics()->record_cycle_end(); old->heuristics()->record_cycle_end(); + old->clear_failed_evacuation(); gen_heap->mmu_tracker()->record_full(GCId::current()); gen_heap->log_heap_status("At end of Full GC"); @@ -156,7 +157,7 @@ void ShenandoahGenerationalFullGC::maybe_coalesce_and_fill_region(ShenandoahHeap void ShenandoahGenerationalFullGC::compute_balances() { auto heap = ShenandoahGenerationalHeap::heap(); - // In case this Full GC resulted from degeneration, clear the tally on anticipated promotion. + // Reset the tally on anticipated promotion. heap->old_generation()->set_promotion_potential(0); // Invoke this in case we are able to transfer memory from OLD to YOUNG diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp index c640fc010460..411c58ef0a6b 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp @@ -78,8 +78,7 @@ size_t ShenandoahGenerationalHeap::unsafe_max_tlab_alloc() const { return MIN2(ShenandoahHeapRegion::max_tlab_size_bytes(), young_generation()->available()); } -ShenandoahGenerationalHeap::ShenandoahGenerationalHeap(ShenandoahCollectorPolicy* policy) : - ShenandoahHeap(policy), +ShenandoahGenerationalHeap::ShenandoahGenerationalHeap() : _age_census(nullptr), _min_plab_size(calculate_min_plab()), _max_plab_size(calculate_max_plab()), @@ -178,15 +177,15 @@ bool ShenandoahGenerationalHeap::requires_barriers(stackChunkOop obj) const { return false; } -void ShenandoahGenerationalHeap::evacuate_collection_set(ShenandoahGeneration* generation, bool concurrent) { +void ShenandoahGenerationalHeap::evacuate_collection_set(ShenandoahGeneration* generation) { ShenandoahRegionIterator regions; - ShenandoahGenerationalEvacuationTask task(this, generation, ®ions, concurrent, false /* only promote regions */); + ShenandoahGenerationalEvacuationTask task(this, generation, ®ions, false /* only promote regions */); workers()->run_task(&task); } -void ShenandoahGenerationalHeap::promote_regions_in_place(ShenandoahGeneration* generation, bool concurrent) { +void ShenandoahGenerationalHeap::promote_regions_in_place(ShenandoahGeneration* generation) { ShenandoahRegionIterator regions; - ShenandoahGenerationalEvacuationTask task(this, generation, ®ions, concurrent, true /* only promote regions */); + ShenandoahGenerationalEvacuationTask task(this, generation, ®ions, true /* only promote regions */); workers()->run_task(&task); } @@ -195,6 +194,11 @@ oop ShenandoahGenerationalHeap::evacuate_object(oop p, Thread* thread) { ShenandoahHeapRegion* from_region = heap_region_containing(p); assert(!from_region->is_humongous(), "never evacuate humongous objects"); + if (has_self_forwarded_objects() && from_region->has_self_forwards()) { + // We don't want GC threads to evacuate objects in regions that have evacuation failures. We'd + // rather have them concentrate on regions that still have a chance of being completely evacuated. + return ShenandoahForwarding::try_forward_to(p, p); + } // Try to keep the object in the same generation const ShenandoahAffiliation target_gen = from_region->affiliation(); @@ -206,26 +210,27 @@ oop ShenandoahGenerationalHeap::evacuate_object(oop p, Thread* thread) { } if (age_census()->is_tenurable(from_region->age() + ShenandoahForwarding::age(p))) { // If the object is tenurable, try to promote it - oop result = try_evacuate_object(p, thread, from_region->age()); + oop result = try_evacuate_object(p, thread, from_region); // If we failed to promote this aged object, we'll fall through to code below and evacuate to young-gen. if (result != nullptr) { return result; } } - return try_evacuate_object(p, thread, from_region->age()); + return try_evacuate_object(p, thread, from_region); } assert(target_gen == OLD_GENERATION, "Expected evacuation to old"); - return try_evacuate_object(p, thread, from_region->age()); + return try_evacuate_object(p, thread, from_region); } // try_evacuate_object registers the object and dirties the associated remembered set information when evacuating // to OLD_GENERATION. template -oop ShenandoahGenerationalHeap::try_evacuate_object(oop p, Thread* thread, uint from_region_age) { +oop ShenandoahGenerationalHeap::try_evacuate_object(oop p, Thread* thread, ShenandoahHeapRegion* from_region) { bool alloc_from_lab = true; bool has_plab = false; + uint from_region_age = from_region->age(); HeapWord* copy = nullptr; size_t size = ShenandoahForwarding::size(p); constexpr bool is_promotion = (TO_GENERATION == OLD_GENERATION) && (FROM_GENERATION == YOUNG_GENERATION); @@ -299,7 +304,7 @@ oop ShenandoahGenerationalHeap::try_evacuate_object(oop p, Thread* thread, uint if (copy == nullptr) { if (TO_GENERATION == OLD_GENERATION) { if (FROM_GENERATION == YOUNG_GENERATION) { - // Signal that promotion failed. Will evacuate this old object somewhere in young gen. + // Signal that promotion failed. We will retry to evacuate this old object somewhere in young gen. old_generation()->handle_failed_promotion(thread, size); return nullptr; } else { @@ -309,16 +314,19 @@ oop ShenandoahGenerationalHeap::try_evacuate_object(oop p, Thread* thread, uint } } - control_thread()->handle_alloc_failure_evac(size); - // Install the self-forwarded bit so other evacuators/LRBs see the // object as "already handled, do not try to evacuate". oop fwd = ShenandoahForwarding::try_forward_to(p, p); if (fwd == p) { - // Now self-forwarded. Flag the from-region so the degen/full - // GC entry drain knows to scan it for self_fwd bits to clear. - heap_region_containing(p)->set_has_self_forwards(); + // Now self-forwarded. Flag the from-region so that other threads + // don't waste time evacuating this region. + set_has_self_forwarded_objects(); + from_region->set_has_self_forwards(); } + + // It does not matter if this thread did the self forwarding or not. + // This thread failed to allocate an evacuation copy. + ShenandoahThreadLocalData::set_has_evac_failures(thread, true); return fwd; } @@ -391,9 +399,9 @@ oop ShenandoahGenerationalHeap::try_evacuate_object(oop p, Thread* thread, uint return result; } -template oop ShenandoahGenerationalHeap::try_evacuate_object(oop p, Thread* thread, uint from_region_age); -template oop ShenandoahGenerationalHeap::try_evacuate_object(oop p, Thread* thread, uint from_region_age); -template oop ShenandoahGenerationalHeap::try_evacuate_object(oop p, Thread* thread, uint from_region_age); +template oop ShenandoahGenerationalHeap::try_evacuate_object(oop p, Thread* thread, ShenandoahHeapRegion* from_region); +template oop ShenandoahGenerationalHeap::try_evacuate_object(oop p, Thread* thread, ShenandoahHeapRegion* from_region); +template oop ShenandoahGenerationalHeap::try_evacuate_object(oop p, Thread* thread, ShenandoahHeapRegion* from_region); // Call this function at the end of a GC cycle in order to establish proper sizes of young and old reserves, // setting the old-generation balance so that GC can perform the anticipated evacuations. @@ -670,17 +678,12 @@ void ShenandoahGenerationalHeap::coalesce_and_fill_old_regions(bool concurrent) } }; - ShenandoahPhaseTimings::Phase phase = concurrent ? - ShenandoahPhaseTimings::conc_coalesce_and_fill : - ShenandoahPhaseTimings::degen_gc_coalesce_and_fill; - // This is not cancellable - ShenandoahGlobalCoalesceAndFill coalesce(phase); + ShenandoahGlobalCoalesceAndFill coalesce(ShenandoahPhaseTimings::conc_coalesce_and_fill); workers()->run_task(&coalesce); old_generation()->set_parsable(true); } -template class ShenandoahGenerationalUpdateHeapRefsTask : public WorkerTask { private: // For update refs, _generation will be young or global. Mixed collections use the young generation. @@ -704,16 +707,10 @@ class ShenandoahGenerationalUpdateHeapRefsTask : public WorkerTask { } void work(uint worker_id) override { - if (CONCURRENT) { - ShenandoahWorkerTimingsTracker timer(ShenandoahPhaseTimings::conc_update_refs, ShenandoahPhaseTimings::Work, worker_id, true); - ShenandoahConcurrentWorkerSession worker_session(worker_id); - SuspendibleThreadSetJoiner stsj; - do_work(worker_id); - } else { - ShenandoahWorkerTimingsTracker timer(ShenandoahPhaseTimings::degen_gc_update_refs, ShenandoahPhaseTimings::Work, worker_id, true); - ShenandoahParallelWorkerSession worker_session(worker_id); - do_work(worker_id); - } + ShenandoahWorkerTimingsTracker timer(ShenandoahPhaseTimings::conc_update_refs, ShenandoahPhaseTimings::Work, worker_id, true); + ShenandoahConcurrentWorkerSession worker_session(worker_id); + SuspendibleThreadSetJoiner stsj; + do_work>(worker_id); } private: @@ -721,7 +718,7 @@ class ShenandoahGenerationalUpdateHeapRefsTask : public WorkerTask { void do_work(uint worker_id) { T cl; - if (CONCURRENT && (worker_id == 0)) { + if (worker_id == 0) { // We ask the first worker to replenish the Mutator free set by moving regions previously reserved to hold the // results of evacuation. These reserves are no longer necessary because evacuation has completed. size_t cset_regions = _heap->collection_set()->count(); @@ -732,7 +729,6 @@ class ShenandoahGenerationalUpdateHeapRefsTask : public WorkerTask { // next GC cycle. _heap->free_set()->move_regions_from_collector_to_mutator(cset_regions); } - // If !CONCURRENT, there's no value in expanding Mutator free set ShenandoahHeapRegion* r = _regions->next(); // We update references for global, mixed, and young collections. @@ -744,7 +740,7 @@ class ShenandoahGenerationalUpdateHeapRefsTask : public WorkerTask { assert(update_watermark >= r->bottom(), "sanity"); log_debug(gc)("Update refs worker " UINT32_FORMAT ", looking at region %zu", worker_id, r->index()); - if (r->is_active() && !r->is_cset()) { + if (r->is_update_required()) { if (r->is_young()) { _heap->marked_object_oop_iterate(r, &cl, update_watermark); } else if (r->is_old()) { @@ -770,7 +766,7 @@ class ShenandoahGenerationalUpdateHeapRefsTask : public WorkerTask { } } - if (_heap->check_cancelled_gc_and_yield(CONCURRENT)) { + if (_heap->check_cancelled_gc_and_yield()) { return; } @@ -791,13 +787,15 @@ class ShenandoahGenerationalUpdateHeapRefsTask : public WorkerTask { template void update_references_in_remembered_set(uint worker_id, T &cl, const ShenandoahMarkingContext* ctx, bool is_mixed) { - struct ShenandoahRegionChunk assignment; + ShenandoahRegionChunk assignment; ShenandoahScanRemembered* scanner = _heap->old_generation()->card_scan(); - while (!_heap->check_cancelled_gc_and_yield(CONCURRENT) && _work_chunks->next(&assignment)) { + while (!_heap->check_cancelled_gc_and_yield() && _work_chunks->next(&assignment)) { // Keep grabbing next work chunk to process until finished, or asked to yield ShenandoahHeapRegion* r = assignment._r; - if (r->is_active() && !r->is_cset() && r->is_old()) { + if (r->is_update_required() && r->is_old()) { + // allocations into old (i.e., promotions or evacuations) do _not_ update references + // when they copy, so we move the UWM up for each such allocation. HeapWord* start_of_range = r->bottom() + assignment._chunk_offset; HeapWord* end_of_range = r->get_update_watermark(); if (end_of_range > start_of_range + assignment._chunk_size) { @@ -831,8 +829,8 @@ class ShenandoahGenerationalUpdateHeapRefsTask : public WorkerTask { template void update_references_in_old_region(T &cl, const ShenandoahMarkingContext* ctx, ShenandoahScanRemembered* scanner, - const ShenandoahHeapRegion* r, HeapWord* start_of_range, - HeapWord* end_of_range) const { + const ShenandoahHeapRegion* r, HeapWord* start_of_range, + HeapWord* end_of_range) const { // In case last object in my range spans boundary of my chunk, I may need to scan all the way to top() ShenandoahObjectToOopBoundedClosure objs(&cl, start_of_range, r->top()); @@ -848,7 +846,10 @@ class ShenandoahGenerationalUpdateHeapRefsTask : public WorkerTask { while (p < end_of_range) { // p is known to point to the beginning of marked object obj oop obj = cast_to_oop(p); - objs.do_object(obj); + if (!ShenandoahForwarding::is_real_forwarded(obj)) { + // Update references in the object if it was not evacuated + objs.do_object(obj); + } HeapWord* prev_p = p; p += ShenandoahForwarding::size(obj); if (p < tams) { @@ -891,16 +892,12 @@ class ShenandoahGenerationalUpdateHeapRefsTask : public WorkerTask { } }; -void ShenandoahGenerationalHeap::update_heap_references(ShenandoahGeneration* generation, bool concurrent) { - assert(!is_full_gc_in_progress(), "Only for concurrent and degenerated GC"); +void ShenandoahGenerationalHeap::update_heap_references(ShenandoahGeneration* generation) { + assert(!is_full_gc_in_progress(), "Only for concurrent GC"); ShenandoahRegionChunkIterator work_list(this); - if (concurrent) { - ShenandoahGenerationalUpdateHeapRefsTask task(generation, &_update_refs_iterator, &work_list); - workers()->run_task(&task); - } else { - ShenandoahGenerationalUpdateHeapRefsTask task(generation, &_update_refs_iterator, &work_list); - workers()->run_task(&task); - } + ShenandoahRegionIterator update_refs_iterator(this); + ShenandoahGenerationalUpdateHeapRefsTask task(generation, &update_refs_iterator, &work_list); + workers()->run_task(&task); if (ShenandoahEnableCardStats) { // Only do this if we are collecting card stats @@ -979,16 +976,6 @@ void ShenandoahGenerationalHeap::final_update_refs_update_region_states() { parallel_heap_region_iterate(&cl); } -void ShenandoahGenerationalHeap::complete_degenerated_cycle() { - shenandoah_assert_heaplocked_or_safepoint(); - if (!old_generation()->is_parsable()) { - ShenandoahGCPhase phase(ShenandoahPhaseTimings::degen_gc_coalesce_and_fill); - coalesce_and_fill_old_regions(false); - } - - old_generation()->maybe_log_promotion_failure_stats(false); -} - void ShenandoahGenerationalHeap::complete_concurrent_cycle() { if (!old_generation()->is_parsable()) { // Class unloading may render the card offsets unusable, so we must rebuild them before diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.hpp b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.hpp index 6080b465dc13..6aff6351e433 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.hpp @@ -38,7 +38,7 @@ class ShenandoahGenerationalHeap : public ShenandoahHeap { void stop() override; public: - explicit ShenandoahGenerationalHeap(ShenandoahCollectorPolicy* policy); + ShenandoahGenerationalHeap(); void post_initialize() override; void initialize_generations() override; void initialize_heuristics() override; @@ -81,12 +81,12 @@ class ShenandoahGenerationalHeap : public ShenandoahHeap { oop evacuate_object(oop p, Thread* thread) override; template - oop try_evacuate_object(oop p, Thread* thread, uint from_region_age); + oop try_evacuate_object(oop p, Thread* thread, ShenandoahHeapRegion* from_region); // In the generational mode, we will use these two functions for young, mixed, and global collections. // For young and mixed, the generation argument will be the young generation, otherwise it will be the global generation. - void evacuate_collection_set(ShenandoahGeneration* generation, bool concurrent) override; - void promote_regions_in_place(ShenandoahGeneration* generation, bool concurrent); + void evacuate_collection_set(ShenandoahGeneration* generation) override; + void promote_regions_in_place(ShenandoahGeneration* generation); size_t plab_min_size() const { return _min_plab_size; } size_t plab_max_size() const { return _max_plab_size; } @@ -95,7 +95,7 @@ class ShenandoahGenerationalHeap : public ShenandoahHeap { // // In the generational mode, we will use this function for young, mixed, and global collections. // For young and mixed, the generation argument will be the young generation, otherwise it will be the global generation. - void update_heap_references(ShenandoahGeneration* generation, bool concurrent) override; + void update_heap_references(ShenandoahGeneration* generation) override; void final_update_refs_update_region_states() override; private: @@ -124,7 +124,6 @@ class ShenandoahGenerationalHeap : public ShenandoahHeap { void compute_old_generation_balance(size_t old_xfer_limit, size_t old_trashed_regions, size_t young_trashed_regions); // Balances generations, coalesces and fills old regions if necessary - void complete_degenerated_cycle(); void complete_concurrent_cycle(); private: void initialize_controller() override; diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp index b03610066334..0da82666e5a5 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp @@ -26,7 +26,7 @@ #include "cds/aotMappedHeapWriter.hpp" -#include "classfile/systemDictionary.hpp" +#include "gc/shared/allocTracer.hpp" #include "gc/shared/classUnloadingContext.hpp" #include "gc/shared/fullGCForwarding.hpp" #include "gc/shared/gc_globals.hpp" @@ -37,6 +37,7 @@ #include "gc/shared/memAllocator.hpp" #include "gc/shared/plab.hpp" #include "gc/shared/tlab_globals.hpp" +#include "gc/shared/workerUtils.hpp" #include "gc/shenandoah/heuristics/shenandoahOldHeuristics.hpp" #include "gc/shenandoah/heuristics/shenandoahYoungHeuristics.hpp" #include "gc/shenandoah/mode/shenandoahGenerationalMode.hpp" @@ -558,7 +559,7 @@ void ShenandoahHeap::initialize_heuristics() { #pragma warning( disable:4355 ) // 'this' : used in base member initializer list #endif -ShenandoahHeap::ShenandoahHeap(ShenandoahCollectorPolicy* policy) : +ShenandoahHeap::ShenandoahHeap() : CollectedHeap(), _active_generation(nullptr), _initial_size(0), @@ -575,13 +576,11 @@ ShenandoahHeap::ShenandoahHeap(ShenandoahCollectorPolicy* policy) : _gc_state_changed(false), _gc_no_progress_count(0), _cancel_requested_time(0), - _update_refs_iterator(this), _global_generation(nullptr), _control_thread(nullptr), _uncommit_thread(nullptr), _young_generation(nullptr), _old_generation(nullptr), - _shenandoah_policy(policy), _gc_mode(nullptr), _free_set(nullptr), _allocator(nullptr), @@ -606,7 +605,8 @@ ShenandoahHeap::ShenandoahHeap(ShenandoahCollectorPolicy* policy) : { // Initialize GC mode early, many subsequent initialization procedures depend on it initialize_mode(); - _cancelled_gc.set(GCCause::_no_gc); + _cancelled_gc.unset(); + _shenandoah_policy = new ShenandoahCollectorPolicy(mode()); } #ifdef _MSC_VER @@ -650,7 +650,6 @@ void ShenandoahHeap::print_heap_on(outputStream* st) const { } if (is_evacuation_in_progress()) st->print("evacuating, "); if (is_update_refs_in_progress()) st->print("updating refs, "); - if (is_degenerated_gc_in_progress()) st->print("degenerated gc, "); if (is_full_gc_in_progress()) st->print("full gc, "); if (is_full_gc_move_in_progress()) st->print("full gc move, "); if (is_concurrent_weak_root_in_progress()) st->print("concurrent weak roots, "); @@ -960,11 +959,7 @@ HeapWord* ShenandoahHeap::allocate_memory(ShenandoahAllocRequest& req) { // Shenandoah will grind along for quite a while allocating one // object at a time using shared (non-tlab) allocations. This check // is testing that the GC overhead limit has not been exceeded. - // This will notify the collector to start a cycle, but will raise - // an OOME to the mutator if the last Full GCs have not made progress. - // gc_no_progress_count is incremented following each degen or full GC that fails to achieve is_good_progress(). - if (result == nullptr && !req.is_lab_alloc() && get_gc_no_progress_count() > ShenandoahNoProgressThreshold) { - control_thread()->handle_alloc_failure(req, false); + if (result == nullptr && !req.is_lab_alloc() && get_gc_no_progress_count() >= ShenandoahNoProgressThreshold) { req.set_actual_size(0); return nullptr; } @@ -972,26 +967,41 @@ HeapWord* ShenandoahHeap::allocate_memory(ShenandoahAllocRequest& req) { if (result == nullptr) { // Block until control thread reacted, then retry allocation. // - // It might happen that one of the threads requesting allocation would unblock - // way later after GC happened, only to fail the second allocation, because - // other threads have already depleted the free storage. In this case, a better - // strategy is to try again, until at least one full GC has completed. - // - // Stop retrying and return nullptr to cause OOMError exception if our allocation failed even after: - // a) We experienced a GC that had good progress, or - // b) We experienced at least one Full GC (whether or not it had good progress) - - const size_t original_count = shenandoah_policy()->full_gc_count(); - while (result == nullptr && should_retry_allocation(original_count)) { - control_thread()->handle_alloc_failure(req, true); + // When a thread cannot allocate, it will signal the control thread to run a concurrent cycle. It + // will then wait for the control thread to signal in turn that the cycle has completed. The thread + // then retries the allocation. It will do this ShenandoahFullGCThreshold times. If no full gc has + // yet run, it will request one and retry the allocation one more time. If another thread has caused + // a full GC to run, other threads will not request another full GC. + + const size_t req_byte = req.size() * HeapWordSize; + AllocTracer::send_allocation_requiring_gc_event(req_byte, checked_cast(control_thread()->get_gc_id())); + ShenandoahAllocationStallLogger stall_logger; + + const size_t original_full_count = shenandoah_policy()->full_gc_count(); + const size_t original_count = shenandoah_policy()->reclaiming_gc_count(); + while (result == nullptr && !control_thread()->should_terminate()) { + control_thread()->handle_alloc_failure(req); result = allocate_memory_work(req, in_new_region); + if (result == nullptr) { + const size_t current_count = shenandoah_policy()->reclaiming_gc_count(); + if (current_count - original_count >= ShenandoahFullGCThreshold) { + if (original_full_count == shenandoah_policy()->full_gc_count()) { + // We are not getting what we need from concurrent collections, and no full gcs ran + // while we waited, so request a full gc. Whether this satisfies the allocation or not, + // we are done trying. + control_thread()->handle_alloc_failure_full(); + result = allocate_memory_work(req, in_new_region); + } + break; + } + } } + if (result != nullptr) { // If our allocation request has been satisfied after it initially failed, we count this as good gc progress notify_gc_progress(); } if (log_develop_is_enabled(Debug, gc, alloc)) { - ResourceMark rm; log_debug(gc, alloc)("Thread: %s, Result: " PTR_FORMAT ", Request: %s, Size: %zu" ", Original: %zu, Latest: %zu", Thread::current()->name(), p2i(result), req.type_string(), req.size(), @@ -1001,8 +1011,6 @@ HeapWord* ShenandoahHeap::allocate_memory(ShenandoahAllocRequest& req) { } else { assert(req.is_gc_alloc(), "Can only accept GC allocs here"); result = allocate_memory_work(req, in_new_region); - // Do not call handle_alloc_failure() here, because we cannot block. - // The allocation failure would be handled by the LRB slowpath with handle_alloc_failure_evac(). } if (in_new_region) { @@ -1025,11 +1033,6 @@ HeapWord* ShenandoahHeap::allocate_memory(ShenandoahAllocRequest& req) { return result; } -inline bool ShenandoahHeap::should_retry_allocation(size_t original_full_gc_count) const { - return shenandoah_policy()->full_gc_count() == original_full_gc_count - && !shenandoah_policy()->is_at_shutdown(); -} - HeapWord* ShenandoahHeap::allocate_memory_work(ShenandoahAllocRequest& req, bool& in_new_region) { // Reserve the promotion budget up front so it is enforced atomically without the heap lock. // If the reserve is exhausted, deny the promotion rather than overshoot it; the reservation @@ -1125,28 +1128,19 @@ class ShenandoahEvacuationTask : public WorkerTask { private: ShenandoahHeap* const _sh; ShenandoahCollectionSet* const _cs; - bool _concurrent; public: ShenandoahEvacuationTask(ShenandoahHeap* sh, - ShenandoahCollectionSet* cs, - bool concurrent) : + ShenandoahCollectionSet* cs) : WorkerTask("Shenandoah Evacuation"), _sh(sh), - _cs(cs), - _concurrent(concurrent) - {} + _cs(cs) { + } void work(uint worker_id) { - if (_concurrent) { - ShenandoahWorkerTimingsTracker timer(ShenandoahPhaseTimings::conc_evac, ShenandoahPhaseTimings::Work, worker_id, true); - ShenandoahConcurrentWorkerSession worker_session(worker_id); - SuspendibleThreadSetJoiner stsj; - do_work(); - } else { - ShenandoahWorkerTimingsTracker timer(ShenandoahPhaseTimings::degen_gc_evac, ShenandoahPhaseTimings::Work, worker_id, true); - ShenandoahParallelWorkerSession worker_session(worker_id); - do_work(); - } + ShenandoahWorkerTimingsTracker timer(ShenandoahPhaseTimings::conc_evac, ShenandoahPhaseTimings::Work, worker_id, true); + ShenandoahConcurrentWorkerSession worker_session(worker_id); + SuspendibleThreadSetJoiner stsj; + do_work(); } private: @@ -1157,7 +1151,11 @@ class ShenandoahEvacuationTask : public WorkerTask { assert(r->has_live(), "Region %zu should have been reclaimed early", r->index()); _sh->marked_object_iterate(r, &cl); - if (_sh->check_cancelled_gc_and_yield(_concurrent)) { + if (ShenandoahCollectorPolicy::should_abandon_evacuations(r)) { + break; + } + + if (_sh->check_cancelled_gc_and_yield()) { break; } } @@ -1177,6 +1175,9 @@ class ShenandoahRetireGCLABClosure : public ThreadClosure { ShenandoahThreadLocalData::set_gclab_size(thread, 0); } + // Clear this for next evacuation phase + ShenandoahThreadLocalData::set_has_evac_failures(thread, false); + if (ShenandoahHeap::heap()->mode()->is_generational()) { ShenandoahPLAB* shenandoah_plab = ShenandoahThreadLocalData::shenandoah_plab(thread); assert(shenandoah_plab != nullptr, "PLAB should be initialized for %s", thread->name()); @@ -1227,12 +1228,68 @@ class ShenandoahPrepareForUpdateRefsHandshakeClosure : public HandshakeClosure { ShenandoahGCStatePropagatorHandshakeClosure _propagator; }; -void ShenandoahHeap::evacuate_collection_set(ShenandoahGeneration* generation, bool concurrent) { +class ShenandoahSelfForwardClosure : public ObjectClosure { + bool _self_forwarded; +public: + ShenandoahSelfForwardClosure() : _self_forwarded(false) {} + + void do_object(oop obj) override { + oop resolved = ShenandoahForwarding::try_forward_to(obj, obj); + if (resolved == obj) { + _self_forwarded = true; + } + } + + bool self_forwarded_objects() const { return _self_forwarded; } +}; + +ShenandoahSelfForwardTask::ShenandoahSelfForwardTask(ShenandoahHeap* heap, ShenandoahCollectionSet* cs) : + WorkerTask("Shenandoah Self-Forward"), + _heap(heap), + _cs(cs) { + // Do not reset claim index, these workers pick up from where the evacuation workers left off + assert(!_cs->all_regions_claimed(), "Only here to self forward objects in unclaimed regions"); + assert(_heap->has_self_forwarded_objects(), "Workers that did not evacuate a region must set this flag"); +} + +void ShenandoahSelfForwardTask::work(uint worker_id) { + ShenandoahConcurrentWorkerSession worker_session(worker_id); + SuspendibleThreadSetJoiner joiner; + ShenandoahHeapRegion* r; + while ((r = _cs->claim_next()) != nullptr) { + ShenandoahSelfForwardClosure cl; + _heap->marked_object_iterate(r, &cl); + if (cl.self_forwarded_objects()) { + r->set_has_self_forwards(); + } + + if (_heap->check_cancelled_gc_and_yield()) { + break; + } + } +} + +void ShenandoahHeap::evacuate_collection_set(ShenandoahGeneration* generation) { assert(generation->is_global(), "Only global generation expected here"); - ShenandoahEvacuationTask task(this, _collection_set, concurrent); + ShenandoahEvacuationTask task(this, _collection_set); workers()->run_task(&task); } +void ShenandoahHeap::self_forward_stranded_objects() { + // When an object cannot be evacuated, it will be self-forwarded. Regions with self-forwarded objects + // can only be "partially" recycled. We have made a policy/design decision to stop trying to evacuate + // such regions. Instead, the workers will focus on evacuating regions that still have a chance of being + // completely evacuated. However, once a worker itself cannot refill its LABs, it will do nothing more + // beside create more partially evacuated regions. For this reason, such a worker exits the evacuation + // task. This leaves the remaining regions to the remaining workers who, we hope, may yet complete more + // successful evacuations. For the case that all workers exit before all the collection set regions are + // evacuated, we have this step below which simply self forwards all the objects in all the regions that + // were not evacuated out of the collection set. + log_debug(gc)("Cleaning up failed evacuations"); + ShenandoahSelfForwardTask self_forward_task(this, collection_set()); + workers()->run_task(&self_forward_task); +} + class ShenandoahCompleteStackWatermarkHandshakeClosure : public HandshakeClosure { public: ShenandoahCompleteStackWatermarkHandshakeClosure() : HandshakeClosure("Shenandoah Complete stacks handshake") {} @@ -1258,7 +1315,6 @@ void ShenandoahHeap::concurrent_prepare_for_update_refs() { // by the following handshake. MutexLocker lock(Threads_lock); - // A cancellation at this point means the degenerated cycle must resume from update-refs. set_gc_state_concurrent(EVACUATION, false); set_gc_state_concurrent(UPDATE_REFS, true); } @@ -1271,8 +1327,6 @@ void ShenandoahHeap::concurrent_prepare_for_update_refs() { // Now retire gclabs and plabs and propagate gc_state for mutator threads Handshake::execute(&prepare_for_update_refs); - - _update_refs_iterator.reset(); } void ShenandoahHeap::op_final_roots() { @@ -1307,16 +1361,20 @@ oop ShenandoahHeap::evacuate_object(oop p, Thread* thread) { assert(thread == Thread::current(), "Expected thread parameter to be current thread."); ShenandoahHeapRegion* r = heap_region_containing(p); - assert(!r->is_humongous(), "never evacuate humongous objects"); + if (has_self_forwarded_objects() && r->has_self_forwards()) { + // We don't want GC threads to evacuate objects in regions that have evacuation failures. We'd + // rather have them concentrate on regions that still have a chance of being completely evacuated. + return ShenandoahForwarding::try_forward_to(p, p); + } - ShenandoahAffiliation target_gen = r->affiliation(); - return try_evacuate_object(p, thread, r, target_gen); + assert(!r->is_humongous(), "never evacuate humongous objects"); + assert(r->is_young(), "Only expect evacuations from young in this mode"); + return try_evacuate_object(p, thread, r, YOUNG_GENERATION); } oop ShenandoahHeap::try_evacuate_object(oop p, Thread* thread, ShenandoahHeapRegion* from_region, ShenandoahAffiliation target_gen) { assert(target_gen == YOUNG_GENERATION, "Only expect evacuations to young in this mode"); - assert(from_region->is_young(), "Only expect evacuations from young in this mode"); bool alloc_from_lab = true; HeapWord* copy = nullptr; size_t size = ShenandoahForwarding::size(p); @@ -1341,18 +1399,21 @@ oop ShenandoahHeap::try_evacuate_object(oop p, Thread* thread, ShenandoahHeapReg #endif if (copy == nullptr) { - control_thread()->handle_alloc_failure_evac(size); - // Install the self-forwarded bit on p so other evacuators/LRBs see // the object as "already handled, do not try to evacuate". The CAS // may fail if another thread concurrently installed a real forwardee // (they succeeded where we failed) or self-forwarded first. oop fwd = ShenandoahForwarding::try_forward_to(p, p); if (fwd == p) { - // Now self-forwarded. Flag the region so the degen/full GC - // entry drain knows to scan it for self_fwd bits to clear. + // Now self-forwarded. Flag the region so other threads will not + // try to evacuate objects from here. + set_has_self_forwarded_objects(); from_region->set_has_self_forwards(); } + + // It does not matter if this thread did the self forwarding or not. + // This thread failed to allocate an evacuation copy. + ShenandoahThreadLocalData::set_has_evac_failures(thread, true); return fwd; } @@ -1408,78 +1469,48 @@ oop ShenandoahHeap::try_evacuate_object(oop p, Thread* thread, ShenandoahHeapReg } } -// Clear the self_fwd bit on a live cset object, if set. Runs at a safepoint, -// so a plain store is sufficient — no concurrent writers to the mark word. -class ShenandoahUnSelfForwardObjectClosure : public ObjectClosure { +class ShenandoahTrashRegionTask : public WorkerTask { + ShenandoahCollectionSet* _collection_set; + Atomic _free_bytes_in_evac_failed_regions; public: - void do_object(oop obj) override { - ShenandoahForwarding::unset_self_forwarded(obj); - } -}; + explicit ShenandoahTrashRegionTask(ShenandoahCollectionSet* collection_set) + : WorkerTask("ShenandoahTrashRegions") + , _collection_set(collection_set), _free_bytes_in_evac_failed_regions(0) {} -// Parallel task over flagged cset regions. Iterates the live objects via the -// mark bitmap (skipping evacuated and never-marked memory), clears self_fwd -// bits, and resets the region flag once done. -class ShenandoahUnSelfForwardTask : public WorkerTask { -private: - ShenandoahHeap* const _heap; - ShenandoahCollectionSet* const _cs; - -public: - ShenandoahUnSelfForwardTask(ShenandoahHeap* heap, ShenandoahCollectionSet* cs) : - WorkerTask("Shenandoah Un-Self-Forward"), - _heap(heap), - _cs(cs) {} + size_t free_bytes_in_evac_failed_regions() const { + return _free_bytes_in_evac_failed_regions.load_relaxed(); + } void work(uint worker_id) override { - ShenandoahParallelWorkerSession worker_session(worker_id); - ShenandoahUnSelfForwardObjectClosure cl; ShenandoahHeapRegion* r; - while ((r = _cs->claim_next()) != nullptr) { + size_t free_bytes = 0; + while ((r = _collection_set->claim_next()) != nullptr) { if (r->has_self_forwards()) { - _heap->marked_object_iterate(r, &cl); - r->clear_has_self_forwards(); + r->partially_recycle(); + free_bytes += r->free(); + } else { + r->make_trash(); } } + _free_bytes_in_evac_failed_regions.add_then_fetch(free_bytes, memory_order_relaxed); } }; -void ShenandoahHeap::un_self_forward_cset_regions() { - assert(ShenandoahSafepoint::is_at_shenandoah_safepoint(), "must be at safepoint"); - ShenandoahCollectionSet* cs = collection_set(); - if (cs == nullptr || cs->is_empty()) { - return; - } - cs->clear_current_index(); - ShenandoahUnSelfForwardTask task(this, cs); - workers()->run_task(&task); - DEBUG_ONLY(assert_no_self_forwards()); -} - -#ifdef ASSERT -void ShenandoahHeap::assert_no_self_forwards() const { - assert(ShenandoahSafepoint::is_at_shenandoah_safepoint(), "must be at safepoint"); - ShenandoahCollectionSet* cs = collection_set(); - if (cs == nullptr) return; - cs->clear_current_index(); - ShenandoahHeapRegion* r; - while ((r = cs->next()) != nullptr) { - assert(!r->has_self_forwards(), "region still flagged after drain"); - } - cs->clear_current_index(); -} -#endif - void ShenandoahHeap::trash_cset_regions() { - ShenandoahHeapLocker locker(lock()); - ShenandoahCollectionSet* set = collection_set(); - ShenandoahHeapRegion* r; set->clear_current_index(); - while ((r = set->next()) != nullptr) { - r->make_trash(); + if (has_self_forwarded_objects()) { + ShenandoahTrashRegionTask task(set); + workers()->run_task(&task); + log_debug(gc, free)("Memory available in regions that failed evacuation: " PROPERFMT, + PROPERFMTARGS(task.free_bytes_in_evac_failed_regions())); + } else { + ShenandoahHeapRegion* r; + while ((r = set->next()) != nullptr) { + r->make_trash(); + } } - collection_set()->clear(); + set->clear(); } void ShenandoahHeap::print_heap_regions_on(outputStream* st) const { @@ -1626,7 +1657,7 @@ size_t ShenandoahHeap::max_tlab_size() const { void ShenandoahHeap::collect_as_vm_thread(GCCause::Cause cause) { // These requests are ignored because we can't easily have Shenandoah jump into - // a synchronous (degenerated or full) cycle while it is in the middle of a concurrent + // a synchronous (full) cycle while it is in the middle of a concurrent // cycle. We _could_ cancel the concurrent cycle and then try to run a cycle directly // on the VM thread, but this would confuse the control thread mightily and doesn't // seem worth the trouble. Instead, we will have the caller thread run (and wait for) a @@ -1726,8 +1757,7 @@ void ShenandoahHeap::set_active_generation(ShenandoahGeneration* generation) { _active_generation = generation; } -void ShenandoahHeap::on_cycle_start(GCCause::Cause cause, ShenandoahGeneration* generation, - bool is_degenerated, bool is_out_of_cycle) { +void ShenandoahHeap::on_cycle_start(GCCause::Cause cause, ShenandoahGeneration* generation) { shenandoah_policy()->record_collection_cause(cause); const GCCause::Cause current = gc_cause(); @@ -1736,10 +1766,11 @@ void ShenandoahHeap::on_cycle_start(GCCause::Cause cause, ShenandoahGeneration* set_gc_cause(cause); - if (is_degenerated) { - generation->heuristics()->record_degenerated_cycle_start(is_out_of_cycle); - } else { - generation->heuristics()->record_cycle_start(); + generation->heuristics()->record_cycle_start(); + if (mode()->is_generational() && generation->is_global()) { + // We are starting a global cycle, for heuristic's purposes, inform young and old too + young_generation()->heuristics()->record_cycle_start(); + old_generation()->heuristics()->record_cycle_start(); } } @@ -2130,29 +2161,6 @@ void ShenandoahHeap::do_class_unloading() { } } -void ShenandoahHeap::stw_weak_refs(ShenandoahGeneration* generation, bool full_gc) { - // Weak refs processing - ShenandoahPhaseTimings::Phase phase = full_gc ? ShenandoahPhaseTimings::full_gc_weakrefs - : ShenandoahPhaseTimings::degen_gc_weakrefs; - ShenandoahTimingsTracker t(phase); - ShenandoahGCWorkerPhase worker_phase(phase); - generation->ref_processor()->process_references(phase, workers(), false /* concurrent */); -} - -void ShenandoahHeap::prepare_update_heap_references() { - assert(ShenandoahSafepoint::is_at_shenandoah_safepoint(), "must be at safepoint"); - - // Evacuation is over, no GCLABs are needed anymore. GCLABs are under URWM, so we need to - // make them parsable for update code to work correctly. Plus, we can compute new sizes - // for future GCLABs here. - if (UseTLAB) { - ShenandoahGCPhase phase(ShenandoahPhaseTimings::degen_gc_init_update_refs_manage_gclabs); - gclabs_retire(ResizeTLAB); - } - - _update_refs_iterator.reset(); -} - void ShenandoahHeap::propagate_gc_state_to_all_threads() { assert(ShenandoahSafepoint::is_at_shenandoah_safepoint(), "Must be at Shenandoah safepoint"); if (_gc_state_changed) { @@ -2270,19 +2278,8 @@ size_t ShenandoahHeap::tlab_used() const { return _free_set->used_not_holding_lock(); } -bool ShenandoahHeap::try_cancel_gc(GCCause::Cause cause) { - while (true) { - const GCCause::Cause prev = _cancelled_gc.get(); - if (prev != GCCause::_no_gc && prev != GCCause::_shenandoah_concurrent_gc && cause != GCCause::_shenandoah_stop_vm) { - // Only when the gc has not been cancelled, or it has been cancelled to interrupt an old marking cycle - // do we allow the new cancellation request to happen. We make an exception for stopping the VM. - return false; - } - - if (_cancelled_gc.cmpxchg(cause, prev) == prev) { - return true; - } - } +bool ShenandoahHeap::try_cancel_gc() { + return !is_stopping() && _cancelled_gc.try_set(); } void ShenandoahHeap::cancel_concurrent_mark() { @@ -2297,7 +2294,7 @@ void ShenandoahHeap::cancel_concurrent_mark() { } bool ShenandoahHeap::cancel_gc(GCCause::Cause cause) { - if (try_cancel_gc(cause)) { + if (try_cancel_gc()) { FormatBuffer<> msg("Cancelling GC: %s", GCCause::to_string(cause)); log_info(gc,thread)("%s", msg.buffer()); Events::log(Thread::current(), "%s", msg.buffer()); @@ -2332,90 +2329,6 @@ void ShenandoahHeap::stop() { } } -void ShenandoahHeap::stw_unload_classes(bool full_gc) { - if (!unload_classes()) return; - ClassUnloadingContext ctx(_workers->active_workers(), - true /* unregister_nmethods_during_purge */, - false /* lock_nmethod_free_separately */); - - // Unload classes and purge SystemDictionary. - { - ShenandoahPhaseTimings::Phase phase = full_gc ? - ShenandoahPhaseTimings::full_gc_purge_class_unload : - ShenandoahPhaseTimings::degen_gc_purge_class_unload; - ShenandoahIsAliveSelector is_alive; - { - CodeCache::UnlinkingScope scope(is_alive.is_alive_closure()); - ShenandoahGCPhase gc_phase(phase); - ShenandoahGCWorkerPhase worker_phase(phase); - bool unloading_occurred = SystemDictionary::do_unloading(gc_timer()); - - ShenandoahClassUnloadingTask unlink_task(phase, unloading_occurred); - _workers->run_task(&unlink_task); - } - // Release unloaded nmethods's memory. - ClassUnloadingContext::context()->purge_and_free_nmethods(); - } - - { - ShenandoahGCPhase phase(full_gc ? - ShenandoahPhaseTimings::full_gc_purge_cldg : - ShenandoahPhaseTimings::degen_gc_purge_cldg); - ClassLoaderDataGraph::purge(true /* at_safepoint */); - } - // Resize and verify metaspace - MetaspaceGC::compute_new_size(); - - if (mode()->is_generational()) { - old_generation()->set_parsable(false); - } - - DEBUG_ONLY(MetaspaceUtils::verify();) -} - -// Weak roots are either pre-evacuated (final mark) or updated (final update refs), -// so they should not have forwarded oops. -// However, we do need to "null" dead oops in the roots, if can not be done -// in concurrent cycles. -void ShenandoahHeap::stw_process_weak_roots(bool full_gc) { - uint num_workers = _workers->active_workers(); - ShenandoahPhaseTimings::Phase timing_phase = full_gc ? - ShenandoahPhaseTimings::full_gc_purge_weak_par : - ShenandoahPhaseTimings::degen_gc_purge_weak_par; - ShenandoahGCPhase phase(timing_phase); - ShenandoahGCWorkerPhase worker_phase(timing_phase); - // Cleanup weak roots - if (has_forwarded_objects()) { - ShenandoahForwardedIsAliveClosure is_alive; - ShenandoahNonConcUpdateRefsClosure keep_alive; - ShenandoahParallelWeakRootsCleaningTask - cleaning_task(timing_phase, &is_alive, &keep_alive, num_workers); - _workers->run_task(&cleaning_task); - } else { - ShenandoahIsAliveClosure is_alive; -#ifdef ASSERT - ShenandoahAssertNotForwardedClosure verify_cl; - ShenandoahParallelWeakRootsCleaningTask - cleaning_task(timing_phase, &is_alive, &verify_cl, num_workers); -#else - ShenandoahParallelWeakRootsCleaningTask - cleaning_task(timing_phase, &is_alive, &do_nothing_cl, num_workers); -#endif - _workers->run_task(&cleaning_task); - } -} - -void ShenandoahHeap::parallel_cleaning(ShenandoahGeneration* generation, bool full_gc) { - assert(SafepointSynchronize::is_at_safepoint(), "Must be at a safepoint"); - assert(is_stw_gc_in_progress(), "Only for Degenerated and Full GC"); - ShenandoahGCPhase phase(full_gc ? - ShenandoahPhaseTimings::full_gc_purge : - ShenandoahPhaseTimings::degen_gc_purge); - stw_weak_refs(generation, full_gc); - stw_process_weak_roots(full_gc); - stw_unload_classes(full_gc); -} - void ShenandoahHeap::set_has_forwarded_objects(bool cond) { set_gc_state_at_safepoint(HAS_FORWARDED, cond); } @@ -2434,10 +2347,6 @@ address ShenandoahHeap::in_cset_fast_test_addr() { return (address) heap->collection_set()->biased_map_address(); } -void ShenandoahHeap::set_degenerated_gc_in_progress(bool in_progress) { - _degenerated_gc_in_progress.set_cond(in_progress); -} - void ShenandoahHeap::set_full_gc_in_progress(bool in_progress) { _full_gc_in_progress.set_cond(in_progress); } @@ -2574,10 +2483,6 @@ void ShenandoahHeap::assert_gc_workers(uint nworkers) { // Use ParallelGCThreads inside safepoints assert(nworkers == ParallelGCThreads, "Use ParallelGCThreads (%u) within safepoint, not %u", ParallelGCThreads, nworkers); - } else { - // Use ConcGCThreads outside safepoints - assert(nworkers == ConcGCThreads, "Use ConcGCThreads (%u) outside safepoints, %u", - ConcGCThreads, nworkers); } } #endif @@ -2588,7 +2493,6 @@ ShenandoahVerifier* ShenandoahHeap::verifier() { return _verifier; } -template class ShenandoahUpdateHeapRefsTask : public WorkerTask { private: ShenandoahHeap* _heap; @@ -2601,25 +2505,19 @@ class ShenandoahUpdateHeapRefsTask : public WorkerTask { } void work(uint worker_id) { - if (CONCURRENT) { - ShenandoahWorkerTimingsTracker timer(ShenandoahPhaseTimings::conc_update_refs, ShenandoahPhaseTimings::Work, worker_id, true); - ShenandoahConcurrentWorkerSession worker_session(worker_id); - SuspendibleThreadSetJoiner stsj; - do_work(worker_id); - } else { - ShenandoahWorkerTimingsTracker timer(ShenandoahPhaseTimings::degen_gc_update_refs, ShenandoahPhaseTimings::Work, worker_id, true); - ShenandoahParallelWorkerSession worker_session(worker_id); - do_work(worker_id); - } + ShenandoahWorkerTimingsTracker timer(ShenandoahPhaseTimings::conc_update_refs, ShenandoahPhaseTimings::Work, worker_id, true); + ShenandoahConcurrentWorkerSession worker_session(worker_id); + SuspendibleThreadSetJoiner stsj; + do_work>(worker_id); } private: template void do_work(uint worker_id) { - if (CONCURRENT && (worker_id == 0)) { + if (worker_id == 0) { // We ask the first worker to replenish the Mutator free set by moving regions previously reserved to hold the // results of evacuation. These reserves are no longer necessary because evacuation has completed. - size_t cset_regions = _heap->collection_set()->count(); + const size_t cset_regions = _heap->collection_set()->count(); // Now that evacuation is done, we can reassign any regions that had been reserved to hold the results of evacuation // to the mutator free set. At the end of GC, we will have cset_regions newly evacuated fully empty regions from @@ -2627,16 +2525,18 @@ class ShenandoahUpdateHeapRefsTask : public WorkerTask { // next GC cycle. _heap->free_set()->move_regions_from_collector_to_mutator(cset_regions); } - // If !CONCURRENT, there's no value in expanding Mutator free set + T cl; ShenandoahHeapRegion* r = _regions->next(); while (r != nullptr) { + // Regions put into service after final mark will not have an update watermark. Regions with self forwarded objects + // must also have the references in these objects be updated. HeapWord* update_watermark = r->get_update_watermark(); assert (update_watermark >= r->bottom(), "sanity"); - if (r->is_active() && !r->is_cset()) { + if (r->is_update_required()) { _heap->marked_object_oop_iterate(r, &cl, update_watermark); } - if (_heap->check_cancelled_gc_and_yield(CONCURRENT)) { + if (_heap->check_cancelled_gc_and_yield()) { return; } r = _regions->next(); @@ -2644,37 +2544,26 @@ class ShenandoahUpdateHeapRefsTask : public WorkerTask { } }; -void ShenandoahHeap::update_heap_references(ShenandoahGeneration* generation, bool concurrent) { +void ShenandoahHeap::update_heap_references(ShenandoahGeneration* generation) { assert(generation->is_global(), "Should only get global generation here"); - assert(!is_full_gc_in_progress(), "Only for concurrent and degenerated GC"); - - if (concurrent) { - ShenandoahUpdateHeapRefsTask task(&_update_refs_iterator); - workers()->run_task(&task); - } else { - ShenandoahUpdateHeapRefsTask task(&_update_refs_iterator); - workers()->run_task(&task); - } + assert(!is_full_gc_in_progress(), "Only for concurrent GC"); + ShenandoahRegionIterator update_refs_iterator(this); + ShenandoahUpdateHeapRefsTask task(&update_refs_iterator); + workers()->run_task(&task); } -void ShenandoahHeap::update_heap_region_states(bool concurrent) { +void ShenandoahHeap::update_heap_region_states() { assert(SafepointSynchronize::is_at_safepoint(), "Must be at a safepoint"); - assert(!is_full_gc_in_progress(), "Only for concurrent and degenerated GC"); + assert(!is_full_gc_in_progress(), "Only for concurrent GC"); { - ShenandoahGCPhase phase(concurrent ? - ShenandoahPhaseTimings::final_update_refs_update_region_states : - ShenandoahPhaseTimings::degen_gc_final_update_refs_update_region_states); - + ShenandoahGCPhase phase(ShenandoahPhaseTimings::final_update_refs_update_region_states); final_update_refs_update_region_states(); - assert_pinned_region_status(); } { - ShenandoahGCPhase phase(concurrent ? - ShenandoahPhaseTimings::final_update_refs_trash_cset : - ShenandoahPhaseTimings::degen_gc_final_update_refs_trash_cset); + ShenandoahGCPhase phase(ShenandoahPhaseTimings::final_update_refs_trash_cset); trash_cset_regions(); } } @@ -2722,10 +2611,8 @@ void ShenandoahHeap::rebuild_free_set_within_phase() { } } -void ShenandoahHeap::rebuild_free_set(bool concurrent) { - ShenandoahGCPhase phase(concurrent ? - ShenandoahPhaseTimings::final_update_refs_rebuild_freeset : - ShenandoahPhaseTimings::degen_gc_final_update_refs_rebuild_freeset); +void ShenandoahHeap::rebuild_free_set() { + ShenandoahGCPhase phase(ShenandoahPhaseTimings::final_update_refs_rebuild_freeset); rebuild_free_set_within_phase(); } @@ -2819,10 +2706,6 @@ void ShenandoahHeap::safepoint_synchronize_end() { void ShenandoahHeap::try_inject_alloc_failure() { if (ShenandoahAllocFailureALot && !cancelled_gc() && ((os::random() % 1000) > 950)) { _inject_alloc_failure.set(); - os::naked_short_sleep(1); - if (cancelled_gc()) { - log_info(gc)("Allocation failure was successfully injected"); - } } } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp b/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp index 5530af1d6713..945ffbcdc7ce 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp @@ -29,6 +29,7 @@ #include "gc/shared/collectedHeap.hpp" #include "gc/shared/markBitMap.hpp" +#include "gc/shared/workerThread.hpp" #include "gc/shenandoah/mode/shenandoahMode.hpp" #include "gc/shenandoah/shenandoahAllocRate.hpp" #include "gc/shenandoah/shenandoahAllocRequest.hpp" @@ -135,6 +136,15 @@ class ShenandoahHeapLocker : public StackObj { typedef Stack ShenandoahScanObjectStack; +class ShenandoahSelfForwardTask : public WorkerTask { + ShenandoahHeap* const _heap; + ShenandoahCollectionSet* const _cs; + +public: + ShenandoahSelfForwardTask(ShenandoahHeap* heap, ShenandoahCollectionSet* cs); + void work(uint worker_id) override; +}; + // Shenandoah GC is low-pause concurrent GC that uses a load reference barrier // for concurent evacuation and a snapshot-at-the-beginning write barrier for // concurrent marking. See ShenandoahControlThread for GC cycle structure. @@ -150,7 +160,6 @@ class ShenandoahHeap : public CollectedHeap { // Supported GC friend class ShenandoahConcurrentGC; friend class ShenandoahOldGC; - friend class ShenandoahDegenGC; friend class ShenandoahFullGC; friend class ShenandoahUnload; @@ -184,7 +193,7 @@ class ShenandoahHeap : public CollectedHeap { ShenandoahHeuristics* heuristics(); -// ---------- Initialization, termination, identification, printing routines + // ---------- Initialization, termination, identification, printing routines // public: static ShenandoahHeap* heap(); @@ -192,7 +201,7 @@ class ShenandoahHeap : public CollectedHeap { const char* name() const override { return "Shenandoah"; } ShenandoahHeap::Name kind() const override { return CollectedHeap::Shenandoah; } - ShenandoahHeap(ShenandoahCollectorPolicy* policy); + ShenandoahHeap(); jint initialize() override; void post_initialize() override; virtual void initialize_generations(); @@ -216,7 +225,7 @@ class ShenandoahHeap : public CollectedHeap { void prepare_for_verify() override; void verify(VerifyOption vo) override; -// WhiteBox testing support. + // WhiteBox testing support. bool supports_concurrent_gc_breakpoints() const override { return mode()->is_concurrent(); } @@ -361,7 +370,6 @@ class ShenandoahHeap : public CollectedHeap { bool _gc_state_changed; ShenandoahSharedBitmap _gc_state; ShenandoahSharedFlag _heap_changed; - ShenandoahSharedFlag _degenerated_gc_in_progress; ShenandoahSharedFlag _full_gc_in_progress; ShenandoahSharedFlag _full_gc_move_in_progress; ShenandoahSharedFlag _concurrent_strong_root_in_progress; @@ -411,7 +419,6 @@ class ShenandoahHeap : public CollectedHeap { void set_concurrent_old_mark_in_progress(bool in_progress); void set_evacuation_in_progress(bool in_progress); void set_update_refs_in_progress(bool in_progress); - void set_degenerated_gc_in_progress(bool in_progress); void set_full_gc_in_progress(bool in_progress); void set_full_gc_move_in_progress(bool in_progress); void set_has_forwarded_objects(bool cond); @@ -424,7 +431,6 @@ class ShenandoahHeap : public CollectedHeap { inline bool is_concurrent_old_mark_in_progress() const; inline bool is_update_refs_in_progress() const; inline bool is_evacuation_in_progress() const; - inline bool is_degenerated_gc_in_progress() const; inline bool is_full_gc_in_progress() const; inline bool is_full_gc_move_in_progress() const; inline bool has_forwarded_objects() const; @@ -441,32 +447,25 @@ class ShenandoahHeap : public CollectedHeap { // the responsiveness of the heuristic when starting a cycle. double _cancel_requested_time; - // Indicates the reason the current GC has been cancelled (GCCause::_no_gc means the gc is not cancelled). - ShenandoahSharedEnumFlag _cancelled_gc; + // True when gc threads should stop + ShenandoahSharedFlag _cancelled_gc; // Returns true if cancel request was successfully communicated. // Returns false if some other thread already communicated cancel // request. A true return value does not mean GC has been // cancelled, only that the process of cancelling GC has begun. - bool try_cancel_gc(GCCause::Cause cause); + bool try_cancel_gc(); public: // True if gc has been cancelled inline bool cancelled_gc() const; // Used by workers in the GC cycle to detect cancellation and honor STS requirements - inline bool check_cancelled_gc_and_yield(bool sts_active = true); - - // This indicates the reason the last GC cycle was cancelled. - inline GCCause::Cause cancelled_cause() const; + inline bool check_cancelled_gc_and_yield(); - // Clears the cancellation cause and resets the oom handler + // Clears the cancellation inline void clear_cancelled_gc(); - // Clears the cancellation cause iff the current cancellation reason equals the given - // expected cancellation cause. Does not reset the oom handler. - inline GCCause::Cause clear_cancellation(GCCause::Cause expected); - void cancel_concurrent_mark(); // Returns true if and only if this call caused a gc to be cancelled. @@ -475,25 +474,37 @@ class ShenandoahHeap : public CollectedHeap { // Returns true if the soft maximum heap has been changed using management APIs. bool check_soft_max_changed(); -protected: - // This is shared between shConcurrentGC and shDegenerateGC so that degenerated - // GC can resume update refs from where the concurrent GC was cancelled. It is - // also used in shGenerationalHeap, which uses a different closure for update refs. - ShenandoahRegionIterator _update_refs_iterator; + // True if self forwarded objects exist in the heap + bool has_self_forwarded_objects() const { + return _has_self_forwarded_objects.is_set(); + } + + // Set when self forwarded objects exist in the heap + void set_has_self_forwarded_objects() { + _has_self_forwarded_objects.try_set(); + } + + void unset_has_self_forwarded_objects() { + _has_self_forwarded_objects.unset(); + } private: + ShenandoahSharedFlag _has_self_forwarded_objects; + inline void reset_cancellation_time(); // GC support // Evacuation - virtual void evacuate_collection_set(ShenandoahGeneration* generation, bool concurrent); + virtual void evacuate_collection_set(ShenandoahGeneration* generation); + + // Self forwards objects that were never seen by evacuating threads + void self_forward_stranded_objects(); + // Concurrent root processing void prepare_concurrent_roots(); void finish_concurrent_roots(); // Concurrent class unloading support void do_class_unloading(); - // Reference updating - void prepare_update_heap_references(); // Retires LABs used for evacuation void concurrent_prepare_for_update_refs(); @@ -501,16 +512,16 @@ class ShenandoahHeap : public CollectedHeap { // Turn off weak roots flag void op_final_roots(); - virtual void update_heap_references(ShenandoahGeneration* generation, bool concurrent); + virtual void update_heap_references(ShenandoahGeneration* generation); // Final update region states - void update_heap_region_states(bool concurrent); + void update_heap_region_states(); virtual void final_update_refs_update_region_states(); void rendezvous_threads(const char* name); void recycle_trash(); public: // The following two functions rebuild the free set at the end of GC, in preparation for an idle phase. - void rebuild_free_set(bool concurrent); + void rebuild_free_set(); void rebuild_free_set_within_phase(); void notify_gc_progress(); void notify_gc_no_progress(); @@ -547,6 +558,7 @@ class ShenandoahHeap : public CollectedHeap { public: ShenandoahController* control_thread() const { return _control_thread; } + inline bool is_stopping() const; ShenandoahGeneration* global_generation() const { return _global_generation; } ShenandoahYoungGeneration* young_generation() const { @@ -572,7 +584,7 @@ class ShenandoahHeap : public CollectedHeap { return _evac_tracker; } - void on_cycle_start(GCCause::Cause cause, ShenandoahGeneration* generation, bool is_degenerated, bool is_out_of_cycle); + void on_cycle_start(GCCause::Cause cause, ShenandoahGeneration* generation); void on_cycle_end(ShenandoahGeneration* generation); ShenandoahVerifier* verifier(); @@ -608,13 +620,7 @@ class ShenandoahHeap : public CollectedHeap { void set_unload_classes(bool uc); bool unload_classes() const; - // Perform STW class unloading and weak root cleaning - void parallel_cleaning(ShenandoahGeneration* generation, bool full_gc); - private: - void stw_unload_classes(bool full_gc); - void stw_process_weak_roots(bool full_gc); - void stw_weak_refs(ShenandoahGeneration* generation, bool full_gc); inline void assert_lock_for_affiliation(ShenandoahAffiliation orig_affiliation, ShenandoahAffiliation new_affiliation); @@ -731,9 +737,6 @@ class ShenandoahHeap : public CollectedHeap { HeapWord* allocate_from_gclab_slow(Thread* thread, size_t size); HeapWord* allocate_new_gclab(size_t min_size, size_t word_size, size_t* actual_size); - // We want to retry an unsuccessful attempt at allocation until at least a full gc. - bool should_retry_allocation(size_t original_full_gc_count) const; - public: HeapWord* allocate_memory(ShenandoahAllocRequest& request); HeapWord* mem_allocate(size_t size) override; @@ -844,22 +847,9 @@ class ShenandoahHeap : public CollectedHeap { // self-forwarded bit on src, flags src's region, and returns src. virtual oop evacuate_object(oop src, Thread* thread); - // Parallel scan of flagged cset regions to clear self-forwarded bits on live - // objects. Must be called at a safepoint; intended for the degenerated and - // full GC entry paths. - void un_self_forward_cset_regions(); - - DEBUG_ONLY(void assert_no_self_forwards() const;) - // ---------- Helper functions // public: - template - inline void conc_update_with_forwarded(T* p); - - template - inline void non_conc_update_with_forwarded(T* p); - static inline void atomic_update_oop(oop update, oop* addr, oop compare); static inline void atomic_update_oop(oop update, narrowOop* addr, oop compare); static inline void atomic_update_oop(oop update, narrowOop* addr, narrowOop compare); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeap.inline.hpp b/src/hotspot/share/gc/shenandoah/shenandoahHeap.inline.hpp index f0723ddfef18..072275343306 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahHeap.inline.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeap.inline.hpp @@ -102,53 +102,6 @@ inline ShenandoahHeapRegion* ShenandoahHeap::heap_region_containing(const void* return result; } -template -inline void ShenandoahHeap::non_conc_update_with_forwarded(T* p) { - T o = RawAccess<>::oop_load(p); - if (!CompressedOops::is_null(o)) { - oop obj = CompressedOops::decode_not_null(o); - if (in_collection_set(obj)) { - // Corner case: when evacuation fails, there are objects in collection - // set that are not forwarded, and can still be in cset. - shenandoah_assert_forwarded_except(p, obj, cancelled_gc()); - oop resolved = ShenandoahForwarding::forwardee_or_null(obj); - if (resolved == nullptr) { - resolved = obj; - } - shenandoah_assert_not_in_cset_except(p, resolved, cancelled_gc()); - - if (resolved != obj) { - // Unconditionally store the update: no concurrent updates expected. - RawAccess::oop_store(p, resolved); - } - } - } -} - -template -inline void ShenandoahHeap::conc_update_with_forwarded(T* p) { - T o = RawAccess<>::oop_load(p); - if (!CompressedOops::is_null(o)) { - oop obj = CompressedOops::decode_not_null(o); - if (in_collection_set(obj)) { - // For concurrent update-refs, we cannot reach the state - // with non-forwarded objects in cset. - shenandoah_assert_forwarded(p, obj); - oop resolved = ShenandoahForwarding::forwardee(obj); - shenandoah_assert_not_in_cset(p, resolved); - - // We should not be updating the cset regions themselves. - shenandoah_assert_not_in_cset_loc_except(p, !is_in(p)); - - if (resolved != obj) { - // Either we succeed in updating the reference, or something else gets in our way. - // We don't care if that is another concurrent GC update, or another mutator update. - atomic_update_oop(resolved, p, o); - } - } - } -} - // Atomic updates of heap location. This is only expected to work with updating the same // logical object with its forwardee. The reason why we need stronger-than-relaxed memory // ordering has to do with coordination with GC barriers and mutator accesses. @@ -250,12 +203,16 @@ inline void ShenandoahHeap::atomic_clear_oop(narrowOop* addr, narrowOop compare) AtomicAccess::cmpxchg(addr, compare, narrowOop(), memory_order_relaxed); } +inline bool ShenandoahHeap::is_stopping() const { + return control_thread()->should_terminate(); +} + inline bool ShenandoahHeap::cancelled_gc() const { - return _cancelled_gc.get() != GCCause::_no_gc; + return _cancelled_gc.is_set() || is_stopping(); } -inline bool ShenandoahHeap::check_cancelled_gc_and_yield(bool sts_active) { - if (sts_active && !cancelled_gc()) { +inline bool ShenandoahHeap::check_cancelled_gc_and_yield() { + if (!cancelled_gc()) { if (SuspendibleThreadSet::should_yield()) { SuspendibleThreadSet::yield(); } @@ -263,23 +220,11 @@ inline bool ShenandoahHeap::check_cancelled_gc_and_yield(bool sts_active) { return cancelled_gc(); } -inline GCCause::Cause ShenandoahHeap::cancelled_cause() const { - return _cancelled_gc.get(); -} - inline void ShenandoahHeap::clear_cancelled_gc() { - _cancelled_gc.set(GCCause::_no_gc); + _cancelled_gc.unset(); reset_cancellation_time(); } -inline GCCause::Cause ShenandoahHeap::clear_cancellation(const GCCause::Cause expected) { - const GCCause::Cause cancellation_cause = _cancelled_gc.cmpxchg(GCCause::_no_gc, expected); - if (cancellation_cause == expected) { - reset_cancellation_time(); - } - return cancellation_cause; -} - inline void ShenandoahHeap::reset_cancellation_time() { if (_cancel_requested_time > 0) { log_debug(gc)("GC cancellation took %.3fs", (os::elapsedTime() - _cancel_requested_time)); @@ -487,10 +432,6 @@ inline bool ShenandoahHeap::is_concurrent_weak_root_in_progress() const { return is_gc_state(WEAK_ROOTS); } -inline bool ShenandoahHeap::is_degenerated_gc_in_progress() const { - return _degenerated_gc_in_progress.is_set(); -} - inline bool ShenandoahHeap::is_full_gc_in_progress() const { return _full_gc_in_progress.is_set(); } @@ -500,7 +441,7 @@ inline bool ShenandoahHeap::is_full_gc_move_in_progress() const { } inline bool ShenandoahHeap::is_stw_gc_in_progress() const { - return is_full_gc_in_progress() || is_degenerated_gc_in_progress(); + return is_full_gc_in_progress(); } inline bool ShenandoahHeap::is_concurrent_strong_root_in_progress() const { @@ -571,7 +512,7 @@ class ShenandoahObjectToOopClosure : public ObjectClosure { public: ShenandoahObjectToOopClosure(T* cl) : _cl(cl) {} - void do_object(oop obj) { + void do_object(oop obj) override { obj->oop_iterate(_cl); } }; @@ -584,11 +525,24 @@ class ShenandoahObjectToOopBoundedClosure : public ObjectClosure { ShenandoahObjectToOopBoundedClosure(T* cl, HeapWord* bottom, HeapWord* top) : _cl(cl), _bounds(bottom, top) {} - void do_object(oop obj) { + void do_object(oop obj) override { obj->oop_iterate(_cl, _bounds); } }; +template +class ShenandoahNonForwardedObjectToOopClosure : public ObjectClosure { + T* _cl; +public: + ShenandoahNonForwardedObjectToOopClosure(T* cl) : _cl(cl) {} + + void do_object(oop obj) override { + if (!ShenandoahForwarding::is_real_forwarded(obj)) { + obj->oop_iterate(_cl); + } + } +}; + template inline void ShenandoahHeap::marked_object_oop_iterate(ShenandoahHeapRegion* region, T* cl, HeapWord* top) { if (region->is_humongous()) { @@ -599,8 +553,15 @@ inline void ShenandoahHeap::marked_object_oop_iterate(ShenandoahHeapRegion* regi marked_object_iterate(region, &objs); } } else { - ShenandoahObjectToOopClosure objs(cl); - marked_object_iterate(region, &objs, top); + if (region->has_self_forwards()) { + assert(region->is_cset(), "Self forwards only allowed in cset region: %zu", region->index()); + ShenandoahNonForwardedObjectToOopClosure objs(cl); + marked_object_iterate(region, &objs, top); + } else { + assert(!region->is_cset(), "Should not be iterating objects in cset region: %zu", region->index()); + ShenandoahObjectToOopClosure objs(cl); + marked_object_iterate(region, &objs, top); + } } } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.cpp b/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.cpp index 289bf95ecf57..b86a152c19b6 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.cpp @@ -74,9 +74,7 @@ ShenandoahHeapRegion::ShenandoahHeapRegion(HeapWord* start, size_t index, bool c _mixed_candidate_garbage_words(0), _update_watermark(start), _age(0), - _needs_bitmap_reset(false) - { - + _needs_bitmap_reset(false) { assert(Universe::on_page_boundary(_bottom) && Universe::on_page_boundary(_end), "invalid space boundaries"); if (ZapUnusedHeapArea && committed) { @@ -110,6 +108,21 @@ void ShenandoahHeapRegion::make_regular_allocation(ShenandoahAffiliation affilia } } +void ShenandoahHeapRegion::make_regular_for_partial_recycling() { + shenandoah_assert_heaplocked_or_safepoint(); + assert(has_self_forwards(), "Only for regions holding self forwarded objects"); + switch (state()) { + case _cset: + set_state(_regular); + return; + case _pinned_cset: + set_state(_pinned); + return; + default: + report_illegal_transition("partially recycled"); + } +} + // Change affiliation to YOUNG_GENERATION if _state is not _pinned_cset, _regular, or _pinned. This implements // behavior previously performed as a side effect of make_regular_bypass(). This is used by Full GC in non-generational // modes to transition regions from FREE. Note that all non-free regions in single-generational modes are young. @@ -137,8 +150,7 @@ void ShenandoahHeapRegion::make_affiliated_maybe() { void ShenandoahHeapRegion::make_regular_bypass() { shenandoah_assert_heaplocked(); - assert (ShenandoahHeap::heap()->is_full_gc_in_progress() || - ShenandoahHeap::heap()->is_degenerated_gc_in_progress(), + assert (ShenandoahHeap::heap()->is_full_gc_in_progress(), "Only for STW GC"); reset_age(); switch (state()) { @@ -284,7 +296,8 @@ void ShenandoahHeapRegion::make_cset() { } void ShenandoahHeapRegion::make_trash() { - shenandoah_assert_heaplocked(); + shenandoah_assert_heaplocked_or_safepoint(); + assert(!has_self_forwards(), "Should not have evacuation failures"); reset_age(); switch (state()) { case _humongous_start: @@ -439,6 +452,10 @@ void ShenandoahHeapRegion::print_on(outputStream* st) const { st->print("|S %5zu%1s", byte_size_in_proper_unit(get_shared_allocs()), proper_unit_for_byte_size(get_shared_allocs())); st->print("|L %5zu%1s", byte_size_in_proper_unit(get_live_data_bytes()), proper_unit_for_byte_size(get_live_data_bytes())); st->print("|CP %3zu", pin_count()); + + if (has_self_forwards()) { + st->print("|EF"); + } st->cr(); #undef SHR_PTR_FORMAT @@ -485,10 +502,9 @@ bool ShenandoahHeapRegion::oop_coalesce_and_fill(bool cancellable, bool do_card_ if (do_card_table_updates) { heap->old_generation()->card_scan()->coalesce_objects(obj_addr, fill_size); } else { - // A humongous object allocation failure during evacuation will skip the degenerated cycle and - // jump straight to a full GC. If this region is pinned when the full GC cycle starts, it will - // not be compacted. Therefore, if the region is old, we must fill in any unmarked objects. However, - // promoted objects will not have been registered yet, so we cannot use the card table here. + // If this region is pinned when the full GC cycle starts, it will not be compacted. Therefore, if the region is + // old, we must fill in any unmarked objects. However, promoted objects will not have been registered yet, so we + // cannot use the card table here. assert(heap->is_full_gc_in_progress(), "Can only skip card table updates during a full GC"); } @@ -858,6 +874,97 @@ void ShenandoahHeapRegion::set_affiliation(ShenandoahAffiliation new_affiliation heap->set_affiliation(this, new_affiliation); } +// We would like to reclaim what we can from a partially evacuated region. We may have a mixture of objects that were +// self-forwarded and objects that were successfully evacuated to another region. The simplest thing we can do is slide +// top to the end of the last self forwarded object. This requires no changes on the allocation path. +// +// We must also leave old regions in a walkable state because they could be visited by a remembered set scan. +class ShenandoahReclaimSelfForwarded : public ObjectClosure { + + // This is a nullptr for young regions. For old regions, we use it to patch up the card table. + ShenandoahScanRemembered* _cards; + + // This is the _end_ of the last self-forwarded object the closure encountered. We use this + // to know where to start the filler object and where to set top when the iteration is complete. + HeapWord* _previous; + +public: + explicit ShenandoahReclaimSelfForwarded(ShenandoahScanRemembered* cards, ShenandoahHeapRegion* region) + : _cards(cards), _previous(region->bottom()) { + assert(!region->is_old() || cards != nullptr, "Must have card table reference for old region: %zu", region->index()); + } + + void do_object(oop obj) override { + shenandoah_assert_forwarded(nullptr, obj); + + if (ShenandoahForwarding::is_self_forwarded(obj)) { + HeapWord* current = cast_from_oop(obj); + const size_t object_size = ShenandoahForwarding::size(obj); + + // Clear the self forwarded bit, this is just a regular object in a regular region now. + obj->unset_self_forwarded(); + + if (_cards != nullptr) { + // We will have reset registrations and cards before iterating here, so we need to (conservatively) + // update the card table and object registrations. We don't need a lock because each region will only + // be visited by one thread. + _cards->register_object_without_lock(current); + _cards->mark_range_as_dirty(current, object_size); + + if (_previous != current) { + // We create a filler object to keep the old region parsable. We know there are no pointers in + // these filler objects, so we don't need to dirty cards, but we still need to register the object. + CollectedHeap::fill_with_object(_previous, pointer_delta(current, _previous)); + _cards->register_object_without_lock(_previous); + } + } + _previous = current + object_size; + } + } + + HeapWord* last_self_forwarded_object() const { + return _previous; + } +}; + +void ShenandoahHeapRegion::partially_recycle() { + assert(has_self_forwards(), "Region %zu must have self forwarded objects", index()); + + // This is only allowed for partially evacuated cset regions + make_regular_for_partial_recycling(); + + ShenandoahHeap* heap = ShenandoahHeap::heap(); + ShenandoahScanRemembered* cards = is_old() ? heap->old_generation()->card_scan() : nullptr; + if (cards != nullptr) { + // Some of the objects here may be dead or forwarded, we don't want any stale object registrations or dirty cards. + // We will rebuild the registrations and dirty cards for self-forwarded objects that stay in this region. + cards->mark_range_as_empty(this->bottom(), region_size_words()); + } + + // Clear self-forwarded objects and fill in the gaps between them + ShenandoahReclaimSelfForwarded reclaimer(cards, this); + heap->marked_object_iterate(this, &reclaimer); + + // Reset some of the region states that would be cleared if this region was completely recycled + const HeapWord* old_top = top(); + _top_at_evac_start = _bottom; + _mixed_candidate_garbage_words = 0; + clear_live_data(); + reset_alloc_metadata(); + heap->marking_context()->reset_top_at_mark_start(this); + set_update_watermark(bottom()); + clear_has_self_forwards(); + + // Adjust top to the end of our last encountered self-forwarded object. Everything above this is reusable memory. + set_top(reclaimer.last_self_forwarded_object()); + const size_t reclaimed_bytes = pointer_delta(old_top, top()) * HeapWordSize; + if (reclaimed_bytes > 0) { + // Region could be used for new allocations, and we don't want those to be promoted prematurely + log_debug(gc)("Reclaimed " PROPERFMT " from partially evacuated region (%zu)", PROPERFMTARGS(reclaimed_bytes), index()); + reset_age(); + } +} + void ShenandoahHeapRegion::decrement_humongous_waste() { assert(is_humongous(), "Should only use this for humongous regions"); size_t waste_bytes = free(); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.hpp b/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.hpp index 0a4c90575d09..b4b09f11597c 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.hpp @@ -175,6 +175,7 @@ class ShenandoahHeapRegion { // Allowed transitions from the outside code: void make_regular_allocation(ShenandoahAffiliation affiliation); + void make_regular_for_partial_recycling(); void make_affiliated_maybe(); void make_regular_bypass(); void make_humongous_start(); @@ -219,6 +220,7 @@ class ShenandoahHeapRegion { // Macro-properties: bool is_alloc_allowed() const { auto cur_state = state(); return is_empty_state(cur_state) || cur_state == _regular || cur_state == _pinned; } bool is_stw_move_allowed() const { auto cur_state = state(); return cur_state == _regular || cur_state == _cset || (ShenandoahHumongousMoves && cur_state == _humongous_start); } + bool is_update_required() const { return is_active() && (!is_cset() || has_self_forwards()); } RegionState state() const { return _state.load_acquire(); } int state_ordinal() const { return region_state_to_ordinal(state()); } @@ -274,8 +276,7 @@ class ShenandoahHeapRegion { ShenandoahSharedFlag _recycling; // Used to indicate that the region is being recycled; see try_recycle*(). // Set when an evacuation failure self-forwarded at least one object in this - // region. The drain at degen/full GC entry scans flagged regions and CAS- - // clears the self_fwd bits. Safety-net reset on region recycle. + // region. The flag is cleared when the region is partially recycled during final-update-refs. ShenandoahSharedFlag _has_self_forwards; // This is only read/written by a gc worker to avoid unnecessary bitmap resets @@ -519,6 +520,8 @@ class ShenandoahHeapRegion { _age = 0; } + void partially_recycle(); + inline bool need_bitmap_reset() const { return _needs_bitmap_reset; } @@ -533,7 +536,7 @@ class ShenandoahHeapRegion { // Self-forward accounting: set by an evacuating thread after it successfully // installs a self-forward mark on an object in this region. Tested and cleared - // at the drain phase (degen/full GC entry) and again on region recycle. + // when the region is partially recycled in final-update-refs. bool has_self_forwards() const { return _has_self_forwards.is_set(); } void set_has_self_forwards() { _has_self_forwards.try_set(); } void clear_has_self_forwards() { _has_self_forwards.unset(); } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeapRegionCounters.cpp b/src/hotspot/share/gc/shenandoah/shenandoahHeapRegionCounters.cpp index 3e809ea84b9b..2e627031963a 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahHeapRegionCounters.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeapRegionCounters.cpp @@ -1,7 +1,7 @@ /* * Copyright (c) 2016, 2020, Red Hat, Inc. All rights reserved. * Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -191,9 +191,10 @@ jlong ShenandoahHeapRegionCounters::encode_heap_status(ShenandoahHeap* heap) { generation->name(), phase, BOOL_TO_STR(heap->is_concurrent_old_mark_in_progress()), status); } - if (heap->is_degenerated_gc_in_progress()) { + if (heap->has_self_forwarded_objects()) { status |= (1 << 6); } + if (heap->is_full_gc_in_progress()) { status |= (1 << 7); } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeapRegionCounters.hpp b/src/hotspot/share/gc/shenandoah/shenandoahHeapRegionCounters.hpp index d50188bf70cc..b8fe55e18c58 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahHeapRegionCounters.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeapRegionCounters.hpp @@ -47,7 +47,7 @@ * 0 = idle, 1 = marking, 2 = evacuating, 3 = updating refs * * For mode: - * 0 = concurrent, 1 = degenerated, 2 = full + * 0 = concurrent, 1 = evacuation failures, 2 = full * * two variable counters per region, with $max_regions (see above) counters: * - sun.gc.shenandoah.regions.region.$i.data diff --git a/src/hotspot/share/gc/shenandoah/shenandoahInPlacePromoter.cpp b/src/hotspot/share/gc/shenandoah/shenandoahInPlacePromoter.cpp index efea98b61f07..ab7b530628ac 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahInPlacePromoter.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahInPlacePromoter.cpp @@ -144,9 +144,6 @@ void ShenandoahInPlacePromotionPlanner::complete_planning() const { void ShenandoahInPlacePromoter::maybe_promote_region(ShenandoahHeapRegion* r) const { if (r->is_regular_or_regular_pinned() && (r->get_top_before_promote() != nullptr)) { // This region was scheduled for promotion. The promotion must be completed. - // The 'always_tenure' override flag set by WB.fullGC() is not carried over - // into the degenerated cycle so we cannot rely on is_tenurable again. We checked - // it when we made the plan for this region, that plan is authoritative. assert(r->is_young() && r->is_active(), "Region scheduled for promotion must still be young and active"); promote(r); return; diff --git a/src/hotspot/share/gc/shenandoah/shenandoahMark.cpp b/src/hotspot/share/gc/shenandoah/shenandoahMark.cpp index 9c6ede961d4e..80ed946a4811 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahMark.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahMark.cpp @@ -56,7 +56,7 @@ ShenandoahMark::ShenandoahMark(ShenandoahGeneration* generation) : } template -void ShenandoahMark::mark_loop_prework(uint w, TaskTerminator *t, StringDedup::Requests* const req, bool update_refs) { +void ShenandoahMark::mark_loop_prework(uint w, TaskTerminator *t, StringDedup::Requests* const req) { ShenandoahObjToScanQueueSet* queues = task_queues(); ShenandoahObjToScanQueue* q = get_queue(w); ShenandoahObjToScanQueue* old_q = get_old_queue(w); @@ -66,22 +66,12 @@ void ShenandoahMark::mark_loop_prework(uint w, TaskTerminator *t, StringDedup::R // TODO: We can clean up this if we figure out how to do templated oop closures that // play nice with specialized_oop_iterators. - if (update_refs) { - using Closure = ShenandoahMarkUpdateRefsClosure; - Closure cl(q, rp, old_q); - if (UseCompressedOops) { - mark_loop_work(&cl, ld, w, t, req); - } else { - mark_loop_work(&cl, ld, w, t, req); - } + using Closure = ShenandoahMarkRefsClosure; + Closure cl(q, rp, old_q); + if (UseCompressedOops) { + mark_loop_work(&cl, ld, w, t, req); } else { - using Closure = ShenandoahMarkRefsClosure; - Closure cl(q, rp, old_q); - if (UseCompressedOops) { - mark_loop_work(&cl, ld, w, t, req); - } else { - mark_loop_work(&cl, ld, w, t, req); - } + mark_loop_work(&cl, ld, w, t, req); } heap->flush_liveness_cache(w); @@ -90,24 +80,21 @@ void ShenandoahMark::mark_loop_prework(uint w, TaskTerminator *t, StringDedup::R template void ShenandoahMark::mark_loop(uint worker_id, TaskTerminator* terminator, ShenandoahGenerationType generation_type, StringDedup::Requests* const req) { - bool update_refs = ShenandoahHeap::heap()->has_forwarded_objects(); switch (generation_type) { case YOUNG: - mark_loop_prework(worker_id, terminator, req, update_refs); + mark_loop_prework(worker_id, terminator, req); break; case OLD: - // Old generation collection only performs marking, it should not update references. - mark_loop_prework(worker_id, terminator, req, false); + mark_loop_prework(worker_id, terminator, req); break; case GLOBAL: - mark_loop_prework(worker_id, terminator, req, update_refs); + mark_loop_prework(worker_id, terminator, req); break; case NON_GEN: - mark_loop_prework(worker_id, terminator, req, update_refs); + mark_loop_prework(worker_id, terminator, req); break; default: ShouldNotReachHere(); - break; } } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahMark.hpp b/src/hotspot/share/gc/shenandoah/shenandoahMark.hpp index cb003bfbaab2..b2d860e144de 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahMark.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahMark.hpp @@ -103,7 +103,7 @@ class ShenandoahMark: public StackObj { static void dedup_string(oop obj, StringDedup::Requests* const req); template - void mark_loop_prework(uint worker_id, TaskTerminator *terminator, StringDedup::Requests* const req, bool update_refs); + void mark_loop_prework(uint worker_id, TaskTerminator *terminator, StringDedup::Requests* const req); template NOINLINE // Main hot loop, start inlining from here diff --git a/src/hotspot/share/gc/shenandoah/shenandoahMark.inline.hpp b/src/hotspot/share/gc/shenandoah/shenandoahMark.inline.hpp index 3df384dcb243..668b951abd1a 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahMark.inline.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahMark.inline.hpp @@ -54,7 +54,7 @@ void ShenandoahMark::do_task(ShenandoahObjToScanQueue* q, T* cl, ShenandoahLiveD shenandoah_assert_not_forwarded(nullptr, obj); shenandoah_assert_marked(nullptr, obj); - shenandoah_assert_not_in_cset_except(nullptr, obj, ShenandoahHeap::heap()->cancelled_gc()); + shenandoah_assert_not_in_cset_except(nullptr, obj, ShenandoahHeap::heap()->cancelled_gc() || ShenandoahHeap::heap()->has_self_forwarded_objects()); Klass* klass = obj->klass(); @@ -392,7 +392,7 @@ void ShenandoahMark::mark_non_generational_ref(T* p, ShenandoahObjToScanQueue* q oop obj = CompressedOops::decode_not_null(o); shenandoah_assert_not_forwarded(p, obj); - shenandoah_assert_not_in_cset_except(p, obj, ShenandoahHeap::heap()->cancelled_gc()); + shenandoah_assert_not_in_cset_except(p, obj, ShenandoahHeap::heap()->cancelled_gc() || ShenandoahHeap::heap()->has_self_forwarded_objects()); mark_ref(q, mark_context, weak, obj); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahMmuTracker.cpp b/src/hotspot/share/gc/shenandoah/shenandoahMmuTracker.cpp index 0b38f6e0ceaf..a8a3772314b1 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahMmuTracker.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahMmuTracker.cpp @@ -1,6 +1,6 @@ /* * Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -86,7 +86,6 @@ void ShenandoahMmuTracker::fetch_cpu_times(double &gc_time, double &mutator_time void ShenandoahMmuTracker::update_utilization(size_t gcid, const char* msg) { double current = os::elapsedTime(); _most_recent_gcid = gcid; - _most_recent_is_full = false; if (gcid == 0) { fetch_cpu_times(_most_recent_gc_time, _most_recent_mutator_time); @@ -140,17 +139,8 @@ void ShenandoahMmuTracker::record_mixed(size_t gcid) { update_utilization(gcid, "Mixed Concurrent GC"); } -void ShenandoahMmuTracker::record_degenerated(size_t gcid, const char* msg) { - if ((gcid == _most_recent_gcid) && _most_recent_is_full) { - // Do nothing. This is a redundant recording for the full gc that just completed. - } else { - update_utilization(gcid, msg); - } -} - void ShenandoahMmuTracker::record_full(size_t gcid) { update_utilization(gcid, "Full GC"); - _most_recent_is_full = true; } void ShenandoahMmuTracker::report() { diff --git a/src/hotspot/share/gc/shenandoah/shenandoahMmuTracker.hpp b/src/hotspot/share/gc/shenandoah/shenandoahMmuTracker.hpp index cb2db12c233d..6d1ab0988c67 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahMmuTracker.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahMmuTracker.hpp @@ -67,8 +67,6 @@ class ShenandoahMmuTracker { size_t _most_recent_gcid; uint _active_processors; - bool _most_recent_is_full; - ShenandoahMmuTask* _mmu_periodic_task; TruncatedSeq _mmu_average; @@ -85,20 +83,13 @@ class ShenandoahMmuTracker { void initialize(); // At completion of each GC cycle (not including interrupted cycles), we invoke one of the following to record the - // GC utilization during this cycle. Incremental efforts spent in an interrupted GC cycle will be accumulated into - // the CPU time reports for the subsequent completed [degenerated or full] GC cycle. - // - // We may redundantly record degen and full in the case that a degen upgrades to full. When this happens, we will invoke - // both record_full() and record_degenerated() with the same value of gcid. record_full() is called first and the log - // reports such a cycle as a FULL cycle. + // GC utilization during this cycle. void record_young(size_t gcid); void record_global(size_t gcid); void record_bootstrap(size_t gcid); void record_old_marking_increment(bool old_marking_done); void record_mixed(size_t gcid); void record_full(size_t gcid); - // Records GC utilization for a degenerated cycle, where "msg" describes the degeneration type. - void record_degenerated(size_t gcid, const char* msg); // This is called by the periodic task timer. The interval is defined by // GCPauseIntervalMillis and defaults to 5 seconds. This method computes diff --git a/src/hotspot/share/gc/shenandoah/shenandoahNMethod.cpp b/src/hotspot/share/gc/shenandoah/shenandoahNMethod.cpp index b375b0c5d391..851cec261b2c 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahNMethod.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahNMethod.cpp @@ -112,8 +112,8 @@ void ShenandoahNMethod::parse(nmethod* nm, GrowableArray& oops, bool& has_ if (value != nullptr) { oop* addr = r->oop_addr(); shenandoah_assert_correct(addr, value); - shenandoah_assert_not_in_cset_except(addr, value, ShenandoahHeap::heap()->cancelled_gc()); - shenandoah_assert_not_forwarded_except(addr, value, ShenandoahHeap::heap()->cancelled_gc()); + shenandoah_assert_not_in_cset_except(addr, value, ShenandoahHeap::heap()->cancelled_gc() || ShenandoahHeap::heap()->has_self_forwarded_objects()); + shenandoah_assert_not_forwarded_except(addr, value, ShenandoahHeap::heap()->cancelled_gc() || ShenandoahHeap::heap()->has_self_forwarded_objects()); // Non-null immediate oop found. null oops can safely be // ignored since the method will be re-registered if they // are later patched to be non-null. diff --git a/src/hotspot/share/gc/shenandoah/shenandoahOldGC.cpp b/src/hotspot/share/gc/shenandoah/shenandoahOldGC.cpp index 2b813560d6b6..890fff1ca9c7 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahOldGC.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahOldGC.cpp @@ -28,17 +28,19 @@ #include "gc/shenandoah/shenandoahFreeSet.hpp" #include "gc/shenandoah/shenandoahGeneration.hpp" #include "gc/shenandoah/shenandoahGenerationalHeap.hpp" -#include "gc/shenandoah/shenandoahHeap.inline.hpp" #include "gc/shenandoah/shenandoahMonitoringSupport.hpp" #include "gc/shenandoah/shenandoahOldGC.hpp" #include "gc/shenandoah/shenandoahOldGeneration.hpp" +#include "gc/shenandoah/shenandoahVerifier.hpp" #include "gc/shenandoah/shenandoahYoungGeneration.hpp" #include "prims/jvmtiTagMap.hpp" #include "utilities/events.hpp" -ShenandoahOldGC::ShenandoahOldGC(ShenandoahOldGeneration* generation, ShenandoahSharedFlag& allow_preemption) : - ShenandoahConcurrentGC(generation, false), _old_generation(generation), _allow_preemption(allow_preemption) { +ShenandoahOldGC::ShenandoahOldGC(ShenandoahOldGeneration* generation, ShenandoahSharedFlag& allow_preemption) + : ShenandoahConcurrentGC(nullptr, generation, false) + , _old_generation(generation) + , _allow_preemption(allow_preemption) { } // Final mark for old-gen is different for young than old, so we @@ -65,7 +67,7 @@ void ShenandoahOldGC::op_final_mark() { // We need to do this because weak root cleaning reports the number of dead handles JvmtiTagMap::set_needs_cleaning(); - _generation->prepare_regions_and_collection_set(true); + _generation->prepare_regions_and_collection_set(); heap->set_unload_classes(false); heap->prepare_concurrent_roots(); @@ -84,6 +86,7 @@ void ShenandoahOldGC::op_final_mark() { } } +// Intentionally does not update controller phase. Allocation stalls are always attributed to state of young. bool ShenandoahOldGC::collect(GCCause::Cause cause) { auto heap = ShenandoahGenerationalHeap::heap(); assert(!_old_generation->is_doing_mixed_evacuations(), "Should not start an old gc with pending mixed evacuations"); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahOldGC.hpp b/src/hotspot/share/gc/shenandoah/shenandoahOldGC.hpp index 2c637f5f72af..e64a422ef6e8 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahOldGC.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahOldGC.hpp @@ -27,8 +27,9 @@ #include "gc/shared/gcCause.hpp" #include "gc/shenandoah/shenandoahConcurrentGC.hpp" -#include "gc/shenandoah/shenandoahVerifier.hpp" +#include "gc/shenandoah/shenandoahSharedVariables.hpp" +class ShenandoahController; class ShenandoahOldGeneration; class ShenandoahOldGC : public ShenandoahConcurrentGC { diff --git a/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.cpp b/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.cpp index be09113c3b5e..f7686faca9b3 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.cpp @@ -45,20 +45,6 @@ #include "runtime/threads.hpp" #include "utilities/events.hpp" -class ShenandoahPurgeSATBTask : public WorkerTask { -public: - explicit ShenandoahPurgeSATBTask() : WorkerTask("Purge SATB") { - Threads::change_thread_claim_token(); - } - - void work(uint worker_id) override { - ShenandoahParallelWorkerSession worker_session(worker_id); - ShenandoahSATBMarkQueueSet &satb_queues = ShenandoahBarrierSet::satb_mark_queue_set(); - ShenandoahFlushSATB flusher(satb_queues); - Threads::possibly_parallel_threads_do(true /* is_par */, &flusher); - } -}; - class ShenandoahConcurrentCoalesceAndFillTask : public WorkerTask { private: uint _nworkers; @@ -432,26 +418,16 @@ bool ShenandoahOldGeneration::coalesce_and_fill() { } } -void ShenandoahOldGeneration::transfer_pointers_from_satb() const { - const ShenandoahHeap* heap = ShenandoahHeap::heap(); - assert(heap->is_concurrent_old_mark_in_progress(), "Only necessary during old marking."); - log_debug(gc)("Transfer SATB buffers"); - ShenandoahPurgeSATBTask purge_satb_task; - heap->workers()->run_task(&purge_satb_task); -} - bool ShenandoahOldGeneration::contains(oop obj) const { return ShenandoahHeap::heap()->is_in_old(obj); } -void ShenandoahOldGeneration::prepare_regions_and_collection_set(bool concurrent) { +void ShenandoahOldGeneration::prepare_regions_and_collection_set() { ShenandoahHeap* heap = ShenandoahHeap::heap(); - assert(!heap->is_full_gc_in_progress(), "Only for concurrent and degenerated GC"); + assert(!heap->is_full_gc_in_progress(), "Only for concurrent GC"); { - ShenandoahGCPhase phase(concurrent ? - ShenandoahPhaseTimings::final_update_region_states : - ShenandoahPhaseTimings::degen_gc_final_update_region_states); + ShenandoahGCPhase phase(ShenandoahPhaseTimings::final_update_region_states); ShenandoahFinalMarkUpdateRegionStateClosure cl(complete_marking_context(), this); parallel_heap_region_iterate(&cl); @@ -461,9 +437,7 @@ void ShenandoahOldGeneration::prepare_regions_and_collection_set(bool concurrent { // This doesn't actually choose a collection set, but prepares a list of // regions as 'candidates' for inclusion in a mixed collection. - ShenandoahGCPhase phase(concurrent ? - ShenandoahPhaseTimings::choose_cset : - ShenandoahPhaseTimings::degen_gc_choose_cset); + ShenandoahGCPhase phase(ShenandoahPhaseTimings::choose_cset); ShenandoahHeapLocker locker(heap->lock()); _old_heuristics->prepare_for_old_collections(); } @@ -471,16 +445,14 @@ void ShenandoahOldGeneration::prepare_regions_and_collection_set(bool concurrent { // Though we did not choose a collection set above, we still may have // freed up immediate garbage regions so proceed with rebuilding the free set. - ShenandoahGCPhase phase(concurrent ? - ShenandoahPhaseTimings::final_rebuild_freeset : - ShenandoahPhaseTimings::degen_gc_final_rebuild_freeset); + ShenandoahGCPhase phase(ShenandoahPhaseTimings::final_rebuild_freeset); ShenandoahFreeSet* free_set = heap->free_set(); ShenandoahHeapLocker locker(heap->lock()); // This is completion of old-gen marking. We rebuild in order to reclaim immediate garbage and to // prepare for subsequent mixed evacuations. size_t young_trash_regions, old_trash_regions, first_old, last_old, num_old; - heap->free_set()->prepare_to_rebuild(young_trash_regions, old_trash_regions, first_old, last_old, num_old); + free_set->prepare_to_rebuild(young_trash_regions, old_trash_regions, first_old, last_old, num_old); // At the end of old-gen, we may find that we have reclaimed immediate garbage, allowing a longer allocation runway. // We may also find that we have accumulated canddiate regions for mixed evacuation. If so, we will want to expand // the OldCollector reserve in order to make room for these mixed evacuations. @@ -494,7 +466,7 @@ void ShenandoahOldGeneration::prepare_regions_and_collection_set(bool concurrent (gen_heap->young_generation()->free_unaffiliated_regions() + young_trash_regions) * ShenandoahHeapRegion::region_size_bytes()); gen_heap->compute_old_generation_balance(max_transfer, old_trash_regions, young_trash_regions); - heap->free_set()->finish_rebuild(young_trash_regions, old_trash_regions, num_old); + free_set->finish_rebuild(young_trash_regions, old_trash_regions, num_old); } } @@ -623,11 +595,6 @@ ShenandoahHeuristics* ShenandoahOldGeneration::initialize_heuristics(ShenandoahM return _heuristics; } -void ShenandoahOldGeneration::record_success_concurrent(bool abbreviated) { - heuristics()->record_success_concurrent(); - ShenandoahHeap::heap()->shenandoah_policy()->record_success_old(); -} - void ShenandoahOldGeneration::handle_failed_evacuation() { if (_failed_evacuation.try_set()) { log_debug(gc)("Old gen evac failure."); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.hpp b/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.hpp index 8f41b77fdc2f..47165b2278cd 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.hpp @@ -51,7 +51,7 @@ class ShenandoahOldGeneration : public ShenandoahGeneration { ssize_t _region_balance; // Set when evacuation in the old generation fails. When this is set, the control thread will initiate a - // full GC instead of a futile degenerated cycle. + // full GC instead of a futile concurrent cycle. ShenandoahSharedFlag _failed_evacuation; // Bytes reserved within old-gen to hold the results of promotion. This is separate from @@ -161,7 +161,7 @@ class ShenandoahOldGeneration : public ShenandoahGeneration { // This will signal the heuristic to trigger an old generation collection void handle_failed_transfer(); - // This will signal the control thread to run a full GC instead of a futile degenerated gc + // This will signal the control thread to run a full GC void handle_failed_evacuation(); // Increment promotion failure counters, optionally log a more detailed message @@ -245,35 +245,12 @@ class ShenandoahOldGeneration : public ShenandoahGeneration { void transition_old_generation_after_global_gc(); void prepare_gc() override; - void prepare_regions_and_collection_set(bool concurrent) override; - void record_success_concurrent(bool abbreviated) override; + void prepare_regions_and_collection_set() override; void cancel_marking() override; // Cancels old gc and transitions to the idle state void cancel_gc(); - // The SATB barrier will be "enabled" until old marking completes. This means it is - // possible for an entire young collection cycle to execute while the SATB barrier is enabled. - // Consider a situation like this, where we have a pointer 'B' at an object 'A' which is in - // the young collection set: - // - // +--Young, CSet------+ +--Young, Regular----+ - // | | | | - // | | | | - // | A <--------------------+ B | - // | | | | - // | | | | - // +-------------------+ +--------------------+ - // - // If a mutator thread overwrites pointer B, the SATB barrier will dutifully enqueue - // object A. However, this object will be trashed when the young cycle completes. We must, - // therefore, filter this object from the SATB buffer before any old mark threads see it. - // We do this with a handshake before final-update-refs (see shenandoahConcurrentGC.cpp). - // - // This method is here only for degenerated cycles. A concurrent cycle may be cancelled before - // we have a chance to execute the handshake to flush the SATB in final-update-refs. - void transfer_pointers_from_satb() const; - // True if there are old regions waiting to be selected for a mixed collection bool has_unprocessed_collection_candidates(); @@ -295,7 +272,6 @@ class ShenandoahOldGeneration : public ShenandoahGeneration { // Abandon any regions waiting for mixed collections void abandon_collection_candidates(); -public: enum State { FILLING, IDLE, MARKING, EVACUATING, EVACUATING_AFTER_GLOBAL }; diff --git a/src/hotspot/share/gc/shenandoah/shenandoahPLAB.cpp b/src/hotspot/share/gc/shenandoah/shenandoahPLAB.cpp index 59ed97dd5131..d73c232ae867 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahPLAB.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahPLAB.cpp @@ -188,11 +188,11 @@ void ShenandoahPLAB::retire() { _heap->old_generation()->unexpend_promoted(not_promoted); } - // plab->retire() overwrites unused memory between plab->top() and plab->hard_end() with a dummy object to make memory parsable. - // We do _not_ need to register this remnant object with the card table because all paths where a PLAB object would - // be created are covered by a subsequent phase in the cycle. For the concurrent and degenerated cycles, all PLABs - // are retired in preparation for update-references. All objects in these PLABs will be registered by update-card-tables. - // For a full GC, the entire remembered set will be rebuilt in the final phase. Note also that an empty TLAB will _not_ - // create a filler object when it is retired. + // plab->retire() overwrites unused memory between plab->top() and plab->hard_end() with a dummy object to make memory + // parsable. We do _not_ need to register this remnant object with the card table because all paths where a PLAB + // object would be created are covered by a subsequent phase in the cycle. All PLABs are retired in preparation for + // update-references. For concurrent cycles, all objects in these PLABs will be registered by update-card-tables. + // For a full GC, the entire remembered set will be rebuilt in the final phase. Note also that an empty TLAB will + // _not_ create a filler object when it is retired. _plab->retire(); } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahPhaseTimings.cpp b/src/hotspot/share/gc/shenandoah/shenandoahPhaseTimings.cpp index ca418ab45d4a..0879ae922b29 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahPhaseTimings.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahPhaseTimings.cpp @@ -96,7 +96,6 @@ ShenandoahWorkerData* ShenandoahPhaseTimings::worker_data(Phase phase, WorkerPha bool ShenandoahPhaseTimings::is_root_work_phase(Phase phase) { switch (phase) { case finish_mark: - case degen_gc_update_roots: case full_gc_mark: case full_gc_update_roots: case full_gc_adjust_roots: diff --git a/src/hotspot/share/gc/shenandoah/shenandoahPhaseTimings.hpp b/src/hotspot/share/gc/shenandoah/shenandoahPhaseTimings.hpp index 68c568dcc1dd..a525af801bba 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahPhaseTimings.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahPhaseTimings.hpp @@ -110,6 +110,8 @@ class outputStream; SHENANDOAH_SIMPLE_PHASE_DEF(f, final_roots_propagate_gc_state, " Propagate GC State") \ SHENANDOAH_WORKER_PHASE_DEF(f, conc_evac, "Concurrent Evacuation", \ " CE: ") \ + SHENANDOAH_WORKER_PHASE_DEF(f, conc_self_forward_stranded, "Self Forward Stranded Objects", \ + " SF: ") \ SHENANDOAH_SIMPLE_PHASE_DEF(f, conc_update_card_table, "Concurrent Update Cards") \ SHENANDOAH_SIMPLE_PHASE_DEF(f, complete_abbreviated, "Complete Abbreviated Cycle") \ SHENANDOAH_WORKER_PHASE_DEF(f, complete_abbreviated_promote_in_place, " Promote Regions", \ @@ -136,42 +138,8 @@ class outputStream; SHENANDOAH_WORKER_PHASE_DEF(f, conc_coalesce_and_fill, "Concurrent Coalesce and Fill", \ " CC&F: ") \ \ - SHENANDOAH_SIMPLE_PHASE_DEF(f, degen_gc_gross, "Pause Degenerated GC (G)") \ - SHENANDOAH_SIMPLE_PHASE_DEF(f, degen_gc, "Pause Degenerated GC (N)") \ - SHENANDOAH_SIMPLE_PHASE_DEF(f, degen_gc_un_self_forward, " Un-Self-Forward") \ - SHENANDOAH_WORKER_PHASE_DEF(f, degen_gc_mark, " Mark", \ - " DM: ") \ - SHENANDOAH_SIMPLE_PHASE_DEF(f, degen_gc_purge, " System Purge") \ - SHENANDOAH_WORKER_PHASE_DEF(f, degen_gc_weakrefs, " Weak References", \ - " WRP: ") \ - SHENANDOAH_WORKER_PHASE_DEF(f, degen_gc_purge_class_unload, " Unload Classes", \ - " DCU: ") \ - SHENANDOAH_WORKER_PHASE_DEF(f, degen_gc_purge_weak_par, " Weak Roots", \ - " DWR: ") \ - SHENANDOAH_SIMPLE_PHASE_DEF(f, degen_gc_purge_cldg, " CLDG") \ - SHENANDOAH_SIMPLE_PHASE_DEF(f, degen_gc_final_update_region_states, " Update Region States") \ - SHENANDOAH_SIMPLE_PHASE_DEF(f, degen_gc_final_manage_labs, " Manage GC/TLABs") \ - SHENANDOAH_SIMPLE_PHASE_DEF(f, degen_gc_choose_cset, " Choose Collection Set") \ - SHENANDOAH_SIMPLE_PHASE_DEF(f, degen_gc_final_rebuild_freeset, " Rebuild Free Set") \ - SHENANDOAH_WORKER_PHASE_DEF(f, degen_gc_evac, " Evacuation", \ - " DE: ") \ - SHENANDOAH_SIMPLE_PHASE_DEF(f, degen_gc_init_update_refs_manage_gclabs, " Manage GCLABs") \ - SHENANDOAH_WORKER_PHASE_DEF(f, degen_gc_update_refs, " Update References", \ - " DUR: ") \ - SHENANDOAH_SIMPLE_PHASE_DEF(f, degen_gc_final_update_refs_update_region_states, " Update Region States") \ - SHENANDOAH_SIMPLE_PHASE_DEF(f, degen_gc_final_update_refs_trash_cset, " Trash Collection Set") \ - SHENANDOAH_SIMPLE_PHASE_DEF(f, degen_gc_final_update_refs_rebuild_freeset, " Rebuild Free Set") \ - SHENANDOAH_WORKER_PHASE_DEF(f, degen_gc_update_roots, " Degen Update Roots", \ - " DU: ") \ - SHENANDOAH_SIMPLE_PHASE_DEF(f, degen_gc_cleanup_complete, " Cleanup") \ - SHENANDOAH_SIMPLE_PHASE_DEF(f, degen_gc_promote_regions, " Degen Promote Regions") \ - SHENANDOAH_WORKER_PHASE_DEF(f, degen_gc_coalesce_and_fill, " Degen Coalesce and Fill", \ - " DC&F") \ - SHENANDOAH_SIMPLE_PHASE_DEF(f, degen_gc_propagate_gc_state, " Propagate GC State") \ - \ SHENANDOAH_SIMPLE_PHASE_DEF(f, full_gc_gross, "Pause Full GC (G)") \ SHENANDOAH_SIMPLE_PHASE_DEF(f, full_gc, "Pause Full GC (N)") \ - SHENANDOAH_SIMPLE_PHASE_DEF(f, full_gc_un_self_forward, " Un-Self-Forward") \ SHENANDOAH_SIMPLE_PHASE_DEF(f, full_gc_heapdump_pre, " Pre Heap Dump") \ SHENANDOAH_SIMPLE_PHASE_DEF(f, full_gc_prepare, " Prepare") \ SHENANDOAH_WORKER_PHASE_DEF(f, full_gc_update_roots, " Update Roots", \ diff --git a/src/hotspot/share/gc/shenandoah/shenandoahReferenceProcessor.cpp b/src/hotspot/share/gc/shenandoah/shenandoahReferenceProcessor.cpp index 835d141df950..30636b3d448e 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahReferenceProcessor.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahReferenceProcessor.cpp @@ -504,7 +504,7 @@ void ShenandoahReferenceProcessor::process_references(ShenandoahRefProcThreadLoc // set_oop_field maintains the card mark barrier as this list is constructed. if (!CompressedOops::is_null(*list)) { oop head = lrb(CompressedOops::decode_not_null(*list)); - shenandoah_assert_not_in_cset_except(&head, head, ShenandoahHeap::heap()->cancelled_gc() || !ShenandoahLoadRefBarrier); + shenandoah_assert_not_in_cset_except(&head, head, ShenandoahHeap::heap()->has_self_forwarded_objects() || ShenandoahHeap::heap()->cancelled_gc() || !ShenandoahLoadRefBarrier); oop prev = _pending_list.exchange(head); set_oop_field(p, prev); if (prev == nullptr) { @@ -577,7 +577,7 @@ void ShenandoahReferenceProcessor::process_references(ShenandoahPhaseTimings::Ph void ShenandoahReferenceProcessor::enqueue_references_locked() { // Prepend internal pending list to external pending list - shenandoah_assert_not_in_cset_except(&_pending_list, _pending_list.load_relaxed(), ShenandoahHeap::heap()->cancelled_gc() || !ShenandoahLoadRefBarrier); + shenandoah_assert_not_in_cset_except(&_pending_list, _pending_list.load_relaxed(), ShenandoahHeap::heap()->has_self_forwarded_objects() || ShenandoahHeap::heap()->cancelled_gc() || !ShenandoahLoadRefBarrier); // During reference processing, we maintain a local list of references that are identified by // _pending_list and _pending_list_tail. _pending_list_tail points to the next field of the last Reference object on @@ -604,7 +604,7 @@ void ShenandoahReferenceProcessor::enqueue_references(bool concurrent) { return; } if (!concurrent) { - // When called from mark-compact or degen-GC, the locking is done by the VMOperation, + // When called from STW mark, the locking is done by the VMOperation, enqueue_references_locked(); } else { // Heap_lock protects external pending list diff --git a/src/hotspot/share/gc/shenandoah/shenandoahRegulatorThread.cpp b/src/hotspot/share/gc/shenandoah/shenandoahRegulatorThread.cpp index 8823e7e4af74..85c2b6b07979 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahRegulatorThread.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahRegulatorThread.cpp @@ -66,27 +66,21 @@ void ShenandoahRegulatorThread::regulate_young_and_old_cycles() { if (request_concurrent_gc(_heap->global_generation())) { // Some of vmTestbase/metaspace tests depend on following line to count GC cycles _global_heuristics->log_trigger("%s", GCCause::to_string(GCCause::_metadata_GC_threshold)); - _global_heuristics->cancel_trigger_request(); } } else { if (_old_heuristics->should_resume_old_cycle()) { if (request_concurrent_gc(_heap->old_generation())) { - _old_heuristics->cancel_trigger_request(); - log_debug(gc)("Heuristics request to resume old collection accepted"); + log_debug(gc, thread)("Heuristics request to resume old collection accepted"); } } else if (start_old_cycle()) { - log_debug(gc)("Heuristics request for old collection accepted"); - _young_heuristics->cancel_trigger_request(); - _old_heuristics->cancel_trigger_request(); + log_debug(gc, thread)("Heuristics request for old collection accepted"); } else if (start_young_cycle()) { - log_debug(gc)("Heuristics request for young collection accepted"); - _young_heuristics->cancel_trigger_request(); + log_debug(gc, thread)("Heuristics request for young collection accepted"); } } } else if (mode == ShenandoahGenerationalControlThread::servicing_old) { if (start_young_cycle()) { - log_debug(gc)("Heuristics request to interrupt old for young collection accepted"); - _young_heuristics->cancel_trigger_request(); + log_debug(gc, thread)("Heuristics request to interrupt old for young collection accepted"); } } @@ -99,12 +93,13 @@ void ShenandoahRegulatorThread::regulate_young_and_global_cycles() { while (!should_terminate()) { SuspendibleThreadSetJoiner joiner; if (_control_thread->gc_mode() == ShenandoahGenerationalControlThread::none) { - if (start_global_cycle()) { - log_debug(gc)("Heuristics request for global collection accepted."); - _global_heuristics->cancel_trigger_request(); + if (should_start_metaspace_gc() && request_concurrent_gc(_heap->global_generation())) { + // Some of vmTestbase/metaspace tests depend on following line to count GC cycles + _global_heuristics->log_trigger("%s", GCCause::to_string(GCCause::_metadata_GC_threshold)); + } else if (_old_heuristics->should_start_gc() && request_concurrent_gc(_heap->global_generation())) { + log_debug(gc, thread)("Heuristics request for global collection accepted."); } else if (start_young_cycle()) { - log_debug(gc)("Heuristics request for young collection accepted."); - _young_heuristics->cancel_trigger_request(); + log_debug(gc, thread)("Heuristics request for young collection accepted."); } } @@ -145,10 +140,6 @@ bool ShenandoahRegulatorThread::start_young_cycle() const { return _young_heuristics->should_start_gc() && request_concurrent_gc(_heap->young_generation()); } -bool ShenandoahRegulatorThread::start_global_cycle() const { - return _global_heuristics->should_start_gc() && request_concurrent_gc(_heap->global_generation()); -} - bool ShenandoahRegulatorThread::request_concurrent_gc(ShenandoahGeneration* generation) const { double now = os::elapsedTime(); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahRegulatorThread.hpp b/src/hotspot/share/gc/shenandoah/shenandoahRegulatorThread.hpp index 62a5dcce9df6..9111d6f42383 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahRegulatorThread.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahRegulatorThread.hpp @@ -61,7 +61,6 @@ class ShenandoahRegulatorThread: public ConcurrentGCThread { // These return true if a cycle was started. bool start_old_cycle() const; bool start_young_cycle() const; - bool start_global_cycle() const; // The generational mode can only unload classes in a global cycle. The regulator // thread itself will trigger a global cycle if metaspace is out of memory. diff --git a/src/hotspot/share/gc/shenandoah/shenandoahRootProcessor.cpp b/src/hotspot/share/gc/shenandoah/shenandoahRootProcessor.cpp index 1b7d0b75304e..b1cfa32b4051 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahRootProcessor.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahRootProcessor.cpp @@ -178,15 +178,6 @@ void ShenandoahConcurrentRootScanner::update_tlab_stats() { } } -ShenandoahRootUpdater::ShenandoahRootUpdater(uint n_workers, ShenandoahPhaseTimings::Phase phase) : - ShenandoahRootProcessor(phase), - _vm_roots(phase), - _cld_roots(phase, n_workers, false /*heap iteration*/), - _thread_roots(phase, n_workers > 1), - _weak_roots(phase), - _code_roots(phase) { -} - ShenandoahRootAdjuster::ShenandoahRootAdjuster(uint n_workers, ShenandoahPhaseTimings::Phase phase) : ShenandoahRootProcessor(phase), _vm_roots(phase), diff --git a/src/hotspot/share/gc/shenandoah/shenandoahRootProcessor.hpp b/src/hotspot/share/gc/shenandoah/shenandoahRootProcessor.hpp index 55367e706a2e..f6c1fb13819f 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahRootProcessor.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahRootProcessor.hpp @@ -195,22 +195,6 @@ class ShenandoahHeapIterationRootScanner : public ShenandoahRootProcessor { void roots_do(OopClosure* cl); }; -// Update all roots at a safepoint -class ShenandoahRootUpdater : public ShenandoahRootProcessor { -private: - ShenandoahVMRoots _vm_roots; - ShenandoahClassLoaderDataRoots _cld_roots; - ShenandoahThreadRoots _thread_roots; - ShenandoahVMWeakRoots _weak_roots; - ShenandoahCodeCacheRoots _code_roots; - -public: - ShenandoahRootUpdater(uint n_workers, ShenandoahPhaseTimings::Phase phase); - - template - void roots_do(uint worker_id, IsAlive* is_alive, KeepAlive* keep_alive); -}; - // Adjuster all roots at a safepoint during full gc class ShenandoahRootAdjuster : public ShenandoahRootProcessor { private: diff --git a/src/hotspot/share/gc/shenandoah/shenandoahRootProcessor.inline.hpp b/src/hotspot/share/gc/shenandoah/shenandoahRootProcessor.inline.hpp index 13ce0fc20715..773b483ba72a 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahRootProcessor.inline.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahRootProcessor.inline.hpp @@ -211,19 +211,4 @@ void ShenandoahSTWRootScanner::roots_do(T* oops, uint worker_id) { _vm_roots.oops_do(oops, worker_id); } -template -void ShenandoahRootUpdater::roots_do(uint worker_id, IsAlive* is_alive, KeepAlive* keep_alive) { - NMethodToOopClosure nmethods_cl(keep_alive, NMethodToOopClosure::FixRelocations); - CLDToOopClosure clds(keep_alive, ClassLoaderData::_claim_strong); - - // Process light-weight/limited parallel roots then - _vm_roots.oops_do(keep_alive, worker_id); - _weak_roots.weak_oops_do(is_alive, keep_alive, worker_id); - _cld_roots.cld_do(&clds, worker_id); - - // Process heavy-weight/fully parallel roots the last - _code_roots.nmethods_do(&nmethods_cl, worker_id); - _thread_roots.oops_do(keep_alive, nullptr, worker_id); -} - #endif // SHARE_GC_SHENANDOAH_SHENANDOAHROOTPROCESSOR_INLINE_HPP diff --git a/src/hotspot/share/gc/shenandoah/shenandoahSTWMark.cpp b/src/hotspot/share/gc/shenandoah/shenandoahSTWMark.cpp index 290c0f389629..de8bd8674687 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahSTWMark.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahSTWMark.cpp @@ -62,11 +62,10 @@ void ShenandoahSTWMarkTask::work(uint worker_id) { _mark->finish_mark(worker_id); } -ShenandoahSTWMark::ShenandoahSTWMark(ShenandoahGeneration* generation, bool full_gc) : +ShenandoahSTWMark::ShenandoahSTWMark(ShenandoahGeneration* generation) : ShenandoahMark(generation), - _root_scanner(full_gc ? ShenandoahPhaseTimings::full_gc_mark : ShenandoahPhaseTimings::degen_gc_mark), - _terminator(ShenandoahHeap::heap()->workers()->active_workers(), task_queues()), - _full_gc(full_gc) { + _root_scanner(ShenandoahPhaseTimings::full_gc_mark), + _terminator(ShenandoahHeap::heap()->workers()->active_workers(), task_queues()) { assert(ShenandoahSafepoint::is_at_shenandoah_safepoint(), "Must be at a Shenandoah safepoint"); } @@ -134,13 +133,9 @@ void ShenandoahSTWMark::mark_roots(uint worker_id) { _root_scanner.roots_do(&init_mark, worker_id); break; } - case YOUNG: { - ShenandoahMarkRefsClosure init_mark(queue, rp, nullptr); - _root_scanner.roots_do(&init_mark, worker_id); - break; - } + case YOUNG: case OLD: - // We never exclusively mark the old generation on a safepoint. This would be encompassed + // We never exclusively mark the old or young generation on a safepoint. This would be encompassed // by a 'global' collection. Note that both GLOBAL and NON_GEN mark the entire heap, but // the GLOBAL closure is specialized for the generational mode. default: @@ -149,7 +144,7 @@ void ShenandoahSTWMark::mark_roots(uint worker_id) { } void ShenandoahSTWMark::finish_mark(uint worker_id) { - ShenandoahPhaseTimings::Phase phase = _full_gc ? ShenandoahPhaseTimings::full_gc_mark : ShenandoahPhaseTimings::degen_gc_mark; + ShenandoahPhaseTimings::Phase phase = ShenandoahPhaseTimings::full_gc_mark; ShenandoahWorkerTimingsTracker timer(phase, ShenandoahPhaseTimings::Work, worker_id); mark_loop(worker_id, &_terminator, _generation->type(), false /* not cancellable */); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahSTWMark.hpp b/src/hotspot/share/gc/shenandoah/shenandoahSTWMark.hpp index d2066ecafb9d..e3416432780c 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahSTWMark.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahSTWMark.hpp @@ -28,7 +28,6 @@ #include "gc/shenandoah/shenandoahMark.hpp" #include "gc/shenandoah/shenandoahRootProcessor.hpp" -class ShenandoahSTWMarkTask; class ShenandoahGeneration; class ShenandoahSTWMark : public ShenandoahMark { @@ -37,9 +36,8 @@ class ShenandoahSTWMark : public ShenandoahMark { private: ShenandoahSTWRootScanner _root_scanner; TaskTerminator _terminator; - bool _full_gc; public: - ShenandoahSTWMark(ShenandoahGeneration* generation, bool full_gc); + ShenandoahSTWMark(ShenandoahGeneration* generation); void mark(); private: diff --git a/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.cpp b/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.cpp index 0affa3e4617b..a594ea70410b 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.cpp @@ -832,16 +832,6 @@ void ShenandoahScanRememberedTask::do_work(uint worker_id) { } scanner->process_region_slice(region, assignment._chunk_offset, clusters, end_of_range, &cl, false, worker_id); } -#ifdef ENABLE_REMEMBERED_SET_CANCELLATION - // This check is currently disabled to avoid crashes that occur - // when we try to cancel remembered set scanning; it should be re-enabled - // after the issues are fixed, as it would allow more prompt cancellation and - // transition to degenerated / full GCs. Note that work that has been assigned/ - // claimed above must be completed before we return here upon cancellation. - if (heap->check_cancelled_gc_and_yield(_is_concurrent)) { - return; - } -#endif } } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.hpp b/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.hpp index 59e8db07f8e3..207656532184 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.hpp @@ -706,8 +706,8 @@ class ShenandoahScanRemembered: public CHeapObj { _scc = new ShenandoahCardCluster(rs); // We allocate ParallelGCThreads worth even though we usually only - // use up to ConcGCThreads, because degenerate collections may employ - // ParallelGCThreads for remembered set scanning. + // use up to ConcGCThreads, because allocation stalls may push the + // number of concurrent threads up to the parallel thread count. if (ShenandoahEnableCardStats) { _card_stats = NEW_C_HEAP_ARRAY(HdrSeq*, ParallelGCThreads, mtGC); for (uint i = 0; i < ParallelGCThreads; i++) { diff --git a/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.inline.hpp b/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.inline.hpp index 2e7e4e0ee9ad..ad3fb3e4738a 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.inline.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.inline.hpp @@ -49,8 +49,7 @@ void ShenandoahScanRemembered::mark_card_as_dirty(HeapWord* p) const { // that start within its range and for Object arrays that start before its range and extend into its assigned range. // // Do not CANCEL within process_clusters. It is assumed that if a worker thread accepts responsibility for processing -// a chunk of work, it will finish the work it starts. Otherwise, the chunk of work will be lost in the transition to -// degenerated execution, leading to dangling references. +// a chunk of work, it will finish the work it starts. template void ShenandoahScanRemembered::process_clusters(size_t first_cluster, size_t count, HeapWord* end_of_range, ClosureType* cl, bool use_write_table, uint worker_id) { diff --git a/src/hotspot/share/gc/shenandoah/shenandoahThreadLocalData.cpp b/src/hotspot/share/gc/shenandoah/shenandoahThreadLocalData.cpp index 1a42e0e7b113..799a69600b05 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahThreadLocalData.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahThreadLocalData.cpp @@ -35,6 +35,7 @@ ShenandoahThreadLocalData::ShenandoahThreadLocalData() : _card_table(nullptr), _gclab(nullptr), _gclab_size(0), + _has_evac_failures(false), _shenandoah_plab(nullptr), _evacuation_stats(new ShenandoahEvacuationStats()), _invisible_root(nullptr), diff --git a/src/hotspot/share/gc/shenandoah/shenandoahThreadLocalData.hpp b/src/hotspot/share/gc/shenandoah/shenandoahThreadLocalData.hpp index 60dcd2528f7f..c5c1dbf8c8fb 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahThreadLocalData.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahThreadLocalData.hpp @@ -56,6 +56,7 @@ class ShenandoahThreadLocalData { // In generational mode, it is exclusive to the young generation. PLAB* _gclab; size_t _gclab_size; + bool _has_evac_failures; // Thread-local allocation buffer only used in generational mode. // Used both by mutator threads and by GC worker threads @@ -149,6 +150,18 @@ class ShenandoahThreadLocalData { data(thread)->_gclab_size = v; } + static void set_has_evac_failures(Thread* thread, bool value) { + data(thread)->_has_evac_failures = value; + } + + static bool has_evac_failures() { + return has_evac_failures(Thread::current()); + } + + static bool has_evac_failures(Thread* thread) { + return data(thread)->_has_evac_failures; + } + static void begin_evacuation(Thread* thread, size_t bytes, ShenandoahAffiliation from, ShenandoahAffiliation to) { data(thread)->_evacuation_stats->begin_evacuation(bytes, from, to); } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahUtils.cpp b/src/hotspot/share/gc/shenandoah/shenandoahUtils.cpp index fb6a77b48ae9..ce4d961ab436 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahUtils.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahUtils.cpp @@ -51,14 +51,13 @@ const char* ShenandoahGCSession::cycle_end_message(ShenandoahGenerationType type } } -ShenandoahGCSession::ShenandoahGCSession(GCCause::Cause cause, ShenandoahGeneration* generation, - bool is_degenerated, bool is_out_of_cycle) : +ShenandoahGCSession::ShenandoahGCSession(GCCause::Cause cause, ShenandoahGeneration* generation) : _heap(ShenandoahHeap::heap()), _generation(generation), _timer(_heap->gc_timer()), _tracer(_heap->tracer()) { assert(!ShenandoahGCPhase::is_current_phase_valid(), "No current GC phase"); - _heap->on_cycle_start(cause, _generation, is_degenerated, is_out_of_cycle); + _heap->on_cycle_start(cause, _generation); _timer->register_gc_start(); _tracer->report_gc_start(cause, _timer->gc_start()); _heap->trace_heap_before_gc(_tracer); @@ -192,3 +191,20 @@ ShenandoahConcurrentWorkerSession::~ShenandoahConcurrentWorkerSession() { ShenandoahParallelWorkerSession::~ShenandoahParallelWorkerSession() { _event.commit(GCId::current(), WorkerThread::worker_id(), ShenandoahPhaseTimings::phase_desc(ShenandoahGCPhase::current_phase())); } + +ShenandoahAllocationStallLogger::ShenandoahAllocationStallLogger() + : _stall_start_seconds(os::elapsedTime()) { + if (log_is_enabled(Info, gc, start)) { + ResourceMark rm; // for Thread::name() + log_info(gc, start)("Allocation Stall (%s)", Thread::current()->name()); + } +} + +ShenandoahAllocationStallLogger::~ShenandoahAllocationStallLogger() { + if (log_is_enabled(Info, gc)) { + ResourceMark rm; + log_info(gc)("Allocation Stall (%s) %.3fms", + Thread::current()->name(), + (os::elapsedTime() - _stall_start_seconds) * MILLIUNITS); + } +} diff --git a/src/hotspot/share/gc/shenandoah/shenandoahUtils.hpp b/src/hotspot/share/gc/shenandoah/shenandoahUtils.hpp index c37d720983b5..941269611259 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahUtils.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahUtils.hpp @@ -79,8 +79,7 @@ class ShenandoahGCSession : public StackObj { static const char* cycle_end_message(ShenandoahGenerationType type); public: - ShenandoahGCSession(GCCause::Cause cause, ShenandoahGeneration* generation, - bool is_degenerated = false, bool is_out_of_cycle = false); + ShenandoahGCSession(GCCause::Cause cause, ShenandoahGeneration* generation); ~ShenandoahGCSession(); }; @@ -227,8 +226,7 @@ class ShenandoahSafepoint : public AllStatic { type == VM_Operation::VMOp_ShenandoahInitUpdateRefs || type == VM_Operation::VMOp_ShenandoahFinalUpdateRefs || type == VM_Operation::VMOp_ShenandoahFinalVerify || - type == VM_Operation::VMOp_ShenandoahFullGC || - type == VM_Operation::VMOp_ShenandoahDegeneratedGC; + type == VM_Operation::VMOp_ShenandoahFullGC; } }; @@ -273,6 +271,15 @@ class ShenandoahNoUncommitMark : public StackObj { } }; +// Emit log messages for the beginning and end of an allocation stall. +class ShenandoahAllocationStallLogger : public StackObj { +public: + ShenandoahAllocationStallLogger(); + ~ShenandoahAllocationStallLogger(); +private: + const double _stall_start_seconds; +}; + // Casting a double that cannot be represented as a size_t may result in undefined behavior. // This small function checks if the given double is representable in a size_t and returns // that representation if it is. Otherwise, if the double cannot be safely cast to a size_t diff --git a/src/hotspot/share/gc/shenandoah/shenandoahVMOperations.cpp b/src/hotspot/share/gc/shenandoah/shenandoahVMOperations.cpp index 62ad794edc84..f44641de968e 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahVMOperations.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahVMOperations.cpp @@ -24,7 +24,6 @@ #include "gc/shenandoah/shenandoahConcurrentGC.hpp" -#include "gc/shenandoah/shenandoahDegeneratedGC.hpp" #include "gc/shenandoah/shenandoahFullGC.hpp" #include "gc/shenandoah/shenandoahGeneration.hpp" #include "gc/shenandoah/shenandoahHeap.inline.hpp" @@ -105,16 +104,6 @@ void VM_ShenandoahFullGC::doit() { _full_gc->entry_full(_gc_cause); } -VM_ShenandoahDegeneratedGC::VM_ShenandoahDegeneratedGC(ShenandoahDegenGC* gc) - : VM_ShenandoahReferenceOperation(gc->generation()), _gc(gc) { -} - -void VM_ShenandoahDegeneratedGC::doit() { - ShenandoahGCPauseMark mark(_gc_id, "Degenerated GC", SvcGCMarker::CONCURRENT); - set_active_generation(); - _gc->entry_degenerated(); -} - VM_ShenandoahInitUpdateRefs::VM_ShenandoahInitUpdateRefs(ShenandoahConcurrentGC* gc) : VM_ShenandoahOperation(gc->generation()), _gc(gc) { } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahVMOperations.hpp b/src/hotspot/share/gc/shenandoah/shenandoahVMOperations.hpp index d9d3ca90f40b..4530175c0815 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahVMOperations.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahVMOperations.hpp @@ -28,7 +28,6 @@ #include "gc/shared/gcVMOperations.hpp" class ShenandoahConcurrentGC; -class ShenandoahDegenGC; class ShenandoahFullGC; // VM_operations for the Shenandoah Collector. @@ -42,7 +41,6 @@ class ShenandoahFullGC; // - VM_ShenandoahFinalVerify: final verification at the end of the cycle // - VM_ShenandoahReferenceOperation: // - VM_ShenandoahFullGC: do full GC -// - VM_ShenandoahDegeneratedGC: do STW degenerated GC class VM_ShenandoahOperation : public VM_Operation { protected: @@ -91,15 +89,6 @@ class VM_ShenandoahFinalMarkStartEvac: public VM_ShenandoahOperation { void doit() override; }; -class VM_ShenandoahDegeneratedGC: public VM_ShenandoahReferenceOperation { - ShenandoahDegenGC* const _gc; -public: - explicit VM_ShenandoahDegeneratedGC(ShenandoahDegenGC* gc); - VM_Operation::VMOp_Type type() const override { return VMOp_ShenandoahDegeneratedGC; } - const char* name() const override { return "Shenandoah Degenerated GC"; } - void doit() override; -}; - class VM_ShenandoahFullGC : public VM_ShenandoahReferenceOperation { GCCause::Cause _gc_cause; ShenandoahFullGC* const _full_gc; diff --git a/src/hotspot/share/gc/shenandoah/shenandoahVerifier.cpp b/src/hotspot/share/gc/shenandoah/shenandoahVerifier.cpp index ddb84f09e595..8f824eab0ce6 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahVerifier.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahVerifier.cpp @@ -1196,7 +1196,7 @@ void ShenandoahVerifier::verify_after_update_refs(ShenandoahGeneration* generati _verify_liveness_disable, // no reliable liveness data anymore _verify_regions_nocset, // no cset regions, trash regions have appeared // expect generation and heap sizes to match exactly, including trash - _verify_size_exact_including_trash, + _verify_size_exact, _verify_gcstate_stable // update refs had cleaned up forwarded objects ); } @@ -1213,38 +1213,23 @@ void ShenandoahVerifier::verify_after_gc(ShenandoahGeneration* generation) { _verify_liveness_disable, // no reliable liveness data anymore _verify_regions_nocset, // no cset regions, trash regions have appeared // expect generation and heap sizes to match exactly, including trash - _verify_size_exact_including_trash, + _verify_size_exact, _verify_gcstate_stable // GC state was turned off ); } -void ShenandoahVerifier::verify_after_degenerated(ShenandoahGeneration* generation) { - verify_at_safepoint( - generation, - "After Degenerated GC", - _verify_remembered_disable, // do not verify remembered set - _verify_forwarded_none, // all objects are non-forwarded - _verify_marked_disable, // no need to check unreachable objects, end of cycle - _verify_cset_none, // no cset references - _verify_liveness_disable, // no reliable liveness data anymore - _verify_regions_notrash_nocset, // no trash, no cset - _verify_size_exact, // expect generation and heap sizes to match exactly - _verify_gcstate_stable // degenerated refs had cleaned up forwarded objects - ); -} - void ShenandoahVerifier::verify_before_fullgc(ShenandoahGeneration* generation) { verify_at_safepoint( generation, "Before Full GC", - _verify_remembered_disable, // do not verify remembered set - _verify_forwarded_allow, // can have forwarded objects - _verify_marked_disable, // do not verify marked: lots ot time wasted checking dead allocations - _verify_cset_disable, // cset might be foobared - _verify_liveness_disable, // no reliable liveness data anymore - _verify_regions_disable, // no reliable region data here - _verify_size_disable, // if we degenerate during evacuation, usage not valid: padding and deferred accounting - _verify_gcstate_disable // no reliable gcstate data + _verify_remembered_disable, // do not verify remembered set + _verify_forwarded_none, // cannot have forwarded objects + _verify_marked_disable, // do not verify marked: lots ot time wasted checking dead allocations + _verify_cset_none, // cset should be empty + _verify_liveness_disable, // no reliable liveness data anymore + _verify_regions_notrash_nocset, // no reliable region data here + _verify_size_exact, // expect generation and heap sizes to match exactly + _verify_gcstate_stable // no forwarded objects ); } @@ -1300,12 +1285,12 @@ class ShenandoahVerifyInToSpaceClosure : public BasicOopIterateClosure { "Verify Roots In To-Space", "Should be marked", __FILE__, __LINE__); } - if (heap->in_collection_set(obj)) { + if (heap->in_collection_set(obj) && !ShenandoahForwarding::is_self_forwarded(obj)) { ShenandoahAsserts::print_failure(ShenandoahAsserts::_safe_all, obj, p, nullptr, "Verify Roots In To-Space", "Should not be in collection set", __FILE__, __LINE__); } - if (ShenandoahForwarding::is_forwarded(obj)) { + if (ShenandoahForwarding::is_real_forwarded(obj)) { ShenandoahAsserts::print_failure(ShenandoahAsserts::_safe_all, obj, p, nullptr, "Verify Roots In To-Space", "Should not be forwarded", __FILE__, __LINE__); } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahVerifier.hpp b/src/hotspot/share/gc/shenandoah/shenandoahVerifier.hpp index 0479d5f67ce1..e35ef4c0b973 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahVerifier.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahVerifier.hpp @@ -223,7 +223,6 @@ class ShenandoahVerifier : public CHeapObj { void verify_after_gc(ShenandoahGeneration* generation); void verify_before_fullgc(ShenandoahGeneration* generation); void verify_after_fullgc(ShenandoahGeneration* generation); - void verify_after_degenerated(ShenandoahGeneration* generation); void verify_generic(ShenandoahGeneration* generation, VerifyOption option); // Roots should only contain to-space oops diff --git a/src/hotspot/share/gc/shenandoah/shenandoahWorkerPolicy.cpp b/src/hotspot/share/gc/shenandoah/shenandoahWorkerPolicy.cpp index 87095e87406f..5b43f64eb14b 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahWorkerPolicy.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahWorkerPolicy.cpp @@ -24,6 +24,7 @@ #include "gc/shared/gc_globals.hpp" +#include "gc/shenandoah/shenandoahHeap.inline.hpp" #include "gc/shenandoah/shenandoahWorkerPolicy.hpp" uint ShenandoahWorkerPolicy::calc_workers_for_init_marking() { @@ -31,11 +32,11 @@ uint ShenandoahWorkerPolicy::calc_workers_for_init_marking() { } uint ShenandoahWorkerPolicy::calc_workers_for_conc_marking() { - return ConcGCThreads; + return concurrent_workers_count(); } uint ShenandoahWorkerPolicy::calc_workers_for_rs_scanning() { - return ConcGCThreads; + return concurrent_workers_count(); } uint ShenandoahWorkerPolicy::calc_workers_for_final_marking() { @@ -43,27 +44,23 @@ uint ShenandoahWorkerPolicy::calc_workers_for_final_marking() { } uint ShenandoahWorkerPolicy::calc_workers_for_conc_refs_processing() { - return ConcGCThreads; + return concurrent_workers_count(); } uint ShenandoahWorkerPolicy::calc_workers_for_conc_root_processing() { - return ConcGCThreads; + return concurrent_workers_count(); } uint ShenandoahWorkerPolicy::calc_workers_for_conc_evac() { - return ConcGCThreads; + return concurrent_workers_count(); } uint ShenandoahWorkerPolicy::calc_workers_for_fullgc() { return ParallelGCThreads; } -uint ShenandoahWorkerPolicy::calc_workers_for_stw_degenerated() { - return ParallelGCThreads; -} - uint ShenandoahWorkerPolicy::calc_workers_for_conc_update_ref() { - return ConcGCThreads; + return concurrent_workers_count(); } uint ShenandoahWorkerPolicy::calc_workers_for_final_update_ref() { @@ -71,9 +68,13 @@ uint ShenandoahWorkerPolicy::calc_workers_for_final_update_ref() { } uint ShenandoahWorkerPolicy::calc_workers_for_conc_reset() { - return ConcGCThreads; + return concurrent_workers_count(); } uint ShenandoahWorkerPolicy::calc_workers_for_conc_cleanup() { - return ConcGCThreads; + return concurrent_workers_count(); +} + +uint ShenandoahWorkerPolicy::concurrent_workers_count() { + return checked_cast(ShenandoahHeap::heap()->control_thread()->concurrent_worker_count()); } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahWorkerPolicy.hpp b/src/hotspot/share/gc/shenandoah/shenandoahWorkerPolicy.hpp index 0de1cf3664b6..d5a0a4b5ac9f 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahWorkerPolicy.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahWorkerPolicy.hpp @@ -53,9 +53,6 @@ class ShenandoahWorkerPolicy : AllStatic { // Calculate workers for parallel full gc static uint calc_workers_for_fullgc(); - // Calculate workers for parallel degenerated gc - static uint calc_workers_for_stw_degenerated(); - // Calculate workers for concurrent reference update static uint calc_workers_for_conc_update_ref(); @@ -67,6 +64,9 @@ class ShenandoahWorkerPolicy : AllStatic { // Calculate workers for concurrent cleanup static uint calc_workers_for_conc_cleanup(); + +private: + static uint concurrent_workers_count(); }; #endif // SHARE_GC_SHENANDOAH_SHENANDOAHWORKERPOLICY_HPP diff --git a/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp b/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp index 7437586f7123..38a290ec5a02 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp @@ -179,7 +179,7 @@ "GC mode to use. Among other things, this defines which " \ "barriers are in in use. Possible values are:" \ " satb - snapshot-at-the-beginning concurrent GC (three pass mark-evac-update);" \ - " passive - stop the world GC only (either degenerated or full);" \ + " passive - stop the world full GC only (diagnostic mode);" \ " generational - generational concurrent GC") \ \ product(ccstr, ShenandoahGCHeuristics, "adaptive", \ @@ -233,7 +233,7 @@ "heap or generation, trigger a learning cycle if we are " \ "in learning mode. Learning mode happens during initialization " \ "and following a drastic state change, such as following a " \ - "degenerated or Full GC cycle. In percents of soft max " \ + "System.GC call or a Full GC cycle. In percents of soft max " \ "heap size.") \ range(0,100) \ \ @@ -424,20 +424,13 @@ \ product(uintx, ShenandoahCriticalFreeThreshold, 1, EXPERIMENTAL, \ "How much of the heap needs to be free after recovery cycles, " \ - "either Degenerated or Full GC to be claimed successful. If this "\ - "much space is not available, next recovery step would be " \ - "triggered.") \ + "for a Full GC to be claimed successful. If this much space is " \ + "not available, next recovery step would be triggered.") \ range(0, 100) \ \ - product(bool, ShenandoahDegeneratedGC, true, DIAGNOSTIC, \ - "Enable Degenerated GC as the graceful degradation step. " \ - "Disabling this option leads to degradation to Full GC instead. " \ - "When running in passive mode, this can be toggled to measure " \ - "either Degenerated GC or Full GC costs.") \ - \ - product(uintx, ShenandoahFullGCThreshold, 3, EXPERIMENTAL, \ - "How many back-to-back Degenerated GCs should happen before " \ - "going to a Full GC.") \ + product(uintx, ShenandoahFullGCThreshold, 2, EXPERIMENTAL, \ + "How many consecutive concurrent GCs a thread will wait for " \ + "before going to a Full GC.") \ \ product(uintx, ShenandoahNoProgressThreshold, 5, EXPERIMENTAL, \ "After this number of consecutive Full GCs fail to make " \ diff --git a/src/hotspot/share/runtime/arguments.cpp b/src/hotspot/share/runtime/arguments.cpp index 1a044215b6f5..2e77d8fcd686 100644 --- a/src/hotspot/share/runtime/arguments.cpp +++ b/src/hotspot/share/runtime/arguments.cpp @@ -537,6 +537,7 @@ static SpecialFlag const special_jvm_flags[] = { // -------------- Obsolete Flags - sorted by expired_in -------------- { "MetaspaceReclaimPolicy", JDK_Version::undefined(), JDK_Version::jdk(21), JDK_Version::undefined() }, + { "ShenandoahDegeneratedGC", JDK_Version::undefined(), JDK_Version::jdk(28), JDK_Version::jdk(29) }, #if defined(AARCH64) { "NearCpool", JDK_Version::undefined(), JDK_Version::jdk(25), JDK_Version::undefined() }, #endif diff --git a/src/hotspot/share/runtime/vmOperation.hpp b/src/hotspot/share/runtime/vmOperation.hpp index c72ec5d3834a..740de2a090c7 100644 --- a/src/hotspot/share/runtime/vmOperation.hpp +++ b/src/hotspot/share/runtime/vmOperation.hpp @@ -93,7 +93,6 @@ template(ShenandoahInitUpdateRefs) \ template(ShenandoahFinalUpdateRefs) \ template(ShenandoahFinalVerify) \ - template(ShenandoahDegeneratedGC) \ template(Exit) \ template(LinuxDllLoad) \ template(WhiteBoxOperation) \ diff --git a/test/hotspot/gtest/gc/shenandoah/test_shenandoahCollectorPolicy.cpp b/test/hotspot/gtest/gc/shenandoah/test_shenandoahCollectorPolicy.cpp index 70d458ec5e50..8b9b379d88a9 100644 --- a/test/hotspot/gtest/gc/shenandoah/test_shenandoahCollectorPolicy.cpp +++ b/test/hotspot/gtest/gc/shenandoah/test_shenandoahCollectorPolicy.cpp @@ -22,51 +22,27 @@ * */ +#include "gc/shenandoah/mode/shenandoahGenerationalMode.hpp" #include "gc/shenandoah/shenandoahCollectorPolicy.hpp" +#include "gc/shenandoah/shenandoahController.hpp" #include "unittest.hpp" +#include "utilities/ostream.hpp" -TEST(ShenandoahCollectorPolicyTest, track_degen_cycles_sanity) { - ShenandoahCollectorPolicy policy; - EXPECT_EQ(policy.consecutive_degenerated_gc_count(), 0UL); - EXPECT_EQ(policy.should_upgrade_degenerated_gc(), false); -} - -TEST(ShenandoahCollectorPolicyTest, track_degen_cycles_no_upgrade) { - ShenandoahCollectorPolicy policy; - policy.record_degenerated(true, true, true); - policy.record_degenerated(true, true, true); - EXPECT_EQ(policy.consecutive_degenerated_gc_count(), 2UL); - EXPECT_EQ(policy.should_upgrade_degenerated_gc(), false); -} - -TEST(ShenandoahCollectorPolicyTest, track_degen_cycles_upgrade) { - ShenandoahCollectorPolicy policy; - policy.record_degenerated(true, true, false); - policy.record_degenerated(true, true, false); - EXPECT_EQ(policy.consecutive_degenerated_gc_count(), 2UL); - EXPECT_EQ(policy.should_upgrade_degenerated_gc(), true); -} - -TEST(ShenandoahCollectorPolicyTest, track_degen_cycles_reset_progress) { - ShenandoahCollectorPolicy policy; - policy.record_degenerated(true, true, false); - policy.record_degenerated(true, true, true); - EXPECT_EQ(policy.consecutive_degenerated_gc_count(), 2UL); - EXPECT_EQ(policy.should_upgrade_degenerated_gc(), false); -} - -TEST(ShenandoahCollectorPolicyTest, track_degen_cycles_full_reset) { - ShenandoahCollectorPolicy policy; - policy.record_degenerated(true, true, false); - policy.record_success_full(); - EXPECT_EQ(policy.consecutive_degenerated_gc_count(), 0UL); - EXPECT_EQ(policy.should_upgrade_degenerated_gc(), false); -} +using ::testing::HasSubstr; -TEST(ShenandoahCollectorPolicyTest, track_degen_cycles_reset) { - ShenandoahCollectorPolicy policy; - policy.record_degenerated(true, true, false); - policy.record_success_concurrent(true, true); - EXPECT_EQ(policy.consecutive_degenerated_gc_count(), 0UL); - EXPECT_EQ(policy.should_upgrade_degenerated_gc(), false); +TEST_VM(ShenandoahCollectorPolicyTest, track_allocation_stalls) { + ShenandoahGenerationalMode mode; + ShenandoahCollectorPolicy policy(&mode); + for (int i = 0; i < ShenandoahController::PHASE_LIMIT; ++i) { + policy.record_allocation_stall(static_cast(i)); + } + stringStream ss; + policy.print_gc_stats(&ss); + ASSERT_THAT(ss.base(), HasSubstr("6 Stalls")); + ASSERT_THAT(ss.base(), HasSubstr("1 happened at Outside of Cycle")); + ASSERT_THAT(ss.base(), HasSubstr("1 happened at Initializing")); + ASSERT_THAT(ss.base(), HasSubstr("1 happened at Roots")); + ASSERT_THAT(ss.base(), HasSubstr("1 happened at Mark")); + ASSERT_THAT(ss.base(), HasSubstr("1 happened at Evacuation")); + ASSERT_THAT(ss.base(), HasSubstr("1 happened at Update References")); } diff --git a/test/hotspot/jtreg/gc/TestAllocHumongousFragment.java b/test/hotspot/jtreg/gc/TestAllocHumongousFragment.java index bcd6e33c81e8..3c327978d90c 100644 --- a/test/hotspot/jtreg/gc/TestAllocHumongousFragment.java +++ b/test/hotspot/jtreg/gc/TestAllocHumongousFragment.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2016, 2018, Red Hat, Inc. All rights reserved. * Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. @@ -33,22 +33,11 @@ * * @run main/othervm -Xmx1g -Xms1g -Xlog:gc -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -XX:ShenandoahTargetNumRegions=2048 * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:-ShenandoahDegeneratedGC -XX:+ShenandoahVerify - * TestAllocHumongousFragment - * - * @run main/othervm -Xmx1g -Xms1g -Xlog:gc -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -XX:ShenandoahTargetNumRegions=2048 - * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:+ShenandoahDegeneratedGC -XX:+ShenandoahVerify - * TestAllocHumongousFragment - * - * @run main/othervm -Xmx1g -Xms1g -Xlog:gc -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -XX:ShenandoahTargetNumRegions=2048 - * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:-ShenandoahDegeneratedGC + * -XX:+ShenandoahVerify * TestAllocHumongousFragment * * @run main/othervm -Xmx1g -Xms1g -Xlog:gc -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -XX:ShenandoahTargetNumRegions=2048 * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:+ShenandoahDegeneratedGC * TestAllocHumongousFragment */ diff --git a/test/hotspot/jtreg/gc/shenandoah/TestAllocIntArrays.java b/test/hotspot/jtreg/gc/shenandoah/TestAllocIntArrays.java index 8488b8f4a8d0..c8d073483a1f 100644 --- a/test/hotspot/jtreg/gc/shenandoah/TestAllocIntArrays.java +++ b/test/hotspot/jtreg/gc/shenandoah/TestAllocIntArrays.java @@ -32,22 +32,11 @@ * * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -Xmx1g -Xms1g * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:+ShenandoahDegeneratedGC -XX:+ShenandoahVerify - * TestAllocIntArrays - * - * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -Xmx1g -Xms1g - * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:-ShenandoahDegeneratedGC -XX:+ShenandoahVerify - * TestAllocIntArrays - * - * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -Xmx1g -Xms1g - * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:+ShenandoahDegeneratedGC + * -XX:+ShenandoahVerify * TestAllocIntArrays * * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -Xmx1g -Xms1g * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:-ShenandoahDegeneratedGC * TestAllocIntArrays */ diff --git a/test/hotspot/jtreg/gc/shenandoah/TestAllocObjectArrays.java b/test/hotspot/jtreg/gc/shenandoah/TestAllocObjectArrays.java index bc8c451450cc..ea9648749e9b 100644 --- a/test/hotspot/jtreg/gc/shenandoah/TestAllocObjectArrays.java +++ b/test/hotspot/jtreg/gc/shenandoah/TestAllocObjectArrays.java @@ -32,23 +32,13 @@ * * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -Xmx1g -Xms1g * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:+ShenandoahDegeneratedGC -XX:+ShenandoahVerify - * TestAllocObjectArrays - * - * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -Xmx1g -Xms1g - * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:-ShenandoahDegeneratedGC -XX:+ShenandoahVerify + * -XX:+ShenandoahVerify * TestAllocObjectArrays * * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -Xmx1g -Xms1g * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:+ShenandoahDegeneratedGC * TestAllocObjectArrays * - * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -Xmx1g -Xms1g - * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:-ShenandoahDegeneratedGC - * TestAllocObjectArrays */ /* diff --git a/test/hotspot/jtreg/gc/shenandoah/TestAllocObjects.java b/test/hotspot/jtreg/gc/shenandoah/TestAllocObjects.java index a1d06945b790..7e070727c609 100644 --- a/test/hotspot/jtreg/gc/shenandoah/TestAllocObjects.java +++ b/test/hotspot/jtreg/gc/shenandoah/TestAllocObjects.java @@ -30,22 +30,11 @@ * * @run main/othervm -Xmx1g -Xms1g -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:+ShenandoahDegeneratedGC -XX:+ShenandoahVerify - * TestAllocObjects - * - * @run main/othervm -Xmx1g -Xms1g -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions - * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:-ShenandoahDegeneratedGC -XX:+ShenandoahVerify - * TestAllocObjects - * - * @run main/othervm -Xmx1g -Xms1g -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions - * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:+ShenandoahDegeneratedGC + * -XX:+ShenandoahVerify * TestAllocObjects * * @run main/othervm -Xmx1g -Xms1g -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:-ShenandoahDegeneratedGC * TestAllocObjects */ diff --git a/test/hotspot/jtreg/gc/shenandoah/TestDynamicSoftMaxHeapSize.java b/test/hotspot/jtreg/gc/shenandoah/TestDynamicSoftMaxHeapSize.java index 7ca9c7f46d60..0a09bde65181 100644 --- a/test/hotspot/jtreg/gc/shenandoah/TestDynamicSoftMaxHeapSize.java +++ b/test/hotspot/jtreg/gc/shenandoah/TestDynamicSoftMaxHeapSize.java @@ -31,7 +31,6 @@ * @run main/othervm -Xms100m -Xmx512m -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions * -XX:+UseShenandoahGC -Xlog:gc=info -Dtarget=10000 * -XX:ShenandoahGCMode=satb - * -XX:+ShenandoahDegeneratedGC * -XX:ShenandoahGCHeuristics=adaptive * TestDynamicSoftMaxHeapSize * @@ -45,7 +44,6 @@ * @run main/othervm -Xms100m -Xmx512m -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions * -XX:+UseShenandoahGC -Xlog:gc=info -Dtarget=10000 * -XX:ShenandoahGCMode=satb - * -XX:+ShenandoahDegeneratedGC * -XX:ShenandoahGCHeuristics=aggressive * TestDynamicSoftMaxHeapSize * @@ -59,7 +57,6 @@ * @run main/othervm -Xms100m -Xmx512m -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions * -XX:+UseShenandoahGC -Xlog:gc=info -Dtarget=10000 * -XX:ShenandoahGCMode=satb - * -XX:+ShenandoahDegeneratedGC * -XX:ShenandoahGCHeuristics=compact * TestDynamicSoftMaxHeapSize * @@ -73,7 +70,6 @@ * @run main/othervm -Xms100m -Xmx512m -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions * -XX:+UseShenandoahGC -Xlog:gc=info -Dtarget=10000 * -XX:ShenandoahGCMode=satb - * -XX:+ShenandoahDegeneratedGC * -XX:ShenandoahGCHeuristics=static * TestDynamicSoftMaxHeapSize * @@ -86,13 +82,6 @@ * * @run main/othervm -Xms16m -Xmx512m -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:+ShenandoahDegeneratedGC - * -Dtarget=10000 - * TestDynamicSoftMaxHeapSize - * - * @run main/othervm -Xms16m -Xmx512m -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions - * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:-ShenandoahDegeneratedGC * -Dtarget=10000 * TestDynamicSoftMaxHeapSize */ diff --git a/test/hotspot/jtreg/gc/shenandoah/TestHeapUncommit.java b/test/hotspot/jtreg/gc/shenandoah/TestHeapUncommit.java index ffa7027a3c80..a0ce91c2ac97 100644 --- a/test/hotspot/jtreg/gc/shenandoah/TestHeapUncommit.java +++ b/test/hotspot/jtreg/gc/shenandoah/TestHeapUncommit.java @@ -32,22 +32,11 @@ * * @run main/othervm -Xmx1g -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -XX:+ShenandoahUncommit -XX:ShenandoahUncommitDelay=0 * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:+ShenandoahDegeneratedGC -XX:+ShenandoahVerify - * TestHeapUncommit - * - * @run main/othervm -Xmx1g -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -XX:+ShenandoahUncommit -XX:ShenandoahUncommitDelay=0 - * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:-ShenandoahDegeneratedGC -XX:+ShenandoahVerify - * TestHeapUncommit - * - * @run main/othervm -Xmx1g -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -XX:+ShenandoahUncommit -XX:ShenandoahUncommitDelay=0 - * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:+ShenandoahDegeneratedGC + * -XX:+ShenandoahVerify * TestHeapUncommit * * @run main/othervm -Xmx1g -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -XX:+ShenandoahUncommit -XX:ShenandoahUncommitDelay=0 * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:-ShenandoahDegeneratedGC * TestHeapUncommit */ diff --git a/test/hotspot/jtreg/gc/shenandoah/TestJcmdHeapDump.java b/test/hotspot/jtreg/gc/shenandoah/TestJcmdHeapDump.java index e790851e2e84..ce48e7b2d62f 100644 --- a/test/hotspot/jtreg/gc/shenandoah/TestJcmdHeapDump.java +++ b/test/hotspot/jtreg/gc/shenandoah/TestJcmdHeapDump.java @@ -31,12 +31,6 @@ * * @run main/othervm/timeout=480 -Xmx16m -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:+ShenandoahDegeneratedGC - * TestJcmdHeapDump - * - * @run main/othervm/timeout=480 -Xmx16m -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions - * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:-ShenandoahDegeneratedGC * TestJcmdHeapDump */ diff --git a/test/hotspot/jtreg/gc/shenandoah/TestLotsOfCycles.java b/test/hotspot/jtreg/gc/shenandoah/TestLotsOfCycles.java index fbf3cd5c34b8..71ae891b424f 100644 --- a/test/hotspot/jtreg/gc/shenandoah/TestLotsOfCycles.java +++ b/test/hotspot/jtreg/gc/shenandoah/TestLotsOfCycles.java @@ -29,13 +29,6 @@ * * @run main/othervm/timeout=480 -Xmx16m -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:+ShenandoahDegeneratedGC - * -Dtarget=10000 - * TestLotsOfCycles - * - * @run main/othervm/timeout=480 -Xmx16m -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions - * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:-ShenandoahDegeneratedGC * -Dtarget=10000 * TestLotsOfCycles */ diff --git a/test/hotspot/jtreg/gc/shenandoah/TestObjItrWithHeapDump.java b/test/hotspot/jtreg/gc/shenandoah/TestObjItrWithHeapDump.java index 0aad385d4e36..a540fd1e0796 100644 --- a/test/hotspot/jtreg/gc/shenandoah/TestObjItrWithHeapDump.java +++ b/test/hotspot/jtreg/gc/shenandoah/TestObjItrWithHeapDump.java @@ -68,7 +68,6 @@ public static void main(String[] args) throws Exception { testWith("-XX:+UnlockDiagnosticVMOptions", "-XX:+UnlockExperimentalVMOptions", "-XX:+UseShenandoahGC", - "-XX:-ShenandoahDegeneratedGC", "-XX:ShenandoahGCMode=" + mode, "-XX:ShenandoahGCHeuristics=" + h, "-Xlog:gc+classhisto=trace", diff --git a/test/hotspot/jtreg/gc/shenandoah/TestRegionSampling.java b/test/hotspot/jtreg/gc/shenandoah/TestRegionSampling.java index 442f0ab8b2d1..3191c2342c05 100644 --- a/test/hotspot/jtreg/gc/shenandoah/TestRegionSampling.java +++ b/test/hotspot/jtreg/gc/shenandoah/TestRegionSampling.java @@ -29,12 +29,6 @@ * * @run main/othervm -Xmx1g -Xms1g -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -XX:+ShenandoahRegionSampling * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:+ShenandoahDegeneratedGC - * TestRegionSampling - * - * @run main/othervm -Xmx1g -Xms1g -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -XX:+ShenandoahRegionSampling - * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:-ShenandoahDegeneratedGC * TestRegionSampling */ diff --git a/test/hotspot/jtreg/gc/shenandoah/TestResizeTLAB.java b/test/hotspot/jtreg/gc/shenandoah/TestResizeTLAB.java index f42b14c0d365..8b8383b91e43 100644 --- a/test/hotspot/jtreg/gc/shenandoah/TestResizeTLAB.java +++ b/test/hotspot/jtreg/gc/shenandoah/TestResizeTLAB.java @@ -32,28 +32,12 @@ * * @run main/othervm -Xmx1g -Xms1g -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:+ShenandoahDegeneratedGC -XX:+ShenandoahVerify * -XX:+ShenandoahVerify * -XX:+ResizeTLAB * TestResizeTLAB * * @run main/othervm -Xmx1g -Xms1g -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:+ShenandoahDegeneratedGC -XX:+ShenandoahVerify - * -XX:+ShenandoahVerify - * -XX:-ResizeTLAB - * TestResizeTLAB - * - * @run main/othervm -Xmx1g -Xms1g -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions - * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:-ShenandoahDegeneratedGC -XX:+ShenandoahVerify - * -XX:+ShenandoahVerify - * -XX:+ResizeTLAB - * TestResizeTLAB - * - * @run main/othervm -Xmx1g -Xms1g -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions - * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:-ShenandoahDegeneratedGC -XX:+ShenandoahVerify * -XX:+ShenandoahVerify * -XX:-ResizeTLAB * TestResizeTLAB diff --git a/test/hotspot/jtreg/gc/shenandoah/TestRetainObjects.java b/test/hotspot/jtreg/gc/shenandoah/TestRetainObjects.java index 010bdb5e4f1d..0af2f25fbb8e 100644 --- a/test/hotspot/jtreg/gc/shenandoah/TestRetainObjects.java +++ b/test/hotspot/jtreg/gc/shenandoah/TestRetainObjects.java @@ -30,22 +30,11 @@ * * @run main/othervm -Xmx1g -Xms1g -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:+ShenandoahDegeneratedGC -XX:+ShenandoahVerify - * TestRetainObjects - * - * @run main/othervm -Xmx1g -Xms1g -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions - * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:-ShenandoahDegeneratedGC -XX:+ShenandoahVerify - * TestRetainObjects - * - * @run main/othervm -Xmx1g -Xms1g -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions - * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:+ShenandoahDegeneratedGC + * -XX:+ShenandoahVerify * TestRetainObjects * * @run main/othervm -Xmx1g -Xms1g -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:-ShenandoahDegeneratedGC * TestRetainObjects */ diff --git a/test/hotspot/jtreg/gc/shenandoah/TestSieveObjects.java b/test/hotspot/jtreg/gc/shenandoah/TestSieveObjects.java index fa140d62a662..6247163dfb84 100644 --- a/test/hotspot/jtreg/gc/shenandoah/TestSieveObjects.java +++ b/test/hotspot/jtreg/gc/shenandoah/TestSieveObjects.java @@ -32,22 +32,11 @@ * * @run main/othervm -Xmx1g -Xms1g -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:+ShenandoahDegeneratedGC -XX:+ShenandoahVerify - * TestSieveObjects - * - * @run main/othervm -Xmx1g -Xms1g -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions - * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:-ShenandoahDegeneratedGC -XX:+ShenandoahVerify - * TestSieveObjects - * - * @run main/othervm -Xmx1g -Xms1g -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions - * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:+ShenandoahDegeneratedGC + * -XX:+ShenandoahVerify * TestSieveObjects * * @run main/othervm -Xmx1g -Xms1g -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:-ShenandoahDegeneratedGC * TestSieveObjects */ diff --git a/test/hotspot/jtreg/gc/shenandoah/TestSoftMaxHeapSizeAvailableCalc.java b/test/hotspot/jtreg/gc/shenandoah/TestSoftMaxHeapSizeAvailableCalc.java index 100666fe859d..0b656255b708 100644 --- a/test/hotspot/jtreg/gc/shenandoah/TestSoftMaxHeapSizeAvailableCalc.java +++ b/test/hotspot/jtreg/gc/shenandoah/TestSoftMaxHeapSizeAvailableCalc.java @@ -33,7 +33,6 @@ * @run main/othervm -XX:SoftMaxHeapSize=512m -Xmx2g -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions * -XX:+UseShenandoahGC -Xlog:gc=info * -XX:ShenandoahGCMode=satb - * -XX:+ShenandoahDegeneratedGC * -XX:ShenandoahGCHeuristics=adaptive * -XX:ShenandoahLearningSteps=0 * TestSoftMaxHeapSizeAvailableCalc @@ -47,7 +46,6 @@ * @run main/othervm -XX:SoftMaxHeapSize=512m -Xmx2g -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions * -XX:+UseShenandoahGC -Xlog:gc=info * -XX:ShenandoahGCMode=satb - * -XX:+ShenandoahDegeneratedGC * -XX:ShenandoahGCHeuristics=static * TestSoftMaxHeapSizeAvailableCalc */ diff --git a/test/hotspot/jtreg/gc/shenandoah/TestStringDedup.java b/test/hotspot/jtreg/gc/shenandoah/TestStringDedup.java index 760e396beb1a..9c88b3df3407 100644 --- a/test/hotspot/jtreg/gc/shenandoah/TestStringDedup.java +++ b/test/hotspot/jtreg/gc/shenandoah/TestStringDedup.java @@ -35,12 +35,7 @@ * * @run main/othervm -Xmx256m -Xlog:stringdedup -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -XX:+UseStringDeduplication * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:+ShenandoahDegeneratedGC -DGCCount=1 - * TestStringDedup - * - * @run main/othervm -Xmx256m -Xlog:stringdedup -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -XX:+UseStringDeduplication - * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:-ShenandoahDegeneratedGC -DGCCount=1 + * -DGCCount=1 * TestStringDedup */ diff --git a/test/hotspot/jtreg/gc/shenandoah/TestStringDedupStress.java b/test/hotspot/jtreg/gc/shenandoah/TestStringDedupStress.java index fa8c67e095df..218ca16b846e 100644 --- a/test/hotspot/jtreg/gc/shenandoah/TestStringDedupStress.java +++ b/test/hotspot/jtreg/gc/shenandoah/TestStringDedupStress.java @@ -34,12 +34,6 @@ * * @run main/othervm -Xmx1g -Xlog:stringdedup -Xlog:gc -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -XX:+UseStringDeduplication * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:+ShenandoahDegeneratedGC - * TestStringDedupStress - * - * @run main/othervm -Xmx1g -Xlog:stringdedup -Xlog:gc -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -XX:+UseStringDeduplication - * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:-ShenandoahDegeneratedGC * TestStringDedupStress */ @@ -54,7 +48,6 @@ * * @run main/othervm -Xmx1g -Xlog:stringdedup -Xlog:gc -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -XX:+UseStringDeduplication * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=generational - * -XX:+ShenandoahDegeneratedGC * -DtargetStrings=3000000 * TestStringDedupStress */ diff --git a/test/hotspot/jtreg/gc/shenandoah/TestStringInternCleanup.java b/test/hotspot/jtreg/gc/shenandoah/TestStringInternCleanup.java index 0aeafc09bc04..0c5a26289040 100644 --- a/test/hotspot/jtreg/gc/shenandoah/TestStringInternCleanup.java +++ b/test/hotspot/jtreg/gc/shenandoah/TestStringInternCleanup.java @@ -30,22 +30,11 @@ * * @run main/othervm -Xmx64m -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -XX:+ClassUnloadingWithConcurrentMark * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:+ShenandoahDegeneratedGC -XX:+ShenandoahVerify - * TestStringInternCleanup - * - * @run main/othervm -Xmx64m -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -XX:+ClassUnloadingWithConcurrentMark - * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:-ShenandoahDegeneratedGC -XX:+ShenandoahVerify - * TestStringInternCleanup - * - * @run main/othervm -Xmx64m -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -XX:+ClassUnloadingWithConcurrentMark - * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:+ShenandoahDegeneratedGC + * -XX:+ShenandoahVerify * TestStringInternCleanup * * @run main/othervm -Xmx64m -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -XX:+ClassUnloadingWithConcurrentMark * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:-ShenandoahDegeneratedGC * TestStringInternCleanup */ diff --git a/test/hotspot/jtreg/gc/shenandoah/TestVerifyJCStress.java b/test/hotspot/jtreg/gc/shenandoah/TestVerifyJCStress.java index 8af77a07d0b2..4cb2176f0d93 100644 --- a/test/hotspot/jtreg/gc/shenandoah/TestVerifyJCStress.java +++ b/test/hotspot/jtreg/gc/shenandoah/TestVerifyJCStress.java @@ -32,12 +32,11 @@ * * @run main/othervm -Xmx1g -Xms1g -XX:+UnlockExperimentalVMOptions -XX:+UnlockDiagnosticVMOptions * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:+ShenandoahDegeneratedGC -XX:+ShenandoahVerify + * -XX:+ShenandoahVerify * TestVerifyJCStress * * @run main/othervm -Xmx1g -Xms1g -XX:+UnlockExperimentalVMOptions -XX:+UnlockDiagnosticVMOptions * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:-ShenandoahDegeneratedGC -XX:+ShenandoahVerify * TestVerifyJCStress */ diff --git a/test/hotspot/jtreg/gc/shenandoah/generational/TestHumongousAllocFailureDegeneration.java b/test/hotspot/jtreg/gc/shenandoah/generational/TestHumongousAllocFailureRecovery.java similarity index 65% rename from test/hotspot/jtreg/gc/shenandoah/generational/TestHumongousAllocFailureDegeneration.java rename to test/hotspot/jtreg/gc/shenandoah/generational/TestHumongousAllocFailureRecovery.java index 29a0aedb3335..b625c19f412b 100644 --- a/test/hotspot/jtreg/gc/shenandoah/generational/TestHumongousAllocFailureDegeneration.java +++ b/test/hotspot/jtreg/gc/shenandoah/generational/TestHumongousAllocFailureRecovery.java @@ -25,7 +25,7 @@ /* * @test id=generational * @bug 8391591 - * @summary Humongous allocation failure triggers a degenerated cycle. + * @summary Humongous allocation failure should recover without full GC. * @requires vm.gc.Shenandoah * @library /test/lib * @run main/othervm @@ -33,22 +33,26 @@ * -XX:ShenandoahGCMode=generational -Xmx128m -Xms128m * -XX:ShenandoahRegionSize=1m -XX:+AlwaysPreTouch * -XX:ConcGCThreads=1 -XX:ParallelGCThreads=1 - * TestHumongousAllocFailureDegeneration + * TestHumongousAllocFailureRecovery */ import com.sun.management.GarbageCollectionNotificationInfo; import java.lang.management.GarbageCollectorMXBean; import java.lang.management.ManagementFactory; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; import javax.management.Notification; import javax.management.NotificationEmitter; import javax.management.NotificationListener; import javax.management.openmbean.CompositeData; -public class TestHumongousAllocFailureDegeneration { +public class TestHumongousAllocFailureRecovery { private static final int MB = 1024 * 1024; private static final int HUMONGOUS_SIZE_MB = 32; private static final int ITERATIONS = 16; @@ -70,20 +74,33 @@ private static void unsubscribeToCollectorNotifications(NotificationListener lis } } - private static boolean isHumongousAllocFailureDegen(GarbageCollectionNotificationInfo info) { + private static boolean isFullGC(GarbageCollectionNotificationInfo info) { return info.getGcName().equals("Shenandoah Pauses") - && info.getGcAction().contains("Degenerated") - && info.getGcCause().equals("Humongous Allocation Failure"); + && info.getGcAction().contains("Full"); + } + + private static boolean isSystemGC(GarbageCollectionNotificationInfo info) { + return info.getGcCause().contains("System.gc"); + } + + private static boolean isHumongousAllocationFailure(GarbageCollectionNotificationInfo info) { + return info.getGcCause().contains("Humongous Allocation Failure"); } public static void main(String[] args) throws Exception { - final AtomicBoolean sawDegenForHumongous = new AtomicBoolean(false); + final List unexpectedFullGCs = Collections.synchronizedList(new ArrayList<>()); + final AtomicLong humongousAllocFailures = new AtomicLong(); + final CountDownLatch sawSystemGC = new CountDownLatch(1); NotificationListener listener = (Notification n, Object o) -> { if (isCollectorNotification(n)) { GarbageCollectionNotificationInfo info = GarbageCollectionNotificationInfo.from((CompositeData) n.getUserData()); - if (isHumongousAllocFailureDegen(info)) { - sawDegenForHumongous.set(true); + if (isSystemGC(info)) { + sawSystemGC.countDown(); + } else if (isFullGC(info)) { + unexpectedFullGCs.add(info.getGcCause()); + } else if (isHumongousAllocationFailure(info)) { + humongousAllocFailures.incrementAndGet(); } } }; @@ -96,17 +113,21 @@ public static void main(String[] args) throws Exception { sink = new byte[MB * HUMONGOUS_SIZE_MB]; } - // Wait for gc notifications until we've encountered a degenerated cycle caused by - // humongous allocation failure. Fail if none arrives before the deadline. - long deadlineNanos = System.nanoTime() + TimeUnit.SECONDS.toNanos(60); - while (!sawDegenForHumongous.get()) { - if (System.nanoTime() > deadlineNanos) { - throw new RuntimeException("Timed out waiting for a degenerated cycle caused by " - + "humongous allocation failure"); - } - Thread.sleep(10); + // Invoke an explicit GC. When our stream listener hears this, we know the test is complete. + System.gc(); + + if (!sawSystemGC.await(30, TimeUnit.SECONDS)) { + throw new RuntimeException("Timed out waiting for System.gc"); } unsubscribeToCollectorNotifications(listener); + + if (humongousAllocFailures.get() == 0) { + throw new RuntimeException("Test conditions not met, no humongous allocation failures"); + } + + if (!unexpectedFullGCs.isEmpty()) { + throw new RuntimeException("Unexpected full GCs: " + unexpectedFullGCs); + } } } diff --git a/test/hotspot/jtreg/gc/shenandoah/generational/TestPromoteInPlaceDuringAbbreviatedCycle.java b/test/hotspot/jtreg/gc/shenandoah/generational/TestPromoteInPlaceDuringAbbreviatedCycle.java index e9be9f39fbd1..577aedbe068e 100644 --- a/test/hotspot/jtreg/gc/shenandoah/generational/TestPromoteInPlaceDuringAbbreviatedCycle.java +++ b/test/hotspot/jtreg/gc/shenandoah/generational/TestPromoteInPlaceDuringAbbreviatedCycle.java @@ -118,9 +118,7 @@ private static boolean isCollectorNotification(Notification n) { private static boolean isIllegalPause(GarbageCollectionNotificationInfo info) { return info.getGcName().equals("Shenandoah Pauses") - && (info.getGcAction().contains("Update Refs") - || info.getGcAction().contains("Degen") - || info.getGcAction().contains("Full")); + && (info.getGcAction().contains("Update Refs") || info.getGcAction().contains("Full")); } private static void subscribeToCollectorNotifications(NotificationListener listener) { diff --git a/test/hotspot/jtreg/gc/shenandoah/jni/TestPinnedGarbage.java b/test/hotspot/jtreg/gc/shenandoah/jni/TestPinnedGarbage.java index eabe244a9d13..dc74451bf5f9 100644 --- a/test/hotspot/jtreg/gc/shenandoah/jni/TestPinnedGarbage.java +++ b/test/hotspot/jtreg/gc/shenandoah/jni/TestPinnedGarbage.java @@ -31,12 +31,7 @@ * * @run main/othervm/native -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -Xmx128m * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:+ShenandoahVerify -XX:+ShenandoahDegeneratedGC - * TestPinnedGarbage - * - * @run main/othervm/native -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -Xmx128m - * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:+ShenandoahVerify -XX:-ShenandoahDegeneratedGC + * -XX:+ShenandoahVerify * TestPinnedGarbage */ diff --git a/test/hotspot/jtreg/gc/shenandoah/mxbeans/TestChurnNotifications.java b/test/hotspot/jtreg/gc/shenandoah/mxbeans/TestChurnNotifications.java index 6473c310ec79..caeb0464feb2 100644 --- a/test/hotspot/jtreg/gc/shenandoah/mxbeans/TestChurnNotifications.java +++ b/test/hotspot/jtreg/gc/shenandoah/mxbeans/TestChurnNotifications.java @@ -31,12 +31,7 @@ * * @run main/othervm -Xmx128m -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:+ShenandoahDegeneratedGC -Dprecise=true - * TestChurnNotifications - * - * @run main/othervm -Xmx128m -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions - * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:-ShenandoahDegeneratedGC -Dprecise=true + * -Dprecise=true * TestChurnNotifications */ diff --git a/test/hotspot/jtreg/gc/shenandoah/mxbeans/TestPauseNotifications.java b/test/hotspot/jtreg/gc/shenandoah/mxbeans/TestPauseNotifications.java index e5c3acaccac6..01e56db31f6d 100644 --- a/test/hotspot/jtreg/gc/shenandoah/mxbeans/TestPauseNotifications.java +++ b/test/hotspot/jtreg/gc/shenandoah/mxbeans/TestPauseNotifications.java @@ -31,12 +31,6 @@ * * @run main/othervm -Xmx128m -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:+ShenandoahDegeneratedGC - * TestPauseNotifications - * - * @run main/othervm -Xmx128m -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions - * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:-ShenandoahDegeneratedGC * TestPauseNotifications */ @@ -116,8 +110,7 @@ public class TestPauseNotifications { private static boolean isExpectedPauseAction(String action) { return "Init Mark".equals(action) || "Final Mark".equals(action) || "Full GC".equals(action) - || "Degenerated GC".equals(action) || "Init Update Refs".equals(action) - || "Final Update Refs".equals(action) || "Final Roots".equals(action); + || "Init Update Refs".equals(action) || "Final Update Refs".equals(action) || "Final Roots".equals(action); } public static void main(String[] args) throws Exception { diff --git a/test/hotspot/jtreg/gc/shenandoah/options/TestHumongousMoves.java b/test/hotspot/jtreg/gc/shenandoah/options/TestHumongousMoves.java index e2254fdc0ab9..a553f95f1bec 100644 --- a/test/hotspot/jtreg/gc/shenandoah/options/TestHumongousMoves.java +++ b/test/hotspot/jtreg/gc/shenandoah/options/TestHumongousMoves.java @@ -31,14 +31,12 @@ * * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -Xmx1g -Xms1g * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:-ShenandoahHumongousMoves - * -XX:-ShenandoahDegeneratedGC -XX:+ShenandoahVerify + * -XX:-ShenandoahHumongousMoves -XX:+ShenandoahVerify * TestHumongousMoves * * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -Xmx1g -Xms1g * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:+ShenandoahHumongousMoves - * -XX:-ShenandoahDegeneratedGC -XX:+ShenandoahVerify + * -XX:+ShenandoahHumongousMoves -XX:+ShenandoahVerify * TestHumongousMoves */ diff --git a/test/hotspot/jtreg/gc/stress/gcbasher/TestGCBasherWithShenandoah.java b/test/hotspot/jtreg/gc/stress/gcbasher/TestGCBasherWithShenandoah.java index 858efacd7586..c56723e84a3d 100644 --- a/test/hotspot/jtreg/gc/stress/gcbasher/TestGCBasherWithShenandoah.java +++ b/test/hotspot/jtreg/gc/stress/gcbasher/TestGCBasherWithShenandoah.java @@ -37,12 +37,7 @@ * * @run main/othervm/timeout=200 -Xlog:gc*=info -Xmx1g -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:+ShenandoahVerify -XX:+ShenandoahDegeneratedGC - * gc.stress.gcbasher.TestGCBasherWithShenandoah 120000 - * - * @run main/othervm/timeout=200 -Xlog:gc*=info -Xmx1g -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions - * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:+ShenandoahVerify -XX:-ShenandoahDegeneratedGC + * -XX:+ShenandoahVerify * gc.stress.gcbasher.TestGCBasherWithShenandoah 120000 */ @@ -116,13 +111,7 @@ * @run main/othervm/timeout=200 -Xlog:gc*=info,nmethod+barrier=trace -Xmx1g -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive * -XX:+DeoptimizeNMethodBarriersALot -XX:-Inline - * -XX:+ShenandoahVerify -XX:+ShenandoahDegeneratedGC - * gc.stress.gcbasher.TestGCBasherWithShenandoah 120000 - * - * @run main/othervm/timeout=200 -Xlog:gc*=info,nmethod+barrier=trace -Xmx1g -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions - * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:+DeoptimizeNMethodBarriersALot -XX:-Inline - * -XX:+ShenandoahVerify -XX:-ShenandoahDegeneratedGC + * -XX:+ShenandoahVerify * gc.stress.gcbasher.TestGCBasherWithShenandoah 120000 */ diff --git a/test/hotspot/jtreg/gc/stress/gcold/TestGCOldWithShenandoah.java b/test/hotspot/jtreg/gc/stress/gcold/TestGCOldWithShenandoah.java index 9b2eb530b2ae..2dc806545fa9 100644 --- a/test/hotspot/jtreg/gc/stress/gcold/TestGCOldWithShenandoah.java +++ b/test/hotspot/jtreg/gc/stress/gcold/TestGCOldWithShenandoah.java @@ -1,26 +1,26 @@ /* -* Copyright (c) 2016, 2024, Oracle and/or its affiliates. All rights reserved. -* Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. -* -* This code is free software; you can redistribute it and/or modify it -* under the terms of the GNU General Public License version 2 only, as -* published by the Free Software Foundation. -* -* This code is distributed in the hope that it will be useful, but WITHOUT -* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -* version 2 for more details (a copy is included in the LICENSE file that -* accompanied this code). -* -* You should have received a copy of the GNU General Public License version -* 2 along with this work; if not, write to the Free Software Foundation, -* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. -* -* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA -* or visit www.oracle.com if you need additional information or have any -* questions. -*/ + * Copyright (c) 2016, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ package gc.stress.gcold; @@ -33,22 +33,11 @@ * * @run main/othervm/timeout=600 -Xmx384M -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:+ShenandoahDegeneratedGC -XX:+ShenandoahVerify - * gc.stress.gcold.TestGCOld 50 1 20 10 10000 - * - * @run main/othervm/timeout=600 -Xmx384M -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions - * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:-ShenandoahDegeneratedGC -XX:+ShenandoahVerify - * gc.stress.gcold.TestGCOld 50 1 20 10 10000 - * - * @run main/othervm/timeout=600 -Xmx384M -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions - * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:+ShenandoahDegeneratedGC + * -XX:+ShenandoahVerify * gc.stress.gcold.TestGCOld 50 1 20 10 10000 * * @run main/othervm/timeout=600 -Xmx384M -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:-ShenandoahDegeneratedGC * gc.stress.gcold.TestGCOld 50 1 20 10 10000 */ From 0bdcad834657ef4ed2ea2aaf6781ea394ca01970 Mon Sep 17 00:00:00 2001 From: Igor Veresov Date: Mon, 19 Oct 2026 19:14:01 +0000 Subject: [PATCH 115/169] 8394819: AOT incorrectly preserves initialization state of superinterfaces Reviewed-by: iklam, heidinga, shade, kvn --- src/hotspot/share/cds/aotArtifactFinder.cpp | 4 +++- .../appcds/resolvedConstants/AOTLinkedLambdas.java | 12 +++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/hotspot/share/cds/aotArtifactFinder.cpp b/src/hotspot/share/cds/aotArtifactFinder.cpp index 30476c96109c..41dbd2de7fa3 100644 --- a/src/hotspot/share/cds/aotArtifactFinder.cpp +++ b/src/hotspot/share/cds/aotArtifactFinder.cpp @@ -216,7 +216,9 @@ void AOTArtifactFinder::add_aot_inited_class(InstanceKlass* ik) { add_aot_inited_class(s); } - Array* interfaces = ik->local_interfaces(); + // An uninitialized direct interface may have initialized superinterfaces + // that declare default methods. Preserve their initialization state too. + Array* interfaces = ik->transitive_interfaces(); int len = interfaces->length(); for (int i = 0; i < len; i++) { InstanceKlass* intf = interfaces->at(i); diff --git a/test/hotspot/jtreg/runtime/cds/appcds/resolvedConstants/AOTLinkedLambdas.java b/test/hotspot/jtreg/runtime/cds/appcds/resolvedConstants/AOTLinkedLambdas.java index 08c9191a33c8..4fe5a9c2c6c1 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/resolvedConstants/AOTLinkedLambdas.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/resolvedConstants/AOTLinkedLambdas.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -29,6 +29,7 @@ * @requires vm.cds * @requires vm.cds.supports.aot.class.linking * @library /test/lib /test/hotspot/jtreg/runtime/cds/appcds/test-classes/ + * @modules java.base/jdk.internal.misc * @build AOTLinkedLambdas * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app.jar * AOTLinkedLambdasApp InitTracker @@ -41,6 +42,7 @@ import java.util.function.Supplier; import static java.util.stream.Collectors.*; +import jdk.internal.misc.Unsafe; import jdk.test.lib.cds.CDSOptions; import jdk.test.lib.cds.CDSTestUtils; import jdk.test.lib.cds.SimpleCDSAppTester; @@ -56,6 +58,7 @@ public static void main(String[] args) throws Exception { .classpath(appJar) .addVmArgs("-esa", // see JDK-8340836 "-XX:+AOTClassLinking", + "--add-exports=java.base/jdk.internal.misc=ALL-UNNAMED", "-Xlog:aot+resolve=trace", "-Xlog:aot+class=debug", "-Xlog:cds+class=debug") @@ -206,6 +209,13 @@ static void testClinitOrder() { // ID2 - has , but no default method ID2 id2 = () -> {}; id2.doit(); + // ID1 must be initialized even though the intervening ID2 is not. + if (Unsafe.getUnsafe().shouldBeInitialized(ID1.class)) { + throw new RuntimeException("ID1 should have been initialized"); + } + if (!Unsafe.getUnsafe().shouldBeInitialized(ID2.class)) { + throw new RuntimeException("ID2 should not have been initialized"); + } InitTracker.assertOrder("InitTracker, FooB, BarB, FooA, BarA, IA, IC"); System.out.println(ID2._dummy); InitTracker.assertOrder("InitTracker, FooB, BarB, FooA, BarA, IA, IC, ID2"); From eb356f8f7d6b8c09a2334fac6c0532ff4c41ebc8 Mon Sep 17 00:00:00 2001 From: Ioi Lam Date: Mon, 19 Oct 2026 19:49:41 +0000 Subject: [PATCH 116/169] 8391227: Deprecate -XX:AOTMode=on Reviewed-by: kvn, asmehra --- src/hotspot/share/cds/cdsConfig.cpp | 9 +++++++-- src/hotspot/share/cds/cds_globals.hpp | 3 ++- src/hotspot/share/prims/jvmtiAgent.cpp | 4 ++-- src/java.base/share/man/java.md | 13 ++++++------- .../cds/appcds/aotCache/AOTCacheConsistency.java | 4 ++-- .../aotCache/AOTCompatibleOopCompression.java | 2 +- .../runtime/cds/appcds/aotCache/AOTLoggingTag.java | 2 +- .../aotClassLinking/AOTCacheOtherDirectoryTest.java | 6 +++--- .../aotCode/AOTCodeTestUnknownExternAddress.java | 2 +- test/lib/jdk/test/lib/cds/CDSAppTester.java | 2 +- 10 files changed, 26 insertions(+), 21 deletions(-) diff --git a/src/hotspot/share/cds/cdsConfig.cpp b/src/hotspot/share/cds/cdsConfig.cpp index 278c7397f12b..083bb3a294ee 100644 --- a/src/hotspot/share/cds/cdsConfig.cpp +++ b/src/hotspot/share/cds/cdsConfig.cpp @@ -88,8 +88,8 @@ void CDSConfig::ergo_initialize() { if (is_dumping_static_archive() && !is_dumping_final_static_archive()) { // Note: -Xshare and -XX:AOTMode flags are mutually exclusive. - // - Classic workflow: -Xshare:on and -Xshare:dump cannot take effect at the same time. - // - JEP 483 workflow: -XX:AOTMode:record and -XX:AOTMode=on cannot take effect at the same time. + // - Classic CDS workflow: -Xshare:on and -Xshare:dump cannot take effect at the same time. + // - AOT workflow: -XX:AOTMode:record and -XX:AOTMode=required cannot take effect at the same time. // So we can never come to here with RequireSharedSpaces==true. assert(!RequireSharedSpaces, "sanity"); @@ -515,6 +515,11 @@ void CDSConfig::check_aotmode_off() { } void CDSConfig::check_aotmode_auto_or_on() { + if (AOTMode != nullptr && strcmp(AOTMode, "on") == 0) { + warning("Option -XX:AOTMode-on was deprecated in version 28 and will likely be " + "removed in a future release. Use option -XX:AOTMode=required instead."); + } + if (!FLAG_IS_DEFAULT(AOTConfiguration)) { vm_exit_during_initialization(err_msg("AOTConfiguration can only be used with when AOTMode is record or create (selected AOTMode = %s)", FLAG_IS_DEFAULT(AOTMode) ? "auto" : AOTMode)); diff --git a/src/hotspot/share/cds/cds_globals.hpp b/src/hotspot/share/cds/cds_globals.hpp index 17405b6c0796..d0898a39b301 100644 --- a/src/hotspot/share/cds/cds_globals.hpp +++ b/src/hotspot/share/cds/cds_globals.hpp @@ -105,7 +105,8 @@ \ product(ccstr, AOTMode, nullptr, \ "Specifies how AOTCache should be created or used. Valid values " \ - "are: off, record, create, auto, on; the default is auto") \ + "are: off, record, create, auto, required, and (deprecated) on; " \ + "the default is auto") \ constraint(AOTModeConstraintFunc, AtParse) \ \ product(ccstr, AOTConfiguration, nullptr, \ diff --git a/src/hotspot/share/prims/jvmtiAgent.cpp b/src/hotspot/share/prims/jvmtiAgent.cpp index 5fe5378995b8..82c43f64e98d 100644 --- a/src/hotspot/share/prims/jvmtiAgent.cpp +++ b/src/hotspot/share/prims/jvmtiAgent.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -578,7 +578,7 @@ static bool invoke_Agent_OnAttach(JvmtiAgent* agent, outputStream* st) { #if INCLUDE_CDS static void check_cds_dump(JvmtiAgent* agent) { if (CDSConfig::new_aot_flags_used()) { // JEP 483 - // Agents are allowed with -XX:AOTMode=record and -XX:AOTMode=on/auto. + // Agents are allowed with -XX:AOTMode=record and -XX:AOTMode=required/auto. // Agents are completely disabled when -XX:AOTMode=create (see cdsConfig.cpp) assert(!CDSConfig::is_dumping_final_static_archive(), "agents should have been disabled with -XX:AOTMode=create"); } else if (CDSConfig::is_dumping_classic_static_archive() || CDSConfig::is_dumping_dynamic_archive()) { diff --git a/src/java.base/share/man/java.md b/src/java.base/share/man/java.md index dbb8566b373d..24f8f4436c4e 100644 --- a/src/java.base/share/man/java.md +++ b/src/java.base/share/man/java.md @@ -3027,6 +3027,9 @@ they're used. : Enables the use of Java Flight Recorder (JFR) during the runtime of the application. Since JDK 8u40 this option has not been required to use JFR. +[`-XX:AOTMode=on`]{#-XX__AOTModeOn} +: Requires the AOTCache to be loaded. Use `-XX:AOTMode=required` instead. + ## Obsolete Java Options These `java` options are still accepted but ignored, and a warning is issued @@ -4161,8 +4164,8 @@ The AOT cache can be used with the following command-line options: : Specifies the location of the AOT cache. The standard extension for *cachefile* is `.aot`. This option cannot be used together with `-XX:AOTCacheOutput`. - This option is compatible with `AOTMode` settings of `on`, `create`, or `auto` (the default). - The *cachefile* is read in AOT modes `on` and `auto`, and is ignored by mode `off`. + This option is compatible with `AOTMode` settings of `required`, `create`, or `auto` (the default). + The *cachefile* is read in AOT modes `required` and `auto`, and is ignored by mode `off`. The *cachefile* is written by AOT mode `create`. In that case, this option is equivalent to `-XX:AOTCacheOutput=`*cachefile*. @@ -4182,11 +4185,7 @@ The AOT cache can be used with the following command-line options: [`-XX:AOTMode=`]{#-XX_AOTMode}*mode* : Specifies the AOT Mode for this run. - *mode* must be one of the following: `auto`, `off`, `record`, `create`, `on`, or `required`. - - Note that `on` is an alias for `required`. All discussion below about `required` - also applies to `on`. The use of `on` is discouraged. The support of `on` may be deprecated and - removed in future releases. + *mode* must be one of the following: `auto`, `off`, `record`, `create`, or `required`. - `auto`: This AOT mode is the default, and takes effect if no `-XX:AOTMode` option is present. It automatically sets the AOT mode to `record`, `required`, or `off`, as follows: diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/AOTCacheConsistency.java b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/AOTCacheConsistency.java index a48051bb6725..ba325df2e7ab 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/AOTCacheConsistency.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/AOTCacheConsistency.java @@ -59,7 +59,7 @@ public static void main(String args[]) throws Exception { // Modify each of the region individually. The production should fail to run // with these args; - String extraVMArgs[] = {"-XX:+VerifySharedSpaces", "-XX:AOTMode=on"}; + String extraVMArgs[] = {"-XX:+VerifySharedSpaces", "-XX:AOTMode=required"}; tester.setCheckExitValue(false); for (int i = 0; i < regions.length; i++) { @@ -73,4 +73,4 @@ public static void main(String args[]) throws Exception { } } } -} \ No newline at end of file +} diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/AOTCompatibleOopCompression.java b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/AOTCompatibleOopCompression.java index 0aaf2f5fb778..864f7557d8c0 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/AOTCompatibleOopCompression.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/AOTCompatibleOopCompression.java @@ -63,7 +63,7 @@ public String[] vmArgs(RunMode runMode) { if (runMode == RunMode.ASSEMBLY) { return new String[] {"-XX:+UnlockDiagnosticVMOptions", "-XX:+AOTCompatibleOopCompression"}; } else if (runMode == RunMode.PRODUCTION) { - return new String[] {"-Xlog:aot", "-XX:AOTMode=on", "-XX:HeapBaseMinAddress=0x800000000"}; + return new String[] {"-Xlog:aot", "-XX:AOTMode=required", "-XX:HeapBaseMinAddress=0x800000000"}; } else { return new String[0]; } diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/AOTLoggingTag.java b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/AOTLoggingTag.java index 864ce88cd118..86c2b420f758 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/AOTLoggingTag.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/AOTLoggingTag.java @@ -87,7 +87,7 @@ public static void main(String[] args) throws Exception { printTestCase("Production Run: errors should be printed with [aot] decoration"); pb = ProcessTools.createLimitedTestJavaProcessBuilder( "-XX:AOTCache=nosuchfile.aot", - "-XX:AOTMode=on", + "-XX:AOTMode=required", "-cp", appJar, helloClass); out = CDSTestUtils.executeAndLog(pb, "prod"); out.shouldMatch("\\[aot *\\] An error has occurred while processing the AOT cache\\. Run with -Xlog:aot for details\\."); diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/aotClassLinking/AOTCacheOtherDirectoryTest.java b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/aotClassLinking/AOTCacheOtherDirectoryTest.java index 622d99cc0679..5a03431ef012 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/aotClassLinking/AOTCacheOtherDirectoryTest.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/aotClassLinking/AOTCacheOtherDirectoryTest.java @@ -74,7 +74,7 @@ public static void main(String[] args) throws Exception { .runAOTTrainingAndAssemblyWorkflow(); // Sanity test, run from same directory - String[] cmdLine = new String[] { "-XX:-AOTClassLinking", "-XX:AOTMode=on", + String[] cmdLine = new String[] { "-XX:-AOTClassLinking", "-XX:AOTMode=required", "-XX:AOTCache=" + targetDirName + File.separator + aotCacheName + ".aot", "-Xlog:cds,aot=trace,aot+map+oops=trace:file=production1.map:none:filesize=0", "-cp", destPath.toString(), mainClass}; @@ -84,7 +84,7 @@ public static void main(String[] args) throws Exception { output.shouldContain("CodeSource = " + realPath); // At runtime, run from /target so the classpath is different but use the same JAR - String[] cmdLine2 = new String[] { "-XX:-AOTClassLinking", "-XX:AOTMode=on", + String[] cmdLine2 = new String[] { "-XX:-AOTClassLinking", "-XX:AOTMode=required", "-XX:AOTCache=" + aotCacheName + ".aot", "-Xlog:cds,aot=trace,aot+map+oops=trace:file=production2.map:none:filesize=0", "-cp", jarName, mainClass}; @@ -95,7 +95,7 @@ public static void main(String[] args) throws Exception { output.shouldContain("CodeSource = " + realPath); // At runtime, run from /target2 so the classpath is different but use the same JAR - String[] cmdLine3 = new String[] { "-XX:-AOTClassLinking", "-XX:AOTMode=on", + String[] cmdLine3 = new String[] { "-XX:-AOTClassLinking", "-XX:AOTMode=required", "-XX:AOTCache=" + targetDirName + File.separator + aotCacheName + ".aot", "-Xlog:cds,aot=trace,aot+map+oops=trace:file=production3.map:none:filesize=0", "-cp", destPath2.toString(), mainClass}; diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCode/AOTCodeTestUnknownExternAddress.java b/test/hotspot/jtreg/runtime/cds/appcds/aotCode/AOTCodeTestUnknownExternAddress.java index 6ee6b8e41688..d0437d76ef94 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/aotCode/AOTCodeTestUnknownExternAddress.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCode/AOTCodeTestUnknownExternAddress.java @@ -94,7 +94,7 @@ public static void main(String... args) throws Exception { "-XX:CompileCommand=compileonly,TestUnknownAddress::test*", "-Xcomp", "-XX:-TieredCompilation", "-Xlog:aot+codecache+init=debug", - "-XX:AOTMode=on", + "-XX:AOTMode=required", "-XX:AOTCache=" + aotCacheFile, "-cp", appJar, appClass); diff --git a/test/lib/jdk/test/lib/cds/CDSAppTester.java b/test/lib/jdk/test/lib/cds/CDSAppTester.java index d434e1c1faec..449a2674df9a 100644 --- a/test/lib/jdk/test/lib/cds/CDSAppTester.java +++ b/test/lib/jdk/test/lib/cds/CDSAppTester.java @@ -455,7 +455,7 @@ public OutputAnalyzer productionRun(String[] extraVmArgs, String[] extraAppArgs) } else if (isDynamicWorkflow()) { cmdLine = StringArrayUtils.concat(cmdLine, "-Xshare:on", "-XX:SharedArchiveFile=" + dynamicArchiveFile); } else if (isAOTWorkflow()) { - cmdLine = StringArrayUtils.concat(cmdLine, "-XX:AOTMode=on", "-XX:AOTCache=" + aotCacheFile); + cmdLine = StringArrayUtils.concat(cmdLine, "-XX:AOTMode=required", "-XX:AOTCache=" + aotCacheFile); } if (extraVmArgs != null) { From ab66258bdd708a4180c44f825de6d83449a6e1d2 Mon Sep 17 00:00:00 2001 From: David Holmes Date: Mon, 19 Oct 2026 21:23:57 +0000 Subject: [PATCH 117/169] 8394890: ProblemList the failing PKCS11 tests Reviewed-by: dcubed --- test/jdk/ProblemList.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/jdk/ProblemList.txt b/test/jdk/ProblemList.txt index 50a24e959075..634a8641e33e 100644 --- a/test/jdk/ProblemList.txt +++ b/test/jdk/ProblemList.txt @@ -456,6 +456,9 @@ sun/security/smartcardio/TestTransmit.java 8039280 generic- sun/security/pkcs11/sslecc/ClientJSSEServerJSSE.java 8316183 linux-ppc64le +sun/security/pkcs11/Provider/CheckRegistration.java 8394846 generic-all +sun/security/pkcs11/tls/TestExtendedMasterSecretHandshake.java 8394846 generic-all + ############################################################################ # jdk_sound From 6b59d436c809cdf29c4a648fb3797770a90ad25d Mon Sep 17 00:00:00 2001 From: Justus Garbe Date: Mon, 19 Oct 2026 22:03:00 +0000 Subject: [PATCH 118/169] 8352077: Fatal error: unhandled bytecode Reviewed-by: sspitsyn, lmesnik --- src/hotspot/share/prims/jvmtiEnv.cpp | 7 +- src/hotspot/share/prims/jvmtiImpl.cpp | 7 + test/hotspot/jtreg/TEST.groups | 1 + .../SetBreakpoint/BreakpointMetaspaceOOM.java | 190 ++++++++++++++++++ .../libBreakpointMetaspaceOOM.cpp | 84 ++++++++ 5 files changed, 287 insertions(+), 2 deletions(-) create mode 100644 test/hotspot/jtreg/serviceability/jvmti/SetBreakpoint/BreakpointMetaspaceOOM.java create mode 100644 test/hotspot/jtreg/serviceability/jvmti/SetBreakpoint/libBreakpointMetaspaceOOM.cpp diff --git a/src/hotspot/share/prims/jvmtiEnv.cpp b/src/hotspot/share/prims/jvmtiEnv.cpp index 6f5280d3d5b1..c89f93371715 100644 --- a/src/hotspot/share/prims/jvmtiEnv.cpp +++ b/src/hotspot/share/prims/jvmtiEnv.cpp @@ -2514,8 +2514,11 @@ JvmtiEnv::SetBreakpoint(Method* method, jlocation location) { ResourceMark rm; JvmtiBreakpoint bp(method, location); JvmtiBreakpoints& jvmti_breakpoints = JvmtiCurrentBreakpoints::get_jvmti_breakpoints(); - if (jvmti_breakpoints.set(bp) == JVMTI_ERROR_DUPLICATE) - return JVMTI_ERROR_DUPLICATE; + + jvmtiError err = (jvmtiError) jvmti_breakpoints.set(bp); + if (err != JVMTI_ERROR_NONE) { + return err; + } if (TraceJVMTICalls) { jvmti_breakpoints.print(); diff --git a/src/hotspot/share/prims/jvmtiImpl.cpp b/src/hotspot/share/prims/jvmtiImpl.cpp index 9f7b1c70b3a0..ec5b157d59c4 100644 --- a/src/hotspot/share/prims/jvmtiImpl.cpp +++ b/src/hotspot/share/prims/jvmtiImpl.cpp @@ -261,6 +261,13 @@ int JvmtiBreakpoints::set(JvmtiBreakpoint& bp) { // Ensure that bp._method is not deallocated before VM_ChangeBreakpoints::doit(). methodHandle mh(Thread::current(), bp.method()); + + // Build MethodCounters on the calling JavaThread so the breakpoint count can't be dropped + // later in the VM operation. + if (mh->get_method_counters(Thread::current()) == nullptr) { + return JVMTI_ERROR_OUT_OF_MEMORY; + } + VM_ChangeBreakpoints set_breakpoint(VM_ChangeBreakpoints::SET_BREAKPOINT, &bp); VMThread::execute(&set_breakpoint); return JVMTI_ERROR_NONE; diff --git a/test/hotspot/jtreg/TEST.groups b/test/hotspot/jtreg/TEST.groups index 05af7904a06a..b0cae5e8eb01 100644 --- a/test/hotspot/jtreg/TEST.groups +++ b/test/hotspot/jtreg/TEST.groups @@ -592,6 +592,7 @@ tier1_serviceability = \ -serviceability/jvmti/RedefineClasses/RedefineRunningMethods.java \ -serviceability/jvmti/RedefineClasses/RedefineRunningMethodsWithBacktrace.java \ -serviceability/jvmti/RedefineClasses/TestRedefineObject.java \ + -serviceability/jvmti/SetBreakpoint/BreakpointMetaspaceOOM.java \ -serviceability/sa/ClhsdbScanOops.java \ -serviceability/sa/ClhsdbJstackXcompStress.java \ -serviceability/sa/TestJmapCore.java \ diff --git a/test/hotspot/jtreg/serviceability/jvmti/SetBreakpoint/BreakpointMetaspaceOOM.java b/test/hotspot/jtreg/serviceability/jvmti/SetBreakpoint/BreakpointMetaspaceOOM.java new file mode 100644 index 000000000000..e4160491ad8c --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/SetBreakpoint/BreakpointMetaspaceOOM.java @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2026, Justus Garbe. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8352077 + * @summary A breakpoint set while metaspace is exhausted must not be left half-installed. + * @requires vm.jvmti & vm.compiler1.enabled & vm.flagless + * @library /test/lib + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/native/timeout=240 + * -agentlib:BreakpointMetaspaceOOM + * -Xbootclasspath/a:. + * -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -Xbatch + * -XX:MaxMetaspaceSize=32m + * BreakpointMetaspaceOOM + */ + +import java.lang.classfile.ClassBuilder; +import java.lang.classfile.ClassFile; +import java.lang.classfile.Label; +import java.lang.constant.ClassDesc; +import java.lang.constant.MethodTypeDesc; +import java.lang.reflect.Method; +import java.util.function.IntUnaryOperator; + +import jdk.test.lib.Asserts; +import jdk.test.whitebox.WhiteBox; + +import static java.lang.constant.ConstantDescs.*; + +public class BreakpointMetaspaceOOM { + + static final int ERR_NONE = 0; // JVMTI_ERROR_NONE + static final int ERR_OUT_OF_MEMORY = 110; // JVMTI_ERROR_OUT_OF_MEMORY + + static final int TIER_C1_PROFILED = 3; // CompLevel_full_profile (C1 with full profiling) + + static final int CALLS = 20_000; + static final int BRANCHES = 1_000; + + static native void prepare(Method m); + static native int setBreakpoint(); + static native int breakpointHits(); + + static final MethodTypeDesc APPLY_AS_INT = MethodTypeDesc.of(CD_int, CD_int); + static final ClassDesc INT_UNARY_OPERATOR = ClassDesc.of("java.util.function.IntUnaryOperator"); + + static final class GenLoader extends ClassLoader { + GenLoader() { super(BreakpointMetaspaceOOM.class.getClassLoader()); } + Class define(String name, byte[] bytes) { return defineClass(name, bytes, 0, bytes.length); } + } + + private static void noArgConstructor(ClassBuilder cb) { + cb.withMethodBody(INIT_NAME, MTD_void, ClassFile.ACC_PUBLIC, + b -> b.aload(0).invokespecial(CD_Object, INIT_NAME, MTD_void).return_()); + } + + private static byte[] incrementerBytes() { + return ClassFile.of().build(ClassDesc.of("Target"), cb -> { + cb.withSuperclass(CD_Object).withInterfaceSymbols(INT_UNARY_OPERATOR); + noArgConstructor(cb); + cb.withMethodBody("applyAsInt", APPLY_AS_INT, ClassFile.ACC_PUBLIC, + b -> b.iload(1).iconst_1().iadd().ireturn()); + }); + } + + private static byte[] branchHeavyBytes() { + return ClassFile.of().build(ClassDesc.of("Big"), cb -> { + cb.withSuperclass(CD_Object).withInterfaceSymbols(ClassDesc.of("java.lang.Runnable")); + noArgConstructor(cb); + cb.withMethodBody("run", MTD_void, ClassFile.ACC_PUBLIC, b -> { + for (int i = 0; i < BRANCHES; i++) { + Label fallThrough = b.newLabel(); + b.iconst_0().ifne(fallThrough).labelBinding(fallThrough); + } + b.return_(); + }); + }); + } + + private static byte[] fillerBytes(String name, int methodCount) { + return ClassFile.of().build(ClassDesc.of(name), cb -> { + cb.withSuperclass(CD_Object); + for (int i = 0; i < methodCount; i++) { + cb.withMethodBody("f".concat(Integer.toString(i)), MTD_void, + ClassFile.ACC_PUBLIC | ClassFile.ACC_STATIC, b -> b.return_()); + } + }); + } + + private static void exhaustMetaspace(GenLoader loader) { + int index = 0; + for (int methodCount : new int[] {1000, 100, 10, 1, 0}) { + try { + for (;;) { + String name = "Filler".concat(Integer.toString(index++)); + loader.define(name, fillerBytes(name, methodCount)); + } + } catch (OutOfMemoryError expected) { + // Arena full at this size; shrink the class and keep going. + } + } + System.out.println("Metaspace exhausted"); + } + + public static void main(String[] args) throws Exception { + WhiteBox wb = WhiteBox.getWhiteBox(); + + GenLoader loader = new GenLoader(); + IntUnaryOperator incrementer = + (IntUnaryOperator) loader.define("Target", incrementerBytes()).getDeclaredConstructor().newInstance(); + Method targetMethod = incrementer.getClass().getDeclaredMethod("applyAsInt", int.class); + Runnable branchy = + (Runnable) loader.define("Big", branchHeavyBytes()).getDeclaredConstructor().newInstance(); + + // Resolve and warm everything needed after the fill + prepare(targetMethod); + branchy.run(); + wb.isMethodCompiled(targetMethod); + fillerBytes("Warmup", 1); + Asserts.assertTrue(true); + breakpointHits(); + + // Fill a throwaway loader, kept apart from the target's, so it can be collected later + // to lift the OOM state again. + GenLoader fillers = new GenLoader(); + exhaustMetaspace(fillers); + + // Make the JIT attempt branchy's profiling allocation under the full arena, tripping + // the sticky out-of-memory state that causes the breakpoint count to be dropped. + for (int i = 0; i < CALLS; i++) { + branchy.run(); + } + + int rc = setBreakpoint(); + System.out.print("SetBreakpoint returned "); + System.out.println(rc); + + // Drop the fillers and collect them so the metaspace OOM state clears: MethodCounters + // can now be built again (with a breakpoint count of zero -- the count was lost), the + // method runs, and C1 compiles it at tier 3 over the stale 0xCA. + fillers = null; + wb.fullGC(); + System.out.println("Fillers unloaded"); + + for (int i = 0; i < CALLS; i++) { + incrementer.applyAsInt(i); + } + wb.enqueueMethodForCompilation(targetMethod, TIER_C1_PROFILED); + + // On the unfixed VM a compilation above aborts with the 0xCA crash (the call loop can + // trigger it, or the explicit request) and we never get here, so jtreg records the + // crash as a failure. Reaching this point means no crash: + if (rc == ERR_OUT_OF_MEMORY) { + // The VM reported the allocation failure and installed no breakpoint, so none may + // have fired. + Asserts.assertEquals(0, breakpointHits(), "breakpoint fired after SetBreakpoint reported OUT_OF_MEMORY"); + } else if (rc == ERR_NONE) { + // The breakpoint was installed in full. A VM that tracks it correctly refuses to + // compile the method, and every interpreted call must have hit it. + Asserts.assertFalse(wb.isMethodCompiled(targetMethod), "a method with a live breakpoint was compiled"); + Asserts.assertEquals(CALLS, breakpointHits(), "breakpoint hit count did not match the number of calls"); + } else { + Asserts.fail("SetBreakpoint returned an unexpected JVMTI error code. rc=" + rc); + } + } +} diff --git a/test/hotspot/jtreg/serviceability/jvmti/SetBreakpoint/libBreakpointMetaspaceOOM.cpp b/test/hotspot/jtreg/serviceability/jvmti/SetBreakpoint/libBreakpointMetaspaceOOM.cpp new file mode 100644 index 000000000000..8a361ddb7d52 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/SetBreakpoint/libBreakpointMetaspaceOOM.cpp @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2026, Justus Garbe. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include +#include "jvmti.h" +#include "jvmti_common.hpp" + +extern "C" { + +static jvmtiEnv* jvmti = nullptr; +static jmethodID target = nullptr; +static std::atomic hits{0}; + +static void JNICALL +Breakpoint(jvmtiEnv* env, JNIEnv* jni, jthread thread, jmethodID method, jlocation location) { + hits++; +} + +JNIEXPORT jint JNICALL +Agent_OnLoad(JavaVM* vm, char* options, void* reserved) { + if (vm->GetEnv((void**)&jvmti, JVMTI_VERSION_1_0) != JNI_OK) { + LOG("Agent_OnLoad: GetEnv failed"); + return JNI_ERR; + } + + jvmtiCapabilities caps; + memset(&caps, 0, sizeof(caps)); + caps.can_generate_breakpoint_events = 1; + check_jvmti_error(jvmti->AddCapabilities(&caps), "AddCapabilities"); + + jvmtiEventCallbacks callbacks; + memset(&callbacks, 0, sizeof(callbacks)); + callbacks.Breakpoint = Breakpoint; + check_jvmti_error(jvmti->SetEventCallbacks(&callbacks, sizeof(callbacks)), + "SetEventCallbacks"); + + check_jvmti_error(jvmti->SetEventNotificationMode(JVMTI_ENABLE, + JVMTI_EVENT_BREAKPOINT, + nullptr), + "SetEventNotificationMode"); + + return JNI_OK; +} + +// Resolve the jmethodID while metaspace still has room: FromReflectedMethod can allocate, +// and the whole point of the test is to call setBreakpoint() once metaspace is exhausted. +JNIEXPORT void JNICALL +Java_BreakpointMetaspaceOOM_prepare(JNIEnv* env, jclass cls, jobject method) { + target = env->FromReflectedMethod(method); +} + +// Returns the raw JVMTI error code so the Java side can see exactly what SetBreakpoint +// reported under the out-of-memory condition. +JNIEXPORT jint JNICALL +Java_BreakpointMetaspaceOOM_setBreakpoint(JNIEnv* env, jclass cls) { + return jvmti->SetBreakpoint(target, 0); +} + +JNIEXPORT jint JNICALL +Java_BreakpointMetaspaceOOM_breakpointHits(JNIEnv* env, jclass cls) { + return hits; +} + +} From da7dfc1b50aafa206aab051fd646c3b26b0d3940 Mon Sep 17 00:00:00 2001 From: Ioi Lam Date: Tue, 20 Oct 2026 05:01:32 +0000 Subject: [PATCH 119/169] 8394731: Rename HeapShared to AOTHeap Reviewed-by: kvn, asmehra --- src/hotspot/share/cds/aotArtifactFinder.cpp | 24 +- src/hotspot/share/cds/aotArtifactFinder.hpp | 8 +- src/hotspot/share/cds/aotCacheAccess.cpp | 6 +- src/hotspot/share/cds/aotClassInitializer.cpp | 3 +- src/hotspot/share/cds/aotClassLinker.cpp | 6 +- .../share/cds/aotConstantPoolResolver.cpp | 4 +- .../share/cds/{heapShared.cpp => aotHeap.cpp} | 332 +++++++++--------- .../share/cds/{heapShared.hpp => aotHeap.hpp} | 18 +- ...apShared.inline.hpp => aotHeap.inline.hpp} | 28 +- .../share/cds/aotLinkedClassBulkLoader.cpp | 6 +- src/hotspot/share/cds/aotMappedHeapLoader.cpp | 1 - src/hotspot/share/cds/aotMappedHeapWriter.cpp | 22 +- src/hotspot/share/cds/aotMappedHeapWriter.hpp | 8 +- src/hotspot/share/cds/aotMetaspace.cpp | 30 +- src/hotspot/share/cds/aotModules.cpp | 24 +- src/hotspot/share/cds/aotOopChecker.cpp | 6 +- .../share/cds/aotReferenceObjSupport.cpp | 6 +- src/hotspot/share/cds/aotStreamedHeap.hpp | 2 +- .../share/cds/aotStreamedHeapLoader.cpp | 6 +- .../share/cds/aotStreamedHeapWriter.cpp | 20 +- .../share/cds/aotStreamedHeapWriter.hpp | 1 - src/hotspot/share/cds/aotThread.cpp | 3 +- src/hotspot/share/cds/archiveBuilder.cpp | 8 +- src/hotspot/share/cds/archiveUtils.cpp | 1 - src/hotspot/share/cds/cdsConfig.cpp | 8 +- src/hotspot/share/cds/cdsEnumKlass.cpp | 14 +- src/hotspot/share/cds/cdsHeapVerifier.cpp | 14 +- src/hotspot/share/cds/cdsHeapVerifier.hpp | 10 +- src/hotspot/share/cds/dynamicArchive.cpp | 1 - src/hotspot/share/cds/filemap.cpp | 26 +- src/hotspot/share/cds/filemap.hpp | 1 - src/hotspot/share/ci/ciEnv.cpp | 4 +- src/hotspot/share/classfile/classLoader.cpp | 6 +- .../share/classfile/classLoaderData.cpp | 4 +- src/hotspot/share/classfile/javaClasses.cpp | 12 +- src/hotspot/share/classfile/javaClasses.hpp | 2 +- src/hotspot/share/classfile/moduleEntry.cpp | 10 +- src/hotspot/share/classfile/stringTable.cpp | 20 +- .../share/classfile/systemDictionary.cpp | 6 +- .../classfile/systemDictionaryShared.cpp | 4 +- src/hotspot/share/classfile/vmClasses.cpp | 8 +- src/hotspot/share/code/aotCodeCache.cpp | 1 - .../shared/stringdedup/stringDedupTable.cpp | 8 +- src/hotspot/share/memory/universe.cpp | 18 +- src/hotspot/share/memory/universe.hpp | 2 +- src/hotspot/share/oops/constantPool.cpp | 12 +- src/hotspot/share/oops/cpCache.cpp | 10 +- src/hotspot/share/oops/instanceKlass.cpp | 8 +- src/hotspot/share/oops/klass.cpp | 12 +- src/hotspot/share/prims/jvm.cpp | 4 +- src/hotspot/share/prims/whitebox.cpp | 1 - src/hotspot/share/runtime/threads.cpp | 12 +- .../java/lang/invoke/StringConcatFactory.java | 4 +- .../cacheObject/VerifyArchivedFields.java | 4 +- 54 files changed, 405 insertions(+), 414 deletions(-) rename src/hotspot/share/cds/{heapShared.cpp => aotHeap.cpp} (89%) rename src/hotspot/share/cds/{heapShared.hpp => aotHeap.hpp} (97%) rename src/hotspot/share/cds/{heapShared.inline.hpp => aotHeap.inline.hpp} (82%) diff --git a/src/hotspot/share/cds/aotArtifactFinder.cpp b/src/hotspot/share/cds/aotArtifactFinder.cpp index 41dbd2de7fa3..90009271fb45 100644 --- a/src/hotspot/share/cds/aotArtifactFinder.cpp +++ b/src/hotspot/share/cds/aotArtifactFinder.cpp @@ -25,10 +25,10 @@ #include "cds/aotArtifactFinder.hpp" #include "cds/aotClassInitializer.hpp" #include "cds/aotClassLinker.hpp" +#include "cds/aotHeap.hpp" #include "cds/aotLogging.hpp" #include "cds/aotReferenceObjSupport.hpp" #include "cds/dumpTimeClassInfo.inline.hpp" -#include "cds/heapShared.hpp" #include "cds/lambdaProxyClassDictionary.hpp" #include "cds/regeneratedClasses.hpp" #include "classfile/systemDictionaryShared.hpp" @@ -44,7 +44,7 @@ static GrowableArrayCHeap* _all_cached_classes = nullptr; // This is a stack that tracks all the AOT-inited classes that are waiting to be passed -// to HeapShared::copy_and_rescan_aot_inited_mirror(). +// to AOTHeap::copy_and_rescan_aot_inited_mirror(). static GrowableArrayCHeap* _pending_aot_inited_classes = nullptr; static const int TABLE_SIZE = 15889; // prime number @@ -99,17 +99,17 @@ void AOTArtifactFinder::find_artifacts() { BasicType bt = (BasicType)i; if (!is_reference_type(bt)) { oop orig_mirror = Universe::java_mirror(bt); - oop scratch_mirror = HeapShared::scratch_java_mirror(bt); - HeapShared::scan_java_mirror(orig_mirror); + oop scratch_mirror = AOTHeap::scratch_java_mirror(bt); + AOTHeap::scan_java_mirror(orig_mirror); log_trace(aot, heap, mirror)( "Archived %s mirror object from " PTR_FORMAT, type2name(bt), p2i(scratch_mirror)); - Universe::set_archived_basic_type_mirror_index(bt, HeapShared::append_root(scratch_mirror)); + Universe::set_archived_basic_type_mirror_index(bt, AOTHeap::append_root(scratch_mirror)); } } // Universe::fillerArrayKlass() isn't in the class hierarchy, so handle it specially. - HeapShared::scan_java_mirror(Universe::fillerArrayKlass()->java_mirror()); + AOTHeap::scan_java_mirror(Universe::fillerArrayKlass()->java_mirror()); } #endif @@ -121,7 +121,7 @@ void AOTArtifactFinder::find_artifacts() { // This class is loading only by AOT tooling (not as part of the app's training run). // Skip this class for now, but it might be added later if // - One of its subtypes is included - // - One of its instances is found by HeapShared. + // - One of its instances is found by AOTHeap. skip = true; } } @@ -157,7 +157,7 @@ void AOTArtifactFinder::find_artifacts() { if (CDSConfig::is_dumping_aot_linked_classes()) { while (_pending_aot_inited_classes->length() > 0) { InstanceKlass* ik = _pending_aot_inited_classes->pop(); - HeapShared::copy_and_rescan_aot_inited_mirror(ik); + AOTHeap::copy_and_rescan_aot_inited_mirror(ik); } } #endif @@ -184,7 +184,7 @@ void AOTArtifactFinder::find_artifacts() { void AOTArtifactFinder::start_scanning_for_oops() { #if INCLUDE_CDS_JAVA_HEAP if (CDSConfig::is_dumping_heap()) { - HeapShared::start_scanning_for_oops(); + AOTHeap::start_scanning_for_oops(); } #endif } @@ -192,7 +192,7 @@ void AOTArtifactFinder::start_scanning_for_oops() { void AOTArtifactFinder::end_scanning_for_oops() { #if INCLUDE_CDS_JAVA_HEAP if (CDSConfig::is_dumping_heap()) { - HeapShared::end_scanning_for_oops(); + AOTHeap::end_scanning_for_oops(); } #endif } @@ -308,7 +308,7 @@ void AOTArtifactFinder::add_cached_class(Klass* k) { void AOTArtifactFinder::scan_oops_in_instance_class(InstanceKlass* ik) { #if INCLUDE_CDS_JAVA_HEAP if (CDSConfig::is_dumping_heap()) { - HeapShared::scan_java_class(ik); + AOTHeap::scan_java_class(ik); scan_oops_in_array_class(ik->array_klasses()); } #endif @@ -318,7 +318,7 @@ void AOTArtifactFinder::scan_oops_in_array_class(ArrayKlass* ak) { #if INCLUDE_CDS_JAVA_HEAP if (CDSConfig::is_dumping_heap()) { while (ak != nullptr) { - HeapShared::scan_java_class(ak); + AOTHeap::scan_java_class(ak); ak = ak->array_klass_or_null(); } } diff --git a/src/hotspot/share/cds/aotArtifactFinder.hpp b/src/hotspot/share/cds/aotArtifactFinder.hpp index 50057b6caee6..25aa64f3639b 100644 --- a/src/hotspot/share/cds/aotArtifactFinder.hpp +++ b/src/hotspot/share/cds/aotArtifactFinder.hpp @@ -39,10 +39,10 @@ class TypeArrayKlass; // It also decides what Klasses must be cached in aot-initialized state. // // ArchiveBuilder uses [1] as roots to scan for all MetaspaceObjs that need to be cached. -// HeapShared uses [2] to create an image of the archived heap. +// AOTHeap uses [2] to create an image of the archived heap. // // [1] is stored in _all_cached_classes in aotArtifactFinder.cpp. -// [2] is stored in HeapShared::archived_object_cache(). +// [2] is stored in AOTHeap::archived_object_cache(). // // Although many Klasses and heap objects are created in the assembly phase, we only store a subset of them into // the AOT cache. For example: @@ -68,9 +68,9 @@ class TypeArrayKlass; // turn discover more Klasses and java heap objects. // - The scanning continues until we reach a steady state. // -// Note1: See TODO comments in HeapShared::archive_object() for exceptions to this rule. +// Note1: See TODO comments in AOTHeap::archive_object() for exceptions to this rule. // -// Note2: The scanning of Java objects is done in heapShared.cpp. Please see calls into the HeapShared class +// Note2: The scanning of Java objects is done in heapShared.cpp. Please see calls into the AOTHeap class // from AOTArtifactFinder. class AOTArtifactFinder : AllStatic { diff --git a/src/hotspot/share/cds/aotCacheAccess.cpp b/src/hotspot/share/cds/aotCacheAccess.cpp index b8d853792dc7..6ae52d9d03e4 100644 --- a/src/hotspot/share/cds/aotCacheAccess.cpp +++ b/src/hotspot/share/cds/aotCacheAccess.cpp @@ -23,10 +23,10 @@ */ #include "cds/aotCacheAccess.hpp" +#include "cds/aotHeap.hpp" #include "cds/archiveBuilder.hpp" #include "cds/cdsConfig.hpp" #include "cds/filemap.hpp" -#include "cds/heapShared.hpp" #include "classfile/stringTable.hpp" #include "compiler/compiler_globals.hpp" #include "logging/log.hpp" @@ -99,11 +99,11 @@ Metadata* AOTCacheAccess::try_narrow_ptr_to_metadata(narrowPtr narrowp, size_t s #if INCLUDE_CDS_JAVA_HEAP int AOTCacheAccess::get_archived_object_permanent_index(oop obj) { - return HeapShared::get_root_index(obj); // -1 if obj is not a root. + return AOTHeap::get_root_index(obj); // -1 if obj is not a root. } oop AOTCacheAccess::get_archived_object(int permanent_index) { - oop o = HeapShared::get_root(permanent_index); + oop o = AOTHeap::get_root(permanent_index); assert(oopDesc::is_oop_or_null(o), "sanity"); return o; } diff --git a/src/hotspot/share/cds/aotClassInitializer.cpp b/src/hotspot/share/cds/aotClassInitializer.cpp index 7f39d35bd8d6..62076931e69f 100644 --- a/src/hotspot/share/cds/aotClassInitializer.cpp +++ b/src/hotspot/share/cds/aotClassInitializer.cpp @@ -26,7 +26,6 @@ #include "cds/aotLinkedClassBulkLoader.hpp" #include "cds/archiveBuilder.hpp" #include "cds/cdsConfig.hpp" -#include "cds/heapShared.hpp" #include "cds/regeneratedClasses.hpp" #include "classfile/symbolTable.hpp" #include "classfile/systemDictionaryShared.hpp" @@ -99,7 +98,7 @@ bool AOTClassInitializer::can_archive_initialized_mirror(InstanceKlass* ik) { // // When CDSConfig::is_dumping_aot_linked_classes is enabled, // AOTArtifactFinder::find_artifacts() finds the classes of all - // heap objects that are reachable from HeapShared::_run_time_special_subgraph, + // heap objects that are reachable from AOTHeap::_run_time_special_subgraph, // and mark these classes as aot-inited. This preserves the initialized // mirrors of these classes, and their methods are NOT executed // at runtime. See aotArtifactFinder.hpp for more info. diff --git a/src/hotspot/share/cds/aotClassLinker.cpp b/src/hotspot/share/cds/aotClassLinker.cpp index 0ed17dd67461..94e97820e58c 100644 --- a/src/hotspot/share/cds/aotClassLinker.cpp +++ b/src/hotspot/share/cds/aotClassLinker.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -24,11 +24,11 @@ #include "cds/aotClassLinker.hpp" #include "cds/aotConstantPoolResolver.hpp" +#include "cds/aotHeap.hpp" #include "cds/aotLinkedClassTable.hpp" #include "cds/archiveBuilder.hpp" #include "cds/archiveUtils.inline.hpp" #include "cds/cdsConfig.hpp" -#include "cds/heapShared.hpp" #include "cds/lambdaFormInvokers.inline.hpp" #include "classfile/classLoader.hpp" #include "classfile/dictionary.hpp" @@ -145,7 +145,7 @@ bool AOTClassLinker::try_add_candidate(InstanceKlass* ik) { if (!CDSConfig::is_dumping_method_handles()) { return false; } - if (HeapShared::is_lambda_proxy_klass(ik)) { + if (AOTHeap::is_lambda_proxy_klass(ik)) { InstanceKlass* nest_host = ik->nest_host_not_null(); if (!try_add_candidate(nest_host)) { ResourceMark rm; diff --git a/src/hotspot/share/cds/aotConstantPoolResolver.cpp b/src/hotspot/share/cds/aotConstantPoolResolver.cpp index 4add165e1434..a13785057a4e 100644 --- a/src/hotspot/share/cds/aotConstantPoolResolver.cpp +++ b/src/hotspot/share/cds/aotConstantPoolResolver.cpp @@ -24,9 +24,9 @@ #include "cds/aotClassLinker.hpp" #include "cds/aotConstantPoolResolver.hpp" +#include "cds/aotHeap.hpp" #include "cds/archiveBuilder.hpp" #include "cds/cdsConfig.hpp" -#include "cds/heapShared.hpp" #include "cds/lambdaFormInvokers.inline.hpp" #include "classfile/systemDictionary.hpp" #include "classfile/systemDictionaryShared.hpp" @@ -165,7 +165,7 @@ void AOTConstantPoolResolver::preresolve_string_cp_entries(InstanceKlass* ik, TR if (LambdaFormInvokers::may_be_regenerated_class(ik->name())) { eager_resolve = true; } - if (ik->is_hidden() && HeapShared::is_archivable_hidden_klass(ik)) { + if (ik->is_hidden() && AOTHeap::is_archivable_hidden_klass(ik)) { eager_resolve = true; } diff --git a/src/hotspot/share/cds/heapShared.cpp b/src/hotspot/share/cds/aotHeap.cpp similarity index 89% rename from src/hotspot/share/cds/heapShared.cpp rename to src/hotspot/share/cds/aotHeap.cpp index 588c2b6f4d0e..2737e0f2165a 100644 --- a/src/hotspot/share/cds/heapShared.cpp +++ b/src/hotspot/share/cds/aotHeap.cpp @@ -26,6 +26,7 @@ #include "cds/aotClassInitializer.hpp" #include "cds/aotClassLocation.hpp" #include "cds/aotCompressedPointers.hpp" +#include "cds/aotHeap.inline.hpp" #include "cds/aotLogging.hpp" #include "cds/aotMappedHeapLoader.hpp" #include "cds/aotMappedHeapWriter.hpp" @@ -40,7 +41,6 @@ #include "cds/cdsConfig.hpp" #include "cds/cdsEnumKlass.hpp" #include "cds/cdsHeapVerifier.hpp" -#include "cds/heapShared.inline.hpp" #include "cds/regeneratedClasses.hpp" #include "classfile/classLoaderData.hpp" #include "classfile/javaClasses.inline.hpp" @@ -100,13 +100,13 @@ struct ArchivableStaticFieldInfo { } }; -HeapArchiveMode HeapShared::_heap_load_mode = HeapArchiveMode::_uninitialized; -HeapArchiveMode HeapShared::_heap_write_mode = HeapArchiveMode::_uninitialized; +HeapArchiveMode AOTHeap::_heap_load_mode = HeapArchiveMode::_uninitialized; +HeapArchiveMode AOTHeap::_heap_write_mode = HeapArchiveMode::_uninitialized; -size_t HeapShared::_alloc_count[HeapShared::ALLOC_STAT_SLOTS]; -size_t HeapShared::_alloc_size[HeapShared::ALLOC_STAT_SLOTS]; -size_t HeapShared::_total_obj_count; -size_t HeapShared::_total_obj_size; +size_t AOTHeap::_alloc_count[AOTHeap::ALLOC_STAT_SLOTS]; +size_t AOTHeap::_alloc_size[AOTHeap::ALLOC_STAT_SLOTS]; +size_t AOTHeap::_total_obj_count; +size_t AOTHeap::_total_obj_size; #ifdef ASSERT // All classes that have at least one instance in the cached heap. @@ -140,11 +140,11 @@ static ArchivableStaticFieldInfo archive_subgraph_entry_fields[] = { {nullptr, nullptr}, }; -KlassSubGraphInfo* HeapShared::_dump_time_special_subgraph; -ArchivedKlassSubGraphInfoRecord* HeapShared::_run_time_special_subgraph; -GrowableArrayCHeap* HeapShared::_pending_roots = nullptr; -OopHandle HeapShared::_scratch_basic_type_mirrors[T_VOID+1]; -MetaspaceObjToOopHandleTable* HeapShared::_scratch_objects_table = nullptr; +KlassSubGraphInfo* AOTHeap::_dump_time_special_subgraph; +ArchivedKlassSubGraphInfoRecord* AOTHeap::_run_time_special_subgraph; +GrowableArrayCHeap* AOTHeap::_pending_roots = nullptr; +OopHandle AOTHeap::_scratch_basic_type_mirrors[T_VOID+1]; +MetaspaceObjToOopHandleTable* AOTHeap::_scratch_objects_table = nullptr; static GrowableArray* _dumptime_resolved_methods = nullptr; static Array* _runtime_resolved_methods = nullptr; @@ -157,7 +157,7 @@ static bool is_subgraph_root_class_of(ArchivableStaticFieldInfo fields[], Instan return false; } -bool HeapShared::is_subgraph_root_class(InstanceKlass* ik) { +bool AOTHeap::is_subgraph_root_class(InstanceKlass* ik) { assert(CDSConfig::is_dumping_heap(), "dump-time only"); if (CDSConfig::is_dumping_klass_subgraphs()) { // Legacy CDS archive support (to be deprecated) @@ -167,7 +167,7 @@ bool HeapShared::is_subgraph_root_class(InstanceKlass* ik) { } } -oop HeapShared::CachedOopInfo::orig_referrer() const { +oop AOTHeap::CachedOopInfo::orig_referrer() const { return _orig_referrer.resolve(); } @@ -175,7 +175,7 @@ oop HeapShared::CachedOopInfo::orig_referrer() const { // while copying the oops into the AOT heap region. We don't want to // have any side effects during the copying, so we avoid calling // p->identity_hash() which can update the object header. -unsigned HeapShared::oop_address_hash(oop const& p) { +unsigned AOTHeap::oop_address_hash(oop const& p) { assert(SafepointSynchronize::is_at_safepoint() || JavaThread::current()->is_in_no_safepoint_scope(), "sanity"); return primitive_hash(cast_from_oop(p)); @@ -188,7 +188,7 @@ unsigned HeapShared::oop_address_hash(oop const& p) { // copying the objects into the cache. This will allow the hashcode to be // dynamically and randomly computed in each production, which generally // desirable to make the hashcodes more random between runs. -unsigned HeapShared::archived_object_cache_hash(OopHandle const& oh) { +unsigned AOTHeap::archived_object_cache_hash(OopHandle const& oh) { oop o = oh.resolve(); if (o == nullptr) { return 0; @@ -208,7 +208,7 @@ unsigned HeapShared::archived_object_cache_hash(OopHandle const& oh) { } } -bool HeapShared::oop_handle_equals(const OopHandle& a, const OopHandle& b) { +bool AOTHeap::oop_handle_equals(const OopHandle& a, const OopHandle& b) { return a.resolve() == b.resolve(); } @@ -234,7 +234,7 @@ static void reset_states(oop obj, TRAPS) { } } -void HeapShared::reset_archived_object_states(TRAPS) { +void AOTHeap::reset_archived_object_states(TRAPS) { assert(CDSConfig::is_dumping_heap(), "dump-time only"); log_debug(aot)("Resetting platform loader"); reset_states(SystemDictionary::java_platform_loader(), CHECK); @@ -259,7 +259,7 @@ void HeapShared::reset_archived_object_states(TRAPS) { reset_states(boot_loader(), CHECK); } -void HeapShared::ensure_determinism(TRAPS) { +void AOTHeap::ensure_determinism(TRAPS) { TempNewSymbol class_name = SymbolTable::new_symbol("jdk/internal/util/WeakReferenceKey"); TempNewSymbol method_name = SymbolTable::new_symbol("ensureDeterministicAOTCache"); @@ -276,22 +276,22 @@ void HeapShared::ensure_determinism(TRAPS) { assert(result.get_jboolean() == false, "sanity"); } -void HeapShared::prepare_for_archiving(TRAPS) { +void AOTHeap::prepare_for_archiving(TRAPS) { reset_archived_object_states(CHECK); ensure_determinism(CHECK); } -HeapShared::ArchivedObjectCache* HeapShared::_archived_object_cache = nullptr; +AOTHeap::ArchivedObjectCache* AOTHeap::_archived_object_cache = nullptr; // Controls the hashing method for the _archived_object_cache. // Changes from false to true once, after all objects are copied, // inside make_archived_object_cache_gc_safe(). // See archived_object_cache_hash() for more details. -bool HeapShared::_use_identity_hash_for_archived_object_cache = false; +bool AOTHeap::_use_identity_hash_for_archived_object_cache = false; -bool HeapShared::is_archived_heap_in_use() { - if (HeapShared::is_loading()) { - if (HeapShared::is_loading_streaming_mode()) { +bool AOTHeap::is_archived_heap_in_use() { + if (AOTHeap::is_loading()) { + if (AOTHeap::is_loading_streaming_mode()) { return AOTStreamedHeapLoader::is_in_use(); } else { return AOTMappedHeapLoader::is_in_use(); @@ -301,7 +301,7 @@ bool HeapShared::is_archived_heap_in_use() { return false; } -bool HeapShared::can_use_archived_heap() { +bool AOTHeap::can_use_archived_heap() { FileMapInfo* static_mapinfo = FileMapInfo::current_info(); if (static_mapinfo == nullptr) { return false; @@ -319,34 +319,34 @@ bool HeapShared::can_use_archived_heap() { return true; } -bool HeapShared::is_too_large_to_archive(size_t size) { - if (HeapShared::is_writing_streaming_mode()) { +bool AOTHeap::is_too_large_to_archive(size_t size) { + if (AOTHeap::is_writing_streaming_mode()) { return false; } else { return AOTMappedHeapWriter::is_too_large_to_archive(size); } } -bool HeapShared::is_too_large_to_archive(oop obj) { - if (HeapShared::is_writing_streaming_mode()) { +bool AOTHeap::is_too_large_to_archive(oop obj) { + if (AOTHeap::is_writing_streaming_mode()) { return false; } else { return AOTMappedHeapWriter::is_too_large_to_archive(obj); } } -bool HeapShared::is_string_too_large_to_archive(oop string) { +bool AOTHeap::is_string_too_large_to_archive(oop string) { typeArrayOop value = java_lang_String::value_no_keepalive(string); return is_too_large_to_archive(value); } -void HeapShared::initialize_loading_mode(HeapArchiveMode mode) { +void AOTHeap::initialize_loading_mode(HeapArchiveMode mode) { assert(_heap_load_mode == HeapArchiveMode::_uninitialized, "already set?"); assert(mode != HeapArchiveMode::_uninitialized, "sanity"); _heap_load_mode = mode; }; -void HeapShared::initialize_writing_mode() { +void AOTHeap::initialize_writing_mode() { assert(!FLAG_IS_ERGO(AOTStreamableObjects), "Should not have been ergonomically set yet"); if (!CDSConfig::is_dumping_archive()) { @@ -383,14 +383,14 @@ void HeapShared::initialize_writing_mode() { } } -void HeapShared::initialize_streaming() { +void AOTHeap::initialize_streaming() { assert(is_loading_streaming_mode(), "shouldn't call this"); if (can_use_archived_heap()) { AOTStreamedHeapLoader::initialize(); } } -void HeapShared::enable_gc() { +void AOTHeap::enable_gc() { #ifdef ASSERT // At this point, a GC may start and will be able to see some or all // of the cached oops. The class of each oop seen by the GC must have @@ -411,26 +411,26 @@ void HeapShared::enable_gc() { } } -void HeapShared::materialize_thread_object() { +void AOTHeap::materialize_thread_object() { if (AOTStreamedHeapLoader::is_in_use()) { AOTStreamedHeapLoader::materialize_thread_object(); } } -void HeapShared::archive_interned_string(oop string) { - assert(HeapShared::is_writing_mapping_mode(), "Only used by this mode"); +void AOTHeap::archive_interned_string(oop string) { + assert(AOTHeap::is_writing_mapping_mode(), "Only used by this mode"); bool success = archive_reachable_objects_from(1, _dump_time_special_subgraph, string); assert(success, "shared strings array must not point to arrays or strings that are too large to archive"); } -void HeapShared::update_scratch_mirror_field(oop scratch_m, int field_offset, oop new_field_obj) { +void AOTHeap::update_scratch_mirror_field(oop scratch_m, int field_offset, oop new_field_obj) { archive_updated_mirror_field(scratch_m->obj_field(field_offset), new_field_obj); scratch_m->obj_field_put(field_offset, new_field_obj); } // It's possible for a scratch mirror to be scanned before one of its oop fields (such as acmp_maps) is // updated. When this happens, we must ensure that the new oop is also archived. -void HeapShared::archive_updated_mirror_field(oop old_field_obj, oop new_field_obj) { +void AOTHeap::archive_updated_mirror_field(oop old_field_obj, oop new_field_obj) { if (old_field_obj != new_field_obj) { assert(old_field_obj == nullptr, "cannot update a non-null oop field that might have already been scanned"); assert(new_field_obj != nullptr, "must be"); @@ -439,9 +439,9 @@ void HeapShared::archive_updated_mirror_field(oop old_field_obj, oop new_field_o } } -void HeapShared::finalize_initialization(FileMapInfo* static_mapinfo) { - if (HeapShared::is_loading()) { - if (HeapShared::is_loading_streaming_mode()) { +void AOTHeap::finalize_initialization(FileMapInfo* static_mapinfo) { + if (AOTHeap::is_loading()) { + if (AOTHeap::is_loading_streaming_mode()) { // Heap initialization can be done only after vtables are initialized by ReadClosure. AOTStreamedHeapLoader::finish_initialization(static_mapinfo); } else { @@ -452,7 +452,7 @@ void HeapShared::finalize_initialization(FileMapInfo* static_mapinfo) { } } -void HeapShared::make_archived_object_cache_gc_safe() { +void AOTHeap::make_archived_object_cache_gc_safe() { ArchivedObjectCache* new_cache = new (mtClass)ArchivedObjectCache(INITIAL_TABLE_SIZE, MAX_TABLE_SIZE); // It's safe to change the behavior of the hash function now, because iterate_all() @@ -487,19 +487,19 @@ void HeapShared::make_archived_object_cache_gc_safe() { _archived_object_cache = new_cache; } -HeapShared::CachedOopInfo* HeapShared::get_cached_oop_info(oop obj) { +AOTHeap::CachedOopInfo* AOTHeap::get_cached_oop_info(oop obj) { OopHandle oh(Universe::vm_global(), obj); CachedOopInfo* result = _archived_object_cache->get(oh); oh.release(Universe::vm_global()); return result; } -bool HeapShared::has_been_archived(oop obj) { +bool AOTHeap::has_been_archived(oop obj) { assert(CDSConfig::is_dumping_heap(), "dump-time only"); return get_cached_oop_info(obj) != nullptr; } -int HeapShared::append_root(oop obj) { +int AOTHeap::append_root(oop obj) { assert(SafepointSynchronize::is_at_safepoint(), "sanity"); assert(CDSConfig::is_dumping_heap(), "dump-time only"); assert(_pending_roots != nullptr, "sanity"); @@ -527,7 +527,7 @@ int HeapShared::append_root(oop obj) { } } -int HeapShared::add_to_pending_roots(oop obj) { +int AOTHeap::add_to_pending_roots(oop obj) { if (_pending_roots->length() == (int)max_jint) { // This should never happen, but just in case we have HUGE caches ... AOTMetaspace::unrecoverable_writing_error("too many pending roots"); @@ -536,7 +536,7 @@ int HeapShared::add_to_pending_roots(oop obj) { return _pending_roots->append(obj); } -int HeapShared::get_root_index(oop obj) { +int AOTHeap::get_root_index(oop obj) { if (java_lang_Class::is_instance(obj)) { obj = scratch_java_mirror(obj); } @@ -558,15 +558,15 @@ int HeapShared::get_root_index(oop obj) { return -1; } -oop HeapShared::get_root(int index, bool clear) { +oop AOTHeap::get_root(int index, bool clear) { assert(index >= 0, "sanity"); assert(is_archived_heap_in_use(), "getting roots into heap that is not used"); oop result; - if (HeapShared::is_loading_streaming_mode()) { + if (AOTHeap::is_loading_streaming_mode()) { result = AOTStreamedHeapLoader::get_root(index); } else { - assert(HeapShared::is_loading_mapping_mode(), "must be"); + assert(AOTHeap::is_loading_mapping_mode(), "must be"); result = AOTMappedHeapLoader::get_root(index); } @@ -577,13 +577,13 @@ oop HeapShared::get_root(int index, bool clear) { return result; } -void HeapShared::finish_materialize_objects() { +void AOTHeap::finish_materialize_objects() { if (AOTStreamedHeapLoader::is_in_use()) { AOTStreamedHeapLoader::finish_materialize_objects(); } } -void HeapShared::clear_root(int index) { +void AOTHeap::clear_root(int index) { if (CDSConfig::is_using_aot_linked_classes()) { // When AOT linked classes are in use, all roots will be in use all // the time, there's no benefit for clearing the roots. Also, we @@ -597,16 +597,16 @@ void HeapShared::clear_root(int index) { if (log_is_enabled(Debug, aot, heap)) { log_debug(aot, heap)("Clearing root %d: was %zu", index, p2i(get_root(index, false /* clear */))); } - if (HeapShared::is_loading_streaming_mode()) { + if (AOTHeap::is_loading_streaming_mode()) { AOTStreamedHeapLoader::clear_root(index); } else { - assert(HeapShared::is_loading_mapping_mode(), "must be"); + assert(AOTHeap::is_loading_mapping_mode(), "must be"); AOTMappedHeapLoader::clear_root(index); } } } -bool HeapShared::archive_object(oop obj, oop referrer, KlassSubGraphInfo* subgraph_info) { +bool AOTHeap::archive_object(oop obj, oop referrer, KlassSubGraphInfo* subgraph_info) { assert(CDSConfig::is_dumping_heap(), "dump-time only"); assert(!obj->is_stackChunk(), "do not archive stack chunks"); @@ -626,7 +626,7 @@ bool HeapShared::archive_object(oop obj, oop referrer, KlassSubGraphInfo* subgra count_allocation(obj->size()); DEBUG_ONLY(_dumptime_classes_with_cached_oops->add(obj->klass())); - if (HeapShared::is_writing_streaming_mode()) { + if (AOTHeap::is_writing_streaming_mode()) { AOTStreamedHeapWriter::add_source_obj(obj); } else { AOTMappedHeapWriter::add_source_obj(obj); @@ -653,7 +653,7 @@ bool HeapShared::archive_object(oop obj, oop referrer, KlassSubGraphInfo* subgra // For the other subgraphs, we have a weaker mechanism to ensure that // all classes in a subgraph are initialized before the subgraph is programmatically // returned from jdk.internal.misc.CDS::initializeFromArchive(). - // See HeapShared::initialize_from_archived_subgraph(). + // See AOTHeap::initialize_from_archived_subgraph(). { AOTArtifactFinder::add_aot_inited_class(InstanceKlass::cast(k)); } @@ -669,7 +669,7 @@ bool HeapShared::archive_object(oop obj, oop referrer, KlassSubGraphInfo* subgra m = RegeneratedClasses::maybe_get_regenerated_object(m); InstanceKlass* method_holder = m->method_holder(); AOTArtifactFinder::add_cached_class(method_holder); - _dumptime_resolved_methods->append(HeapShared::append_root(obj)); + _dumptime_resolved_methods->append(AOTHeap::append_root(obj)); } } else if (AOTCodeCache::is_dumping_code() && (java_lang_invoke_MethodHandle::is_instance(obj) || is_interned_string(obj))) { @@ -727,24 +727,24 @@ class MetaspaceObjToOopHandleTable: public HashTablepool_holder()->class_loader_data())) { _scratch_objects_table->set_oop(src, dest); } } -refArrayOop HeapShared::scratch_resolved_references(ConstantPool* src) { +refArrayOop AOTHeap::scratch_resolved_references(ConstantPool* src) { oop rr = _scratch_objects_table->get_oop(src); return rr == nullptr ? nullptr : oop_cast(rr); } -void HeapShared::remove_scratch_resolved_references(ConstantPool* src) { +void AOTHeap::remove_scratch_resolved_references(ConstantPool* src) { if (CDSConfig::is_dumping_heap()) { _scratch_objects_table->remove_oop(src); } } -void HeapShared::init_dumping() { +void AOTHeap::init_dumping() { _scratch_objects_table = new (mtClass)MetaspaceObjToOopHandleTable(); _dumptime_resolved_methods = new (mtClassShared) GrowableArray(100, mtClassShared); _pending_roots = new GrowableArrayCHeap(500); @@ -752,7 +752,7 @@ void HeapShared::init_dumping() { DEBUG_ONLY(_dumptime_classes_with_cached_oops = new (mtClassShared)ArchivableKlassTable()); } -void HeapShared::init_scratch_objects_for_basic_type_mirrors(TRAPS) { +void AOTHeap::init_scratch_objects_for_basic_type_mirrors(TRAPS) { for (int i = T_BOOLEAN; i < T_VOID+1; i++) { BasicType bt = (BasicType)i; if (!is_reference_type(bt)) { @@ -767,7 +767,7 @@ void HeapShared::init_scratch_objects_for_basic_type_mirrors(TRAPS) { // that java_mirror will be returned if the mirror is already a scratch mirror. // // See java_lang_Class::create_scratch_mirror() for more info. -oop HeapShared::scratch_java_mirror(oop java_mirror) { +oop AOTHeap::scratch_java_mirror(oop java_mirror) { assert(java_lang_Class::is_instance(java_mirror), "must be"); for (int i = T_BOOLEAN; i < T_VOID+1; i++) { @@ -786,21 +786,21 @@ oop HeapShared::scratch_java_mirror(oop java_mirror) { } } -oop HeapShared::scratch_java_mirror(BasicType t) { +oop AOTHeap::scratch_java_mirror(BasicType t) { assert((uint)t < T_VOID+1, "range check"); assert(!is_reference_type(t), "sanity"); return _scratch_basic_type_mirrors[t].resolve(); } -oop HeapShared::scratch_java_mirror(Klass* k) { +oop AOTHeap::scratch_java_mirror(Klass* k) { return _scratch_objects_table->get_oop(k); } -void HeapShared::set_scratch_java_mirror(Klass* k, oop mirror) { +void AOTHeap::set_scratch_java_mirror(Klass* k, oop mirror) { _scratch_objects_table->set_oop(k, mirror); } -void HeapShared::remove_scratch_objects(Klass* k) { +void AOTHeap::remove_scratch_objects(Klass* k) { // Klass is being deallocated. Java mirror can still be alive, and it should not // point to dead klass. We need to break the link from mirror to the Klass. // See how InstanceKlass::deallocate_contents does it for normal mirrors. @@ -820,24 +820,24 @@ void HeapShared::remove_scratch_objects(Klass* k) { //string compares on names is fragile and will break as soon as somebody //changes the names in the JDK code. See discussion in JDK-8342481 for //related ideas about marking AOT-related classes. -bool HeapShared::is_lambda_form_klass(InstanceKlass* ik) { +bool AOTHeap::is_lambda_form_klass(InstanceKlass* ik) { return ik->is_hidden() && ik->name()->starts_with("java/lang/invoke/LambdaForm$"); } -bool HeapShared::is_lambda_proxy_klass(InstanceKlass* ik) { +bool AOTHeap::is_lambda_proxy_klass(InstanceKlass* ik) { return ik->is_hidden() && (ik->name()->index_of_at(0, "$$Lambda+", 9) > 0); } -bool HeapShared::is_string_concat_klass(InstanceKlass* ik) { +bool AOTHeap::is_string_concat_klass(InstanceKlass* ik) { return ik->is_hidden() && ik->name()->starts_with("java/lang/String$$StringConcat"); } -bool HeapShared::is_archivable_hidden_klass(InstanceKlass* ik) { +bool AOTHeap::is_archivable_hidden_klass(InstanceKlass* ik) { return CDSConfig::is_dumping_method_handles() && (is_lambda_form_klass(ik) || is_lambda_proxy_klass(ik) || is_string_concat_klass(ik)); } -void HeapShared::copy_and_rescan_aot_inited_mirror(InstanceKlass* ik) { +void AOTHeap::copy_and_rescan_aot_inited_mirror(InstanceKlass* ik) { ik->set_has_aot_initialized_mirror(); oop orig_mirror; @@ -921,7 +921,7 @@ void HeapShared::copy_and_rescan_aot_inited_mirror(InstanceKlass* ik) { } } -void HeapShared::copy_java_mirror(oop orig_mirror, oop scratch_m) { +void AOTHeap::copy_java_mirror(oop orig_mirror, oop scratch_m) { // We need to retain the identity_hash, because it may have been used by some hashtables // in the shared heap. if (orig_mirror->has_identity_hash()) { @@ -963,20 +963,20 @@ void HeapShared::copy_java_mirror(oop orig_mirror, oop scratch_m) { static objArrayOop get_archived_resolved_references(InstanceKlass* src_ik) { if (SystemDictionaryShared::is_builtin_loader(src_ik->class_loader_data())) { objArrayOop rr = src_ik->constants()->resolved_references_or_null(); - if (rr != nullptr && !HeapShared::is_too_large_to_archive(rr)) { - return HeapShared::scratch_resolved_references(src_ik->constants()); + if (rr != nullptr && !AOTHeap::is_too_large_to_archive(rr)) { + return AOTHeap::scratch_resolved_references(src_ik->constants()); } } return nullptr; } -int HeapShared::archive_exception_instance(oop exception) { +int AOTHeap::archive_exception_instance(oop exception) { bool success = archive_reachable_objects_from(1, _dump_time_special_subgraph, exception); assert(success, "sanity"); return append_root(exception); } -void HeapShared::get_pointer_info(oop src_obj, bool& has_oop_pointers, bool& has_native_pointers) { +void AOTHeap::get_pointer_info(oop src_obj, bool& has_oop_pointers, bool& has_native_pointers) { OopHandle oh(&src_obj); CachedOopInfo* info = archived_object_cache()->get(oh); assert(info != nullptr, "must be"); @@ -984,7 +984,7 @@ void HeapShared::get_pointer_info(oop src_obj, bool& has_oop_pointers, bool& has has_native_pointers = info->has_native_pointers(); } -void HeapShared::set_has_native_pointers(oop src_obj) { +void AOTHeap::set_has_native_pointers(oop src_obj) { OopHandle oh(&src_obj); CachedOopInfo* info = archived_object_cache()->get(oh); assert(info != nullptr, "must be"); @@ -993,7 +993,7 @@ void HeapShared::set_has_native_pointers(oop src_obj) { // Between start_scanning_for_oops() and end_scanning_for_oops(), we discover all Java heap objects that // should be stored in the AOT cache. The scanning is coordinated by AOTArtifactFinder. -void HeapShared::start_scanning_for_oops() { +void AOTHeap::start_scanning_for_oops() { { NoSafepointVerifier nsv; @@ -1004,7 +1004,7 @@ void HeapShared::start_scanning_for_oops() { // Cache for recording where the archived objects are copied to create_archived_object_cache(); - if (HeapShared::is_writing_mapping_mode() && (UseG1GC || UseCompressedOops)) { + if (AOTHeap::is_writing_mapping_mode() && (UseG1GC || UseCompressedOops)) { aot_log_info(aot)("Heap range = [" PTR_FORMAT " - " PTR_FORMAT "]", UseCompressedOops ? p2i(CompressedOops::begin()) : G1GC_ONLY(UseG1GC ? p2i((address)G1CollectedHeap::heap()->reserved().start()) :) 0L, @@ -1019,25 +1019,25 @@ void HeapShared::start_scanning_for_oops() { Universe::archive_exception_instances(); } -void HeapShared::end_scanning_for_oops() { +void AOTHeap::end_scanning_for_oops() { if (is_writing_mapping_mode()) { StringTable::init_shared_table(); } delete_seen_objects_table(); } -void HeapShared::write_heap(AOTMappedHeapInfo* mapped_heap_info, AOTStreamedHeapInfo* streamed_heap_info) { +void AOTHeap::write_heap(AOTMappedHeapInfo* mapped_heap_info, AOTStreamedHeapInfo* streamed_heap_info) { { NoSafepointVerifier nsv; CDSHeapVerifier::verify(); check_special_subgraph_classes(); } - if (HeapShared::is_writing_mapping_mode()) { + if (AOTHeap::is_writing_mapping_mode()) { StringTable::write_shared_table(); AOTMappedHeapWriter::write(_pending_roots, mapped_heap_info); } else { - assert(HeapShared::is_writing_streaming_mode(), "are there more modes?"); + assert(AOTHeap::is_writing_streaming_mode(), "are there more modes?"); AOTStreamedHeapWriter::write(_pending_roots, streamed_heap_info); } @@ -1056,7 +1056,7 @@ void HeapShared::write_heap(AOTMappedHeapInfo* mapped_heap_info, AOTStreamedHeap make_archived_object_cache_gc_safe(); } -void HeapShared::scan_java_mirror(oop orig_mirror) { +void AOTHeap::scan_java_mirror(oop orig_mirror) { oop m = scratch_java_mirror(orig_mirror); if (m != nullptr) { // nullptr if for custom class loader copy_java_mirror(orig_mirror, m); @@ -1065,7 +1065,7 @@ void HeapShared::scan_java_mirror(oop orig_mirror) { } } -void HeapShared::scan_java_class(Klass* orig_k) { +void AOTHeap::scan_java_class(Klass* orig_k) { scan_java_mirror(orig_k->java_mirror()); if (orig_k->is_instance_klass()) { @@ -1073,13 +1073,13 @@ void HeapShared::scan_java_class(Klass* orig_k) { orig_ik->constants()->prepare_resolved_references_for_archiving(); objArrayOop rr = get_archived_resolved_references(orig_ik); if (rr != nullptr) { - bool success = HeapShared::archive_reachable_objects_from(1, _dump_time_special_subgraph, rr); + bool success = AOTHeap::archive_reachable_objects_from(1, _dump_time_special_subgraph, rr); assert(success, "must be"); } } } -void HeapShared::archive_subgraphs() { +void AOTHeap::archive_subgraphs() { assert(CDSConfig::is_dumping_heap(), "must be"); if (CDSConfig::is_dumping_klass_subgraphs()) { @@ -1090,13 +1090,13 @@ void HeapShared::archive_subgraphs() { // // Subgraph archiving support // -HeapShared::DumpTimeKlassSubGraphInfoTable* HeapShared::_dump_time_subgraph_info_table = nullptr; -HeapShared::RunTimeKlassSubGraphInfoTable HeapShared::_run_time_subgraph_info_table; +AOTHeap::DumpTimeKlassSubGraphInfoTable* AOTHeap::_dump_time_subgraph_info_table = nullptr; +AOTHeap::RunTimeKlassSubGraphInfoTable AOTHeap::_run_time_subgraph_info_table; // Get the subgraph_info for Klass k. A new subgraph_info is created if // there is no existing one for k. The subgraph_info records the "buffered" // address of the class. -KlassSubGraphInfo* HeapShared::init_subgraph_info(Klass* k) { +KlassSubGraphInfo* AOTHeap::init_subgraph_info(Klass* k) { assert(CDSConfig::is_dumping_heap(), "dump time only"); bool created; KlassSubGraphInfo* info = @@ -1105,7 +1105,7 @@ KlassSubGraphInfo* HeapShared::init_subgraph_info(Klass* k) { return info; } -KlassSubGraphInfo* HeapShared::get_subgraph_info(Klass* k) { +KlassSubGraphInfo* AOTHeap::get_subgraph_info(Klass* k) { assert(CDSConfig::is_dumping_heap(), "dump time only"); KlassSubGraphInfo* info = _dump_time_subgraph_info_table->get(k); assert(info != nullptr, "must have been initialized"); @@ -1123,7 +1123,7 @@ void KlassSubGraphInfo::add_subgraph_entry_field(int static_field_offset, oop v) if (v == nullptr) { _subgraph_entry_fields->append(-1); } else { - _subgraph_entry_fields->append(HeapShared::append_root(v)); + _subgraph_entry_fields->append(AOTHeap::append_root(v)); } } @@ -1150,7 +1150,7 @@ void KlassSubGraphInfo::add_subgraph_object_klass(Klass* orig_k) { // -XX:AOTInitTestClass must be used carefully in regression tests to // include only classes that are safe to aot-initialize. assert(ik->class_loader() == nullptr || - HeapShared::is_lambda_proxy_klass(ik) || + AOTHeap::is_lambda_proxy_klass(ik) || AOTClassInitializer::has_test_class(), "we can archive only instances of boot classes or lambda proxy classes"); } else { @@ -1218,7 +1218,7 @@ void KlassSubGraphInfo::check_allowed_klass(InstanceKlass* ik) { const char* lambda_msg = ""; if (CDSConfig::is_dumping_method_handles()) { lambda_msg = ", or a lambda proxy class"; - if (HeapShared::is_lambda_proxy_klass(ik) && + if (AOTHeap::is_lambda_proxy_klass(ik) && (ik->class_loader() == nullptr || ik->class_loader() == SystemDictionary::java_platform_loader() || ik->class_loader() == SystemDictionary::java_system_loader())) { @@ -1293,14 +1293,14 @@ void ArchivedKlassSubGraphInfoRecord::init(KlassSubGraphInfo* info) { ArchivePtrMarker::mark_pointer(&_subgraph_object_klasses); } -class HeapShared::CopyKlassSubGraphInfoToArchive : StackObj { +class AOTHeap::CopyKlassSubGraphInfoToArchive : StackObj { CompactHashtableWriter* _writer; public: CopyKlassSubGraphInfoToArchive(CompactHashtableWriter* writer) : _writer(writer) {} bool do_entry(Klass* klass, KlassSubGraphInfo& info) { if (info.subgraph_object_klasses() != nullptr || info.subgraph_entry_fields() != nullptr) { - ArchivedKlassSubGraphInfoRecord* record = HeapShared::archive_subgraph_info(&info); + ArchivedKlassSubGraphInfoRecord* record = AOTHeap::archive_subgraph_info(&info); Klass* buffered_k = ArchiveBuilder::get_buffered_klass(klass); unsigned int hash = SystemDictionaryShared::hash_for_shared_dictionary((address)buffered_k); _writer->add(hash, AOTCompressedPointers::encode_not_null(record)); @@ -1309,7 +1309,7 @@ class HeapShared::CopyKlassSubGraphInfoToArchive : StackObj { } }; -ArchivedKlassSubGraphInfoRecord* HeapShared::archive_subgraph_info(KlassSubGraphInfo* info) { +ArchivedKlassSubGraphInfoRecord* AOTHeap::archive_subgraph_info(KlassSubGraphInfo* info) { ArchivedKlassSubGraphInfoRecord* record = (ArchivedKlassSubGraphInfoRecord*)ArchiveBuilder::ro_region_alloc(sizeof(ArchivedKlassSubGraphInfoRecord)); record->init(info); @@ -1326,7 +1326,7 @@ ArchivedKlassSubGraphInfoRecord* HeapShared::archive_subgraph_info(KlassSubGraph // info. The value is stored back to the corresponding field at runtime. // - A list of klasses that need to be loaded/initialized before archived // java object sub-graph can be accessed at runtime. -void HeapShared::write_subgraph_info_table() { +void AOTHeap::write_subgraph_info_table() { // Allocate the contents of the hashtable(s) inside the RO region of the CDS archive. DumpTimeKlassSubGraphInfoTable* d_table = _dump_time_subgraph_info_table; CompactHashtableStats stats; @@ -1343,14 +1343,14 @@ void HeapShared::write_subgraph_info_table() { } } -void HeapShared::serialize_tables(SerializeClosure* soc) { +void AOTHeap::serialize_tables(SerializeClosure* soc) { _run_time_subgraph_info_table.serialize_header(soc); soc->do_ptr(&_run_time_special_subgraph); soc->do_ptr(&_runtime_resolved_methods); DEBUG_ONLY(soc->do_ptr(&_runtime_classes_with_cached_oops)); } -void HeapShared::load_cached_resolved_methods() { +void AOTHeap::load_cached_resolved_methods() { precond(CDSConfig::is_using_aot_linked_classes()); if (_runtime_resolved_methods != nullptr) { JavaThread* current = JavaThread::current(); @@ -1394,16 +1394,16 @@ static void verify_the_heap(Klass* k, const char* which) { } } -// Before GC can execute, we must ensure that all oops reachable from HeapShared::roots() +// Before GC can execute, we must ensure that all oops reachable from AOTHeap::roots() // have a valid klass. I.e., oopDesc::klass() must have already been resolved. -void HeapShared::resolve_classes(JavaThread* current) { +void AOTHeap::resolve_classes(JavaThread* current) { assert(CDSConfig::is_using_archive(), "runtime only!"); if (CDSConfig::is_using_klass_subgraphs()) { resolve_classes_for_subgraphs(current, archive_subgraph_entry_fields); } } -void HeapShared::resolve_classes_for_subgraphs(JavaThread* current, ArchivableStaticFieldInfo fields[]) { +void AOTHeap::resolve_classes_for_subgraphs(JavaThread* current, ArchivableStaticFieldInfo fields[]) { for (int i = 0; fields[i].valid(); i++) { ArchivableStaticFieldInfo* info = &fields[i]; TempNewSymbol klass_name = SymbolTable::new_symbol(info->klass_name); @@ -1413,7 +1413,7 @@ void HeapShared::resolve_classes_for_subgraphs(JavaThread* current, ArchivableSt } } -void HeapShared::resolve_classes_for_subgraph_of(JavaThread* current, Klass* k) { +void AOTHeap::resolve_classes_for_subgraph_of(JavaThread* current, Klass* k) { JavaThread* THREAD = current; ExceptionMark em(THREAD); const ArchivedKlassSubGraphInfoRecord* record = @@ -1436,7 +1436,7 @@ static const char* java_lang_invoke_core_klasses[] = { "java/lang/invoke/BoundMethodHandle$Species_L", }; -void HeapShared::initialize_java_lang_invoke(TRAPS) { +void AOTHeap::initialize_java_lang_invoke(TRAPS) { if (CDSConfig::is_using_aot_linked_classes() || CDSConfig::is_dumping_method_handles()) { int len = sizeof(java_lang_invoke_core_klasses)/sizeof(char*); for (int i = 0; i < len; i++) { @@ -1445,7 +1445,7 @@ void HeapShared::initialize_java_lang_invoke(TRAPS) { } } -bool HeapShared::is_core_java_lang_invoke_klass(InstanceKlass* klass) { +bool AOTHeap::is_core_java_lang_invoke_klass(InstanceKlass* klass) { ResourceMark rm; char* s2 = klass->name()->as_C_string(); int len = sizeof(java_lang_invoke_core_klasses)/sizeof(char*); @@ -1473,7 +1473,7 @@ bool HeapShared::is_core_java_lang_invoke_klass(InstanceKlass* klass) { // the aot-initialized mirror of Fruit has a static field that references HashSet, which // should be initialized before any Java code can access the Fruit class. Note that // HashSet itself doesn't necessary need to be an aot-initialized class. -void HeapShared::init_classes_for_special_subgraph(Handle class_loader, TRAPS) { +void AOTHeap::init_classes_for_special_subgraph(Handle class_loader, TRAPS) { if (!is_archived_heap_in_use()) { return; } @@ -1504,7 +1504,7 @@ void HeapShared::init_classes_for_special_subgraph(Handle class_loader, TRAPS) { } } -void HeapShared::initialize_from_archived_subgraph(JavaThread* current, Klass* k) { +void AOTHeap::initialize_from_archived_subgraph(JavaThread* current, Klass* k) { JavaThread* THREAD = current; if (!CDSConfig::is_using_klass_subgraphs()) { return; // nothing to do @@ -1540,7 +1540,7 @@ void HeapShared::initialize_from_archived_subgraph(JavaThread* current, Klass* k } const ArchivedKlassSubGraphInfoRecord* -HeapShared::resolve_or_init_classes_for_subgraph_of(Klass* k, bool do_init, TRAPS) { +AOTHeap::resolve_or_init_classes_for_subgraph_of(Klass* k, bool do_init, TRAPS) { assert(!CDSConfig::is_dumping_heap(), "Should not be called when dumping heap"); if (!k->in_aot_cache()) { @@ -1596,7 +1596,7 @@ HeapShared::resolve_or_init_classes_for_subgraph_of(Klass* k, bool do_init, TRAP return record; } -void HeapShared::resolve_or_init(const char* klass_name, bool do_init, TRAPS) { +void AOTHeap::resolve_or_init(const char* klass_name, bool do_init, TRAPS) { TempNewSymbol klass_name_sym = SymbolTable::new_symbol(klass_name); InstanceKlass* k = SystemDictionaryShared::find_builtin_class(klass_name_sym); if (k == nullptr) { @@ -1609,7 +1609,7 @@ void HeapShared::resolve_or_init(const char* klass_name, bool do_init, TRAPS) { } } -void HeapShared::resolve_or_init(Klass* k, bool do_init, TRAPS) { +void AOTHeap::resolve_or_init(Klass* k, bool do_init, TRAPS) { if (!do_init) { if (k->class_loader_data() == nullptr) { Klass* resolved_k = SystemDictionary::resolve_or_null(k->name(), CHECK); @@ -1620,7 +1620,7 @@ void HeapShared::resolve_or_init(Klass* k, bool do_init, TRAPS) { } } } else { - assert(k->class_loader_data() != nullptr, "must have been resolved by HeapShared::resolve_classes"); + assert(k->class_loader_data() != nullptr, "must have been resolved by AOTHeap::resolve_classes"); if (k->is_instance_klass()) { InstanceKlass* ik = InstanceKlass::cast(k); ik->initialize(CHECK); @@ -1631,7 +1631,7 @@ void HeapShared::resolve_or_init(Klass* k, bool do_init, TRAPS) { } } -void HeapShared::init_archived_fields_for(Klass* k, const ArchivedKlassSubGraphInfoRecord* record) { +void AOTHeap::init_archived_fields_for(Klass* k, const ArchivedKlassSubGraphInfoRecord* record) { verify_the_heap(k, "before"); Array* entry_field_records = record->entry_field_records(); @@ -1671,7 +1671,7 @@ void HeapShared::init_archived_fields_for(Klass* k, const ArchivedKlassSubGraphI verify_the_heap(k, "after "); } -void HeapShared::clear_archived_roots_of(Klass* k) { +void AOTHeap::clear_archived_roots_of(Klass* k) { unsigned int hash = SystemDictionaryShared::hash_for_shared_dictionary_quick(k); const ArchivedKlassSubGraphInfoRecord* record = _run_time_subgraph_info_table.lookup(k, hash, 0); if (record != nullptr) { @@ -1689,7 +1689,7 @@ void HeapShared::clear_archived_roots_of(Klass* k) { // Push all oop fields (or oop array elemenets in case of an objArray) in // _referencing_obj onto the _stack. -class HeapShared::OopFieldPusher: public BasicOopIterateClosure { +class AOTHeap::OopFieldPusher: public BasicOopIterateClosure { PendingOopStack* _stack; GrowableArray _found_oop_fields; int _level; @@ -1770,13 +1770,13 @@ class PointsToOopsChecker : public BasicOopIterateClosure { bool result() { return _result; } }; -HeapShared::CachedOopInfo HeapShared::make_cached_oop_info(oop obj, oop referrer) { +AOTHeap::CachedOopInfo AOTHeap::make_cached_oop_info(oop obj, oop referrer) { PointsToOopsChecker points_to_oops_checker; obj->oop_iterate(&points_to_oops_checker); return CachedOopInfo(OopHandle(Universe::vm_global(), referrer), points_to_oops_checker.result()); } -void HeapShared::init_box_classes(TRAPS) { +void AOTHeap::init_box_classes(TRAPS) { if (is_archived_heap_in_use()) { vmClasses::Boolean_klass()->initialize(CHECK); vmClasses::Character_klass()->initialize(CHECK); @@ -1790,8 +1790,8 @@ void HeapShared::init_box_classes(TRAPS) { } } -// Used by HeapShared::find_flat_field_klasses(). -class HeapShared::FlatFieldKlassFinder : public FieldClosure { +// Used by AOTHeap::find_flat_field_klasses(). +class AOTHeap::FlatFieldKlassFinder : public FieldClosure { KlassSubGraphInfo* _subgraph_info; oop _obj; const ValuePayloadContext* _vpc; @@ -1821,7 +1821,7 @@ class HeapShared::FlatFieldKlassFinder : public FieldClosure { } }; -void HeapShared::add_flat_field_klass(KlassSubGraphInfo* subgraph_info, ValueKlass* k) { +void AOTHeap::add_flat_field_klass(KlassSubGraphInfo* subgraph_info, ValueKlass* k) { subgraph_info->add_subgraph_object_klass(k); if (InstanceKlass::cast(k)->is_enum_subclass() || (subgraph_info == _dump_time_special_subgraph)) { @@ -1839,12 +1839,12 @@ void HeapShared::add_flat_field_klass(KlassSubGraphInfo* subgraph_info, ValueKla // @NullRestricted Point p1; // @NullRestricted Point p2; ... } // -// Klasses of non-flat fields are already recorded by HeapShared::archive_object(). +// Klasses of non-flat fields are already recorded by AOTHeap::archive_object(). // -// If only a single instance of Line is archived, HeapShared::archive_object() would +// If only a single instance of Line is archived, AOTHeap::archive_object() would // have never visited a heap oop instance of Point, but we must store Point in // AOT-initialized state. This function finds Point. -void HeapShared::find_flat_field_klasses(KlassSubGraphInfo* subgraph_info, oop orig_obj) { +void AOTHeap::find_flat_field_klasses(KlassSubGraphInfo* subgraph_info, oop orig_obj) { Klass* klass = orig_obj->klass(); if (klass->is_flatArray_klass()) { @@ -1884,7 +1884,7 @@ void HeapShared::find_flat_field_klasses(KlassSubGraphInfo* subgraph_info, oop o // trace all objects that are reachable from it, and make sure these objects are archived. // (3) Record the klasses of all objects that are reachable from orig_obj (including those that // were already archived when this function is called) -bool HeapShared::archive_reachable_objects_from(int level, +bool AOTHeap::archive_reachable_objects_from(int level, KlassSubGraphInfo* subgraph_info, oop orig_obj) { assert(orig_obj != nullptr, "must be"); @@ -1908,7 +1908,7 @@ bool HeapShared::archive_reachable_objects_from(int level, return true; } -bool HeapShared::walk_one_object(PendingOopStack* stack, int level, KlassSubGraphInfo* subgraph_info, +bool AOTHeap::walk_one_object(PendingOopStack* stack, int level, KlassSubGraphInfo* subgraph_info, oop orig_obj, oop referrer) { assert(orig_obj != nullptr, "must be"); if (!JavaClasses::is_supported_for_archiving(orig_obj)) { @@ -1948,7 +1948,7 @@ bool HeapShared::walk_one_object(PendingOopStack* stack, int level, KlassSubGrap } else if (java_lang_Class::is_instance(orig_obj) && subgraph_info != _dump_time_special_subgraph) { // Without CDSConfig::is_dumping_aot_linked_classes(), we only allow archived objects to // point to the mirrors of (1) j.l.Object, (2) primitive classes, and (3) box classes. These are initialized - // very early by HeapShared::init_box_classes(). + // very early by AOTHeap::init_box_classes(). if (orig_obj == vmClasses::Object_klass()->java_mirror() || java_lang_Class::is_primitive(orig_obj) || orig_obj == vmClasses::Boolean_klass()->java_mirror() @@ -2063,7 +2063,7 @@ bool HeapShared::walk_one_object(PendingOopStack* stack, int level, KlassSubGrap // for loading and initializing before any object in the archived graph can // be accessed at runtime. // -void HeapShared::archive_reachable_objects_from_static_field(InstanceKlass *k, +void AOTHeap::archive_reachable_objects_from_static_field(InstanceKlass *k, const char* klass_name, int field_offset, const char* field_name) { @@ -2112,12 +2112,12 @@ class VerifySharedOopClosure: public BasicOopIterateClosure { template void do_oop_work(T *p) { oop obj = HeapAccess<>::oop_load(p); if (obj != nullptr) { - HeapShared::verify_reachable_objects_from(obj); + AOTHeap::verify_reachable_objects_from(obj); } } }; -void HeapShared::verify_subgraph_from_static_field(InstanceKlass* k, int field_offset) { +void AOTHeap::verify_subgraph_from_static_field(InstanceKlass* k, int field_offset) { precond(CDSConfig::is_dumping_klass_subgraphs()); assert(k->defined_by_boot_loader(), "must be boot class"); @@ -2128,7 +2128,7 @@ void HeapShared::verify_subgraph_from_static_field(InstanceKlass* k, int field_o } } -void HeapShared::verify_subgraph_from(oop orig_obj) { +void AOTHeap::verify_subgraph_from(oop orig_obj) { if (!has_been_archived(orig_obj)) { // It's OK for the root of a subgraph to be not archived. See comments in // archive_reachable_objects_from(). @@ -2141,7 +2141,7 @@ void HeapShared::verify_subgraph_from(oop orig_obj) { delete_seen_objects_table(); } -void HeapShared::verify_reachable_objects_from(oop obj) { +void AOTHeap::verify_reachable_objects_from(oop obj) { _num_total_verifications ++; if (java_lang_Class::is_instance(obj)) { obj = RegeneratedClasses::maybe_get_regenerated_mirror(obj); @@ -2157,7 +2157,7 @@ void HeapShared::verify_reachable_objects_from(oop obj) { } #endif -void HeapShared::check_special_subgraph_classes() { +void AOTHeap::check_special_subgraph_classes() { if (CDSConfig::is_dumping_aot_linked_classes()) { // We can have aot-initialized classes (such as Enums) that can reference objects // of arbitrary types. Currently, we trust the JEP 483 implementation to only @@ -2191,29 +2191,29 @@ void HeapShared::check_special_subgraph_classes() { } } -HeapShared::SeenObjectsTable* HeapShared::_seen_objects_table = nullptr; -HeapShared::PendingOop HeapShared::_object_being_archived; -size_t HeapShared::_num_new_walked_objs; -size_t HeapShared::_num_new_archived_objs; -size_t HeapShared::_num_old_recorded_klasses; +AOTHeap::SeenObjectsTable* AOTHeap::_seen_objects_table = nullptr; +AOTHeap::PendingOop AOTHeap::_object_being_archived; +size_t AOTHeap::_num_new_walked_objs; +size_t AOTHeap::_num_new_archived_objs; +size_t AOTHeap::_num_old_recorded_klasses; -size_t HeapShared::_num_total_subgraph_recordings = 0; -size_t HeapShared::_num_total_walked_objs = 0; -size_t HeapShared::_num_total_archived_objs = 0; -size_t HeapShared::_num_total_recorded_klasses = 0; -size_t HeapShared::_num_total_verifications = 0; +size_t AOTHeap::_num_total_subgraph_recordings = 0; +size_t AOTHeap::_num_total_walked_objs = 0; +size_t AOTHeap::_num_total_archived_objs = 0; +size_t AOTHeap::_num_total_recorded_klasses = 0; +size_t AOTHeap::_num_total_verifications = 0; -bool HeapShared::has_been_seen_during_subgraph_recording(oop obj) { +bool AOTHeap::has_been_seen_during_subgraph_recording(oop obj) { return _seen_objects_table->get(obj) != nullptr; } -void HeapShared::set_has_been_seen_during_subgraph_recording(oop obj) { +void AOTHeap::set_has_been_seen_during_subgraph_recording(oop obj) { assert(!has_been_seen_during_subgraph_recording(obj), "sanity"); _seen_objects_table->put_when_absent(obj, true); ++ _num_new_walked_objs; } -void HeapShared::start_recording_subgraph(InstanceKlass *k, const char* class_name) { +void AOTHeap::start_recording_subgraph(InstanceKlass *k, const char* class_name) { log_info(aot, heap)("Start recording subgraph(s) for archived fields in %s", class_name); init_subgraph_info(k); init_seen_objects_table(); @@ -2222,7 +2222,7 @@ void HeapShared::start_recording_subgraph(InstanceKlass *k, const char* class_na _num_old_recorded_klasses = get_subgraph_info(k)->num_subgraph_object_klasses(); } -void HeapShared::done_recording_subgraph(InstanceKlass *k, const char* class_name) { +void AOTHeap::done_recording_subgraph(InstanceKlass *k, const char* class_name) { size_t num_new_recorded_klasses = get_subgraph_info(k)->num_subgraph_object_klasses() - _num_old_recorded_klasses; log_info(aot, heap)("Done recording subgraph(s) for archived fields in %s: " @@ -2260,7 +2260,7 @@ class ArchivableStaticFieldFinder: public FieldClosure { int offset() { return _offset; } }; -void HeapShared::init_subgraph_entry_fields(ArchivableStaticFieldInfo fields[], +void AOTHeap::init_subgraph_entry_fields(ArchivableStaticFieldInfo fields[], TRAPS) { for (int i = 0; fields[i].valid(); i++) { ArchivableStaticFieldInfo* info = &fields[i]; @@ -2310,7 +2310,7 @@ void HeapShared::init_subgraph_entry_fields(ArchivableStaticFieldInfo fields[], } } -void HeapShared::init_subgraph_entry_fields(TRAPS) { +void AOTHeap::init_subgraph_entry_fields(TRAPS) { assert(CDSConfig::is_dumping_heap(), "must be"); _dump_time_subgraph_info_table = new (mtClass)DumpTimeKlassSubGraphInfoTable(); if (CDSConfig::is_dumping_klass_subgraphs()) { @@ -2318,21 +2318,21 @@ void HeapShared::init_subgraph_entry_fields(TRAPS) { } } -void HeapShared::init_for_dumping(TRAPS) { +void AOTHeap::init_for_dumping(TRAPS) { if (CDSConfig::is_dumping_heap()) { init_subgraph_entry_fields(CHECK); } } -void HeapShared::init_heap_writer() { - if (HeapShared::is_writing_streaming_mode()) { +void AOTHeap::init_heap_writer() { + if (AOTHeap::is_writing_streaming_mode()) { AOTStreamedHeapWriter::init(); } else { AOTMappedHeapWriter::init(); } } -void HeapShared::archive_object_subgraphs(ArchivableStaticFieldInfo fields[]) { +void AOTHeap::archive_object_subgraphs(ArchivableStaticFieldInfo fields[]) { _num_total_subgraph_recordings = 0; _num_total_walked_objs = 0; _num_total_archived_objs = 0; @@ -2344,7 +2344,7 @@ void HeapShared::archive_object_subgraphs(ArchivableStaticFieldInfo fields[]) { // [2] Create a list of all the class of the objects that can be reached // by any of these static fields. // At runtime, these classes are initialized before X's archived fields - // are restored by HeapShared::initialize_from_archived_subgraph(). + // are restored by AOTHeap::initialize_from_archived_subgraph(). for (int i = 0; fields[i].valid(); ) { ArchivableStaticFieldInfo* info = &fields[i]; const char* klass_name = info->klass_name; @@ -2381,7 +2381,7 @@ void HeapShared::archive_object_subgraphs(ArchivableStaticFieldInfo fields[]) { #endif } -bool HeapShared::is_interned_string(oop obj) { +bool AOTHeap::is_interned_string(oop obj) { if (!java_lang_String::is_instance(obj)) { return false; } @@ -2395,14 +2395,14 @@ bool HeapShared::is_interned_string(oop obj) { return StringTable::lookup(name, len) == obj; } -bool HeapShared::is_dumped_interned_string(oop o) { +bool AOTHeap::is_dumped_interned_string(oop o) { return is_interned_string(o) && has_been_archived(o); } // These tables should be used only within the CDS safepoint, so // delete them before we exit the safepoint. Otherwise the table will // contain bad oops after a GC. -void HeapShared::delete_tables_with_raw_oops() { +void AOTHeap::delete_tables_with_raw_oops() { assert(_seen_objects_table == nullptr, "should have been deleted"); if (is_writing_mapping_mode()) { @@ -2413,7 +2413,7 @@ void HeapShared::delete_tables_with_raw_oops() { } } -void HeapShared::debug_trace() { +void AOTHeap::debug_trace() { ResourceMark rm; oop referrer = _object_being_archived.referrer(); if (referrer != nullptr) { @@ -2462,7 +2462,7 @@ class FindEmbeddedNonNullPointers: public BasicOopIterateClosure { }; #endif -void HeapShared::count_allocation(size_t size) { +void AOTHeap::count_allocation(size_t size) { _total_obj_count ++; _total_obj_size += size; for (int i = 0; i < ALLOC_STAT_SLOTS; i++) { @@ -2482,7 +2482,7 @@ static double avg_size(size_t size, size_t count) { return avg; } -void HeapShared::print_stats() { +void AOTHeap::print_stats() { size_t huge_count = _total_obj_count; size_t huge_size = _total_obj_size; @@ -2507,7 +2507,7 @@ void HeapShared::print_stats() { avg_size(_total_obj_size, _total_obj_count)); } -bool HeapShared::is_metadata_field(oop src_obj, int offset) { +bool AOTHeap::is_metadata_field(oop src_obj, int offset) { bool result = false; do_metadata_offsets(src_obj, [&](int metadata_offset) { if (metadata_offset == offset) { @@ -2517,7 +2517,7 @@ bool HeapShared::is_metadata_field(oop src_obj, int offset) { return result; } -void HeapShared::remap_dumped_metadata(oop src_obj, address archived_object) { +void AOTHeap::remap_dumped_metadata(oop src_obj, address archived_object) { do_metadata_offsets(src_obj, [&](int offset) { Metadata** buffered_field_addr = (Metadata**)(archived_object + offset); Metadata* native_ptr = *buffered_field_addr; @@ -2534,7 +2534,7 @@ void HeapShared::remap_dumped_metadata(oop src_obj, address archived_object) { }); } -bool HeapShared::is_archived_boot_layer_available(JavaThread* current) { +bool AOTHeap::is_archived_boot_layer_available(JavaThread* current) { TempNewSymbol klass_name = SymbolTable::new_symbol(ARCHIVED_BOOT_LAYER_CLASS); InstanceKlass* k = SystemDictionary::find_instance_klass(current, klass_name, Handle()); if (k == nullptr) { diff --git a/src/hotspot/share/cds/heapShared.hpp b/src/hotspot/share/cds/aotHeap.hpp similarity index 97% rename from src/hotspot/share/cds/heapShared.hpp rename to src/hotspot/share/cds/aotHeap.hpp index 78f493d17c8e..a6379a815852 100644 --- a/src/hotspot/share/cds/heapShared.hpp +++ b/src/hotspot/share/cds/aotHeap.hpp @@ -22,8 +22,8 @@ * */ -#ifndef SHARE_CDS_HEAPSHARED_HPP -#define SHARE_CDS_HEAPSHARED_HPP +#ifndef SHARE_CDS_AOTHEAP_HPP +#define SHARE_CDS_AOTHEAP_HPP #include "cds/aotMetaspace.hpp" #include "cds/dumpTimeClassInfo.hpp" @@ -128,7 +128,7 @@ enum class HeapArchiveMode { _streaming }; -class HeapShared: AllStatic { +class AOTHeap: AllStatic { friend class VerifySharedOopClosure; public: @@ -230,8 +230,8 @@ class HeapShared: AllStatic { typedef ResizeableHashTable ArchivedObjectCache; + AOTHeap::archived_object_cache_hash, + AOTHeap::oop_handle_equals> ArchivedObjectCache; static ArchivedObjectCache* _archived_object_cache; class DumpTimeKlassSubGraphInfoTable @@ -289,7 +289,7 @@ class HeapShared: AllStatic { typedef ResizeableHashTable SeenObjectsTable; + AOTHeap::oop_address_hash> SeenObjectsTable; static SeenObjectsTable *_seen_objects_table; @@ -416,7 +416,7 @@ class HeapShared: AllStatic { static void set_has_native_pointers(oop src_obj); static uintptr_t archive_location(oop src_obj); - // We use the HeapShared::roots() array to make sure that objects stored in the + // We use the AOTHeap::roots() array to make sure that objects stored in the // archived heap region are not prematurely collected. These roots include: // // - mirrors of classes that have not yet been loaded. @@ -425,7 +425,7 @@ class HeapShared: AllStatic { // - java.lang.Module objects that have not yet been added to the module graph // // When a mirror M becomes referenced by a newly loaded class K, M will be removed - // from HeapShared::roots() via clear_root(), and K will be responsible for + // from AOTHeap::roots() via clear_root(), and K will be responsible for // keeping M alive. // // Other types of roots are also cleared similarly when they become referenced. @@ -498,4 +498,4 @@ class HeapShared: AllStatic { static void log_oop_details(oop source_oop, address buffered_addr); }; -#endif // SHARE_CDS_HEAPSHARED_HPP +#endif // SHARE_CDS_AOTHEAP_HPP diff --git a/src/hotspot/share/cds/heapShared.inline.hpp b/src/hotspot/share/cds/aotHeap.inline.hpp similarity index 82% rename from src/hotspot/share/cds/heapShared.inline.hpp rename to src/hotspot/share/cds/aotHeap.inline.hpp index 52ec5182ec45..33e9a866121c 100644 --- a/src/hotspot/share/cds/heapShared.inline.hpp +++ b/src/hotspot/share/cds/aotHeap.inline.hpp @@ -22,10 +22,10 @@ * */ -#ifndef SHARE_CDS_HEAPSHARED_INLINE_HPP -#define SHARE_CDS_HEAPSHARED_INLINE_HPP +#ifndef SHARE_CDS_AOTHEAP_INLINE_HPP +#define SHARE_CDS_AOTHEAP_INLINE_HPP -#include "cds/heapShared.hpp" +#include "cds/aotHeap.hpp" #include "cds/aotReferenceObjSupport.hpp" #include "cds/regeneratedClasses.hpp" @@ -34,37 +34,37 @@ #if INCLUDE_CDS_JAVA_HEAP -inline bool HeapShared::is_loading() { +inline bool AOTHeap::is_loading() { return _heap_load_mode != HeapArchiveMode::_uninitialized; } -inline bool HeapShared::is_loading_streaming_mode() { +inline bool AOTHeap::is_loading_streaming_mode() { assert(_heap_load_mode != HeapArchiveMode::_uninitialized, "not initialized yet"); return _heap_load_mode == HeapArchiveMode::_streaming; } -inline bool HeapShared::is_loading_mapping_mode() { +inline bool AOTHeap::is_loading_mapping_mode() { assert(_heap_load_mode != HeapArchiveMode::_uninitialized, "not initialized yet"); return _heap_load_mode == HeapArchiveMode::_mapping; } -inline bool HeapShared::is_writing() { +inline bool AOTHeap::is_writing() { return _heap_write_mode != HeapArchiveMode::_uninitialized; } -inline bool HeapShared::is_writing_streaming_mode() { +inline bool AOTHeap::is_writing_streaming_mode() { assert(_heap_write_mode != HeapArchiveMode::_uninitialized, "not initialized yet"); return _heap_write_mode == HeapArchiveMode::_streaming; } -inline bool HeapShared::is_writing_mapping_mode() { +inline bool AOTHeap::is_writing_mapping_mode() { assert(_heap_write_mode != HeapArchiveMode::_uninitialized, "not initialized yet"); return _heap_write_mode == HeapArchiveMode::_mapping; } // Keep the knowledge about which objects have what metadata in one single place template -void HeapShared::do_metadata_offsets(oop src_obj, T callback) { +void AOTHeap::do_metadata_offsets(oop src_obj, T callback) { if (java_lang_Class::is_instance(src_obj)) { assert(java_lang_Class::klass_offset() < java_lang_Class::array_klass_offset(), "metadata offsets must be sorted"); @@ -75,7 +75,7 @@ void HeapShared::do_metadata_offsets(oop src_obj, T callback) { } } -inline void HeapShared::remap_loaded_metadata(oop src_obj) { +inline void AOTHeap::remap_loaded_metadata(oop src_obj) { do_metadata_offsets(src_obj, [&](int offset) { Metadata* metadata = src_obj->metadata_field(offset); if (metadata != nullptr) { @@ -85,7 +85,7 @@ inline void HeapShared::remap_loaded_metadata(oop src_obj) { }); } -inline oop HeapShared::maybe_remap_referent(bool is_java_lang_ref, size_t field_offset, oop referent) { +inline oop AOTHeap::maybe_remap_referent(bool is_java_lang_ref, size_t field_offset, oop referent) { if (referent == nullptr) { return nullptr; } @@ -99,7 +99,7 @@ inline oop HeapShared::maybe_remap_referent(bool is_java_lang_ref, size_t field_ // When the source object points to a "real" mirror, the buffered object should point // to the "scratch" mirror, which has all unarchivable fields scrubbed (to be reinstated // at run time). - referent = HeapShared::scratch_java_mirror(referent); + referent = AOTHeap::scratch_java_mirror(referent); assert(referent != nullptr, "must be"); } @@ -108,4 +108,4 @@ inline oop HeapShared::maybe_remap_referent(bool is_java_lang_ref, size_t field_ #endif // INCLUDE_CDS_JAVA_HEAP -#endif // SHARE_CDS_HEAPSHARED_INLINE_HPP +#endif // SHARE_CDS_AOTHEAP_INLINE_HPP diff --git a/src/hotspot/share/cds/aotLinkedClassBulkLoader.cpp b/src/hotspot/share/cds/aotLinkedClassBulkLoader.cpp index c7911294e992..438587281815 100644 --- a/src/hotspot/share/cds/aotLinkedClassBulkLoader.cpp +++ b/src/hotspot/share/cds/aotLinkedClassBulkLoader.cpp @@ -24,11 +24,11 @@ #include "cds/aotClassInitializer.hpp" #include "cds/aotClassLinker.hpp" +#include "cds/aotHeap.hpp" #include "cds/aotLinkedClassBulkLoader.hpp" #include "cds/aotLinkedClassTable.hpp" #include "cds/aotModules.hpp" #include "cds/cdsConfig.hpp" -#include "cds/heapShared.hpp" #include "classfile/classLoaderData.hpp" #include "classfile/javaClasses.hpp" #include "classfile/javaStackTraceClasses.hpp" @@ -87,7 +87,7 @@ void AOTLinkedClassBulkLoader::preload_classes_impl(TRAPS) { preload_classes_in_table(table->app(), "app", h_system_loader, CHECK); // Do this after all boot/platform/app classes are loaded, but before bytecode execution. - HeapShared::load_cached_resolved_methods(); + AOTHeap::load_cached_resolved_methods(); } void AOTLinkedClassBulkLoader::preload_classes_in_table(Array* classes, @@ -424,7 +424,7 @@ void AOTLinkedClassBulkLoader::init_classes_for_loader(Handle class_loader, Arra } if (!early_only) { - HeapShared::init_classes_for_special_subgraph(class_loader, CHECK); + AOTHeap::init_classes_for_special_subgraph(class_loader, CHECK); } } diff --git a/src/hotspot/share/cds/aotMappedHeapLoader.cpp b/src/hotspot/share/cds/aotMappedHeapLoader.cpp index b2552bea744c..ab2fc7dc474d 100644 --- a/src/hotspot/share/cds/aotMappedHeapLoader.cpp +++ b/src/hotspot/share/cds/aotMappedHeapLoader.cpp @@ -29,7 +29,6 @@ #include "cds/aotMetaspace.hpp" #include "cds/aotModules.hpp" #include "cds/cdsConfig.hpp" -#include "cds/heapShared.inline.hpp" #include "classfile/stringTable.hpp" #include "classfile/systemDictionaryShared.hpp" #include "gc/shared/collectedHeap.hpp" diff --git a/src/hotspot/share/cds/aotMappedHeapWriter.cpp b/src/hotspot/share/cds/aotMappedHeapWriter.cpp index 1d73fb0bf9a4..e40ea832baf6 100644 --- a/src/hotspot/share/cds/aotMappedHeapWriter.cpp +++ b/src/hotspot/share/cds/aotMappedHeapWriter.cpp @@ -22,13 +22,13 @@ * */ +#include "cds/aotHeap.inline.hpp" #include "cds/aotMappedHeap.hpp" #include "cds/aotMappedHeapWriter.hpp" #include "cds/aotModules.hpp" #include "cds/aotReferenceObjSupport.hpp" #include "cds/cdsConfig.hpp" #include "cds/filemap.hpp" -#include "cds/heapShared.inline.hpp" #include "cds/regeneratedClasses.hpp" #include "classfile/javaClasses.hpp" #include "classfile/modules.hpp" @@ -197,7 +197,7 @@ oop AOTMappedHeapWriter::requested_obj_from_buffer_offset(size_t offset) { oop AOTMappedHeapWriter::source_obj_to_requested_obj(oop src_obj) { assert(CDSConfig::is_dumping_heap(), "dump-time only"); - HeapShared::CachedOopInfo* p = HeapShared::get_cached_oop_info(src_obj); + AOTHeap::CachedOopInfo* p = AOTHeap::get_cached_oop_info(src_obj); if (p != nullptr) { return requested_obj_from_buffer_offset(p->buffer_offset()); } else { @@ -355,7 +355,7 @@ void AOTMappedHeapWriter::copy_roots_to_buffer(GrowableArrayCHeaplength(); i++) { int src_obj_index = _source_objs_order->at(i)._index; oop src_obj = _source_objs->at(src_obj_index); - HeapShared::CachedOopInfo* info = HeapShared::get_cached_oop_info(src_obj); + AOTHeap::CachedOopInfo* info = AOTHeap::get_cached_oop_info(src_obj); assert(info != nullptr, "must be"); size_t buffer_offset = copy_one_source_obj_to_buffer(src_obj); info->set_buffer_offset(buffer_offset); @@ -769,7 +769,7 @@ class AOTMappedHeapWriter::EmbeddedOopRelocator: public BasicOopIterateClosure { int field_offset = pointer_delta_as_int((char*)p, cast_from_oop(_src_obj)); T* field_addr = (T*)(_buffered_obj + field_offset); oop referent = load_source_oop_from_buffer(field_addr); - referent = HeapShared::maybe_remap_referent(_is_java_lang_ref, field_offset, referent); + referent = AOTHeap::maybe_remap_referent(_is_java_lang_ref, field_offset, referent); AOTMappedHeapWriter::relocate_field_in_buffer(field_addr, referent, _oopmap); } }; @@ -795,7 +795,7 @@ void AOTMappedHeapWriter::relocate_embedded_oops(GrowableArrayCHeaplength(); i++) { int src_obj_index = _source_objs_order->at(i)._index; oop src_obj = _source_objs->at(src_obj_index); - HeapShared::CachedOopInfo* info = HeapShared::get_cached_oop_info(src_obj); + AOTHeap::CachedOopInfo* info = AOTHeap::get_cached_oop_info(src_obj); assert(info != nullptr, "must be"); oop requested_obj = requested_obj_from_buffer_offset(info->buffer_offset()); update_header_for_requested_obj(requested_obj, src_obj, src_obj->klass()); @@ -805,7 +805,7 @@ void AOTMappedHeapWriter::relocate_embedded_oops(GrowableArrayCHeap::oop_load(elem_addr); - obj = HeapShared::maybe_remap_referent(false /* is_reference_field */, elem_offset, obj); + obj = AOTHeap::maybe_remap_referent(false /* is_reference_field */, elem_offset, obj); if (UseCompressedOops) { relocate_field_in_buffer((narrowOop*)elem_addr, obj, heap_info->oopmap()); } else { @@ -846,13 +846,13 @@ void AOTMappedHeapWriter::mark_native_pointer(oop src_obj, int field_offset) { info._src_obj = src_obj; info._field_offset = field_offset; _native_pointers->append(info); - HeapShared::set_has_native_pointers(src_obj); + AOTHeap::set_has_native_pointers(src_obj); _num_native_ptrs ++; } } void AOTMappedHeapWriter::mark_native_pointers(oop orig_obj) { - HeapShared::do_metadata_offsets(orig_obj, [&](int offset) { + AOTHeap::do_metadata_offsets(orig_obj, [&](int offset) { mark_native_pointer(orig_obj, offset); }); } @@ -868,7 +868,7 @@ void AOTMappedHeapWriter::compute_ptrmap(AOTMappedHeapInfo* heap_info) { NativePointerInfo info = _native_pointers->at(i); oop src_obj = info._src_obj; int field_offset = info._field_offset; - HeapShared::CachedOopInfo* p = HeapShared::get_cached_oop_info(src_obj); + AOTHeap::CachedOopInfo* p = AOTHeap::get_cached_oop_info(src_obj); // requested_field_addr = the address of this field in the requested space oop requested_obj = requested_obj_from_buffer_offset(p->buffer_offset()); Metadata** requested_field_addr = (Metadata**)(cast_from_oop
      (requested_obj) + field_offset); diff --git a/src/hotspot/share/cds/aotMappedHeapWriter.hpp b/src/hotspot/share/cds/aotMappedHeapWriter.hpp index 2420e68d9fea..5dc37ae350f3 100644 --- a/src/hotspot/share/cds/aotMappedHeapWriter.hpp +++ b/src/hotspot/share/cds/aotMappedHeapWriter.hpp @@ -25,8 +25,8 @@ #ifndef SHARE_CDS_AOTMAPPEDHEAPWRITER_HPP #define SHARE_CDS_AOTMAPPEDHEAPWRITER_HPP +#include "cds/aotHeap.hpp" #include "cds/aotMapLogger.hpp" -#include "cds/heapShared.hpp" #include "memory/allocation.hpp" #include "memory/allStatic.hpp" #include "oops/compressedOops.hpp" @@ -41,7 +41,7 @@ class MemRegion; #if INCLUDE_CDS_JAVA_HEAP class AOTMappedHeapWriter : AllStatic { - friend class HeapShared; + friend class AOTHeap; friend class AOTMappedHeapLoader; // AOTMappedHeapWriter manipulates three types of addresses: // @@ -53,9 +53,9 @@ class AOTMappedHeapWriter : AllStatic { // - "source objects" are regular Java objects allocated during the execution // of "java -Xshare:dump". They can be used as regular oops. // - // Between HeapShared::start_scanning_for_oops() and HeapShared::end_scanning_for_oops(), + // Between AOTHeap::start_scanning_for_oops() and AOTHeap::end_scanning_for_oops(), // we recursively search for the oops that need to be stored into the CDS archive. - // These are entered into HeapShared::archived_object_cache(). + // These are entered into AOTHeap::archived_object_cache(). // // - "buffered objects" are copies of the "source objects", and are stored in into // AOTMappedHeapWriter::_buffer, which is a GrowableArray that sits outside of diff --git a/src/hotspot/share/cds/aotMetaspace.cpp b/src/hotspot/share/cds/aotMetaspace.cpp index d22dccce6809..d43c9c297467 100644 --- a/src/hotspot/share/cds/aotMetaspace.cpp +++ b/src/hotspot/share/cds/aotMetaspace.cpp @@ -28,6 +28,7 @@ #include "cds/aotClassLinker.hpp" #include "cds/aotClassLocation.hpp" #include "cds/aotConstantPoolResolver.hpp" +#include "cds/aotHeap.inline.hpp" #include "cds/aotLinkedClassBulkLoader.hpp" #include "cds/aotLogging.hpp" #include "cds/aotMapLogger.hpp" @@ -46,7 +47,6 @@ #include "cds/dynamicArchive.hpp" #include "cds/filemap.hpp" #include "cds/finalImageRecipes.hpp" -#include "cds/heapShared.inline.hpp" #include "cds/lambdaFormInvokers.hpp" #include "cds/lambdaProxyClassDictionary.hpp" #include "classfile/classLoaderDataGraph.hpp" @@ -142,7 +142,7 @@ FileMapInfo* AOTMetaspace::_output_mapinfo = nullptr; // [5] SymbolTable, StringTable, SystemDictionary, and a few other read-only data // are copied into the ro region as read-only tables. // -// The heap region is written by HeapShared::write_heap(). +// The heap region is written by AOTHeap::write_heap(). // // The bitmap region is used to relocate the ro/rw/hp regions. @@ -360,7 +360,7 @@ void AOTMetaspace::post_initialize(TRAPS) { // Close any open file descriptors. However, mmap'ed pages will remain in memory. static_mapinfo->close(); - if (!(HeapShared::is_loading() && HeapShared::is_loading_streaming_mode())) { + if (!(AOTHeap::is_loading() && AOTHeap::is_loading_streaming_mode())) { // Streaming mode will unmap the bm region later in AOTStreamedHeapLoader::cleanup() static_mapinfo->unmap_region(AOTMetaspace::bm); } @@ -419,7 +419,7 @@ void AOTMetaspace::read_extra_data(JavaThread* current, const char* filename) { CLEAR_PENDING_EXCEPTION; } else { #if INCLUDE_CDS_JAVA_HEAP - if (HeapShared::is_string_too_large_to_archive(str)) { + if (AOTHeap::is_string_too_large_to_archive(str)) { log_warning(aot, heap)("[line %d] extra interned string ignored; size too large: %d", reader.last_line_no(), utf8_length); continue; @@ -525,7 +525,7 @@ void AOTMetaspace::serialize(SerializeClosure* soc) { // Dump/restore the symbol/string/subgraph_info tables SymbolTable::serialize_shared_table_header(soc); StringTable::serialize_shared_table_header(soc); - HeapShared::serialize_tables(soc); + AOTHeap::serialize_tables(soc); SystemDictionaryShared::serialize_dictionary_headers(soc); AOTLinkedClassBulkLoader::serialize(soc); FinalImageRecipes::serialize(soc); @@ -843,7 +843,7 @@ void VM_PopulateDumpSharedSpace::doit() { _map_info->set_cloned_vtables(CppVtables::vtables_serialized_base()); _map_info->header()->set_class_location_config(cl_config); - HeapShared::delete_tables_with_raw_oops(); + AOTHeap::delete_tables_with_raw_oops(); CDSConfig::set_is_at_aot_safepoint(false); } @@ -1142,12 +1142,12 @@ void AOTMetaspace::dump_static_archive_impl(StaticArchiveBuilder& builder, TRAPS // determinism is not needed. assert(CDSConfig::allow_only_single_java_thread(), "Required"); } - if (!HeapShared::is_archived_boot_layer_available(THREAD)) { + if (!AOTHeap::is_archived_boot_layer_available(THREAD)) { report_loading_error("archivedBootLayer not available, disabling full module graph"); CDSConfig::stop_dumping_full_module_graph(); } // Do this before link_shared_classes(), as the following line may load new classes. - HeapShared::init_for_dumping(CHECK); + AOTHeap::init_for_dumping(CHECK); } #endif @@ -1176,11 +1176,11 @@ void AOTMetaspace::dump_static_archive_impl(StaticArchiveBuilder& builder, TRAPS #if INCLUDE_CDS_JAVA_HEAP if (CDSConfig::is_dumping_heap()) { - HeapShared::init_heap_writer(); + AOTHeap::init_heap_writer(); if (CDSConfig::is_dumping_full_module_graph()) { AOTModules::ensure_module_entry_tables_exist(); AOTModules::build_tables(CHECK); - HeapShared::prepare_for_archiving(CHECK); + AOTHeap::prepare_for_archiving(CHECK); } AOTReferenceObjSupport::initialize(CHECK); @@ -1434,7 +1434,7 @@ bool AOTMetaspace::try_link_class(JavaThread* current, InstanceKlass* ik) { void VM_PopulateDumpSharedSpace::dump_java_heap_objects() { if (CDSConfig::is_dumping_heap()) { - HeapShared::write_heap(&_mapped_heap_info, &_streamed_heap_info); + AOTHeap::write_heap(&_mapped_heap_info, &_streamed_heap_info); } else if (!CDSConfig::is_dumping_preimage_static_archive()) { CDSConfig::log_reasons_for_not_dumping_heap(); } @@ -1877,12 +1877,12 @@ MapArchiveResult AOTMetaspace::map_archives(FileMapInfo* static_mapinfo, FileMap if (static_mapinfo->can_use_heap_region()) { if (static_mapinfo->object_streaming_mode()) { - HeapShared::initialize_loading_mode(HeapArchiveMode::_streaming); + AOTHeap::initialize_loading_mode(HeapArchiveMode::_streaming); } else { // map_or_load_heap_region() compares the current narrow oop and klass encodings // with the archived ones, so it must be done after all encodings are determined. static_mapinfo->map_or_load_heap_region(); - HeapShared::initialize_loading_mode(HeapArchiveMode::_mapping); + AOTHeap::initialize_loading_mode(HeapArchiveMode::_mapping); } } else { FileMapRegion* r = static_mapinfo->region_at(AOTMetaspace::hp); @@ -2215,7 +2215,7 @@ void AOTMetaspace::initialize_shared_spaces() { // Finish initializing the heap dump mode used in the archive // Heap initialization can be done only after vtables are initialized by ReadClosure. - HeapShared::finalize_initialization(static_mapinfo); + AOTHeap::finalize_initialization(static_mapinfo); Universe::load_archived_object_instances(); AOTCodeCache::initialize(); @@ -2269,7 +2269,7 @@ void AOTMetaspace::initialize_shared_spaces() { CountSharedSymbols cl; SymbolTable::shared_symbols_do(&cl); tty->print_cr("Number of shared symbols: %zu", cl.total()); - if (HeapShared::is_loading() && HeapShared::is_loading_mapping_mode()) { + if (AOTHeap::is_loading() && AOTHeap::is_loading_mapping_mode()) { tty->print_cr("Number of shared strings: %zu", StringTable::shared_entry_count()); } tty->print_cr("VM version: %s\r\n", static_mapinfo->vm_version()); diff --git a/src/hotspot/share/cds/aotModules.cpp b/src/hotspot/share/cds/aotModules.cpp index 69eb41d343dd..3bf43d0f5176 100644 --- a/src/hotspot/share/cds/aotModules.cpp +++ b/src/hotspot/share/cds/aotModules.cpp @@ -22,10 +22,10 @@ * */ +#include "cds/aotHeap.inline.hpp" #include "cds/aotLogging.hpp" #include "cds/aotModules.hpp" #include "cds/cdsConfig.hpp" -#include "cds/heapShared.inline.hpp" #include "cds/serializeClosure.hpp" #include "classfile/classLoaderData.inline.hpp" #include "classfile/moduleEntry.hpp" @@ -476,13 +476,13 @@ void AOTModules::init_archived_modules(JavaThread* current, Handle h_platform_lo void AOTModules::load_archived_platform_and_system_class_loaders() { // The streaming object loader prefers loading the class loader related objects before // the CLD constructor which has a NoSafepointVerifier. - if (!HeapShared::is_loading_streaming_mode()) { + if (!AOTHeap::is_loading_streaming_mode()) { return; } // Ensure these class loaders are eagerly materialized before their CLDs are created. - HeapShared::get_root(_platform_loader_root_index, false /* clear */); - HeapShared::get_root(_system_loader_root_index, false /* clear */); + AOTHeap::get_root(_platform_loader_root_index, false /* clear */); + AOTHeap::get_root(_system_loader_root_index, false /* clear */); if (Universe::is_module_initialized() || !CDSConfig::is_using_full_module_graph()) { return; @@ -551,8 +551,8 @@ void AOTModules::remove_unshareable_info() { _archived_javabase_moduleEntry = ArchiveBuilder::current()->get_buffered_addr(ModuleEntryTable::javabase_moduleEntry()); - _platform_loader_root_index = HeapShared::append_root(SystemDictionary::java_platform_loader()); - _system_loader_root_index = HeapShared::append_root(SystemDictionary::java_system_loader()); + _platform_loader_root_index = AOTHeap::append_root(SystemDictionary::java_platform_loader()); + _system_loader_root_index = AOTHeap::append_root(SystemDictionary::java_system_loader()); } void AOTModules::serialize(SerializeClosure* f) { @@ -579,9 +579,9 @@ ModuleEntry* AOTModules::archived_unnamed_module(ClassLoaderData* loader_data) { precond(_platform_loader_root_index >= 0); precond(_system_loader_root_index >= 0); - if (loader_data->class_loader() == HeapShared::get_root(_platform_loader_root_index)) { + if (loader_data->class_loader() == AOTHeap::get_root(_platform_loader_root_index)) { archived_module = _archived_platform_loader_data.unnamed_module(); - } else if (loader_data->class_loader() == HeapShared::get_root(_system_loader_root_index)) { + } else if (loader_data->class_loader() == AOTHeap::get_root(_system_loader_root_index)) { archived_module = _archived_system_loader_data.unnamed_module(); } } @@ -595,8 +595,8 @@ void AOTModules::clear_archived_oops() { _archived_platform_loader_data.clear_archived_oops(); _archived_system_loader_data.clear_archived_oops(); if (_platform_loader_root_index >= 0) { - HeapShared::clear_root(_platform_loader_root_index); - HeapShared::clear_root(_system_loader_root_index); + AOTHeap::clear_root(_platform_loader_root_index); + AOTHeap::clear_root(_system_loader_root_index); } } @@ -634,8 +634,8 @@ void AOTModules::restore_archived_modules_for_preloading_classes(JavaThread* cur precond(_platform_loader_root_index >= 0); precond(_system_loader_root_index >= 0); - Handle h_platform_loader(current, HeapShared::get_root(_platform_loader_root_index)); - Handle h_system_loader(current, HeapShared::get_root(_system_loader_root_index)); + Handle h_platform_loader(current, AOTHeap::get_root(_platform_loader_root_index)); + Handle h_system_loader(current, AOTHeap::get_root(_system_loader_root_index)); init_archived_modules(current, h_platform_loader, h_system_loader); } diff --git a/src/hotspot/share/cds/aotOopChecker.cpp b/src/hotspot/share/cds/aotOopChecker.cpp index 3c0a142a059f..befc341f7f7e 100644 --- a/src/hotspot/share/cds/aotOopChecker.cpp +++ b/src/hotspot/share/cds/aotOopChecker.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -22,9 +22,9 @@ * */ +#include "cds/aotHeap.hpp" #include "cds/aotMetaspace.hpp" #include "cds/aotOopChecker.hpp" -#include "cds/heapShared.hpp" #include "classfile/javaClasses.hpp" #include "classfile/symbolTable.hpp" #include "classfile/vmClasses.hpp" @@ -71,7 +71,7 @@ void AOTOopChecker::check(oop obj) { ResourceMark rm; log_error(aot)("Must cache only URLs with jrt/file protocols but got: %s", java_lang_String::as_quoted_ascii(protocol)); - HeapShared::debug_trace(); + AOTHeap::debug_trace(); AOTMetaspace::unrecoverable_writing_error(); } } diff --git a/src/hotspot/share/cds/aotReferenceObjSupport.cpp b/src/hotspot/share/cds/aotReferenceObjSupport.cpp index 9af1e836391f..082c116edb91 100644 --- a/src/hotspot/share/cds/aotReferenceObjSupport.cpp +++ b/src/hotspot/share/cds/aotReferenceObjSupport.cpp @@ -22,8 +22,8 @@ * */ +#include "cds/aotHeap.hpp" #include "cds/aotReferenceObjSupport.hpp" -#include "cds/heapShared.hpp" #include "classfile/javaClasses.hpp" #include "classfile/symbolTable.hpp" #include "classfile/systemDictionary.hpp" @@ -96,7 +96,7 @@ class KeepAliveObjectsTable : public HashTable {}; + AOTHeap::oop_address_hash> {}; static KeepAliveObjectsTable* _keep_alive_objs_table; static OopHandle _keep_alive_objs_array; @@ -226,7 +226,7 @@ bool AOTReferenceObjSupport::check_if_ref_obj(oop obj) { PTR_FORMAT ")", p2i(_null_queue.resolve())); log_error(aot, heap)("%s", (referent == nullptr) ? "referent cannot be null" : "referent is not registered with CDS.keepAlive()"); - HeapShared::debug_trace(); + AOTHeap::debug_trace(); AOTMetaspace::unrecoverable_writing_error(); } diff --git a/src/hotspot/share/cds/aotStreamedHeap.hpp b/src/hotspot/share/cds/aotStreamedHeap.hpp index f06b1bcb4c62..1f6cb7c16863 100644 --- a/src/hotspot/share/cds/aotStreamedHeap.hpp +++ b/src/hotspot/share/cds/aotStreamedHeap.hpp @@ -58,7 +58,7 @@ class AOTStreamedHeapHeader { class AOTStreamedHeapInfo { MemRegion _buffer_region; // Contains the archived objects to be written into the CDS archive. CHeapBitMap _oopmap; - size_t _roots_offset; // Offset of the HeapShared::roots() object, from the bottom + size_t _roots_offset; // Offset of the AOTHeap::roots() object, from the bottom // of the archived heap objects, in bytes. size_t _num_roots; diff --git a/src/hotspot/share/cds/aotStreamedHeapLoader.cpp b/src/hotspot/share/cds/aotStreamedHeapLoader.cpp index 528c0f149793..012dc9eccdab 100644 --- a/src/hotspot/share/cds/aotStreamedHeapLoader.cpp +++ b/src/hotspot/share/cds/aotStreamedHeapLoader.cpp @@ -22,13 +22,13 @@ * */ +#include "cds/aotHeap.inline.hpp" #include "cds/aotMetaspace.hpp" #include "cds/aotModules.hpp" #include "cds/aotStreamedHeapLoader.hpp" #include "cds/aotThread.hpp" #include "cds/cdsConfig.hpp" #include "cds/filemap.hpp" -#include "cds/heapShared.inline.hpp" #include "classfile/javaClasses.inline.hpp" #include "classfile/stringTable.hpp" #include "classfile/vmClasses.hpp" @@ -329,7 +329,7 @@ void AOTStreamedHeapLoader::copy_object_impl(oopDesc* archive_object, // A concurrent GC thread might racingly read the object payload after GC is enabled. InPlaceLinkingOopClosure cl(heap_object, linker); heap_object->oop_iterate(&cl); - HeapShared::remap_loaded_metadata(heap_object); + AOTHeap::remap_loaded_metadata(heap_object); return; } @@ -353,7 +353,7 @@ void AOTStreamedHeapLoader::copy_object_impl(oopDesc* archive_object, // shady metadata fields from the archive that do not point at real metadata. // We deal with this by explicitly reading the requested address from the // archive and fixing it to real Metadata before writing it into the heap object. - HeapShared::do_metadata_offsets(heap_object, [&](int metadata_offset) { + AOTHeap::do_metadata_offsets(heap_object, [&](int metadata_offset) { BitMap::idx_t metadata_field_idx = header_bit + (size_t)metadata_offset / sizeof(RawElementT); BitMap::idx_t skip = word_scale; assert(metadata_field_idx >= start_bit && metadata_field_idx + skip <= end_bit, diff --git a/src/hotspot/share/cds/aotStreamedHeapWriter.cpp b/src/hotspot/share/cds/aotStreamedHeapWriter.cpp index d2ea2d02b342..3781aded1458 100644 --- a/src/hotspot/share/cds/aotStreamedHeapWriter.cpp +++ b/src/hotspot/share/cds/aotStreamedHeapWriter.cpp @@ -22,12 +22,12 @@ * */ +#include "cds/aotHeap.inline.hpp" #include "cds/aotModules.hpp" #include "cds/aotReferenceObjSupport.hpp" #include "cds/aotStreamedHeapWriter.hpp" #include "cds/cdsConfig.hpp" #include "cds/filemap.hpp" -#include "cds/heapShared.inline.hpp" #include "cds/regeneratedClasses.hpp" #include "classfile/modules.hpp" #include "classfile/stringTable.hpp" @@ -105,7 +105,7 @@ class FollowOopIterateClosure: public BasicOopIterateClosure { private: template void do_oop_work(T *p) { size_t field_offset = pointer_delta(p, _src_obj, sizeof(char)); - oop obj = HeapShared::maybe_remap_referent(_is_java_lang_ref, field_offset, HeapAccess<>::oop_load(p)); + oop obj = AOTHeap::maybe_remap_referent(_is_java_lang_ref, field_offset, HeapAccess<>::oop_load(p)); if (obj != nullptr) { _dfs_stack->push(obj); } @@ -253,7 +253,7 @@ static BitMap::idx_t bit_idx_for_buffer_offset(size_t buffer_offset) { void AOTStreamedHeapWriter::copy_source_objs_to_buffer(GrowableArrayCHeap* roots) { for (int i = 0; i < _source_objs->length(); i++) { oop src_obj = _source_objs->at(i); - HeapShared::CachedOopInfo* info = HeapShared::get_cached_oop_info(src_obj); + AOTHeap::CachedOopInfo* info = AOTHeap::get_cached_oop_info(src_obj); assert(info != nullptr, "must be"); size_t buffer_offset = copy_one_source_obj_to_buffer(src_obj); info->set_buffer_offset(buffer_offset); @@ -306,7 +306,7 @@ size_t AOTStreamedHeapWriter::copy_one_source_obj_to_buffer(oop src_obj) { ensure_buffer_space(new_used); - if (HeapShared::is_interned_string(src_obj)) { + if (AOTHeap::is_interned_string(src_obj)) { java_lang_String::hash_code(src_obj); // Sets the hash code field(s) java_lang_String::set_deduplication_forbidden(src_obj); // Allows faster interning at runtime assert(java_lang_String::hash_is_set(src_obj), "hash must be set"); @@ -382,7 +382,7 @@ void AOTStreamedHeapWriter::update_header_for_buffered_addr(address buffered_add mw = mw.copy_set_hash(src_hash); } - if (HeapShared::is_interned_string(src_obj)) { + if (AOTHeap::is_interned_string(src_obj)) { // Mark the mark word of interned string so the loader knows to link these to // the string table at runtime. mw = mw.set_marked(); @@ -416,7 +416,7 @@ class AOTStreamedHeapWriter::EmbeddedOopMapper: public BasicOopIterateClosure { template void do_oop_work(T *p) { size_t field_offset = pointer_delta(p, _src_obj, sizeof(char)); - oop obj = HeapShared::maybe_remap_referent(_is_java_lang_ref, field_offset, HeapAccess<>::oop_load(p)); + oop obj = AOTHeap::maybe_remap_referent(_is_java_lang_ref, field_offset, HeapAccess<>::oop_load(p)); AOTStreamedHeapWriter::map_oop_field_in_buffer(obj, (T*)(_buffered_obj + field_offset), _oopmap); } }; @@ -440,7 +440,7 @@ void AOTStreamedHeapWriter::map_embedded_oops(AOTStreamedHeapInfo* heap_info) { for (int i = 0; i < _source_objs->length(); i++) { oop src_obj = _source_objs->at(i); - HeapShared::CachedOopInfo* info = HeapShared::get_cached_oop_info(src_obj); + AOTHeap::CachedOopInfo* info = AOTHeap::get_cached_oop_info(src_obj); assert(info != nullptr, "must be"); address buffered_obj = offset_to_buffered_address
      (info->buffer_offset()); @@ -448,7 +448,7 @@ void AOTStreamedHeapWriter::map_embedded_oops(AOTStreamedHeapInfo* heap_info) { EmbeddedOopMapper mapper(src_obj, buffered_obj, heap_info->oopmap()); src_obj->oop_iterate(&mapper); - HeapShared::remap_dumped_metadata(src_obj, buffered_obj); + AOTHeap::remap_dumped_metadata(src_obj, buffered_obj); }; size_t total_bytes = (size_t)_buffer->length(); @@ -456,7 +456,7 @@ void AOTStreamedHeapWriter::map_embedded_oops(AOTStreamedHeapInfo* heap_info) { } size_t AOTStreamedHeapWriter::source_obj_to_buffered_offset(oop src_obj) { - HeapShared::CachedOopInfo* p = HeapShared::get_cached_oop_info(src_obj); + AOTHeap::CachedOopInfo* p = AOTHeap::get_cached_oop_info(src_obj); return p->buffer_offset(); } @@ -486,7 +486,7 @@ void AOTStreamedHeapWriter::populate_archive_heap_info(AOTStreamedHeapInfo* info info->set_buffer_region(MemRegion(offset_to_buffered_address(0), offset_to_buffered_address(_buffer_used))); info->set_roots_offset(_roots_offset); - info->set_num_roots((size_t)HeapShared::pending_roots()->length()); + info->set_num_roots((size_t)AOTHeap::pending_roots()->length()); info->set_forwarding_offset(_forwarding_offset); info->set_root_highest_object_index_table_offset(_root_highest_object_index_table_offset); info->set_num_archived_objects((size_t)_source_objs->length()); diff --git a/src/hotspot/share/cds/aotStreamedHeapWriter.hpp b/src/hotspot/share/cds/aotStreamedHeapWriter.hpp index c3cc9f2c0921..9690d9d1d283 100644 --- a/src/hotspot/share/cds/aotStreamedHeapWriter.hpp +++ b/src/hotspot/share/cds/aotStreamedHeapWriter.hpp @@ -26,7 +26,6 @@ #define SHARE_CDS_AOTSTREAMEDHEAPWRITER_HPP #include "cds/aotMapLogger.hpp" -#include "cds/heapShared.hpp" #include "memory/allocation.hpp" #include "memory/allStatic.hpp" #include "oops/oopHandle.hpp" diff --git a/src/hotspot/share/cds/aotThread.cpp b/src/hotspot/share/cds/aotThread.cpp index f3382a6f7e4c..7f9304dd5554 100644 --- a/src/hotspot/share/cds/aotThread.cpp +++ b/src/hotspot/share/cds/aotThread.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -24,7 +24,6 @@ #include "cds/aotStreamedHeapLoader.hpp" #include "cds/aotThread.hpp" -#include "cds/heapShared.hpp" #include "classfile/javaClasses.hpp" #include "classfile/javaThreadStatus.hpp" #include "classfile/vmClasses.hpp" diff --git a/src/hotspot/share/cds/archiveBuilder.cpp b/src/hotspot/share/cds/archiveBuilder.cpp index 211d4a3cc871..0cbc08319312 100644 --- a/src/hotspot/share/cds/archiveBuilder.cpp +++ b/src/hotspot/share/cds/archiveBuilder.cpp @@ -25,6 +25,7 @@ #include "cds/aotArtifactFinder.hpp" #include "cds/aotClassLinker.hpp" #include "cds/aotCompressedPointers.hpp" +#include "cds/aotHeap.hpp" #include "cds/aotLogging.hpp" #include "cds/aotMapLogger.hpp" #include "cds/aotMetaspace.hpp" @@ -35,7 +36,6 @@ #include "cds/cppVtables.hpp" #include "cds/dumpAllocStats.hpp" #include "cds/dynamicArchive.hpp" -#include "cds/heapShared.hpp" #include "cds/regeneratedClasses.hpp" #include "classfile/classLoader.hpp" #include "classfile/javaClasses.hpp" @@ -844,10 +844,10 @@ void ArchiveBuilder::make_klasses_shareable() { assert(0, "shouldn't happen"); } if (CDSConfig::is_dumping_method_handles()) { - assert(HeapShared::is_archivable_hidden_klass(ik), "sanity"); + assert(AOTHeap::is_archivable_hidden_klass(ik), "sanity"); } else { // Legacy CDS support for lambda proxies - CDS_JAVA_HEAP_ONLY(assert(HeapShared::is_lambda_proxy_klass(ik), "sanity");) + CDS_JAVA_HEAP_ONLY(assert(AOTHeap::is_lambda_proxy_klass(ik), "sanity");) } } else if (ik->defined_by_boot_loader()) { type = "boot"; @@ -1170,7 +1170,7 @@ void ArchiveBuilder::write_archive(FileMapInfo* mapinfo, AOTMappedHeapInfo* mapp if (log_is_enabled(Info, aot, map)) { AOTMapLogger::dumptime_log(this, mapinfo, mapped_heap_info, streamed_heap_info, bitmap, bitmap_size_in_bytes); } - CDS_JAVA_HEAP_ONLY(HeapShared::destroy_archived_object_cache()); + CDS_JAVA_HEAP_ONLY(AOTHeap::destroy_archived_object_cache()); FREE_C_HEAP_ARRAY(bitmap); } diff --git a/src/hotspot/share/cds/archiveUtils.cpp b/src/hotspot/share/cds/archiveUtils.cpp index 3d4d36ab486b..9dbd8ce1cfa7 100644 --- a/src/hotspot/share/cds/archiveUtils.cpp +++ b/src/hotspot/share/cds/archiveUtils.cpp @@ -33,7 +33,6 @@ #include "cds/dumpAllocStats.hpp" #include "cds/dynamicArchive.hpp" #include "cds/filemap.hpp" -#include "cds/heapShared.hpp" #include "cds/lambdaProxyClassDictionary.hpp" #include "classfile/systemDictionaryShared.hpp" #include "classfile/vmClasses.hpp" diff --git a/src/hotspot/share/cds/cdsConfig.cpp b/src/hotspot/share/cds/cdsConfig.cpp index 083bb3a294ee..4faaa6329874 100644 --- a/src/hotspot/share/cds/cdsConfig.cpp +++ b/src/hotspot/share/cds/cdsConfig.cpp @@ -22,6 +22,7 @@ * */ +#include "cds/aotHeap.hpp" #include "cds/aotLogging.hpp" #include "cds/aotMapLogger.hpp" #include "cds/aotMetaspace.hpp" @@ -30,7 +31,6 @@ #include "cds/cdsConfig.hpp" #include "cds/classListWriter.hpp" #include "cds/filemap.hpp" -#include "cds/heapShared.inline.hpp" #include "classfile/moduleEntry.hpp" #include "code/aotCodeCache.hpp" #include "compiler/compilerDefinitions.inline.hpp" @@ -122,7 +122,7 @@ void CDSConfig::ergo_initialize() { precond(allow_only_single_java_thread()); // The AttachListenerThread may execute Java code or load new classes. It might see - // unexpected results after HeapShared::prepare_for_archiving(). + // unexpected results after AOTHeap::prepare_for_archiving(). // // We disable all new incoming attach requests, so you can't use jcmd, etc, on this JVM. // Since we are not running any application code in this JVM and only executed a very @@ -1025,7 +1025,7 @@ bool CDSConfig::is_dumping_heap() { } bool CDSConfig::is_loading_heap() { - return HeapShared::is_archived_heap_in_use(); + return AOTHeap::is_archived_heap_in_use(); } bool CDSConfig::is_dumping_klass_subgraphs() { @@ -1073,7 +1073,7 @@ bool CDSConfig::is_using_full_module_graph() { return false; } - if (is_using_archive() && HeapShared::can_use_archived_heap()) { + if (is_using_archive() && AOTHeap::can_use_archived_heap()) { // Classes used by the archived full module graph are loaded in JVMTI early phase. assert(!(JvmtiExport::should_post_class_file_load_hook() && JvmtiExport::has_early_class_hook_env()), "CDS should be disabled if early class hooks are enabled"); diff --git a/src/hotspot/share/cds/cdsEnumKlass.cpp b/src/hotspot/share/cds/cdsEnumKlass.cpp index 7684e33e7b8c..4fb82b67bc40 100644 --- a/src/hotspot/share/cds/cdsEnumKlass.cpp +++ b/src/hotspot/share/cds/cdsEnumKlass.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -22,8 +22,8 @@ * */ +#include "cds/aotHeap.hpp" #include "cds/cdsEnumKlass.hpp" -#include "cds/heapShared.inline.hpp" #include "classfile/systemDictionaryShared.hpp" #include "classfile/vmClasses.hpp" #include "memory/resourceArea.hpp" @@ -58,7 +58,7 @@ bool CDSEnumKlass::is_enum_obj(oop orig_obj) { // ModuleLayer::EMPTY_LAYER, for example). // // Instead, we archive all static field of such Enum classes. At runtime, -// HeapShared::initialize_enum_klass() skips the method and instead pulls +// AOTHeap::initialize_enum_klass() skips the method and instead pulls // the static fields out of the archived heap. void CDSEnumKlass::handle_enum_obj(int level, KlassSubGraphInfo* subgraph_info, @@ -101,9 +101,9 @@ void CDSEnumKlass::archive_static_field(int level, KlassSubGraphInfo* subgraph_i guarantee(false, "static field %s::%s is of the wrong type", ik->external_name(), fd.name()->as_C_string()); } - bool success = HeapShared::archive_reachable_objects_from(level, subgraph_info, oop_field); + bool success = AOTHeap::archive_reachable_objects_from(level, subgraph_info, oop_field); assert(success, "VM should have exited with unarchivable objects for _level > 1"); - int root_index = HeapShared::append_root(oop_field); + int root_index = AOTHeap::append_root(oop_field); log_info(aot, heap)("Archived enum obj @%d %s::%s (" INTPTR_FORMAT ")", root_index, ik->external_name(), fd.name()->as_C_string(), p2i((oopDesc*)oop_field)); @@ -111,7 +111,7 @@ void CDSEnumKlass::archive_static_field(int level, KlassSubGraphInfo* subgraph_i } bool CDSEnumKlass::initialize_enum_klass(InstanceKlass* k, TRAPS) { - if (!HeapShared::is_archived_heap_in_use()) { + if (!AOTHeap::is_archived_heap_in_use()) { return false; } @@ -129,7 +129,7 @@ bool CDSEnumKlass::initialize_enum_klass(InstanceKlass* k, TRAPS) { int root_index = info->enum_klass_static_field_root_index_at(i++); fieldDescriptor& fd = fs.field_descriptor(); assert(fd.field_type() == T_OBJECT || fd.field_type() == T_ARRAY, "must be"); - oop root_object = HeapShared::get_root(root_index, /*clear=*/true); + oop root_object = AOTHeap::get_root(root_index, /*clear=*/true); k->java_mirror()->obj_field_put(fd.offset(), root_object); } } diff --git a/src/hotspot/share/cds/cdsHeapVerifier.cpp b/src/hotspot/share/cds/cdsHeapVerifier.cpp index b0aaede89c1f..ee4ccea84a9c 100644 --- a/src/hotspot/share/cds/cdsHeapVerifier.cpp +++ b/src/hotspot/share/cds/cdsHeapVerifier.cpp @@ -375,7 +375,7 @@ void CDSHeapVerifier::do_klass(Klass* k) { if (k->is_instance_klass()) { InstanceKlass* ik = InstanceKlass::cast(k); - if (HeapShared::is_subgraph_root_class(ik)) { + if (AOTHeap::is_subgraph_root_class(ik)) { // ik is inside one of the ArchivableStaticFieldInfo tables // in heapShared.cpp. We assume such classes are programmed to // update their static fields correctly at runtime. @@ -400,11 +400,11 @@ void CDSHeapVerifier::add_static_obj_field(InstanceKlass* ik, oop field, Symbol* // This function is called once for every archived heap object. Warn if this object is referenced by // a static field of a class that's not aot-initialized. -inline bool CDSHeapVerifier::do_entry(OopHandle& orig_obj_handle, HeapShared::CachedOopInfo& value) { +inline bool CDSHeapVerifier::do_entry(OopHandle& orig_obj_handle, AOTHeap::CachedOopInfo& value) { oop orig_obj = orig_obj_handle.resolve(); _archived_objs++; - if (java_lang_String::is_instance(orig_obj) && HeapShared::is_dumped_interned_string(orig_obj)) { + if (java_lang_String::is_instance(orig_obj) && AOTHeap::is_dumped_interned_string(orig_obj)) { // It's quite often for static fields to have interned strings. These are most likely not // problematic (and are hard to filter). So we will ignore them. return true; @@ -451,7 +451,7 @@ class CDSHeapVerifier::TraceFields : public FieldClosure { // Call this function (from gdb, etc) if you want to know why an object is archived. void CDSHeapVerifier::trace_to_root(outputStream* st, oop orig_obj) { - HeapShared::CachedOopInfo* info = HeapShared::get_cached_oop_info(orig_obj); + AOTHeap::CachedOopInfo* info = AOTHeap::get_cached_oop_info(orig_obj); if (info != nullptr) { trace_to_root(st, orig_obj, nullptr, info); } else { @@ -482,10 +482,10 @@ const char* static_field_name(oop mirror, oop field) { return ""; } -int CDSHeapVerifier::trace_to_root(outputStream* st, oop orig_obj, oop orig_field, HeapShared::CachedOopInfo* info) { +int CDSHeapVerifier::trace_to_root(outputStream* st, oop orig_obj, oop orig_field, AOTHeap::CachedOopInfo* info) { int level = 0; if (info->orig_referrer() != nullptr) { - HeapShared::CachedOopInfo* ref = HeapShared::get_cached_oop_info(info->orig_referrer()); + AOTHeap::CachedOopInfo* ref = AOTHeap::get_cached_oop_info(info->orig_referrer()); assert(ref != nullptr, "sanity"); level = trace_to_root(st, info->orig_referrer(), orig_obj, ref) + 1; } else if (java_lang_String::is_instance(orig_obj)) { @@ -521,7 +521,7 @@ int CDSHeapVerifier::trace_to_root(outputStream* st, oop orig_obj, oop orig_fiel void CDSHeapVerifier::verify() { CDSHeapVerifier verf; - HeapShared::archived_object_cache()->iterate(&verf); + AOTHeap::archived_object_cache()->iterate(&verf); } #endif // INCLUDE_CDS_JAVA_HEAP diff --git a/src/hotspot/share/cds/cdsHeapVerifier.hpp b/src/hotspot/share/cds/cdsHeapVerifier.hpp index f8e090801bbc..33de9e8dfbaa 100644 --- a/src/hotspot/share/cds/cdsHeapVerifier.hpp +++ b/src/hotspot/share/cds/cdsHeapVerifier.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,7 +25,7 @@ #ifndef SHARE_CDS_CDSHEAPVERIFIER_HPP #define SHARE_CDS_CDSHEAPVERIFIER_HPP -#include "cds/heapShared.hpp" +#include "cds/aotHeap.hpp" #include "memory/iterator.hpp" #include "oops/oopHandle.hpp" #include "utilities/growableArray.hpp" @@ -53,7 +53,7 @@ class CDSHeapVerifier : public KlassClosure { 15889, // prime number AnyObj::C_HEAP, mtClassShared, - HeapShared::oop_address_hash> _table; + AOTHeap::oop_address_hash> _table; GrowableArray _exclusions; GrowableArray _shared_secret_accessors; @@ -88,7 +88,7 @@ class CDSHeapVerifier : public KlassClosure { return false; } - static int trace_to_root(outputStream* st, oop orig_obj, oop orig_field, HeapShared::CachedOopInfo* p); + static int trace_to_root(outputStream* st, oop orig_obj, oop orig_field, AOTHeap::CachedOopInfo* p); CDSHeapVerifier(); ~CDSHeapVerifier(); @@ -99,7 +99,7 @@ class CDSHeapVerifier : public KlassClosure { virtual void do_klass(Klass* k); // For HashTable::iterate() - inline bool do_entry(OopHandle& orig_obj, HeapShared::CachedOopInfo& value); + inline bool do_entry(OopHandle& orig_obj, AOTHeap::CachedOopInfo& value); static void verify(); diff --git a/src/hotspot/share/cds/dynamicArchive.cpp b/src/hotspot/share/cds/dynamicArchive.cpp index b8adbba22f1f..30afc2c2a5a1 100644 --- a/src/hotspot/share/cds/dynamicArchive.cpp +++ b/src/hotspot/share/cds/dynamicArchive.cpp @@ -33,7 +33,6 @@ #include "cds/cds_globals.hpp" #include "cds/cdsConfig.hpp" #include "cds/dynamicArchive.hpp" -#include "cds/heapShared.hpp" #include "cds/lambdaFormInvokers.hpp" #include "cds/lambdaProxyClassDictionary.hpp" #include "cds/regeneratedClasses.hpp" diff --git a/src/hotspot/share/cds/filemap.cpp b/src/hotspot/share/cds/filemap.cpp index d0788871221d..d0feb197ef28 100644 --- a/src/hotspot/share/cds/filemap.cpp +++ b/src/hotspot/share/cds/filemap.cpp @@ -23,6 +23,7 @@ */ #include "cds/aotClassLocation.hpp" +#include "cds/aotHeap.inline.hpp" #include "cds/aotLogging.hpp" #include "cds/aotMappedHeapLoader.hpp" #include "cds/aotMappedHeapWriter.hpp" @@ -33,7 +34,6 @@ #include "cds/cdsConfig.hpp" #include "cds/dynamicArchive.hpp" #include "cds/filemap.hpp" -#include "cds/heapShared.inline.hpp" #include "classfile/altHashing.hpp" #include "classfile/classFileStream.hpp" #include "classfile/classLoader.hpp" @@ -293,7 +293,7 @@ void FileMapHeader::populate(FileMapInfo *info, size_t core_region_alignment, _compact_headers = UseCompactObjectHeaders; #if INCLUDE_CDS_JAVA_HEAP if (CDSConfig::is_dumping_heap()) { - _object_streaming_mode = HeapShared::is_writing_streaming_mode(); + _object_streaming_mode = AOTHeap::is_writing_streaming_mode(); _narrow_oop_mode = AOTMappedHeapWriter::narrow_oop_mode(); _narrow_oop_base = AOTMappedHeapWriter::narrow_oop_base(); _narrow_oop_shift = AOTMappedHeapWriter::narrow_oop_shift(); @@ -875,7 +875,7 @@ size_t FileMapRegion::used_aligned() const { void FileMapRegion::init(int region_index, size_t mapping_offset, size_t size, bool read_only, bool allow_exec, int crc) { - _is_heap_region = HeapShared::is_heap_region(region_index); + _is_heap_region = AOTHeap::is_heap_region(region_index); _is_bitmap_region = (region_index == AOTMetaspace::bm); _mapping_offset = mapping_offset; _used = size; @@ -977,11 +977,11 @@ void FileMapInfo::write_region(int region, char* base, size_t size, } else if (size == 0) { // This is an unused region (e.g., a heap region when !INCLUDE_CDS_JAVA_HEAP) requested_base = nullptr; - } else if (HeapShared::is_heap_region(region)) { + } else if (AOTHeap::is_heap_region(region)) { assert(CDSConfig::is_dumping_heap(), "sanity"); #if INCLUDE_CDS_JAVA_HEAP assert(!CDSConfig::is_dumping_dynamic_archive(), "must be"); - if (HeapShared::is_writing_mapping_mode()) { + if (AOTHeap::is_writing_mapping_mode()) { requested_base = (char*)AOTMappedHeapWriter::requested_address(); if (UseCompressedOops) { mapping_offset = (size_t)((address)requested_base - AOTMappedHeapWriter::narrow_oop_base()); @@ -1055,7 +1055,7 @@ char* FileMapInfo::write_bitmap_region(CHeapBitMap* rw_ptrmap, if (mapped_heap_info != nullptr && mapped_heap_info->is_used()) { // Remove leading and trailing zeros - assert(HeapShared::is_writing_mapping_mode(), "unexpected dumping mode"); + assert(AOTHeap::is_writing_mapping_mode(), "unexpected dumping mode"); size_t removed_oop_leading_zeros = remove_bitmap_zeros(mapped_heap_info->oopmap()); size_t removed_ptr_leading_zeros = remove_bitmap_zeros(mapped_heap_info->ptrmap()); mapped_heap_info->set_oopmap_start_pos(removed_oop_leading_zeros); @@ -1064,7 +1064,7 @@ char* FileMapInfo::write_bitmap_region(CHeapBitMap* rw_ptrmap, size_in_bytes += mapped_heap_info->oopmap()->size_in_bytes(); size_in_bytes += mapped_heap_info->ptrmap()->size_in_bytes(); } else if (streamed_heap_info != nullptr && streamed_heap_info->is_used()) { - assert(HeapShared::is_writing_streaming_mode(), "unexpected dumping mode"); + assert(AOTHeap::is_writing_streaming_mode(), "unexpected dumping mode"); size_in_bytes += streamed_heap_info->oopmap()->size_in_bytes(); } @@ -1090,7 +1090,7 @@ char* FileMapInfo::write_bitmap_region(CHeapBitMap* rw_ptrmap, } if (mapped_heap_info != nullptr && mapped_heap_info->is_used()) { - assert(HeapShared::is_writing_mapping_mode(), "unexpected dumping mode"); + assert(AOTHeap::is_writing_mapping_mode(), "unexpected dumping mode"); FileMapRegion* r = region_at(AOTMetaspace::hp); r->init_oopmap(written, mapped_heap_info->oopmap()->size()); @@ -1099,7 +1099,7 @@ char* FileMapInfo::write_bitmap_region(CHeapBitMap* rw_ptrmap, r->init_ptrmap(written, mapped_heap_info->ptrmap()->size()); written = write_bitmap(mapped_heap_info->ptrmap(), buffer, written); } else if (streamed_heap_info != nullptr && streamed_heap_info->is_used()) { - assert(HeapShared::is_writing_streaming_mode(), "unexpected dumping mode"); + assert(AOTHeap::is_writing_streaming_mode(), "unexpected dumping mode"); FileMapRegion* r = region_at(AOTMetaspace::hp); r->init_oopmap(written, streamed_heap_info->oopmap()->size()); @@ -1320,7 +1320,7 @@ bool FileMapInfo::read_region(int i, char* base, size_t size, bool do_commit) { } MapArchiveResult FileMapInfo::map_region(int i, intx addr_delta, char* mapped_base_address, ReservedSpace rs) { - assert(!HeapShared::is_heap_region(i), "sanity"); + assert(!AOTHeap::is_heap_region(i), "sanity"); FileMapRegion* r = region_at(i); size_t size = r->used_aligned(); char *requested_addr = mapped_base_address + r->mapping_offset(); @@ -1660,7 +1660,7 @@ void FileMapInfo::stream_heap_region() { assert(object_streaming_mode(), "This should only be done for the streaming approach"); if (map_auxiliary_region(AOTMetaspace::hp, /*readonly=*/true) != nullptr) { - HeapShared::initialize_streaming(); + AOTHeap::initialize_streaming(); } else { on_heap_region_loading_error(); } @@ -1723,11 +1723,11 @@ bool FileMapInfo::can_use_heap_region() { // If JvmtiExport::has_early_class_hook_env() is false, the classes of some objects // in the archived subgraphs may be replaced by the ClassFileLoadHook. But that's OK // because we won't install an archived object subgraph if the klass of any of the - // referenced objects are replaced. See HeapShared::initialize_from_archived_subgraph(). + // referenced objects are replaced. See AOTHeap::initialize_from_archived_subgraph(). } // We pre-compute narrow Klass IDs with the runtime mapping start intended to be the base, and a shift of - // HeapShared::precomputed_narrow_klass_shift. We enforce this encoding at runtime (see + // AOTHeap::precomputed_narrow_klass_shift. We enforce this encoding at runtime (see // CompressedKlassPointers::initialize_for_given_encoding()). Therefore, the following assertions must // hold: address archive_narrow_klass_base = (address)header()->mapped_base_address(); diff --git a/src/hotspot/share/cds/filemap.hpp b/src/hotspot/share/cds/filemap.hpp index 5987e6d52d3c..a92397db85f3 100644 --- a/src/hotspot/share/cds/filemap.hpp +++ b/src/hotspot/share/cds/filemap.hpp @@ -30,7 +30,6 @@ #include "cds/aotMetaspace.hpp" #include "cds/aotStreamedHeap.hpp" #include "cds/archiveUtils.hpp" -#include "cds/heapShared.hpp" #include "include/cds.h" #include "logging/logLevel.hpp" #include "memory/allocation.hpp" diff --git a/src/hotspot/share/ci/ciEnv.cpp b/src/hotspot/share/ci/ciEnv.cpp index bd0e723c0c27..65f9eec967eb 100644 --- a/src/hotspot/share/ci/ciEnv.cpp +++ b/src/hotspot/share/ci/ciEnv.cpp @@ -24,7 +24,7 @@ #include "cds/aotCacheAccess.hpp" #include "cds/aotConstantPoolResolver.hpp" -#include "cds/heapShared.hpp" +#include "cds/aotHeap.hpp" #include "ci/ciConstant.hpp" #include "ci/ciEnv.hpp" #include "ci/ciField.hpp" @@ -2009,7 +2009,7 @@ InstanceKlass::ClassState ciEnv::compute_init_state_for_aot_compile(InstanceKlas // are initialized in production run, but they are not recorded as init dependencies. // CI query should report their status as if in production run, otherwise AOT // code would have uncommon traps at invokedynamic calls. - if (HeapShared::is_core_java_lang_invoke_klass(ik)) { + if (AOTHeap::is_core_java_lang_invoke_klass(ik)) { log_trace(aot, compilation)("%d: core_java_lang_invoke: (%s) %s", task()->compile_id(), InstanceKlass::state2name(ik->init_state()), ik->external_name()); return InstanceKlass::ClassState::fully_initialized; } diff --git a/src/hotspot/share/classfile/classLoader.cpp b/src/hotspot/share/classfile/classLoader.cpp index 00a6442d13c1..fea2a8f9a26f 100644 --- a/src/hotspot/share/classfile/classLoader.cpp +++ b/src/hotspot/share/classfile/classLoader.cpp @@ -23,11 +23,11 @@ */ #include "cds/aotClassLocation.hpp" +#include "cds/aotHeap.hpp" #include "cds/cds_globals.hpp" #include "cds/cdsConfig.hpp" #include "cds/cdsProtectionDomain.hpp" #include "cds/dynamicArchive.hpp" -#include "cds/heapShared.hpp" #include "classfile/classFileStream.hpp" #include "classfile/classLoader.inline.hpp" #include "classfile/classLoaderData.inline.hpp" @@ -1440,10 +1440,10 @@ void ClassLoader::record_hidden_class(InstanceKlass* ik) { return; } - if (HeapShared::is_lambda_proxy_klass(ik)) { + if (AOTHeap::is_lambda_proxy_klass(ik)) { InstanceKlass* nest_host = ik->nest_host_not_null(); ik->set_shared_classpath_index(nest_host->shared_classpath_index()); - } else if (HeapShared::is_lambda_form_klass(ik)) { + } else if (AOTHeap::is_lambda_form_klass(ik)) { ik->set_shared_classpath_index(0); } else { // Generated invoker classes. diff --git a/src/hotspot/share/classfile/classLoaderData.cpp b/src/hotspot/share/classfile/classLoaderData.cpp index 2c74d255d97e..0a70103802c6 100644 --- a/src/hotspot/share/classfile/classLoaderData.cpp +++ b/src/hotspot/share/classfile/classLoaderData.cpp @@ -46,7 +46,7 @@ // The bootstrap loader (represented by null) also has a ClassLoaderData, // the singleton class the_null_class_loader_data(). -#include "cds/heapShared.hpp" +#include "cds/aotHeap.hpp" #include "classfile/classLoaderData.inline.hpp" #include "classfile/classLoaderDataGraph.inline.hpp" #include "classfile/dictionary.hpp" @@ -899,7 +899,7 @@ void ClassLoaderData::free_deallocate_list() { if (m->is_method()) { MetadataFactory::free_metadata(this, (Method*)m); } else if (m->is_constantPool()) { - HeapShared::remove_scratch_resolved_references((ConstantPool*)m); + AOTHeap::remove_scratch_resolved_references((ConstantPool*)m); MetadataFactory::free_metadata(this, (ConstantPool*)m); } else if (m->is_klass()) { JFR_ONLY(Jfr::on_deallocation(static_cast(m));) diff --git a/src/hotspot/share/classfile/javaClasses.cpp b/src/hotspot/share/classfile/javaClasses.cpp index c399d9e0a9b8..6f73e4a794aa 100644 --- a/src/hotspot/share/classfile/javaClasses.cpp +++ b/src/hotspot/share/classfile/javaClasses.cpp @@ -22,10 +22,10 @@ * */ +#include "cds/aotHeap.inline.hpp" #include "cds/aotMetaspace.hpp" #include "cds/aotReferenceObjSupport.hpp" #include "cds/cdsConfig.hpp" -#include "cds/heapShared.inline.hpp" #include "classfile/classLoaderData.inline.hpp" #include "classfile/javaClasses.inline.hpp" #include "classfile/javaClassesImpl.hpp" @@ -963,7 +963,7 @@ void java_lang_Class::fixup_mirror(Klass* k, TRAPS) { } if (k->in_aot_cache() && k->has_archived_mirror_index()) { - if (HeapShared::is_archived_heap_in_use()) { + if (AOTHeap::is_archived_heap_in_use()) { bool present = restore_archived_mirror(k, Handle(), Handle(), Handle(), CHECK); assert(present, "Missing archived mirror for %s", k->external_name()); return; @@ -1092,7 +1092,7 @@ void java_lang_Class::allocate_mirror(Klass* k, bool is_scratch, Handle protecti if (k->is_typeArray_klass()) { BasicType type = TypeArrayKlass::cast(k)->element_type(); if (is_scratch) { - comp_mirror = Handle(THREAD, HeapShared::scratch_java_mirror(type)); + comp_mirror = Handle(THREAD, AOTHeap::scratch_java_mirror(type)); } else { comp_mirror = Handle(THREAD, Universe::java_mirror(type)); } @@ -1101,7 +1101,7 @@ void java_lang_Class::allocate_mirror(Klass* k, bool is_scratch, Handle protecti Klass* element_klass = ObjArrayKlass::cast(k)->element_klass(); assert(element_klass != nullptr, "Must have an element klass"); if (is_scratch) { - comp_mirror = Handle(THREAD, HeapShared::scratch_java_mirror(element_klass)); + comp_mirror = Handle(THREAD, AOTHeap::scratch_java_mirror(element_klass)); } else { comp_mirror = Handle(THREAD, element_klass->java_mirror()); } @@ -1208,7 +1208,7 @@ void java_lang_Class::create_scratch_mirror(Klass* k, TRAPS) { release_set_array_klass(comp_mirror(), k); } - HeapShared::set_scratch_java_mirror(k, mirror()); + AOTHeap::set_scratch_java_mirror(k, mirror()); } // Returns true if the mirror is updated, false if no archived mirror @@ -1499,7 +1499,7 @@ BasicType java_lang_Class::primitive_type(oop java_class) { #ifdef ASSERT if (CDSConfig::is_dumping_heap()) { oop mirror = Universe::java_mirror(type); - oop scratch_mirror = HeapShared::scratch_java_mirror(type); + oop scratch_mirror = AOTHeap::scratch_java_mirror(type); assert(java_class == mirror || java_class == scratch_mirror, "must be consistent"); } else { assert(Universe::java_mirror(type) == java_class, "must be consistent"); diff --git a/src/hotspot/share/classfile/javaClasses.hpp b/src/hotspot/share/classfile/javaClasses.hpp index 5b6b923bc7ad..56862016db49 100644 --- a/src/hotspot/share/classfile/javaClasses.hpp +++ b/src/hotspot/share/classfile/javaClasses.hpp @@ -234,7 +234,7 @@ class java_lang_String : AllStatic { class java_lang_Class : AllStatic { friend class VMStructs; - friend class HeapShared; + friend class AOTHeap; private: diff --git a/src/hotspot/share/classfile/moduleEntry.cpp b/src/hotspot/share/classfile/moduleEntry.cpp index e36348c59321..8a644f027aee 100644 --- a/src/hotspot/share/classfile/moduleEntry.cpp +++ b/src/hotspot/share/classfile/moduleEntry.cpp @@ -23,11 +23,11 @@ */ #include "cds/aotClassLocation.hpp" +#include "cds/aotHeap.hpp" #include "cds/aotModules.hpp" #include "cds/archiveBuilder.hpp" #include "cds/archiveUtils.hpp" #include "cds/cdsConfig.hpp" -#include "cds/heapShared.hpp" #include "classfile/classLoader.hpp" #include "classfile/classLoaderData.inline.hpp" #include "classfile/javaClasses.inline.hpp" @@ -413,7 +413,7 @@ bool ModuleEntry::should_be_archived() const { } void ModuleEntry::remove_unshareable_info() { - _archived_module_index = HeapShared::append_root(module_oop()); + _archived_module_index = AOTHeap::append_root(module_oop()); if (_reads != nullptr) { _reads->set_in_aot_cache(); @@ -449,12 +449,12 @@ void ModuleEntry::load_from_archive(ClassLoaderData* loader_data) { } void ModuleEntry::preload_archived_oops() { - (void)HeapShared::get_root(_archived_module_index, false /* clear */); + (void)AOTHeap::get_root(_archived_module_index, false /* clear */); } void ModuleEntry::restore_archived_oops(ClassLoaderData* loader_data) { assert(CDSConfig::is_using_full_module_graph(), "runtime only"); - Handle module_handle(Thread::current(), HeapShared::get_root(_archived_module_index, /*clear=*/true)); + Handle module_handle(Thread::current(), AOTHeap::get_root(_archived_module_index, /*clear=*/true)); assert(module_handle.not_null(), "huh"); set_module_handle(loader_data->add_handle(module_handle)); @@ -475,7 +475,7 @@ void ModuleEntry::restore_archived_oops(ClassLoaderData* loader_data) { void ModuleEntry::clear_archived_oops() { assert(CDSConfig::is_using_archive() && !CDSConfig::is_using_full_module_graph(), "runtime only"); - HeapShared::clear_root(_archived_module_index); + AOTHeap::clear_root(_archived_module_index); } static int compare_module_by_name(ModuleEntry* a, ModuleEntry* b) { diff --git a/src/hotspot/share/classfile/stringTable.cpp b/src/hotspot/share/classfile/stringTable.cpp index a1e55229bb50..91c2384ee7fe 100644 --- a/src/hotspot/share/classfile/stringTable.cpp +++ b/src/hotspot/share/classfile/stringTable.cpp @@ -22,10 +22,10 @@ * */ +#include "cds/aotHeap.inline.hpp" #include "cds/aotMappedHeapLoader.hpp" #include "cds/archiveBuilder.hpp" #include "cds/cdsConfig.hpp" -#include "cds/heapShared.inline.hpp" #include "classfile/altHashing.hpp" #include "classfile/compactHashtable.hpp" #include "classfile/javaClasses.inline.hpp" @@ -76,7 +76,7 @@ const double CLEAN_DEAD_HIGH_WATER_MARK = 0.5; #if INCLUDE_CDS_JAVA_HEAP inline oop StringTable::read_string_from_compact_hashtable(address base_address, u4 index) { assert(AOTMappedHeapLoader::is_in_use(), "sanity"); - oop s = HeapShared::get_root((int)index, false); + oop s = AOTHeap::get_root((int)index, false); assert(java_lang_String::is_instance(s), "must be"); return s; } @@ -909,7 +909,7 @@ void StringtableDCmd::execute(DCmdSource source, TRAPS) { // Sharing #if INCLUDE_CDS_JAVA_HEAP size_t StringTable::shared_entry_count() { - assert(HeapShared::is_loading_mapping_mode(), "should not reach here"); + assert(AOTHeap::is_loading_mapping_mode(), "should not reach here"); return _shared_table.entry_count(); } @@ -935,18 +935,18 @@ oop StringTable::lookup_shared(const jchar* name, int len) { void StringTable::init_shared_table() { assert(SafepointSynchronize::is_at_safepoint(), "inside AOT safepoint"); precond(CDSConfig::is_dumping_heap()); - assert(HeapShared::is_writing_mapping_mode(), "not used for streamed oops"); + assert(AOTHeap::is_writing_mapping_mode(), "not used for streamed oops"); int n = 0; auto copy_into_aot_heap = [&] (WeakHandle* val) { oop string = val->peek(); - if (string != nullptr && !HeapShared::is_string_too_large_to_archive(string)) { + if (string != nullptr && !AOTHeap::is_string_too_large_to_archive(string)) { // If string is too large, don't put it into the string table. // - If there are no other references to it, it won't be stored into the archive, // so we are all good. - // - If there's a reference to it, we will report an error inside HeapShared.cpp and + // - If there's a reference to it, we will report an error inside AOTHeap.cpp and // dumping will fail. - HeapShared::archive_interned_string(string); + AOTHeap::archive_interned_string(string); } n++; return true; @@ -959,16 +959,16 @@ void StringTable::init_shared_table() { void StringTable::write_shared_table() { assert(SafepointSynchronize::is_at_safepoint(), "inside AOT safepoint"); precond(CDSConfig::is_dumping_heap()); - assert(HeapShared::is_writing_mapping_mode(), "not used for streamed oops"); + assert(AOTHeap::is_writing_mapping_mode(), "not used for streamed oops"); _shared_table_for_dumping.reset(); CompactHashtableWriter writer((int)items_count_acquire(), ArchiveBuilder::string_stats()); auto copy_into_shared_table = [&] (WeakHandle* val) { oop string = val->peek(); - if (string != nullptr && !HeapShared::is_string_too_large_to_archive(string)) { + if (string != nullptr && !AOTHeap::is_string_too_large_to_archive(string)) { unsigned int hash = java_lang_String::hash_code(string); - int root_id = HeapShared::append_root(string); + int root_id = AOTHeap::append_root(string); writer.add(hash, root_id); } return true; diff --git a/src/hotspot/share/classfile/systemDictionary.cpp b/src/hotspot/share/classfile/systemDictionary.cpp index d5fc89f9cd0a..78d29717d830 100644 --- a/src/hotspot/share/classfile/systemDictionary.cpp +++ b/src/hotspot/share/classfile/systemDictionary.cpp @@ -23,8 +23,8 @@ */ #include "cds/aotClassLocation.hpp" +#include "cds/aotHeap.hpp" #include "cds/cdsConfig.hpp" -#include "cds/heapShared.hpp" #include "classfile/classFileParser.hpp" #include "classfile/classFileStream.hpp" #include "classfile/classLoader.hpp" @@ -1005,7 +1005,7 @@ bool SystemDictionary::is_shared_class_visible_impl(Symbol* class_name, // At this point, no modules have been defined yet. KlassSubGraphInfo::check_allowed_klass() // has restricted the classes can be loaded at this step to be only: // [1] cs->is_modules_image(): classes in java.base, or, - // [2] HeapShared::is_a_test_class_in_unnamed_module(ik): classes in bootstrap/unnamed module + // [2] AOTHeap::is_a_test_class_in_unnamed_module(ik): classes in bootstrap/unnamed module assert(cl->is_modules_image(), "only these classes can be loaded before the module system is initialized"); assert(class_loader.is_null(), "sanity"); @@ -1773,7 +1773,7 @@ void SystemDictionary::initialize(TRAPS) { vmClasses::resolve_all(CHECK); // Resolve classes used by archived heap objects if (CDSConfig::is_using_archive()) { - HeapShared::resolve_classes(THREAD); + AOTHeap::resolve_classes(THREAD); } } diff --git a/src/hotspot/share/classfile/systemDictionaryShared.cpp b/src/hotspot/share/classfile/systemDictionaryShared.cpp index c7e16fce69d7..0d6c345762ce 100644 --- a/src/hotspot/share/classfile/systemDictionaryShared.cpp +++ b/src/hotspot/share/classfile/systemDictionaryShared.cpp @@ -26,6 +26,7 @@ #include "cds/aotClassFilter.hpp" #include "cds/aotClassLocation.hpp" #include "cds/aotCompressedPointers.hpp" +#include "cds/aotHeap.hpp" #include "cds/aotLogging.hpp" #include "cds/aotMetaspace.hpp" #include "cds/archiveBuilder.hpp" @@ -37,7 +38,6 @@ #include "cds/dumpTimeClassInfo.inline.hpp" #include "cds/dynamicArchive.hpp" #include "cds/filemap.hpp" -#include "cds/heapShared.hpp" #include "cds/lambdaFormInvokers.inline.hpp" #include "cds/lambdaProxyClassDictionary.hpp" #include "cds/runTimeClassInfo.hpp" @@ -752,7 +752,7 @@ void SystemDictionaryShared::initialize() { _dumptime_table = new (mtClass) DumpTimeSharedClassTable; LambdaProxyClassDictionary::dumptime_init(); if (CDSConfig::is_dumping_heap()) { - HeapShared::init_dumping(); + AOTHeap::init_dumping(); } } } diff --git a/src/hotspot/share/classfile/vmClasses.cpp b/src/hotspot/share/classfile/vmClasses.cpp index 21a910486c88..396d7e5b61e4 100644 --- a/src/hotspot/share/classfile/vmClasses.cpp +++ b/src/hotspot/share/classfile/vmClasses.cpp @@ -22,11 +22,11 @@ * */ +#include "cds/aotHeap.inline.hpp" #include "cds/aotLinkedClassBulkLoader.hpp" #include "cds/aotMappedHeapLoader.hpp" #include "cds/aotModules.hpp" #include "cds/cdsConfig.hpp" -#include "cds/heapShared.inline.hpp" #include "classfile/classLoader.hpp" #include "classfile/classLoaderData.hpp" #include "classfile/dictionary.hpp" @@ -139,7 +139,7 @@ void vmClasses::resolve_all(TRAPS) { #if INCLUDE_CDS if (CDSConfig::is_using_archive()) { #if INCLUDE_CDS_JAVA_HEAP - if (HeapShared::is_loading() && HeapShared::is_loading_mapping_mode()) { + if (AOTHeap::is_loading() && AOTHeap::is_loading_mapping_mode()) { // It's unsafe to access the archived heap regions before they // are fixed up, so we must do the fixup as early as possible // before the archived java objects are accessed by functions @@ -155,8 +155,8 @@ void vmClasses::resolve_all(TRAPS) { // Mirrors are restored after java.lang.Class is loaded. AOTMappedHeapLoader::fixup_region(); } - if (HeapShared::is_archived_heap_in_use() && !CDSConfig::is_using_full_module_graph()) { - // Need to remove all the archived java.lang.Module objects from HeapShared::roots(). + if (AOTHeap::is_archived_heap_in_use() && !CDSConfig::is_using_full_module_graph()) { + // Need to remove all the archived java.lang.Module objects from AOTHeap::roots(). AOTModules::clear_archived_oops(); } #endif // INCLUDE_CDS_JAVA_HEAP diff --git a/src/hotspot/share/code/aotCodeCache.cpp b/src/hotspot/share/code/aotCodeCache.cpp index 4d1bb8c83095..bec24305db2a 100644 --- a/src/hotspot/share/code/aotCodeCache.cpp +++ b/src/hotspot/share/code/aotCodeCache.cpp @@ -29,7 +29,6 @@ #include "cds/aotMetaspace.hpp" #include "cds/cds_globals.hpp" #include "cds/cdsConfig.hpp" -#include "cds/heapShared.hpp" #include "ci/ciEnv.hpp" #include "ci/ciMethod.hpp" #include "ci/ciUtilities.inline.hpp" diff --git a/src/hotspot/share/gc/shared/stringdedup/stringDedupTable.cpp b/src/hotspot/share/gc/shared/stringdedup/stringDedupTable.cpp index e2e78c7bc4a0..e9213eeeca80 100644 --- a/src/hotspot/share/gc/shared/stringdedup/stringDedupTable.cpp +++ b/src/hotspot/share/gc/shared/stringdedup/stringDedupTable.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -22,8 +22,8 @@ * */ +#include "cds/aotHeap.inline.hpp" #include "cds/aotMappedHeapLoader.hpp" -#include "cds/heapShared.inline.hpp" #include "classfile/altHashing.hpp" #include "classfile/javaClasses.inline.hpp" #include "classfile/stringTable.hpp" @@ -518,7 +518,7 @@ void StringDedup::Table::install(typeArrayOop obj, uint hash_code) { // access to a String that is incompletely constructed; the value could be // set before the coder. bool StringDedup::Table::try_deduplicate_shared(oop java_string) { - assert(!HeapShared::is_loading_streaming_mode(), "should not reach here"); + assert(!AOTHeap::is_loading_streaming_mode(), "should not reach here"); typeArrayOop value = java_lang_String::value(java_string); assert(value != nullptr, "precondition"); assert(TypeArrayKlass::cast(value->klass())->element_type() == T_BYTE, "precondition"); @@ -564,7 +564,7 @@ bool StringDedup::Table::try_deduplicate_shared(oop java_string) { } bool StringDedup::Table::try_deduplicate_found_shared(oop java_string, oop found) { - assert(!HeapShared::is_loading_streaming_mode(), "should not reach here"); + assert(!AOTHeap::is_loading_streaming_mode(), "should not reach here"); _cur_stat.inc_known_shared(); typeArrayOop found_value = java_lang_String::value(found); if (found_value == java_lang_String::value(java_string)) { diff --git a/src/hotspot/share/memory/universe.cpp b/src/hotspot/share/memory/universe.cpp index b36f3d66c8ff..b523c90681dd 100644 --- a/src/hotspot/share/memory/universe.cpp +++ b/src/hotspot/share/memory/universe.cpp @@ -22,11 +22,11 @@ * */ +#include "cds/aotHeap.hpp" #include "cds/aotMetaspace.hpp" #include "cds/aotModules.hpp" #include "cds/cdsConfig.hpp" #include "cds/dynamicArchive.hpp" -#include "cds/heapShared.inline.hpp" #include "classfile/classLoader.hpp" #include "classfile/classLoaderDataGraph.hpp" #include "classfile/javaClasses.hpp" @@ -217,12 +217,12 @@ class BuiltinException { #if INCLUDE_CDS_JAVA_HEAP void store_in_cds() { - _archived_root_index = HeapShared::archive_exception_instance(instance()); + _archived_root_index = AOTHeap::archive_exception_instance(instance()); } void load_from_cds() { if (_archived_root_index >= 0) { - oop obj = HeapShared::get_root(_archived_root_index); + oop obj = AOTHeap::get_root(_archived_root_index); assert(obj != nullptr, "must be"); _instance = OopHandle(Universe::vm_global(), obj); } @@ -325,11 +325,11 @@ void Universe::archive_exception_instances() { } void Universe::load_archived_object_instances() { - if (HeapShared::is_archived_heap_in_use()) { + if (AOTHeap::is_archived_heap_in_use()) { for (int i = T_BOOLEAN; i < T_VOID+1; i++) { int index = _archived_basic_type_mirror_indices[i]; if (!is_reference_type((BasicType)i) && index >= 0) { - oop mirror_oop = HeapShared::get_root(index); + oop mirror_oop = AOTHeap::get_root(index); assert(mirror_oop != nullptr, "must be"); _basic_type_mirrors[i] = OopHandle(vm_global(), mirror_oop); } @@ -351,7 +351,7 @@ void Universe::serialize(SerializeClosure* f) { #if INCLUDE_CDS_JAVA_HEAP for (int i = T_BOOLEAN; i < T_VOID+1; i++) { f->do_int(&_archived_basic_type_mirror_indices[i]); - // if f->reading(): We can't call HeapShared::get_root() yet, as the heap + // if f->reading(): We can't call AOTHeap::get_root() yet, as the heap // contents may need to be relocated. _basic_type_mirrors[i] will be // updated later in Universe::load_archived_object_instances(). } @@ -558,7 +558,7 @@ void Universe::genesis(TRAPS) { void Universe::initialize_basic_type_mirrors(TRAPS) { #if INCLUDE_CDS_JAVA_HEAP if (CDSConfig::is_using_archive() && - HeapShared::is_archived_heap_in_use() && + AOTHeap::is_archived_heap_in_use() && _basic_type_mirrors[T_INT].resolve() != nullptr) { // check that all basic type mirrors are mapped also for (int i = T_BOOLEAN; i < T_VOID+1; i++) { @@ -581,7 +581,7 @@ void Universe::initialize_basic_type_mirrors(TRAPS) { } } if (CDSConfig::is_dumping_heap()) { - HeapShared::init_scratch_objects_for_basic_type_mirrors(CHECK); + AOTHeap::init_scratch_objects_for_basic_type_mirrors(CHECK); } } @@ -920,7 +920,7 @@ jint universe_init() { Threads::add(main_thread); } - HeapShared::initialize_writing_mode(); + AOTHeap::initialize_writing_mode(); // Create the string table before the AOT object archive is loaded, // as it might need to access the string table. diff --git a/src/hotspot/share/memory/universe.hpp b/src/hotspot/share/memory/universe.hpp index a80271e205b2..ab0cc10316b5 100644 --- a/src/hotspot/share/memory/universe.hpp +++ b/src/hotspot/share/memory/universe.hpp @@ -167,7 +167,7 @@ class Universe: AllStatic { #if INCLUDE_CDS_JAVA_HEAP // Each slot i stores an index that can be used to restore _basic_type_mirrors[i] - // from the archive heap using HeapShared::get_root(int) + // from the archive heap using AOTHeap::get_root(int) static int _archived_basic_type_mirror_indices[T_VOID+1]; #endif diff --git a/src/hotspot/share/oops/constantPool.cpp b/src/hotspot/share/oops/constantPool.cpp index 95c4673edd2e..cc71d390d9c8 100644 --- a/src/hotspot/share/oops/constantPool.cpp +++ b/src/hotspot/share/oops/constantPool.cpp @@ -23,9 +23,9 @@ */ #include "cds/aotConstantPoolResolver.hpp" +#include "cds/aotHeap.hpp" #include "cds/archiveBuilder.hpp" #include "cds/cdsConfig.hpp" -#include "cds/heapShared.inline.hpp" #include "classfile/classLoader.hpp" #include "classfile/classLoaderData.hpp" #include "classfile/javaClasses.inline.hpp" @@ -231,7 +231,7 @@ void ConstantPool::initialize_resolved_references(ClassLoaderData* loader_data, // Create a "scratch" copy of the resolved references array to archive if (CDSConfig::is_dumping_heap()) { refArrayOop scratch_references = oopFactory::new_refArray(vmClasses::Object_klass(), map_length, CHECK); - HeapShared::add_scratch_resolved_references(this, scratch_references); + AOTHeap::add_scratch_resolved_references(this, scratch_references); } } } @@ -349,7 +349,7 @@ refArrayOop ConstantPool::prepare_resolved_references_for_archiving() { keep_resolved_refs.at_put(rr_index, true); }); - refArrayOop scratch_rr = HeapShared::scratch_resolved_references(this); + refArrayOop scratch_rr = AOTHeap::scratch_resolved_references(this); Array* ref_map = reference_map(); int ref_map_len = ref_map == nullptr ? 0 : ref_map->length(); for (int i = 0; i < rr_len; i++) { @@ -360,7 +360,7 @@ refArrayOop ConstantPool::prepare_resolved_references_for_archiving() { int index = object_to_cp_index(i); if (tag_at(index).is_string()) { assert(java_lang_String::is_instance(obj), "must be"); - if (!HeapShared::is_string_too_large_to_archive(obj)) { + if (!AOTHeap::is_string_too_large_to_archive(obj)) { scratch_rr->obj_at_put(i, obj); } continue; @@ -400,7 +400,7 @@ void ConstantPool::restore_unshareable_info(TRAPS) { if (vmClasses::Object_klass_is_loaded()) { ClassLoaderData* loader_data = pool_holder()->class_loader_data(); #if INCLUDE_CDS_JAVA_HEAP - if (HeapShared::is_archived_heap_in_use() && + if (AOTHeap::is_archived_heap_in_use() && _cache->archived_references() != nullptr) { oop archived = _cache->archived_references(); // Create handle for the archived resolved reference array object @@ -425,7 +425,7 @@ void ConstantPool::restore_unshareable_info(TRAPS) { if (CDSConfig::is_dumping_final_static_archive() && CDSConfig::is_dumping_heap() && resolved_references() != nullptr) { objArrayOop scratch_references = oopFactory::new_objArray(vmClasses::Object_klass(), resolved_references()->length(), CHECK); - HeapShared::add_scratch_resolved_references(this, scratch_references); + AOTHeap::add_scratch_resolved_references(this, scratch_references); } } diff --git a/src/hotspot/share/oops/cpCache.cpp b/src/hotspot/share/oops/cpCache.cpp index 1dc1fa57d08a..2fa899b129af 100644 --- a/src/hotspot/share/oops/cpCache.cpp +++ b/src/hotspot/share/oops/cpCache.cpp @@ -23,9 +23,9 @@ */ #include "cds/aotConstantPoolResolver.hpp" +#include "cds/aotHeap.hpp" #include "cds/archiveBuilder.hpp" #include "cds/cdsConfig.hpp" -#include "cds/heapShared.hpp" #include "classfile/javaStackTraceClasses.hpp" #include "classfile/resolutionErrors.hpp" #include "classfile/systemDictionary.hpp" @@ -409,9 +409,9 @@ void ConstantPoolCache::remove_unshareable_info() { _archived_references_index = -1; if (CDSConfig::is_dumping_heap()) { ConstantPool* src_cp = ArchiveBuilder::current()->get_source_addr(constant_pool()); - oop rr = HeapShared::scratch_resolved_references(src_cp); + oop rr = AOTHeap::scratch_resolved_references(src_cp); if (rr != nullptr) { - _archived_references_index = HeapShared::append_root(rr); + _archived_references_index = AOTHeap::append_root(rr); } } #endif @@ -719,12 +719,12 @@ oop ConstantPoolCache::archived_references() { if (_archived_references_index < 0) { return nullptr; } - return HeapShared::get_root(_archived_references_index); + return AOTHeap::get_root(_archived_references_index); } void ConstantPoolCache::clear_archived_references() { if (_archived_references_index >= 0) { - HeapShared::clear_root(_archived_references_index); + AOTHeap::clear_root(_archived_references_index); _archived_references_index = -1; } } diff --git a/src/hotspot/share/oops/instanceKlass.cpp b/src/hotspot/share/oops/instanceKlass.cpp index 4560b2550dd4..911f249f327d 100644 --- a/src/hotspot/share/oops/instanceKlass.cpp +++ b/src/hotspot/share/oops/instanceKlass.cpp @@ -23,13 +23,13 @@ */ #include "cds/aotClassInitializer.hpp" +#include "cds/aotHeap.hpp" #include "cds/aotLinkedClassBulkLoader.hpp" #include "cds/aotMetaspace.hpp" #include "cds/archiveUtils.hpp" #include "cds/cdsConfig.hpp" #include "cds/cdsEnumKlass.hpp" #include "cds/classListWriter.hpp" -#include "cds/heapShared.hpp" #include "classfile/classFileParser.hpp" #include "classfile/classFileStream.hpp" #include "classfile/classLoader.hpp" @@ -792,7 +792,7 @@ void InstanceKlass::deallocate_contents(ClassLoaderData* loader_data) { if (constants() != nullptr) { assert (!constants()->on_stack(), "shouldn't be called if anything is onstack"); if (!constants()->in_aot_cache()) { - HeapShared::remove_scratch_resolved_references(constants()); + AOTHeap::remove_scratch_resolved_references(constants()); MetadataFactory::free_metadata(loader_data, constants()); } // Delete any cached resolution errors for the constant pool @@ -844,7 +844,7 @@ void InstanceKlass::deallocate_contents(ClassLoaderData* loader_data) { #if INCLUDE_CDS_JAVA_HEAP if (CDSConfig::is_dumping_heap()) { - HeapShared::remove_scratch_objects(this); + AOTHeap::remove_scratch_objects(this); } #endif } @@ -3171,7 +3171,7 @@ void InstanceKlass::remove_unshareable_info() { _methods_jmethod_ids = nullptr; _jni_ids = nullptr; _oop_map_cache = nullptr; - if (CDSConfig::is_dumping_method_handles() && HeapShared::is_lambda_proxy_klass(this)) { + if (CDSConfig::is_dumping_method_handles() && AOTHeap::is_lambda_proxy_klass(this)) { // keep _nest_host } else { // clear _nest_host to ensure re-load at runtime diff --git a/src/hotspot/share/oops/klass.cpp b/src/hotspot/share/oops/klass.cpp index e0b76a9c640e..3962b5552ad1 100644 --- a/src/hotspot/share/oops/klass.cpp +++ b/src/hotspot/share/oops/klass.cpp @@ -22,9 +22,9 @@ * */ +#include "cds/aotHeap.hpp" #include "cds/cdsConfig.hpp" #include "cds/cppVtables.hpp" -#include "cds/heapShared.inline.hpp" #include "classfile/classLoader.hpp" #include "classfile/classLoaderData.inline.hpp" #include "classfile/classLoaderDataGraph.inline.hpp" @@ -826,9 +826,9 @@ void Klass::remove_java_mirror() { assert(InstanceKlass::cast(k)->defined_by_other_loaders(), "sanity"); } } else { - oop scratch_mirror = HeapShared::scratch_java_mirror(orig_mirror); + oop scratch_mirror = AOTHeap::scratch_java_mirror(orig_mirror); if (scratch_mirror != nullptr) { - _archived_mirror_index = HeapShared::append_root(scratch_mirror); + _archived_mirror_index = AOTHeap::append_root(scratch_mirror); } } } @@ -880,7 +880,7 @@ void Klass::restore_unshareable_info(ClassLoaderData* loader_data, Handle protec if (this->has_archived_mirror_index()) { ResourceMark rm(THREAD); log_debug(aot, mirror)("%s has raw archived mirror", external_name()); - if (HeapShared::is_archived_heap_in_use()) { + if (AOTHeap::is_archived_heap_in_use()) { bool present = java_lang_Class::restore_archived_mirror(this, loader, module_handle, protection_domain, CHECK); @@ -908,12 +908,12 @@ void Klass::restore_unshareable_info(ClassLoaderData* loader_data, Handle protec #if INCLUDE_CDS_JAVA_HEAP oop Klass::archived_java_mirror() { assert(has_archived_mirror_index(), "must have archived mirror"); - return HeapShared::get_root(_archived_mirror_index); + return AOTHeap::get_root(_archived_mirror_index); } void Klass::clear_archived_mirror_index() { if (_archived_mirror_index >= 0) { - HeapShared::clear_root(_archived_mirror_index); + AOTHeap::clear_root(_archived_mirror_index); } _archived_mirror_index = -1; } diff --git a/src/hotspot/share/prims/jvm.cpp b/src/hotspot/share/prims/jvm.cpp index 071c7922dc77..b9d989ef1fc3 100644 --- a/src/hotspot/share/prims/jvm.cpp +++ b/src/hotspot/share/prims/jvm.cpp @@ -23,13 +23,13 @@ */ #include "cds/aotClassInitializer.hpp" +#include "cds/aotHeap.hpp" #include "cds/aotMetaspace.hpp" #include "cds/aotModules.hpp" #include "cds/cdsConfig.hpp" #include "cds/classListParser.hpp" #include "cds/classListWriter.hpp" #include "cds/dynamicArchive.hpp" -#include "cds/heapShared.hpp" #include "cds/lambdaFormInvokers.hpp" #include "cds/lambdaProxyClassDictionary.hpp" #include "classfile/classFileStream.hpp" @@ -3526,7 +3526,7 @@ JVM_END JVM_ENTRY(void, JVM_InitializeFromArchive(JNIEnv* env, jclass cls)) Klass* k = java_lang_Class::as_Klass(JNIHandles::resolve(cls)); - HeapShared::initialize_from_archived_subgraph(THREAD, k); + AOTHeap::initialize_from_archived_subgraph(THREAD, k); JVM_END JVM_ENTRY(void, JVM_RegisterLambdaProxyClassForArchiving(JNIEnv* env, diff --git a/src/hotspot/share/prims/whitebox.cpp b/src/hotspot/share/prims/whitebox.cpp index 18b6d00a0896..6438839aeafb 100644 --- a/src/hotspot/share/prims/whitebox.cpp +++ b/src/hotspot/share/prims/whitebox.cpp @@ -27,7 +27,6 @@ #include "cds/aotMetaspace.hpp" #include "cds/cdsConstants.hpp" #include "cds/filemap.hpp" -#include "cds/heapShared.inline.hpp" #include "classfile/classLoader.hpp" #include "classfile/classLoaderDataGraph.hpp" #include "classfile/classLoaderStats.hpp" diff --git a/src/hotspot/share/runtime/threads.cpp b/src/hotspot/share/runtime/threads.cpp index 601c64c8c63e..dd167a6acf75 100644 --- a/src/hotspot/share/runtime/threads.cpp +++ b/src/hotspot/share/runtime/threads.cpp @@ -23,11 +23,11 @@ * */ +#include "cds/aotHeap.hpp" #include "cds/aotLinkedClassBulkLoader.hpp" #include "cds/aotMetaspace.hpp" #include "cds/cds_globals.hpp" #include "cds/cdsConfig.hpp" -#include "cds/heapShared.inline.hpp" #include "classfile/classLoader.hpp" #include "classfile/javaClasses.hpp" #include "classfile/javaThreadStatus.hpp" @@ -364,7 +364,7 @@ void Threads::initialize_java_lang_classes(JavaThread* main_thread, TRAPS) { initialize_class(vmSymbols::java_lang_Thread(), CHECK); create_initial_thread(thread_group, main_thread, CHECK); - HeapShared::init_box_classes(CHECK); + AOTHeap::init_box_classes(CHECK); // The VM creates objects of this class. initialize_class(vmSymbols::java_lang_Module(), CHECK); @@ -387,7 +387,7 @@ void Threads::initialize_java_lang_classes(JavaThread* main_thread, TRAPS) { initialize_class(vmSymbols::java_lang_reflect_Method(), CHECK); initialize_class(vmSymbols::java_lang_ref_Finalizer(), CHECK); - HeapShared::materialize_thread_object(); + AOTHeap::materialize_thread_object(); // Phase 1 of the system initialization in the library, java.lang.System class initialization call_initPhase1(CHECK); @@ -434,7 +434,7 @@ void Threads::initialize_jsr292_core_classes(TRAPS) { initialize_class(vmSymbols::java_lang_invoke_MethodHandleNatives(), CHECK); if (UseSharedSpaces) { - HeapShared::initialize_java_lang_invoke(CHECK); + AOTHeap::initialize_java_lang_invoke(CHECK); } } @@ -700,7 +700,7 @@ jint Threads::create_vm(JavaVMInitArgs* args, bool* canTryAgain) { StubCodeDesc::freeze(); // Prepare AOT heap loader for GC. - HeapShared::enable_gc(); + AOTHeap::enable_gc(); #ifdef ADDRESS_SANITIZER Asan::initialize(); @@ -876,7 +876,7 @@ jint Threads::create_vm(JavaVMInitArgs* args, bool* canTryAgain) { WatcherThread::run_all_tasks(); // Finish materializing AOT objects - HeapShared::finish_materialize_objects(); + AOTHeap::finish_materialize_objects(); create_vm_timer.end(); #ifdef ASSERT diff --git a/src/java.base/share/classes/java/lang/invoke/StringConcatFactory.java b/src/java.base/share/classes/java/lang/invoke/StringConcatFactory.java index 733714b57860..05d99ee8df27 100644 --- a/src/java.base/share/classes/java/lang/invoke/StringConcatFactory.java +++ b/src/java.base/share/classes/java/lang/invoke/StringConcatFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2024, Alibaba Group Holding Limited. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -632,7 +632,7 @@ private StringConcatFactory() { * without copying. */ private static final class InlineHiddenClassStrategy { - // The CLASS_NAME prefix must be the same as used by HeapShared::is_string_concat_klass() + // The CLASS_NAME prefix must be the same as used by AOTHeap::is_string_concat_klass() // in the HotSpot code. static final String CLASS_NAME = "java.lang.String$$StringConcat"; static final String METHOD_NAME = "concat"; diff --git a/test/hotspot/jtreg/runtime/cds/appcds/cacheObject/VerifyArchivedFields.java b/test/hotspot/jtreg/runtime/cds/appcds/cacheObject/VerifyArchivedFields.java index 80eebf60811c..df5f834fbdcf 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/cacheObject/VerifyArchivedFields.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/cacheObject/VerifyArchivedFields.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -37,7 +37,7 @@ public class VerifyArchivedFields { // Note: -XX:VerifyArchivedFields=2 will force a GC every time when - // HeapShared::initialize_from_archived_subgraph(Klass* k, ...) is called. This ensures + // AOTHeap::initialize_from_archived_subgraph(Klass* k, ...) is called. This ensures // that it's safe to do a GC even when the oop->klass() of some archived heap objects // are not yet loaded into the system dictionary. public static void main(String[] args) throws Exception { From 69e24c92e8951653c5ee529815dc7779e6948ed1 Mon Sep 17 00:00:00 2001 From: Lijuan Li Date: Tue, 20 Oct 2026 06:44:02 +0000 Subject: [PATCH 120/169] 8392738: RISC-V: Implement Float16 conversion intrinsics in the template interpreter and C1 Reviewed-by: fyang, dzhang --- .../cpu/riscv/c1_LIRAssembler_riscv.cpp | 4 +- .../cpu/riscv/c1_LIRGenerator_riscv.cpp | 15 ++++- .../cpu/riscv/macroAssembler_riscv.cpp | 60 +++++++++++++++++++ .../cpu/riscv/macroAssembler_riscv.hpp | 7 +++ .../templateInterpreterGenerator_riscv.cpp | 52 +++++++++++++++- src/hotspot/cpu/riscv/vm_version_riscv.hpp | 10 ++++ .../TestFloat16ToFloatUnnormalized.java | 2 +- 7 files changed, 144 insertions(+), 6 deletions(-) diff --git a/src/hotspot/cpu/riscv/c1_LIRAssembler_riscv.cpp b/src/hotspot/cpu/riscv/c1_LIRAssembler_riscv.cpp index bba73d973192..a47b2e98b277 100644 --- a/src/hotspot/cpu/riscv/c1_LIRAssembler_riscv.cpp +++ b/src/hotspot/cpu/riscv/c1_LIRAssembler_riscv.cpp @@ -1391,10 +1391,12 @@ void LIR_Assembler::emit_compare_and_swap(LIR_OpCompareAndSwap* op) { } } -void LIR_Assembler::intrinsic_op(LIR_Code code, LIR_Opr value, LIR_Opr unused, LIR_Opr dest, LIR_Op* op) { +void LIR_Assembler::intrinsic_op(LIR_Code code, LIR_Opr value, LIR_Opr tmp, LIR_Opr dest, LIR_Op* op) { switch (code) { case lir_abs: __ fabs_d(dest->as_double_reg(), value->as_double_reg()); break; case lir_sqrt: __ fsqrt_d(dest->as_double_reg(), value->as_double_reg()); break; + case lir_f2hf: __ flt_to_flt16(dest->as_register(), value->as_float_reg(), tmp->as_float_reg()); break; + case lir_hf2f: __ flt16_to_flt(dest->as_float_reg(), value->as_register()); break; default: ShouldNotReachHere(); } } diff --git a/src/hotspot/cpu/riscv/c1_LIRGenerator_riscv.cpp b/src/hotspot/cpu/riscv/c1_LIRGenerator_riscv.cpp index a3594d15feea..e37726b6632a 100644 --- a/src/hotspot/cpu/riscv/c1_LIRGenerator_riscv.cpp +++ b/src/hotspot/cpu/riscv/c1_LIRGenerator_riscv.cpp @@ -640,7 +640,9 @@ void LIRGenerator::do_MathIntrinsic(Intrinsic* x) { break; case vmIntrinsics::_dabs: // fall through case vmIntrinsics::_dsqrt: // fall through - case vmIntrinsics::_dsqrt_strict: { + case vmIntrinsics::_dsqrt_strict: // fall through + case vmIntrinsics::_floatToFloat16: // fall through + case vmIntrinsics::_float16ToFloat: { assert(x->number_of_arguments() == 1, "wrong type"); LIRItem value(x->argument_at(0), this); value.load_item(); @@ -656,6 +658,17 @@ void LIRGenerator::do_MathIntrinsic(Intrinsic* x) { __ abs(value.result(), dst, LIR_OprFact::illegalOpr); break; } + case vmIntrinsics::_floatToFloat16: { + // flt_to_flt16 needs a float temporary to move the result through. + LIR_Opr tmp = new_register(T_FLOAT); + __ f2hf(value.result(), dst, tmp); + break; + } + case vmIntrinsics::_float16ToFloat: { + // flt16_to_flt only needs t0/t1, which are not visible to the allocator. + __ hf2f(value.result(), dst, LIR_OprFact::illegalOpr); + break; + } default: ShouldNotReachHere(); } diff --git a/src/hotspot/cpu/riscv/macroAssembler_riscv.cpp b/src/hotspot/cpu/riscv/macroAssembler_riscv.cpp index 9f1261ce62a4..8e383ea9e544 100644 --- a/src/hotspot/cpu/riscv/macroAssembler_riscv.cpp +++ b/src/hotspot/cpu/riscv/macroAssembler_riscv.cpp @@ -6885,6 +6885,66 @@ void MacroAssembler::float_to_float16_NaN(Register dst, FloatRegister src, orr(dst, tmp2, tmp1); } +// j.l.Float.float16ToFloat +void MacroAssembler::flt16_to_flt(FloatRegister dst, Register src) { + assert_different_registers(src, t0, t1); + Label L_NaN_Inf, L_done; + + // On riscv, NaN needs a special process as fcvt does not work in that case. + // On riscv, Inf does not need a special process as fcvt can handle it correctly. + // but we consider to get the slow path to process NaN and Inf at the same time, + // as both of them are rare cases, and if we try to get the slow path to handle + // only NaN case it would sacrifise the performance for normal cases, + // i.e. non-NaN and non-Inf cases. + + // check whether it's a NaN or +/- Inf. + mv(t0, 0x7c00); + andr(t1, src, t0); + beq(t0, t1, L_NaN_Inf); + + // non-NaN or non-Inf cases, just use built-in instructions. + fmv_h_x(dst, src); + fcvt_s_h(dst, dst); + j(L_done); + + bind(L_NaN_Inf); + // construct a NaN in 32 bits from the NaN in 16 bits, + // we need the payloads of non-canonical NaNs to be preserved. + mv(t1, 0x7f800000); + // The upper 16 bits of a short argument are unspecified. Sign-extend the + // low 16 bits before shifting so that the float sign bit comes from the + // float16 sign bit. + sext(t0, src, 16); + slli(t0, t0, 13); + orr(t1, t0, t1); + fmv_w_x(dst, t1); + + bind(L_done); +} + +// j.l.Float.floatToFloat16 +void MacroAssembler::flt_to_flt16(Register dst, FloatRegister src, FloatRegister ftmp) { + assert_different_registers(dst, t0, t1); + Label L_NaN, L_done; + + // On riscv, NaN needs a special process as fcvt does not work in that case. + + // check whether it's a NaN. + // replace fclass with feq as performance optimization. + feq_s(t0, src, src); + beqz(t0, L_NaN); + + // non-NaN cases, just use built-in instructions. + fcvt_h_s(ftmp, src); + fmv_x_h(dst, ftmp); + j(L_done); + + bind(L_NaN); + float_to_float16_NaN(dst, src, t0, t1); + + bind(L_done); +} + #define FCVT_SAFE(FLOATCVT, FLOATSIG) \ void MacroAssembler::FLOATCVT##_safe(Register dst, FloatRegister src, Register tmp) { \ Label done; \ diff --git a/src/hotspot/cpu/riscv/macroAssembler_riscv.hpp b/src/hotspot/cpu/riscv/macroAssembler_riscv.hpp index 09835da442b1..de1352a1215a 100644 --- a/src/hotspot/cpu/riscv/macroAssembler_riscv.hpp +++ b/src/hotspot/cpu/riscv/macroAssembler_riscv.hpp @@ -1560,6 +1560,13 @@ INSN(sd_release, sd, sd_rl); // Helper routine processing the slow path of NaN when converting float to float16 void float_to_float16_NaN(Register dst, FloatRegister src, Register tmp1, Register tmp2); + // j.l.Float.float16ToFloat and j.l.Float.floatToFloat16, with the NaN handling + // emitted inline. C2 uses C2_MacroAssembler::float16_to_float() and + // C2_MacroAssembler::float_to_float16() instead, which keep it out of line. + // Both clobber t0 and t1. + void flt16_to_flt(FloatRegister dst, Register src); + void flt_to_flt16(Register dst, FloatRegister src, FloatRegister ftmp); + // vector load/store unit-stride instructions void vlex_v(VectorRegister vd, Register base, Assembler::SEW sew, VectorMask vm = unmasked) { switch (sew) { diff --git a/src/hotspot/cpu/riscv/templateInterpreterGenerator_riscv.cpp b/src/hotspot/cpu/riscv/templateInterpreterGenerator_riscv.cpp index a98b9e496671..2848ccf8a133 100644 --- a/src/hotspot/cpu/riscv/templateInterpreterGenerator_riscv.cpp +++ b/src/hotspot/cpu/riscv/templateInterpreterGenerator_riscv.cpp @@ -967,9 +967,55 @@ address TemplateInterpreterGenerator::generate_CRC32C_updateBytes_entry(Abstract return entry; } -// Not supported -address TemplateInterpreterGenerator::generate_Float_float16ToFloat_entry() { return nullptr; } -address TemplateInterpreterGenerator::generate_Float_floatToFloat16_entry() { return nullptr; } +/** + * Method entry for static method: + * java.lang.Float.float16ToFloat(short floatBinary16) + */ +address TemplateInterpreterGenerator::generate_Float_float16ToFloat_entry() { + assert(VM_Version::supports_float16_float_conversion(), "this intrinsic is not supported"); + if (StubRoutines::hf2f_adr() == nullptr) { + return nullptr; + } + + // x19_sender_sp: sender sp + // stack: + // [ arg ] <-- esp + // retaddr in ra + // result in f10 + + address entry_point = __ pc(); + __ lh(c_rarg0, Address(esp)); // Load and sign-extend the low 16 bits of the argument + __ andi(sp, x19_sender_sp, -16); // Restore caller's SP + // The stub is a leaf which returns through 'ra', so it returns straight + // back to our caller: this is a tail call. + __ far_jump(RuntimeAddress(StubRoutines::hf2f_adr())); + return entry_point; +} + +/** + * Method entry for static method: + * java.lang.Float.floatToFloat16(float value) + */ +address TemplateInterpreterGenerator::generate_Float_floatToFloat16_entry() { + assert(VM_Version::supports_float16_float_conversion(), "this intrinsic is not supported"); + if (StubRoutines::f2hf_adr() == nullptr) { + return nullptr; + } + + // x19_sender_sp: sender sp + // stack: + // [ arg ] <-- esp + // retaddr in ra + // result in x10 + + address entry_point = __ pc(); + __ flw(f10, Address(esp)); + __ andi(sp, x19_sender_sp, -16); // Restore caller's SP + // The stub is a leaf which returns through 'ra', so it returns straight + // back to our caller: this is a tail call. + __ far_jump(RuntimeAddress(StubRoutines::f2hf_adr())); + return entry_point; +} void TemplateInterpreterGenerator::bang_stack_shadow_pages(bool native_call) { // See more discussion in stackOverflow.hpp. diff --git a/src/hotspot/cpu/riscv/vm_version_riscv.hpp b/src/hotspot/cpu/riscv/vm_version_riscv.hpp index 58901ae4d361..68e870fee2d0 100644 --- a/src/hotspot/cpu/riscv/vm_version_riscv.hpp +++ b/src/hotspot/cpu/riscv/vm_version_riscv.hpp @@ -559,10 +559,20 @@ class VM_Version : public Abstract_VM_Version { static bool supports_fast_class_init_checks() { return true; } static bool supports_fencei_barrier() { return ext_Zifencei.enabled(); } + // Zfhmin only provides half-precision load/store/convert instructions, + // Zfh additionally provides half-precision arithmetic. Either one is + // enough to convert between float16 and float. static bool supports_float16_float_conversion() { return UseZfh || UseZfhmin; } + // Shared code gates the Float.float16ToFloat/floatToFloat16 intrinsics on + // this, and those only need the conversion instructions. Note that it does + // not imply half-precision arithmetic is available, which needs UseZfh. + static bool supports_float16() { + return supports_float16_float_conversion(); + } + // Check intrinsic support static bool is_intrinsic_supported(vmIntrinsicID id); diff --git a/test/hotspot/jtreg/compiler/intrinsics/float16/TestFloat16ToFloatUnnormalized.java b/test/hotspot/jtreg/compiler/intrinsics/float16/TestFloat16ToFloatUnnormalized.java index 075dd13fc583..7df6981ffd10 100644 --- a/test/hotspot/jtreg/compiler/intrinsics/float16/TestFloat16ToFloatUnnormalized.java +++ b/test/hotspot/jtreg/compiler/intrinsics/float16/TestFloat16ToFloatUnnormalized.java @@ -24,7 +24,7 @@ /* * @test - * @bug 8393488 + * @bug 8393488 8392738 * @summary Verify float16ToFloat ignores bits above the low 16 bits of its argument * @requires vm.compiler1.enabled & vm.compiler2.enabled * @run main/othervm -Xverify:all -Xint -XX:+UnlockDiagnosticVMOptions From 0e918ea50e3b16f44b55d515ff6a1187eb5195de Mon Sep 17 00:00:00 2001 From: Tobias Hartmann Date: Tue, 20 Oct 2026 06:49:53 +0000 Subject: [PATCH 121/169] 8393322: [Valhalla] C1 misses null checks when copying flat value fields Reviewed-by: kvn, mhaessig --- src/hotspot/share/c1/c1_GraphBuilder.cpp | 47 +++- src/hotspot/share/c1/c1_GraphBuilder.hpp | 8 +- src/hotspot/share/c1/c1_Optimizer.cpp | 7 +- src/hotspot/share/ci/ciValueKlass.cpp | 7 +- .../TestEmptyStoreToNullHolder.java | 58 ----- .../TestEmptyValueFieldNullChecks.java | 224 ++++++++++++++++++ 6 files changed, 273 insertions(+), 78 deletions(-) delete mode 100644 test/hotspot/jtreg/compiler/valhalla/valuetypes/TestEmptyStoreToNullHolder.java create mode 100644 test/hotspot/jtreg/compiler/valhalla/valuetypes/TestEmptyValueFieldNullChecks.java diff --git a/src/hotspot/share/c1/c1_GraphBuilder.cpp b/src/hotspot/share/c1/c1_GraphBuilder.cpp index 3bbdca1d1509..c260b118ed91 100644 --- a/src/hotspot/share/c1/c1_GraphBuilder.cpp +++ b/src/hotspot/share/c1/c1_GraphBuilder.cpp @@ -1803,14 +1803,24 @@ Value GraphBuilder::make_constant(ciConstant field_value, ciField* field) { } } -void GraphBuilder::copy_value_content(ciValueKlass* vk, Value src, int src_off, Value dest, int dest_off, ValueStack* state_before, ciField* enclosing_field) { +void GraphBuilder::copy_value_content(ciValueKlass* vk, Value src, int src_off, Value dest, int dest_off, ValueStack* state_before, + CopyNullChecks& null_checks, ciField* enclosing_field) { for (int i = 0; i < vk->nof_declared_nonstatic_fields(); i++) { ciField* field = vk->declared_nonstatic_field_at(i); int offset = field->offset_in_bytes() - vk->payload_offset(); if (field->is_flat()) { - copy_value_content(field->type()->as_value_klass(), src, src_off + offset, dest, dest_off + offset, state_before, enclosing_field); + copy_value_content(field->type()->as_value_klass(), src, src_off + offset, dest, dest_off + offset, state_before, null_checks, enclosing_field); if (!field->is_null_free()) { // Nullable, copy the null marker using Unsafe because null markers are not real fields + // Unsafe accesses do not perform implicit null checks. Add explicit ones if needed. + if (null_checks.needs_dst) { + null_check(dest); + null_checks.needs_dst = false; + } + if (null_checks.needs_src) { + null_check(src); + null_checks.needs_src = false; + } int null_marker_offset = field->null_marker_offset() - vk->payload_offset(); Value offset = append(new Constant(new LongConstant(src_off + null_marker_offset))); Value nm = append(new UnsafeGet(T_BOOLEAN, src, offset, false)); @@ -1819,9 +1829,11 @@ void GraphBuilder::copy_value_content(ciValueKlass* vk, Value src, int src_off, } } else { Value value = append(new LoadField(src, src_off + offset, field, false, state_before, false)); + null_checks.needs_src = false; StoreField* store = new StoreField(dest, dest_off + offset, field, value, false, state_before, false); store->set_enclosing_field(enclosing_field); append(store); + null_checks.needs_dst = false; } } } @@ -2052,9 +2064,10 @@ void GraphBuilder::access_field(Bytecodes::Code code) { NewInstance* buffer = new NewInstance(value_klass, state_before, false, true); _memory->new_instance(buffer); apush(append_split(buffer)); + CopyNullChecks null_checks = { false, false }; copy_value_content(value_klass, pending_field_access()->obj(), - pending_field_access()->offset() + field->offset_in_bytes() - field->holder()->as_value_klass()->payload_offset(), - buffer, value_klass->payload_offset(), state_before); + pending_field_access()->offset() + field->offset_in_bytes() - field->holder()->as_value_klass()->payload_offset(), + buffer, value_klass->payload_offset(), state_before, null_checks); set_pending_field_access(nullptr); } else { if (!field->is_null_free() && !value_klass->is_initialized()) { @@ -2064,15 +2077,20 @@ void GraphBuilder::access_field(Bytecodes::Code code) { return; } + CopyNullChecks null_checks = { true, false }; + if (value_klass->is_empty()) { + // The content copy emits no access that can implicitly null-check the holder. + null_check(obj); + null_checks.needs_src = false; + } + NewInstance* buffer = new NewInstance(value_klass, state_before, false, true); _memory->new_instance(buffer); append_split(buffer); - if (value_klass->is_initialized() && value_klass->is_empty()) { - // Needs an explicit null check because below code does not perform any actual load if there are no fields - null_check(obj); - } - copy_value_content(value_klass, obj, field->offset_in_bytes(), buffer, value_klass->payload_offset(), state_before); + copy_value_content(value_klass, obj, field->offset_in_bytes(), buffer, value_klass->payload_offset(), + state_before, null_checks); + assert(!null_checks.needs_src && !null_checks.needs_dst, "null checks should have been emitted"); Instruction* result = buffer; if (!field->is_null_free()) { @@ -2131,23 +2149,28 @@ void GraphBuilder::access_field(Bytecodes::Code code) { append(new StoreField(obj, offset, field, val, false, state_before, needs_patching)); } else if (field->is_null_free()) { assert(!value_klass->is_empty(), "should have been handled"); - copy_value_content(value_klass, val, value_klass->payload_offset(), obj, offset, state_before, field); + CopyNullChecks null_checks = { true, true }; + copy_value_content(value_klass, val, value_klass->payload_offset(), obj, offset, state_before, null_checks, field); + assert(!null_checks.needs_src && !null_checks.needs_dst, "null checks should have been emitted"); } else { if (!value_klass->is_initialized()) { // null_reset_value is not available, bailout for now bailout("store to an uninitialized nullable non-atomic flat field"); return; } + CopyNullChecks null_checks = { false, true }; if (value_klass->is_empty()) { - // Needs an explicit null check because the code below does not emit a store that performs an implicit null check for empty classes. + // The content copy emits no access that can implicitly null-check the holder. null_check(obj); + null_checks.needs_dst = false; } // Store the subfields when field is a nullable non-atomic field Value object_null = append(new Constant(objectNull)); Value null_reset_value = append(new Constant(new ObjectConstant(value_klass->get_null_reset_value().as_object()))); Value src = append(new IfOp(val, Instruction::neq, object_null, val, null_reset_value, state_before, false)); - copy_value_content(value_klass, src, value_klass->payload_offset(), obj, offset, state_before); + copy_value_content(value_klass, src, value_klass->payload_offset(), obj, offset, state_before, null_checks); + assert(!null_checks.needs_src && !null_checks.needs_dst, "null checks should have been emitted"); // Store the null marker Value int_one = append(new Constant(new IntConstant(1))); diff --git a/src/hotspot/share/c1/c1_GraphBuilder.hpp b/src/hotspot/share/c1/c1_GraphBuilder.hpp index 0a4f31983508..e6cb358fc901 100644 --- a/src/hotspot/share/c1/c1_GraphBuilder.hpp +++ b/src/hotspot/share/c1/c1_GraphBuilder.hpp @@ -299,7 +299,13 @@ class GraphBuilder { void throw_op(int bci); // value types - void copy_value_content(ciValueKlass* vk, Value src, int src_off, Value dest, int dest_off, ValueStack* state_before, ciField* enclosing_field = nullptr); + struct CopyNullChecks { + bool needs_src; + bool needs_dst; + }; + + void copy_value_content(ciValueKlass* vk, Value src, int src_off, Value dest, int dest_off, ValueStack* state_before, + CopyNullChecks& null_checks, ciField* enclosing_field = nullptr); // stack/code manipulation helpers Instruction* append_with_bci(Instruction* instr, int bci); diff --git a/src/hotspot/share/c1/c1_Optimizer.cpp b/src/hotspot/share/c1/c1_Optimizer.cpp index 17ad26a311a2..58c9e5bf4c5e 100644 --- a/src/hotspot/share/c1/c1_Optimizer.cpp +++ b/src/hotspot/share/c1/c1_Optimizer.cpp @@ -767,9 +767,9 @@ void NullCheckVisitor::do_Throw (Throw* x) { nce()->clear_las void NullCheckVisitor::do_Base (Base* x) {} void NullCheckVisitor::do_OsrEntry (OsrEntry* x) {} void NullCheckVisitor::do_ExceptionObject(ExceptionObject* x) { nce()->handle_ExceptionObject(x); } -void NullCheckVisitor::do_UnsafeGet (UnsafeGet* x) {} -void NullCheckVisitor::do_UnsafePut (UnsafePut* x) {} -void NullCheckVisitor::do_UnsafeGetAndSet(UnsafeGetAndSet* x) {} +void NullCheckVisitor::do_UnsafeGet (UnsafeGet* x) { nce()->clear_last_explicit_null_check(); } +void NullCheckVisitor::do_UnsafePut (UnsafePut* x) { nce()->clear_last_explicit_null_check(); } +void NullCheckVisitor::do_UnsafeGetAndSet(UnsafeGetAndSet* x) { nce()->clear_last_explicit_null_check(); } void NullCheckVisitor::do_ProfileCall (ProfileCall* x) { nce()->clear_last_explicit_null_check(); nce()->handle_ProfileCall(x); } void NullCheckVisitor::do_ProfileReturnType (ProfileReturnType* x) { nce()->handle_ProfileReturnType(x); } @@ -1094,6 +1094,7 @@ void NullCheckEliminator::handle_Invoke(Invoke* x) { void NullCheckEliminator::handle_NewInstance(NewInstance* x) { + clear_last_explicit_null_check(); set_put(x); if (PrintNullCheckElimination) { tty->print_cr("NewInstance %d is non-null", x->id()); diff --git a/src/hotspot/share/ci/ciValueKlass.cpp b/src/hotspot/share/ci/ciValueKlass.cpp index 0655e18e21e4..df27b679b1a6 100644 --- a/src/hotspot/share/ci/ciValueKlass.cpp +++ b/src/hotspot/share/ci/ciValueKlass.cpp @@ -51,10 +51,9 @@ bool ciValueKlass::can_be_returned_as_fields() const { } bool ciValueKlass::is_empty() { - // Do not use ValueKlass::is_empty_value_type here because it does - // consider the container empty even if fields of empty value types - // are not flat - return nof_declared_nonstatic_fields() == 0; + // Flat fields are recursively expanded, nullable flat fields include + // their null marker and non-flat fields remain as oops. + return nof_nonstatic_fields() == 0; } bool ciValueKlass::is_cloneable() const { diff --git a/test/hotspot/jtreg/compiler/valhalla/valuetypes/TestEmptyStoreToNullHolder.java b/test/hotspot/jtreg/compiler/valhalla/valuetypes/TestEmptyStoreToNullHolder.java deleted file mode 100644 index b0995d722b3f..000000000000 --- a/test/hotspot/jtreg/compiler/valhalla/valuetypes/TestEmptyStoreToNullHolder.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -/* - * @test - * @bug 8392533 - * @summary Test that a holder null check is performed when storing to an empty flat field. - * @enablePreview - * @requires vm.compiler1.enabled - * @run main ${test.main.class} - * @run main/othervm -Xbatch -XX:TieredStopAtLevel=1 ${test.main.class} - */ - -package compiler.valhalla.valuetypes; - -public class TestEmptyStoreToNullHolder { - static value class Empty { } - - Empty field = new Empty(); - - static void test(TestEmptyStoreToNullHolder holder) { - holder.field = null; - } - - public static void main(String[] args) { - TestEmptyStoreToNullHolder holder = new TestEmptyStoreToNullHolder(); - for (int i = 0; i < 20_000; i++) { - test(holder); - } - try { - test(null); - throw new RuntimeException("No NullPointerException thrown!"); - } catch (NullPointerException expected) { - // Expected - } - } -} - diff --git a/test/hotspot/jtreg/compiler/valhalla/valuetypes/TestEmptyValueFieldNullChecks.java b/test/hotspot/jtreg/compiler/valhalla/valuetypes/TestEmptyValueFieldNullChecks.java new file mode 100644 index 000000000000..cb19a26ee1ff --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/valuetypes/TestEmptyValueFieldNullChecks.java @@ -0,0 +1,224 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8392533 8393322 + * @summary Test null checks for field accesses with empty value classes. + * @enablePreview + * @requires vm.compiler1.enabled + * @requires (vm.opt.PreloadClasses == null | vm.opt.PreloadClasses == "true") + * @library /test/lib + * @modules java.base/jdk.internal.vm.annotation + * @run main ${test.main.class} + * @run main/othervm -XX:TieredStopAtLevel=1 ${test.main.class} + * @run main/othervm -XX:TieredStopAtLevel=1 + * -XX:+IgnoreUnrecognizedVMOptions -XX:-EliminateNullChecks + * ${test.main.class} + */ + +package compiler.valhalla.valuetypes; + +import jdk.internal.vm.annotation.NullRestricted; +import jdk.test.lib.Asserts; + +public class TestEmptyValueFieldNullChecks { + static boolean initialized = false; + + static value class Empty { } + + static value class UninitializedEmpty { + static { + initialized = true; + } + } + + static class UninitializedHolder { + @NullRestricted + UninitializedEmpty empty; + int value; + + UninitializedHolder() { + empty = new UninitializedEmpty(); + super(); + } + } + + // Recursively empty value class (only contains null marker) + static value class Holder1 { + Empty empty; + + Holder1() { + empty = new Empty(); + super(); + } + } + + // Same as Holder1 but with null-restricted empty field + static value class Holder2 { + @NullRestricted + Empty empty; + + Holder2() { + empty = new Empty(); + super(); + } + } + + Empty empty; + + @NullRestricted + Empty emptyNullFree; + + Holder1 holder1; + + Holder2 holder2; + + @NullRestricted + Holder1 holder1NullFree; + + @NullRestricted + Holder2 holder2NullFree; + + TestEmptyValueFieldNullChecks() { + empty = new Empty(); + emptyNullFree = new Empty(); + holder1 = new Holder1(); + holder2 = new Holder2(); + holder1NullFree = new Holder1(); + holder2NullFree = new Holder2(); + super(); + } + + // A null holder must throw before buffering initializes its empty field class. + static Object testUninitializedEmptyLoad(UninitializedHolder outerHolder) { + Object empty = outerHolder.empty; + // This later access must not absorb the explicit null check across the buffer allocation. + return outerHolder.value == 0 ? empty : null; + } + + // An empty nullable store only updates its null marker with Unsafe and must null-check its holder. + static void testEmptyStore(TestEmptyValueFieldNullChecks outerHolder, Empty value) { + outerHolder.empty = value; + } + + // An empty null-free store emits no access but must null-check both operands. + static void testEmptyNullFreeStore(TestEmptyValueFieldNullChecks outerHolder, Empty value) { + outerHolder.emptyNullFree = value; + } + + // A marker-only atomic store must null-check its holder. + static void testRecursivelyEmptyStore(TestEmptyValueFieldNullChecks outerHolder, Holder1 value) { + outerHolder.holder1 = value; + } + + // A recursively empty nullable store only updates its null marker with Unsafe and must null-check its holder. + static void testRecursivelyEmptyStore2(TestEmptyValueFieldNullChecks outerHolder, Holder2 value) { + outerHolder.holder2 = value; + } + + // A marker-only null-free store only emits Unsafe accesses and must null-check both operands. + static void testRecursivelyEmptyNullFreeStore(TestEmptyValueFieldNullChecks outerHolder, Holder1 value) { + outerHolder.holder1NullFree = value; + } + + // A recursively empty null-free store emits no access but must null-check both operands. + static void testRecursivelyEmptyNullFreeStore2(TestEmptyValueFieldNullChecks outerHolder, Holder2 value) { + outerHolder.holder2NullFree = value; + } + + // An empty nullable load only reads its null marker with Unsafe and must null-check its holder. + static Empty testEmptyLoad(TestEmptyValueFieldNullChecks outerHolder) { + return outerHolder.empty; + } + + // An empty null-free load emits no access but must null-check its holder. + static Empty testEmptyNullFreeLoad(TestEmptyValueFieldNullChecks outerHolder) { + return outerHolder.emptyNullFree; + } + + // A marker-only atomic load must null-check its holder. + static Holder1 testRecursivelyEmptyLoad(TestEmptyValueFieldNullChecks outerHolder) { + return outerHolder.holder1; + } + + // A recursively empty nullable load only reads its null marker with Unsafe and must null-check its holder. + static Holder2 testRecursivelyEmptyLoad2(TestEmptyValueFieldNullChecks outerHolder) { + return outerHolder.holder2; + } + + // A marker-only null-free load only emits Unsafe accesses and must null-check its holder. + static Holder1 testRecursivelyEmptyNullFreeLoad(TestEmptyValueFieldNullChecks outerHolder) { + return outerHolder.holder1NullFree; + } + + // A recursively empty null-free load emits no access but must null-check its holder. + static Holder2 testRecursivelyEmptyNullFreeLoad2(TestEmptyValueFieldNullChecks outerHolder) { + return outerHolder.holder2NullFree; + } + + public static void main(String[] args) { + Empty empty = new Empty(); + TestEmptyValueFieldNullChecks outerHolder = new TestEmptyValueFieldNullChecks(); + Holder1 innerHolder = new Holder1(); + Holder2 innerHolder2 = new Holder2(); + for (int i = 0; i < 20_000; i++) { + Asserts.assertThrows(NullPointerException.class, () -> testUninitializedEmptyLoad(null)); + testEmptyStore(outerHolder, null); + testEmptyNullFreeStore(outerHolder, empty); + testRecursivelyEmptyStore(outerHolder, innerHolder); + testRecursivelyEmptyStore2(outerHolder, innerHolder2); + testRecursivelyEmptyNullFreeStore(outerHolder, innerHolder); + testRecursivelyEmptyNullFreeStore2(outerHolder, innerHolder2); + Asserts.assertEquals(testEmptyLoad(outerHolder), outerHolder.empty); + Asserts.assertEquals(testEmptyNullFreeLoad(outerHolder), outerHolder.emptyNullFree); + Asserts.assertEquals(testRecursivelyEmptyLoad(outerHolder), outerHolder.holder1); + Asserts.assertEquals(testRecursivelyEmptyLoad2(outerHolder), outerHolder.holder2); + Asserts.assertEquals(testRecursivelyEmptyNullFreeLoad(outerHolder), outerHolder.holder1NullFree); + Asserts.assertEquals(testRecursivelyEmptyNullFreeLoad2(outerHolder), outerHolder.holder2NullFree); + } + Asserts.assertFalse(initialized, "Should not have been initialized!"); + + Asserts.assertThrows(NullPointerException.class, () -> testEmptyStore(null, null)); + Asserts.assertThrows(NullPointerException.class, () -> testEmptyStore(null, empty)); + Asserts.assertThrows(NullPointerException.class, () -> testEmptyNullFreeStore(null, empty)); + Asserts.assertThrows(NullPointerException.class, () -> testEmptyNullFreeStore(outerHolder, null)); + Asserts.assertThrows(NullPointerException.class, () -> testRecursivelyEmptyStore(null, null)); + Asserts.assertThrows(NullPointerException.class, () -> testRecursivelyEmptyStore(null, innerHolder)); + Asserts.assertThrows(NullPointerException.class, () -> testRecursivelyEmptyStore2(null, null)); + Asserts.assertThrows(NullPointerException.class, () -> testRecursivelyEmptyStore2(null, innerHolder2)); + Asserts.assertThrows(NullPointerException.class, () -> testRecursivelyEmptyNullFreeStore(null, null)); + Asserts.assertThrows(NullPointerException.class, () -> testRecursivelyEmptyNullFreeStore(null, innerHolder)); + Asserts.assertThrows(NullPointerException.class, () -> testRecursivelyEmptyNullFreeStore(outerHolder, null)); + Asserts.assertThrows(NullPointerException.class, () -> testRecursivelyEmptyNullFreeStore2(null, null)); + Asserts.assertThrows(NullPointerException.class, () -> testRecursivelyEmptyNullFreeStore2(null, innerHolder2)); + Asserts.assertThrows(NullPointerException.class, () -> testRecursivelyEmptyNullFreeStore2(outerHolder, null)); + Asserts.assertThrows(NullPointerException.class, () -> testEmptyLoad(null)); + Asserts.assertThrows(NullPointerException.class, () -> testEmptyNullFreeLoad(null)); + Asserts.assertThrows(NullPointerException.class, () -> testRecursivelyEmptyLoad(null)); + Asserts.assertThrows(NullPointerException.class, () -> testRecursivelyEmptyLoad2(null)); + Asserts.assertThrows(NullPointerException.class, () -> testRecursivelyEmptyNullFreeLoad(null)); + Asserts.assertThrows(NullPointerException.class, () -> testRecursivelyEmptyNullFreeLoad2(null)); + } +} + From 2e0b5665df7aba91af7be8c3ce18abe28bf27646 Mon Sep 17 00:00:00 2001 From: Jan Lahoda Date: Tue, 20 Oct 2026 06:59:47 +0000 Subject: [PATCH 122/169] 8392648: javac generates invalid bytecode Reviewed-by: mcimadamore, vromero --- .../com/sun/tools/javac/comp/Lower.java | 8 +- .../classes/com/sun/tools/javac/jvm/Gen.java | 66 ++ .../com/sun/tools/javac/tree/JCTree.java | 6 + .../com/sun/tools/javac/util/Names.java | 8 + .../tools/javac/code/GenCondCast.java | 785 ++++++++++++++++++ 5 files changed, 871 insertions(+), 2 deletions(-) create mode 100644 test/langtools/tools/javac/code/GenCondCast.java diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Lower.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Lower.java index f338b1ab7264..c0852f26f902 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Lower.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Lower.java @@ -3312,7 +3312,9 @@ JCExpression boxPrimitive(JCExpression tree, Type box) { box, List.nil() .prepend(tree.type)); - return make.App(make.QualIdent(valueOfSym), List.of(tree)); + JCMethodInvocation res = make.App(make.QualIdent(valueOfSym), List.of(tree)); + res.boxingKind = JCMethodInvocation.BoxingKind.BOX; + return res; } /** Unbox an object to a primitive value. */ @@ -3334,7 +3336,9 @@ JCExpression unbox(JCExpression tree, Type primitive) { unboxedType.tsym.name.append(names.Value), // x.intValue() tree.type, List.nil()); - return make.App(make.Select(tree, valueSym)); + JCMethodInvocation res = make.App(make.Select(tree, valueSym)); + res.boxingKind = JCMethodInvocation.BoxingKind.UNBOX; + return res; } /** Visitor method for parenthesized expressions. diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Gen.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Gen.java index 6d5d609a0777..3988c0f90d08 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Gen.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Gen.java @@ -780,6 +780,61 @@ public CondItem genCond(JCTree _tree, boolean markBranches) { //expression: code.undefineVariablesInChain(result.falseJumps, limit); code.undefineVariablesInChain(result.trueJumps, limit); + return result; + } else if (inner_tree instanceof JCTypeCast cast) { + //as part of condition, the cast must always be (boolean), assert that holds: + Assert.check(cast.type.hasTag(BOOLEAN)); + CondItem result = genCond(cast.expr, markBranches); + setTypeAnnotationPositions(cast.pos); + return result; + } else if (inner_tree instanceof JCMethodInvocation mi && + mi.boxingKind == JCMethodInvocation.BoxingKind.UNBOX && + ((JCFieldAccess) mi.meth).selected instanceof JCTree unboxedValue && + findDeepestCast(unboxedValue) instanceof JCTypeCast deepestCast && + deepestCast.expr instanceof JCMethodInvocation boxing && + boxing.boxingKind == JCMethodInvocation.BoxingKind.BOX) { + //case like: + //(Boolean) (b && (result = 1) > 0) + //expanded to: + //((Boolean)Boolean.valueOf((b && (result = 1) > 0))).booleanValue() + //it is impossible to correctly track variable DA across the method calls, + //so call after evaluating the actual condition, + //keep the true and false branches separate + //note that in place of (Boolean), there may be multiple casts, + //to different types, including (Boolean) (Object) (String) (Object) (Boolean) + + //generate the actual condition: + JCExpression boxedValue = boxing.args.head; + CondItem value = genCond(boxedValue, markBranches); + + //now generate the cast cascade, once for true and once for false, + //ignoring the resulting value, but preserving the codepath: + ClassSymbol jlBoolean = types.boxedClass(syms.booleanType); + + Chain secondJumps = value.jumpFalse(); + code.resolve(value.trueJumps); + make.at(boxing.args.head); + VarSymbol _true = + rs.resolveInternalField(_tree, attrEnv, jlBoolean.type, names.TRUE); + deepestCast.expr = make.QualIdent(_true); + genExpr(unboxedValue, unboxedValue.type).drop(); + Chain trueJumps = code.branch(goto_); + + code.resolve(secondJumps); + make.at(boxing.args.head); + VarSymbol _false = + rs.resolveInternalField(_tree, attrEnv, jlBoolean.type, names.FALSE); + deepestCast.expr = make.QualIdent(_false); + genExpr(unboxedValue, unboxedValue.type).drop(); + Chain falseJumps = code.branch(goto_); + + CondItem result = items.makeCondItem(goto_, + trueJumps, + falseJumps); + if (markBranches) result.tree = _tree; + + deepestCast.expr = boxing; + return result; } else { CondItem result = genExpr(_tree, syms.booleanType).mkCond(); @@ -787,6 +842,17 @@ public CondItem genCond(JCTree _tree, boolean markBranches) { return result; } } + //where: + JCTree findDeepestCast(JCTree source) { + JCTree lastTypeCast = null; + + while (TreeInfo.skipParens(source) instanceof JCTypeCast tc) { + lastTypeCast = tc; + source = tc.expr; + } + + return lastTypeCast; + } public Code getCode() { return code; diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/JCTree.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/JCTree.java index 717f95390fc7..27312f4ec817 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/JCTree.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/JCTree.java @@ -1872,6 +1872,7 @@ public static class JCMethodInvocation extends JCPolyExpression implements Metho public JCExpression meth; public List args; public Type varargsElement; + public BoxingKind boxingKind; protected JCMethodInvocation(List typeargs, JCExpression meth, List args) @@ -1909,6 +1910,11 @@ public JCMethodInvocation setType(Type type) { public Tag getTag() { return(APPLY); } + public enum BoxingKind { + NONE, + BOX, + UNBOX; + } } /** diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Names.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Names.java index 30374a40824d..bf2f5d1f372f 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Names.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Names.java @@ -171,6 +171,10 @@ public static Names instance(Context context) { public final Name Varargs; public final Name PermittedSubclasses; + // members of java.lang.Boolean + public final Name FALSE; + public final Name TRUE; + // members of java.lang.annotation.ElementType public final Name ANNOTATION_TYPE; public final Name CONSTRUCTOR; @@ -369,6 +373,10 @@ public Names(Context context) { Varargs = fromString("Varargs"); PermittedSubclasses = fromString("PermittedSubclasses"); + // members of java.lang.Boolean + FALSE = fromString("FALSE"); + TRUE = fromString("TRUE"); + // members of java.lang.annotation.ElementType ANNOTATION_TYPE = fromString("ANNOTATION_TYPE"); CONSTRUCTOR = fromString("CONSTRUCTOR"); diff --git a/test/langtools/tools/javac/code/GenCondCast.java b/test/langtools/tools/javac/code/GenCondCast.java new file mode 100644 index 000000000000..530086bbc515 --- /dev/null +++ b/test/langtools/tools/javac/code/GenCondCast.java @@ -0,0 +1,785 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @bug 8392648 + * @summary Verify casts to boolean and Boolean work reasonably in condition + * position, w.r.t. DA/DU. + * @modules java.base/jdk.internal.classfile.impl + * @compile GenCondCast.java + * @run main GenCondCast + */ + +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; +import java.lang.classfile.Attributes; +import java.lang.classfile.ClassFile; +import java.lang.classfile.ClassModel; +import java.lang.classfile.MethodModel; +import java.lang.classfile.TypeAnnotation.TargetInfo; +import java.lang.classfile.TypeAnnotation.TypeArgumentTarget; +import java.lang.classfile.attribute.RuntimeInvisibleTypeAnnotationsAttribute; +import java.util.List; +import java.util.Objects; +import java.util.function.ToIntFunction; +import jdk.internal.classfile.impl.LabelImpl; + +public class GenCondCast { + static int test1(boolean b) { + int result; + if ((boolean) (b && (result = 1) > 0)) return result; + return 0; + } + + static int test1_flip(boolean b) { + int result; + if ((boolean) ((result = 1) > 0) && b) return result; + return 0; + } + + static int test1_anno(boolean b) { + int result; + if ((@Ann boolean) (b && (result = 1) > 0)) return result; + return 0; + } + + static int test1_flip_anno(boolean b) { + int result; + if ((@Ann boolean) ((result = 1) > 0) && b) return result; + return 0; + } + + static int test2(boolean b) { + int result; + if ((Boolean) (b && (result = 1) > 0)) return result; + return 0; + } + + static int test2_flip(boolean b) { + int result; + if ((Boolean) ((result = 1) > 0) && b) return result; + return 0; + } + + static int test2_anno(boolean b) { + int result; + if ((@Ann Boolean) (b && (result = 1) > 0)) return result; + return 0; + } + + static int test2_flip_anno(boolean b) { + int result; + if ((@Ann Boolean) ((result = 1) > 0) && b) return result; + return 0; + } + + static int test3(boolean b) { + if ((boolean) id(b)) return 1; + else return 0; + } + + static int test3_anno(boolean b) { + if ((@Ann boolean) id(b)) return 1; + else return 0; + } + + static int test4(boolean b) { + if ((Boolean) id(b)) return 1; + else return 0; + } + + static int test4_anno(boolean b) { + if ((@Ann Boolean) id(b)) return 1; + else return 0; + } + + static int testLarge_anno(boolean b) { + int result; + if ((@Ann Boolean) (b && (result = 1) > 0)) { + //enough code here to force long jumps: + record R(String s0, String s1, String s2, String s3, String s4) {} + Object r = new R("", "", "", "", ""); + switch (r) { + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + default -> {} + } + return result; + } else { + return 0; + } + } + + static int testRedundant1_anno(boolean b) { + int result; + if ((@Ann Boolean) (@Ann Boolean) (@Ann Boolean) (b && (result = 1) > 0)) return result; + return 0; + } + + static int testRedundant2_anno(boolean b) { + int result; + if ((@Ann Boolean) (@Ann boolean) (@Ann Boolean) (b && (result = 1) > 0)) return result; + return 0; + } + + static int testCastChainObject1_anno(boolean b) { + int result; + if ((@Ann Boolean) (@Ann Object) (@Ann Boolean) (b && (result = 1) > 0)) return result; + return 0; + } + + static int testCastChainObject2_anno(boolean b) { + int result; + if ((@Ann Boolean) (@Ann Object) (b && (result = 1) > 0)) return result; + return 0; + } + + static int testExpectCCE(boolean b) { + int result; + if ((@Ann Boolean) (@Ann Object) (@Ann String) (@Ann Object) (@Ann Boolean) (b && (result = 1) > 0)) { + return result; + } + return 0; + } + + static int annotated(boolean b) { + { + int result; + if ((@Ann boolean) (b && (result = 1) > 0)) return result; + } + { + int result; + if ((@Ann boolean) ((result = 1) > 0) && b) return result; + } + //note that the boxing&unboxing is duplicated, but the type annotations + //is only assigned to the second duplicate. That's consistent with + //how duplicated code for e.g. try-finally works: + { + int result; + if ((@Ann Boolean) (b && (result = 1) > 0)) return result; + } + { + int result; + if ((@Ann Boolean) ((result = 1) > 0) && b) return result; + } + boolean _ = (@Ann boolean) b; + boolean _ = (@Ann Boolean) b; + return 0; + } + + static Object id(boolean b) { + return b; + } + + static void doTest(ToIntFunction testMethod) { + if (testMethod.applyAsInt(true) != 1) { + throw new IllegalStateException("Wrong value!"); + } + if (testMethod.applyAsInt(false) != 0) { + throw new IllegalStateException("Wrong value!"); + } + } + + public static void main(String... args) throws Exception { + doTest(GenCondCast::test1); + doTest(GenCondCast::test1_flip); + doTest(GenCondCast::test1_anno); + doTest(GenCondCast::test1_flip_anno); + doTest(GenCondCast::test2); + doTest(GenCondCast::test2_flip); + doTest(GenCondCast::test2_anno); + doTest(GenCondCast::test2_flip_anno); + doTest(GenCondCast::test3); + doTest(GenCondCast::test3_anno); + doTest(GenCondCast::test4); + doTest(GenCondCast::test4_anno); + doTest(GenCondCast::testLarge_anno); + doTest(GenCondCast::testRedundant1_anno); + doTest(GenCondCast::testRedundant2_anno); + doTest(GenCondCast::testCastChainObject1_anno); + doTest(GenCondCast::testCastChainObject2_anno); + try { + testExpectCCE(true); + throw new AssertionError("Should have got an exception."); + } catch (ClassCastException _) { + //correct + } + try { + testExpectCCE(false); + throw new AssertionError("Should have got an exception."); + } catch (ClassCastException _) { + //correct + } + + byte[] classBytes = GenCondCast.class.getClassLoader().getResourceAsStream(GenCondCast.class.getName().replace('.', '/') + ".class").readAllBytes(); + ClassModel model = ClassFile.of().parse(classBytes); + { + MethodModel annotated = model.methods().stream().filter(m -> m.methodName().equalsString("annotated")).findAny().orElseThrow(); + RuntimeInvisibleTypeAnnotationsAttribute typeAnnotations = annotated.code().orElseThrow().findAttribute(Attributes.runtimeInvisibleTypeAnnotations()).orElseThrow(); + List actualTAs = typeAnnotations.annotations().stream().map(ta -> toString(ta.targetInfo()) + ", " + ta.targetPath() + ", " + ta.annotation()).toList(); + List expectedTAs = List.of( + "CAST:0:7, [], Annotation[LGenCondCast$Ann;]", + "CAST:0:15, [], Annotation[LGenCondCast$Ann;]", + "CAST:0:44, [], Annotation[LGenCondCast$Ann;]", + "CAST:0:66, [], Annotation[LGenCondCast$Ann;]", + "CAST:0:77, [], Annotation[LGenCondCast$Ann;]", + "CAST:0:82, [], Annotation[LGenCondCast$Ann;]" + ); + + if (!Objects.equals(expectedTAs, actualTAs)) { + throw new AssertionError("expected: " + expectedTAs + ", got: " + actualTAs); + } + } + { + MethodModel testLarge_anno = model.methods().stream().filter(m -> m.methodName().equalsString("testLarge_anno")).findAny().orElseThrow(); + RuntimeInvisibleTypeAnnotationsAttribute typeAnnotations = testLarge_anno.code().orElseThrow().findAttribute(Attributes.runtimeInvisibleTypeAnnotations()).orElseThrow(); + List actualTAs = typeAnnotations.annotations().stream().map(ta -> toString(ta.targetInfo()) + ", " + ta.targetPath() + ", " + ta.annotation()).toList(); + List expectedTAs = List.of( + "CAST:0:32, [], Annotation[LGenCondCast$Ann;]" + ); + + if (!Objects.equals(expectedTAs, actualTAs)) { + throw new AssertionError("expected: " + expectedTAs + ", got: " + actualTAs); + } + } + { + MethodModel testRedundant1_anno = model.methods().stream().filter(m -> m.methodName().equalsString("testRedundant1_anno")).findAny().orElseThrow(); + RuntimeInvisibleTypeAnnotationsAttribute typeAnnotations = testRedundant1_anno.code().orElseThrow().findAttribute(Attributes.runtimeInvisibleTypeAnnotations()).orElseThrow(); + List actualTAs = typeAnnotations.annotations().stream().map(ta -> toString(ta.targetInfo()) + ", " + ta.targetPath() + ", " + ta.annotation()).toList(); + List expectedTAs = List.of( + "CAST:0:20, [], Annotation[LGenCondCast$Ann;]", + "CAST:0:20, [], Annotation[LGenCondCast$Ann;]", + "CAST:0:20, [], Annotation[LGenCondCast$Ann;]" + ); + + if (!Objects.equals(expectedTAs, actualTAs)) { + throw new AssertionError("expected: " + expectedTAs + ", got: " + actualTAs); + } + } + { + MethodModel testRedundant2_anno = model.methods().stream().filter(m -> m.methodName().equalsString("testRedundant2_anno")).findAny().orElseThrow(); + RuntimeInvisibleTypeAnnotationsAttribute typeAnnotations = testRedundant2_anno.code().orElseThrow().findAttribute(Attributes.runtimeInvisibleTypeAnnotations()).orElseThrow(); + List actualTAs = typeAnnotations.annotations().stream().map(ta -> toString(ta.targetInfo()) + ", " + ta.targetPath() + ", " + ta.annotation()).toList(); + List expectedTAs = List.of( + "CAST:0:34, [], Annotation[LGenCondCast$Ann;]", + "CAST:0:24, [], Annotation[LGenCondCast$Ann;]", + "CAST:0:20, [], Annotation[LGenCondCast$Ann;]" + ); + + if (!Objects.equals(expectedTAs, actualTAs)) { + throw new AssertionError("expected: " + expectedTAs + ", got: " + actualTAs); + } + } + { + MethodModel testCastChainObject_anno = model.methods().stream().filter(m -> m.methodName().equalsString("testCastChainObject1_anno")).findAny().orElseThrow(); + RuntimeInvisibleTypeAnnotationsAttribute typeAnnotations = testCastChainObject_anno.code().orElseThrow().findAttribute(Attributes.runtimeInvisibleTypeAnnotations()).orElseThrow(); + List actualTAs = typeAnnotations.annotations().stream().map(ta -> toString(ta.targetInfo()) + ", " + ta.targetPath() + ", " + ta.annotation()).toList(); + List expectedTAs = List.of( + "CAST:0:23, [], Annotation[LGenCondCast$Ann;]", + "CAST:0:23, [], Annotation[LGenCondCast$Ann;]", + "CAST:0:23, [], Annotation[LGenCondCast$Ann;]" + ); + + if (!Objects.equals(expectedTAs, actualTAs)) { + throw new AssertionError("expected: " + expectedTAs + ", got: " + actualTAs); + } + } + } + + static String toString(TargetInfo ti) { + return switch (ti) { + case TypeArgumentTarget tat -> tat.targetType() + ":" + tat.typeArgumentIndex() + ":" + ((LabelImpl) tat.target()).getBCI(); + default -> ti.getClass().getName(); + }; + } + + @Target(ElementType.TYPE_USE) + @interface Ann {} +} From c9c745806e1a063ca32c0fad21207470fa179280 Mon Sep 17 00:00:00 2001 From: wanglu Date: Tue, 20 Oct 2026 07:43:43 +0000 Subject: [PATCH 123/169] 8394823: [ASAN] libSuspendInCritical.cpp triggers GCC -Wmaybe-uninitialized warning only when building with --enable-asan Reviewed-by: dholmes, shade --- .../jtreg/runtime/jni/critical/libSuspendInCritical.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/hotspot/jtreg/runtime/jni/critical/libSuspendInCritical.cpp b/test/hotspot/jtreg/runtime/jni/critical/libSuspendInCritical.cpp index 0c5e95de6114..6daf8e7b0268 100644 --- a/test/hotspot/jtreg/runtime/jni/critical/libSuspendInCritical.cpp +++ b/test/hotspot/jtreg/runtime/jni/critical/libSuspendInCritical.cpp @@ -66,7 +66,7 @@ Java_SuspendInCritical_getNativeCounter(JNIEnv* jni, jclass cls) { JNIEXPORT void JNICALL Java_SuspendInCritical_doCritical(JNIEnv* jni, jclass cls, jbyteArray bytes, jstring str) { jboolean is_copy = JNI_FALSE; - jbyte* b; + jbyte* b = nullptr; if (bytes != nullptr) { LOG("CriticalThread doing GetPrimitiveArrayCritical\n"); b = (jbyte*) jni->GetPrimitiveArrayCritical(bytes, &is_copy); @@ -75,7 +75,7 @@ JNIEXPORT void JNICALL } } - const jchar* c; + const jchar* c = nullptr; if (str != nullptr) { LOG("CriticalThread doing GetStringCritical\n"); c = jni->GetStringCritical(str, &is_copy); From 751021cbde659a3e40b01e45213279447dca17d2 Mon Sep 17 00:00:00 2001 From: Albert Mingkun Yang Date: Tue, 20 Oct 2026 08:16:29 +0000 Subject: [PATCH 124/169] 8393659: Refactor partial-array parallel processing Reviewed-by: tschatzl, gli --- src/hotspot/share/gc/g1/g1ConcurrentMark.cpp | 15 ++-- src/hotspot/share/gc/g1/g1FullGCMarker.cpp | 8 +-- .../share/gc/g1/g1FullGCMarker.inline.hpp | 2 +- .../share/gc/g1/g1ParScanThreadState.cpp | 14 ++-- .../share/gc/parallel/psCompactionManager.cpp | 10 ++- .../parallel/psCompactionManager.inline.hpp | 3 +- .../share/gc/parallel/psPromotionManager.cpp | 14 ++-- .../share/gc/shared/partialArraySplitter.cpp | 1 - .../share/gc/shared/partialArraySplitter.hpp | 63 ++++++---------- .../gc/shared/partialArraySplitter.inline.hpp | 61 ++++++++-------- .../share/gc/shared/partialArrayState.cpp | 13 ++-- .../share/gc/shared/partialArrayState.hpp | 71 ++++++++----------- .../gc/shared/partialArrayTaskStepper.cpp | 10 +-- .../gc/shared/partialArrayTaskStepper.hpp | 35 ++------- .../shared/partialArrayTaskStepper.inline.hpp | 28 +------- .../shared/test_partialArrayTaskStepper.cpp | 61 +++++----------- 16 files changed, 143 insertions(+), 266 deletions(-) diff --git a/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp b/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp index 9961c49802ab..4b27dce4dd3c 100644 --- a/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp +++ b/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp @@ -1960,7 +1960,7 @@ class G1VerifyNoCollectionSetOops { void operator()(G1TaskQueueEntry task_entry) const { oop obj = task_entry.is_partial_array_state() - ? task_entry.to_partial_array_state()->source() + ? task_entry.to_partial_array_state()->array() : task_entry.to_oop(); guarantee(oopDesc::is_oop(obj), "Non-oop " PTR_FORMAT ", location: %s, index: %d", @@ -2450,26 +2450,25 @@ size_t G1CMTask::start_partial_array_processing(objArrayOop obj) { process_klass(obj->klass()); size_t array_length = obj->length(); - size_t initial_chunk_size = _partial_array_splitter.start(_task_queue, obj, nullptr, array_length, ObjArrayMarkingStride); + PartialArraySplitter::Claim chunk = _partial_array_splitter.start(_task_queue, obj, array_length, ObjArrayMarkingStride); - process_array_chunk(obj, 0, initial_chunk_size); + process_array_chunk(chunk._array, chunk._start, chunk._end); + const size_t chunk_len = chunk._end - chunk._start; // Include object header size if (obj->is_refArray()) { - return refArrayOopDesc::object_size(checked_cast(initial_chunk_size)); + return refArrayOopDesc::object_size(checked_cast(chunk_len)); } else { FlatArrayKlass* fak = FlatArrayKlass::cast(obj->klass()); - return flatArrayOopDesc::object_size(fak->layout_helper(), checked_cast(initial_chunk_size)); + return flatArrayOopDesc::object_size(fak->layout_helper(), checked_cast(chunk_len)); } } size_t G1CMTask::process_partial_array(const G1TaskQueueEntry& task, bool stolen) { PartialArrayState* state = task.to_partial_array_state(); - // Access state before release by claim(). - objArrayOop obj = oop_cast(state->source()); - PartialArraySplitter::Claim claim = _partial_array_splitter.claim(state, _task_queue, stolen); + objArrayOop obj = claim._array; process_array_chunk(obj, claim._start, claim._end); diff --git a/src/hotspot/share/gc/g1/g1FullGCMarker.cpp b/src/hotspot/share/gc/g1/g1FullGCMarker.cpp index 6e0ef06da7dd..aeab92625a41 100644 --- a/src/hotspot/share/gc/g1/g1FullGCMarker.cpp +++ b/src/hotspot/share/gc/g1/g1FullGCMarker.cpp @@ -53,11 +53,9 @@ G1FullGCMarker::~G1FullGCMarker() { } void G1FullGCMarker::process_partial_array(PartialArrayState* state, bool stolen) { - // Access state before release by claim(). - objArrayOop obj_array = objArrayOop(state->source()); PartialArraySplitter::Claim claim = _partial_array_splitter.claim(state, task_queue(), stolen); - process_array_chunk(obj_array, claim._start, claim._end); + process_array_chunk(claim._array, claim._start, claim._end); } static uintx calc_array_stride(uint array_len, uint num_threads) { @@ -79,9 +77,9 @@ void G1FullGCMarker::start_partial_array_processing(objArrayOop obj) { } const uintx stride = calc_array_stride(array_length, _collector->workers()); - const size_t initial_chunk_size = _partial_array_splitter.start(task_queue(), obj, nullptr, array_length, stride); + PartialArraySplitter::Claim chunk = _partial_array_splitter.start(task_queue(), obj, array_length, stride); - process_array_chunk(obj, 0, initial_chunk_size); + process_array_chunk(chunk._array, chunk._start, chunk._end); } void G1FullGCMarker::complete_marking(G1ScannerTasksQueueSet* task_queues, diff --git a/src/hotspot/share/gc/g1/g1FullGCMarker.inline.hpp b/src/hotspot/share/gc/g1/g1FullGCMarker.inline.hpp index ff0cfc8b27a0..3762faa2ba78 100644 --- a/src/hotspot/share/gc/g1/g1FullGCMarker.inline.hpp +++ b/src/hotspot/share/gc/g1/g1FullGCMarker.inline.hpp @@ -91,7 +91,7 @@ inline void G1FullGCMarker::process_array_chunk(objArrayOop obj, size_t start, s inline void G1FullGCMarker::dispatch_task(const ScannerTask& task, bool stolen) { if (task.is_partial_array_state()) { - assert(_bitmap->is_marked(task.to_partial_array_state()->source()), "should be marked"); + assert(_bitmap->is_marked(task.to_partial_array_state()->array()), "should be marked"); process_partial_array(task.to_partial_array_state(), stolen); } else { oop obj = task.to_oop(); diff --git a/src/hotspot/share/gc/g1/g1ParScanThreadState.cpp b/src/hotspot/share/gc/g1/g1ParScanThreadState.cpp index f120ac320357..4c2abf2fb54a 100644 --- a/src/hotspot/share/gc/g1/g1ParScanThreadState.cpp +++ b/src/hotspot/share/gc/g1/g1ParScanThreadState.cpp @@ -175,9 +175,7 @@ void G1ParScanThreadState::verify_task(oop* task) const { void G1ParScanThreadState::verify_task(PartialArrayState* task) const { assert(task != nullptr, "invariant"); - // Source isn't used for processing, so not recorded in task. - assert(task->source() == nullptr, "invariant"); - oop p = task->destination(); + oop p = task->array(); assert(_g1h->is_in_reserved(p), "task=" PTR_FORMAT " dest=" PTR_FORMAT, p2i(task), p2i(p)); } @@ -238,10 +236,9 @@ void G1ParScanThreadState::process_array_chunk(objArrayOop obj, size_t start, si MAYBE_INLINE_EVACUATION void G1ParScanThreadState::do_partial_array(PartialArrayState* state, bool stolen) { - // Access state before release by claim(). - objArrayOop to_array = objArrayOop(state->destination()); PartialArraySplitter::Claim claim = _partial_array_splitter.claim(state, _task_queue, stolen); + objArrayOop to_array = claim._array; G1HeapRegionAttr dest_attr = _g1h->region_attr(to_array); G1SkipCardMarkSetter x(&_scanner, dest_attr.is_new_survivor()); // Process claimed task. @@ -260,11 +257,10 @@ void G1ParScanThreadState::start_partial_objarray(oop from_obj, objArrayOop to_array = objArrayOop(to_obj); size_t array_length = to_array->length(); - size_t initial_chunk_size = - // The source array is unused when processing states. - _partial_array_splitter.start(_task_queue, nullptr, to_array, array_length, ParGCArrayScanChunk); + PartialArraySplitter::Claim chunk = + _partial_array_splitter.start(_task_queue, to_array, array_length, ParGCArrayScanChunk); - process_array_chunk(to_array, 0, initial_chunk_size); + process_array_chunk(chunk._array, chunk._start, chunk._end); } MAYBE_INLINE_EVACUATION diff --git a/src/hotspot/share/gc/parallel/psCompactionManager.cpp b/src/hotspot/share/gc/parallel/psCompactionManager.cpp index 6ca80152401b..f8f23dabf8cd 100644 --- a/src/hotspot/share/gc/parallel/psCompactionManager.cpp +++ b/src/hotspot/share/gc/parallel/psCompactionManager.cpp @@ -124,17 +124,15 @@ void ParCompactionManager::push_objArray(objArrayOop obj) { _mark_and_push_closure.do_klass(obj->klass()); size_t array_length = obj->length(); - size_t initial_chunk_size = - _partial_array_splitter.start(&_marking_stack, obj, nullptr, array_length, ObjArrayMarkingStride); - follow_array(obj, 0, initial_chunk_size); + PartialArraySplitter::Claim chunk = + _partial_array_splitter.start(&_marking_stack, obj, array_length, ObjArrayMarkingStride); + follow_array(chunk._array, chunk._start, chunk._end); } void ParCompactionManager::process_array_chunk(PartialArrayState* state, bool stolen) { - // Access before release by claim(). - oop obj = state->source(); PartialArraySplitter::Claim claim = _partial_array_splitter.claim(state, &_marking_stack, stolen); - follow_array(objArrayOop(obj), claim._start, claim._end); + follow_array(claim._array, claim._start, claim._end); } void ParCompactionManager::follow_marking_stacks() { diff --git a/src/hotspot/share/gc/parallel/psCompactionManager.inline.hpp b/src/hotspot/share/gc/parallel/psCompactionManager.inline.hpp index d88511d81736..ede4c02a0a68 100644 --- a/src/hotspot/share/gc/parallel/psCompactionManager.inline.hpp +++ b/src/hotspot/share/gc/parallel/psCompactionManager.inline.hpp @@ -33,7 +33,6 @@ #include "gc/parallel/psParallelCompact.inline.hpp" #include "gc/parallel/psStringDedup.hpp" #include "gc/shared/partialArrayState.hpp" -#include "gc/shared/partialArrayTaskStepper.inline.hpp" #include "gc/shared/taskqueue.inline.hpp" #include "oops/access.inline.hpp" #include "oops/arrayOop.hpp" @@ -107,7 +106,7 @@ inline void ParCompactionManager::follow_array(objArrayOop obj, size_t start, si inline void ParCompactionManager::follow_contents(const ScannerTask& task, bool stolen) { if (task.is_partial_array_state()) { - assert(PSParallelCompact::mark_bitmap()->is_marked(task.to_partial_array_state()->source()), "should be marked"); + assert(PSParallelCompact::mark_bitmap()->is_marked(task.to_partial_array_state()->array()), "should be marked"); process_array_chunk(task.to_partial_array_state(), stolen); } else { oop obj = task.to_oop(); diff --git a/src/hotspot/share/gc/parallel/psPromotionManager.cpp b/src/hotspot/share/gc/parallel/psPromotionManager.cpp index 157db5c07ddc..1d9e42529a26 100644 --- a/src/hotspot/share/gc/parallel/psPromotionManager.cpp +++ b/src/hotspot/share/gc/parallel/psPromotionManager.cpp @@ -263,14 +263,11 @@ void PSPromotionManager::process_array_chunk(objArrayOop obj, size_t start, size } void PSPromotionManager::process_array_chunk(PartialArrayState* state, bool stolen) { - // Access before release by claim(). - objArrayOop to_array = objArrayOop(state->destination()); - precond(to_array->is_array_with_oops()); - PartialArraySplitter::Claim claim = _partial_array_splitter.claim(state, &_claimed_stack_depth, stolen); + precond(claim._array->is_array_with_oops()); - process_array_chunk(to_array, claim._start, claim._end); + process_array_chunk(claim._array, claim._start, claim._end); } void PSPromotionManager::push_objArray(oop old_obj, oop new_obj) { @@ -280,11 +277,10 @@ void PSPromotionManager::push_objArray(oop old_obj, oop new_obj) { objArrayOop to_array = objArrayOop(new_obj); size_t array_length = to_array->length(); - size_t initial_chunk_size = - // The source array is unused when processing states. - _partial_array_splitter.start(&_claimed_stack_depth, nullptr, to_array, array_length, ParGCArrayScanChunk); + PartialArraySplitter::Claim chunk = + _partial_array_splitter.start(&_claimed_stack_depth, to_array, array_length, ParGCArrayScanChunk); - process_array_chunk(to_array, 0, initial_chunk_size); + process_array_chunk(chunk._array, chunk._start, chunk._end); } oop PSPromotionManager::oop_promotion_failed(oop obj, markWord obj_mark) { diff --git a/src/hotspot/share/gc/shared/partialArraySplitter.cpp b/src/hotspot/share/gc/shared/partialArraySplitter.cpp index 04884d5e666c..68f494eb67ec 100644 --- a/src/hotspot/share/gc/shared/partialArraySplitter.cpp +++ b/src/hotspot/share/gc/shared/partialArraySplitter.cpp @@ -22,7 +22,6 @@ * */ -#include "gc/shared/gc_globals.hpp" #include "gc/shared/partialArraySplitter.hpp" #include "gc/shared/partialArrayState.hpp" #include "utilities/macros.hpp" diff --git a/src/hotspot/share/gc/shared/partialArraySplitter.hpp b/src/hotspot/share/gc/shared/partialArraySplitter.hpp index 340f370d1d5d..5235d2cd11da 100644 --- a/src/hotspot/share/gc/shared/partialArraySplitter.hpp +++ b/src/hotspot/share/gc/shared/partialArraySplitter.hpp @@ -32,16 +32,18 @@ #include "utilities/globalDefinitions.hpp" #include "utilities/macros.hpp" -class outputStream; - -// Helper class for splitting the processing of a large objArray into multiple -// tasks, to permit multiple threads to work on different pieces of the array -// in parallel. +// Each worker uses its own splitter to scan arrays in parallel. +// Queued tasks share an atomic progress record; each claim returns a +// disjoint, self-contained chunk and decrements the record's ref-count. class PartialArraySplitter { PartialArrayStateAllocator _allocator; PartialArrayTaskStepper _stepper; TASKQUEUE_STATS_ONLY(PartialArrayTaskStats _stats;) + // The state's ref-count must already include these tasks before publication. + template + void enqueue(Queue* queue, PartialArrayState* state, uint count); + public: PartialArraySplitter(PartialArrayStateManager* manager, uint num_workers); @@ -49,53 +51,30 @@ class PartialArraySplitter { NONCOPYABLE(PartialArraySplitter); - // Setup to process an objArray in chunks. - // - // from_array is the array found by the collector that needs processing. It - // may be null if to_array contains everything needed for processing. - // - // to_array is an unprocessed (possibly partial) copy of from_array, or null - // if a copy of from_array is not required. - // - // length is their length in elements. - // - // chunk_size the size of a single chunk. - // - // If t is a ScannerTask, queue->push(t) must be a valid expression. The - // result of that expression is ignored. - // - // Returns the size of the initial chunk that is to be processed by the - // caller. - // - // Adds PartialArrayState ScannerTasks to the queue if needed to process the - // array in chunks. This permits other workers to steal and process them - // even while the caller is processing the initial chunk. If length doesn't - // exceed the chunk size then the result will be length, indicating the - // caller is to process the entire array. In this case, no tasks will have - // been added to the queue. - template - size_t start(Queue* queue, - objArrayOop from_array, - objArrayOop to_array, - size_t length, - size_t chunk_size); - - // Result type for claim(), carrying multiple values. Provides the claimed - // chunk's start and end array indices. + // A chunk remains valid after its progress record has been recycled. It + // does not own or keep the array alive; that is the collector's responsibility. struct Claim { + objArrayOop _array; size_t _start; size_t _end; }; + // Start processing array[0, length). Return the initial chunk and enqueue + // a continuation if any full-sized chunks remain. Process the remainder + // first so every queued task claims exactly chunk_size elements. + template + Claim start(Queue* queue, + objArrayOop array, + size_t length, + size_t chunk_size); + // Claims a chunk from state, returning the index range for that chunk. The // caller is expected to process that chunk. Adds more state-based tasks to // the queue if needed, permitting other workers to steal and process them // even while the caller is processing this claim. // - // Releases the state. Callers must not use state after the call to this - // function. The state may have been recycled and reused. - // - // The queue has the same requirements as for start(). + // Decrements the state's ref-count for the current task. Callers must not + // use state after this call; it may have been recycled and reused. // // stolen indicates whether the state task was obtained from this queue or // stolen from some other queue. diff --git a/src/hotspot/share/gc/shared/partialArraySplitter.inline.hpp b/src/hotspot/share/gc/shared/partialArraySplitter.inline.hpp index 7679358e2188..ba0ba4346bd3 100644 --- a/src/hotspot/share/gc/shared/partialArraySplitter.inline.hpp +++ b/src/hotspot/share/gc/shared/partialArraySplitter.inline.hpp @@ -36,26 +36,28 @@ #include "utilities/macros.hpp" template -size_t PartialArraySplitter::start(Queue* queue, - objArrayOop source, - objArrayOop destination, - size_t length, - size_t chunk_size) { +void PartialArraySplitter::enqueue(Queue* queue, PartialArrayState* state, uint count) { + TASKQUEUE_STATS_ONLY(_stats.inc_pushed(count);) + for (uint i = 0; i < count; ++i) { + queue->push(ScannerTask(state)); + } +} + +template +PartialArraySplitter::Claim +PartialArraySplitter::start(Queue* queue, + objArrayOop array, + size_t length, + size_t chunk_size) { precond(chunk_size > 0); - PartialArrayTaskStepper::Step step = _stepper.start(length, chunk_size); - // Push initial partial scan tasks. - if (step._ncreate > 0) { - TASKQUEUE_STATS_ONLY(_stats.inc_split();); - TASKQUEUE_STATS_ONLY(_stats.inc_pushed(step._ncreate);) + size_t end = length % chunk_size; + if (end < length) { + TASKQUEUE_STATS_ONLY(_stats.inc_split();) PartialArrayState* state = - _allocator.allocate(source, destination, step._index, length, chunk_size, step._ncreate); - for (uint i = 0; i < step._ncreate; ++i) { - queue->push(ScannerTask(state)); - } - } else { - assert(step._index == length, "invariant"); + _allocator.allocate(array, end, length, chunk_size, 1); + enqueue(queue, state, 1); } - return step._index; + return Claim{array, 0, end}; } template @@ -66,21 +68,20 @@ PartialArraySplitter::claim(PartialArrayState* state, Queue* queue, bool stolen) _stats.inc_processed(); #endif // TASKQUEUE_STATS - // Claim a chunk and get number of additional tasks to enqueue. - PartialArrayTaskStepper::Step step = _stepper.next(state); - // Push additional tasks. - if (step._ncreate > 0) { - TASKQUEUE_STATS_ONLY(_stats.inc_pushed(step._ncreate);) - // Adjust reference count for tasks being added to the queue. - state->add_references(step._ncreate); - for (uint i = 0; i < step._ncreate; ++i) { - queue->push(ScannerTask(state)); - } + size_t start = state->claim_next(); + size_t chunk_size = state->_chunk_size; + uint count = _stepper.continuation_tasks(start, state->_length, chunk_size); + if (count > 0) { + // Increment the ref-count for all new tasks before publication. A thief + // may immediately claim and release any task we push. + state->add_references(count); + enqueue(queue, state, count); } - size_t chunk_size = state->chunk_size(); - // Release state, decrementing refcount, now that we're done with it. + Claim result{state->array(), start, start + chunk_size}; + // Capture everything needed for scanning before decrementing the state's + // ref-count. _allocator.release(state); - return Claim{step._index, step._index + chunk_size}; + return result; } #endif // SHARE_GC_SHARED_PARTIALARRAYSPLITTER_INLINE_HPP diff --git a/src/hotspot/share/gc/shared/partialArrayState.cpp b/src/hotspot/share/gc/shared/partialArrayState.cpp index 3ef2927fe9a7..aaa820349c74 100644 --- a/src/hotspot/share/gc/shared/partialArrayState.cpp +++ b/src/hotspot/share/gc/shared/partialArrayState.cpp @@ -33,18 +33,20 @@ #include "utilities/globalDefinitions.hpp" #include "utilities/macros.hpp" -PartialArrayState::PartialArrayState(oop src, oop dst, +PartialArrayState::PartialArrayState(objArrayOop array, size_t index, size_t length, size_t chunk_size, size_t initial_refcount) - : _source(src), - _destination(dst), + : _array(array), _length(length), _chunk_size(chunk_size), _index(index), _refcount(initial_refcount) { assert(index <= length, "precondition"); + assert(chunk_size > 0, "precondition"); + assert((length - index) % chunk_size == 0, "whole chunks remain"); + assert(initial_refcount > 0, "must have a task reference"); } void PartialArrayState::add_references(size_t count) { @@ -76,7 +78,7 @@ PartialArrayStateAllocator::~PartialArrayStateAllocator() { _manager->release_allocator(); } -PartialArrayState* PartialArrayStateAllocator::allocate(oop src, oop dst, +PartialArrayState* PartialArrayStateAllocator::allocate(objArrayOop array, size_t index, size_t length, size_t chunk_size, @@ -90,7 +92,7 @@ PartialArrayState* PartialArrayStateAllocator::allocate(oop src, oop dst, head->~FreeListEntry(); p = head; } - return ::new (p) PartialArrayState(src, dst, index, length, chunk_size, initial_refcount); + return ::new (p) PartialArrayState(array, index, length, chunk_size, initial_refcount); } void PartialArrayStateAllocator::release(PartialArrayState* state) { @@ -118,6 +120,7 @@ PartialArrayStateManager::~PartialArrayStateManager() { } Arena* PartialArrayStateManager::register_allocator() { + assert(_released_allocators.load_relaxed() == 0, "allocator destruction has begun"); uint idx = _registered_allocators.fetch_then_add(1u, memory_order_relaxed); assert(idx < _max_allocators, "exceeded configured max number of allocators"); return ::new (&_arenas[idx]) Arena(mtGC); diff --git a/src/hotspot/share/gc/shared/partialArrayState.hpp b/src/hotspot/share/gc/shared/partialArrayState.hpp index 75e297526aef..04852ae741eb 100644 --- a/src/hotspot/share/gc/shared/partialArrayState.hpp +++ b/src/hotspot/share/gc/shared/partialArrayState.hpp @@ -28,6 +28,7 @@ #include "memory/allocation.hpp" #include "oops/oopsHierarchy.hpp" #include "runtime/atomic.hpp" +#include "utilities/debug.hpp" #include "utilities/globalDefinitions.hpp" #include "utilities/macros.hpp" @@ -43,58 +44,47 @@ class PartialArrayStateManager; // for later processing, either by the current thread or by some other thread // that steals one of those tasks. // -// Processing a state involves using the state to claim a segment of the -// array, and processing that segment. Claiming is done by atomically -// incrementing the index, thereby claiming the segment from the old to new -// index values. New tasks should also be added as needed to ensure the -// entire array will be processed. A PartialArrayTaskStepper can be used to -// help with this. +// PartialArraySplitter atomically claims disjoint chunks and publishes enough +// continuation tasks for parallel processing. The state is only needed while +// claiming a chunk, not while scanning it. // // States are allocated and released using a PartialArrayStateAllocator. -// States are reference counted to aid in that management. Each task -// referring to a given state that is added to a taskqueue must increase the -// reference count by one. When the processing of a task referring to a state -// is complete, the reference count must be decreased by one. When the -// reference count reaches zero the state is released to the allocator for -// later reuse. +// Each queued or currently claiming task contributes one to the state's +// ref-count. Increment the ref-count before publishing continuation tasks. +// After a task has claimed its chunk and published any continuations, decrement +// the ref-count by one. The worker that decrements it to zero recycles the +// state on its own allocator for later reuse. class PartialArrayState { - oop _source; - oop _destination; + objArrayOop _array; size_t _length; size_t _chunk_size; Atomic _index; Atomic _refcount; friend class PartialArrayStateAllocator; + friend class PartialArraySplitter; - PartialArrayState(oop src, oop dst, + PartialArrayState(objArrayOop array, size_t index, size_t length, size_t chunk_size, size_t initial_refcount); + // Increment the ref-count by count before publishing that many tasks to a queue. + void add_references(size_t count); + + size_t claim_next() { + size_t start = _index.fetch_then_add(_chunk_size, memory_order_relaxed); + assert(start < _length, "every task must have a chunk to claim"); + assert((_length - start) % _chunk_size == 0, "whole chunks remain"); + return start; + } + public: // Deleted to require management by allocator object. ~PartialArrayState() = delete; NONCOPYABLE(PartialArrayState); - // Add count references, one per referring task being added to a taskqueue. - void add_references(size_t count); - - // The source array oop. - oop source() const { return _source; } - - // The destination array oop. In some circumstances the source and - // destination may be the same. - oop destination() const { return _destination; } - - // The length of the array oop. - size_t length() const { return _length; } - - size_t chunk_size() const { return _chunk_size; } - - // A pointer to the start index for the next segment to process, for atomic - // update. - Atomic* index_addr() { return &_index; } + objArrayOop array() const { return _array; } }; // This class provides memory management for PartialArrayStates. @@ -121,7 +111,7 @@ class PartialArrayStateAllocator : public CHeapObj { PartialArrayStateManager* _manager; FreeListEntry* _free_list; - Arena* _arena; // Obtained from _manager. + Arena* _arena; public: explicit PartialArrayStateAllocator(PartialArrayStateManager* manager); @@ -129,17 +119,12 @@ class PartialArrayStateAllocator : public CHeapObj { NONCOPYABLE(PartialArrayStateAllocator); - // Create a new state, obtaining the memory for it from the free-list or - // from the associated manager. - PartialArrayState* allocate(oop src, oop dst, + PartialArrayState* allocate(objArrayOop array, size_t index, size_t length, - size_t chunk_size, - size_t initial_refcount); + size_t chunk_size, size_t initial_refcount); - // Decrement the state's refcount. If the new refcount is zero, add the - // state to the free-list associated with worker_id. The state must have - // been allocated by this allocator, but that allocation doesn't need to - // have been associated with worker_id. + // Decrement the state's ref-count by one. If it reaches zero, recycle on + // this allocator's free-list. The state must belong to the same manager. void release(PartialArrayState* state); }; diff --git a/src/hotspot/share/gc/shared/partialArrayTaskStepper.cpp b/src/hotspot/share/gc/shared/partialArrayTaskStepper.cpp index 126c6c302201..8a5b70a34b67 100644 --- a/src/hotspot/share/gc/shared/partialArrayTaskStepper.cpp +++ b/src/hotspot/share/gc/shared/partialArrayTaskStepper.cpp @@ -28,12 +28,6 @@ #include "utilities/globalDefinitions.hpp" #include "utilities/powerOfTwo.hpp" -static uint compute_task_limit(uint n_workers) { - // Don't need more than n_workers tasks at a time. But allowing up to - // that maximizes available parallelism. - return n_workers; -} - static uint compute_task_fanout(uint task_limit) { assert(task_limit > 0, "precondition"); // There is a tradeoff between providing parallelism more quickly and @@ -48,8 +42,8 @@ static uint compute_task_fanout(uint task_limit) { return result; } -PartialArrayTaskStepper::PartialArrayTaskStepper(uint n_workers) : - _task_limit(compute_task_limit(n_workers)), +PartialArrayTaskStepper::PartialArrayTaskStepper(uint num_workers) : + _task_limit(num_workers), _task_fanout(compute_task_fanout(_task_limit)) { // Current stepper code only supports up to 2^31 elements in the array. diff --git a/src/hotspot/share/gc/shared/partialArrayTaskStepper.hpp b/src/hotspot/share/gc/shared/partialArrayTaskStepper.hpp index 594cc7b245a9..fd9706aa9878 100644 --- a/src/hotspot/share/gc/shared/partialArrayTaskStepper.hpp +++ b/src/hotspot/share/gc/shared/partialArrayTaskStepper.hpp @@ -25,12 +25,8 @@ #ifndef SHARE_GC_SHARED_PARTIALARRAYTASKSTEPPER_HPP #define SHARE_GC_SHARED_PARTIALARRAYTASKSTEPPER_HPP -#include "oops/arrayOop.hpp" -#include "runtime/atomic.hpp" #include "utilities/globalDefinitions.hpp" -class PartialArrayState; - // Helper for partial array chunking tasks. // // When an array is large, we want to split it up into chunks that can be @@ -40,36 +36,19 @@ class PartialArrayState; // substantially expand the task queues. class PartialArrayTaskStepper { public: - PartialArrayTaskStepper(uint n_workers); - - struct Step { - size_t _index; // Array index for the step. - uint _ncreate; // Number of new tasks to create. - }; - - // Called with the length of the array to be processed and chunk size. - // Returns a Step with _index being the end of the initial chunk, which the - // caller should process. This is also the starting index for the next chunk to process. - // The _ncreate is the number of tasks to enqueue to continue processing the - // array. If _ncreate is zero then _index will be length. - inline Step start(size_t length, size_t chunk_size) const; + explicit PartialArrayTaskStepper(uint num_workers); - // Atomically increment state's index by chunk_size() to claim the next - // chunk. Returns a Step with _index being the starting index of the - // claimed chunk and _ncreate being the number of additional partial tasks - // to enqueue. - inline Step next(PartialArrayState* state) const; - - class TestSupport; // For unit tests + // Number of continuation tasks to publish after claiming the full-sized + // chunk starting at start. Initially there is one queued task. Every claim + // must publish the count returned here, even if other claims have advanced + // the shared index in the meantime. + inline uint continuation_tasks(size_t start, size_t length, size_t chunk_size) const; private: - // Limit on the number of partial array tasks to create for a given array. + // Limit on pending tasks for one array. uint _task_limit; // Maximum number of new tasks to create when processing an existing task. uint _task_fanout; - - // For unit tests. - inline Step next_impl(size_t length, size_t chunk_size, Atomic* index_addr) const; }; #endif // SHARE_GC_SHARED_PARTIALARRAYTASKSTEPPER_HPP diff --git a/src/hotspot/share/gc/shared/partialArrayTaskStepper.inline.hpp b/src/hotspot/share/gc/shared/partialArrayTaskStepper.inline.hpp index fb58400cf271..9d28bb9fbe8f 100644 --- a/src/hotspot/share/gc/shared/partialArrayTaskStepper.inline.hpp +++ b/src/hotspot/share/gc/shared/partialArrayTaskStepper.inline.hpp @@ -27,28 +27,11 @@ #include "gc/shared/partialArrayTaskStepper.hpp" -#include "gc/shared/partialArrayState.hpp" #include "utilities/checkedCast.hpp" #include "utilities/debug.hpp" -PartialArrayTaskStepper::Step -PartialArrayTaskStepper::start(size_t length, size_t chunk_size) const { - size_t end = length % chunk_size; // End of initial chunk. - // If the initial chunk is the complete array, then don't need any partial - // tasks. Otherwise, start with just one partial task; see new task - // calculation in next(). - return Step{ end, (length > end) ? 1u : 0u }; -} - -PartialArrayTaskStepper::Step -PartialArrayTaskStepper::next_impl(size_t length, size_t chunk_size, Atomic* index_addr) const { - // The start of the next task is in the state's index. - // Atomically increment by the chunk size to claim the associated chunk. - // Because we limit the number of enqueued tasks to being no more than the - // number of remaining chunks to process, we can use an atomic add for the - // claim, rather than a CAS loop. - size_t start = index_addr->fetch_then_add(chunk_size, memory_order_relaxed); - +uint PartialArrayTaskStepper::continuation_tasks(size_t start, size_t length, size_t chunk_size) const { + precond(chunk_size > 0); assert(start < length, "invariant: start %zu, length %zu", start, length); assert(((length - start) % chunk_size) == 0, "invariant: start %zu, length %zu, chunk size %zu", @@ -80,12 +63,7 @@ PartialArrayTaskStepper::next_impl(size_t length, size_t chunk_size, Atomic(max_pending, remaining_tasks, _task_limit); uint ncreate = MIN2(_task_fanout, MIN2(remaining_tasks, _task_limit + 1) - pending); - return Step{ start, ncreate }; -} - -PartialArrayTaskStepper::Step -PartialArrayTaskStepper::next(PartialArrayState* state) const { - return next_impl(state->length(), state->chunk_size(), state->index_addr()); + return ncreate; } #endif // SHARE_GC_SHARED_PARTIALARRAYTASKSTEPPER_INLINE_HPP diff --git a/test/hotspot/gtest/gc/shared/test_partialArrayTaskStepper.cpp b/test/hotspot/gtest/gc/shared/test_partialArrayTaskStepper.cpp index 6f2ccc6c14e7..0fbc8c0f8dc0 100644 --- a/test/hotspot/gtest/gc/shared/test_partialArrayTaskStepper.cpp +++ b/test/hotspot/gtest/gc/shared/test_partialArrayTaskStepper.cpp @@ -23,50 +23,27 @@ */ #include "gc/shared/partialArrayTaskStepper.inline.hpp" -#include "memory/allStatic.hpp" -#include "runtime/atomic.hpp" #include "unittest.hpp" -using Step = PartialArrayTaskStepper::Step; -using Stepper = PartialArrayTaskStepper; - -class PartialArrayTaskStepper::TestSupport : AllStatic { -public: - static Step next(const Stepper* stepper, - size_t length, - size_t chunk_size, - Atomic* to_length_addr) { - return stepper->next_impl(length, chunk_size, to_length_addr); - } -}; - -using StepperSupport = PartialArrayTaskStepper::TestSupport; - -static uint simulate(const Stepper* stepper, - size_t length, - size_t chunk_size, - Atomic* to_length_addr) { - Step init = stepper->start(length, chunk_size); - to_length_addr->store_relaxed(init._index); - uint queue_count = init._ncreate; - uint task = 0; - for ( ; queue_count > 0; ++task) { - --queue_count; - Step step = StepperSupport::next(stepper, length, chunk_size, to_length_addr); - queue_count += step._ncreate; - } - return task; -} - static void run_test(size_t length, size_t chunk_size, uint n_workers) { const PartialArrayTaskStepper stepper(n_workers); - Atomic to_length; - uint tasks = simulate(&stepper, length, chunk_size, &to_length); - ASSERT_EQ(length, to_length.load_relaxed()); + size_t index = length % chunk_size; + uint pending = index < length ? 1u : 0u; + uint tasks = 0; + while (pending > 0) { + ASSERT_LT(index, length); + --pending; + pending += stepper.continuation_tasks(index, length, chunk_size); + index += chunk_size; + ++tasks; + ASSERT_LE(pending, n_workers); + ASSERT_LE(pending, (length - index) / chunk_size); + } + ASSERT_EQ(length, index); ASSERT_EQ(tasks, length / chunk_size); } -TEST(PartialArrayTaskStepperTest, doit) { +TEST(PartialArrayTaskStepperTest, coverage_and_task_limit) { for (size_t chunk_size = 50; chunk_size <= 500; chunk_size += 50) { for (uint n_workers = 1; n_workers <= 256; n_workers = (n_workers * 3 / 2 + 1)) { for (size_t length = 0; length <= 1000000; length = (length * 2 + 1)) { @@ -85,13 +62,9 @@ TEST(PartialArrayTaskStepperTest, overflow_beyond_array) { const size_t Length = INT32_MAX; const size_t ChunkSize = 1; const size_t Index = 1431655765; - const size_t NumWorkers = 16; // Fanout is 4. - - const Stepper stepper(NumWorkers); + const uint NumWorkers = 16; // Fanout is 4. - Atomic to_length; - to_length.store_relaxed(Index); + const PartialArrayTaskStepper stepper(NumWorkers); - Step step = StepperSupport::next(&stepper, Length, ChunkSize, &to_length); - ASSERT_EQ(1u, step._ncreate); + ASSERT_EQ(1u, stepper.continuation_tasks(Index, Length, ChunkSize)); } From bd97c58dc37185801c16935eecb6cf065eb38d78 Mon Sep 17 00:00:00 2001 From: Matthias Baesken Date: Tue, 20 Oct 2026 08:51:34 +0000 Subject: [PATCH 125/169] 8392567: [GCC static analyzer] potential leak of asd in Java_sun_awt_X11GraphicsConfig_init Reviewed-by: prr, mdoerr --- .../native/libawt_xawt/awt/awt_GraphicsEnv.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/java.desktop/unix/native/libawt_xawt/awt/awt_GraphicsEnv.c b/src/java.desktop/unix/native/libawt_xawt/awt/awt_GraphicsEnv.c index 5985dd93226f..08b0011e76fd 100644 --- a/src/java.desktop/unix/native/libawt_xawt/awt/awt_GraphicsEnv.c +++ b/src/java.desktop/unix/native/libawt_xawt/awt/awt_GraphicsEnv.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -1153,19 +1153,19 @@ JNIEnv *env, jobject this, jint visualNum, jint screen) DASSERT(screen >= 0 && screen < awt_numScreens); AwtGraphicsConfigData *adata = NULL; - AwtScreenData asd = x11Screens[screen]; + AwtScreenData *asd = &x11Screens[screen]; int i; int depth; XImage * tempImage; /* If haven't gotten all of the configs yet, do it now. */ - if (asd.numConfigs == 0) { - getAllConfigs (env, screen, &asd); + if (asd->numConfigs == 0) { + getAllConfigs (env, screen, asd); } /* Check the graphicsConfig for this visual */ - for (i = 0; i < asd.numConfigs; i++) { - AwtGraphicsConfigDataPtr agcPtr = asd.configs[i]; + for (i = 0; i < asd->numConfigs; i++) { + AwtGraphicsConfigDataPtr agcPtr = asd->configs[i]; if ((jint)agcPtr->awt_visInfo.visualid == visualNum) { adata = agcPtr; break; @@ -1188,6 +1188,10 @@ JNIEnv *env, jobject this, jint visualNum, jint screen) tempImage = XCreateImage(awt_display, adata->awt_visInfo.visual, depth, ZPixmap, 0, NULL, 1, 1, 32, 0); + if (tempImage == NULL) { + JNU_ThrowIllegalArgumentException(env, "XCreateImage failed"); + return; + } adata->pixelStride = (tempImage->bits_per_pixel + 7) / 8; (*env)->SetIntField(env, this, x11GraphicsConfigIDs.bitsPerPixel, (jint)tempImage->bits_per_pixel); From a19447accc5a8b67474d060ac2f82420be742f14 Mon Sep 17 00:00:00 2001 From: Quan Anh Mai Date: Tue, 20 Oct 2026 09:00:07 +0000 Subject: [PATCH 126/169] 8394808: C2: MachSafePointNode::bottom_type should be Type::CONTROL Reviewed-by: shade, kvn --- src/hotspot/share/opto/machnode.cpp | 3 --- src/hotspot/share/opto/machnode.hpp | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/hotspot/share/opto/machnode.cpp b/src/hotspot/share/opto/machnode.cpp index 3150e7d60387..7fb49c506221 100644 --- a/src/hotspot/share/opto/machnode.cpp +++ b/src/hotspot/share/opto/machnode.cpp @@ -752,9 +752,6 @@ const RegMask &MachReturnNode::in_RegMask( uint idx ) const { return _in_rms[idx]; } -//============================================================================= -const Type *MachSafePointNode::bottom_type() const { return TypeTuple::MEMBAR; } - //------------------------------Registers-------------------------------------- const RegMask &MachSafePointNode::in_RegMask( uint idx ) const { // Values in the domain use the users calling convention, embodied in the diff --git a/src/hotspot/share/opto/machnode.hpp b/src/hotspot/share/opto/machnode.hpp index a84a3328c72f..ee94e84cfeb4 100644 --- a/src/hotspot/share/opto/machnode.hpp +++ b/src/hotspot/share/opto/machnode.hpp @@ -904,7 +904,7 @@ class MachSafePointNode : public MachReturnNode { void set_jvms(JVMState* s) { _jvms = s; } - virtual const Type *bottom_type() const; + virtual const Type* bottom_type() const { return Type::CONTROL; } virtual const RegMask &in_RegMask(uint) const; From bee4b021140a3a27b082463cce110ab32d3413c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Jeli=C5=84ski?= Date: Tue, 20 Oct 2026 09:02:35 +0000 Subject: [PATCH 127/169] 8394531: Remove support for SIO_LOOPBACK_FAST_PATH option on Windows Reviewed-by: alanb --- .../share/classes/sun/nio/ch/Net.java | 18 ++++----------- src/java.base/share/native/libnet/net_util.h | 3 --- .../unix/native/libnet/net_util_md.c | 5 ----- src/java.base/unix/native/libnio/ch/Net.c | 2 +- .../windows/native/libnet/net_util_md.c | 22 ------------------- src/java.base/windows/native/libnio/ch/Net.c | 19 +--------------- .../StressLoopback.java | 4 +--- .../net/SocketChannelConnectionSetup.java | 11 +--------- 8 files changed, 8 insertions(+), 76 deletions(-) diff --git a/src/java.base/share/classes/sun/nio/ch/Net.java b/src/java.base/share/classes/sun/nio/ch/Net.java index 4564579d0546..80e6e409e327 100644 --- a/src/java.base/share/classes/sun/nio/ch/Net.java +++ b/src/java.base/share/classes/sun/nio/ch/Net.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -67,8 +67,6 @@ public String name() { // set to true if exclusive binding is on for Windows private static final boolean EXCLUSIVE_BIND; - // set to true if the fast tcp loopback should be enabled on Windows - private static final boolean FAST_LOOPBACK; // -- Miscellaneous utilities -- @@ -458,11 +456,6 @@ static Object getSocketOption(FileDescriptor fd, ProtocolFamily family, SocketOp } } - private static boolean isFastTcpLoopbackRequested() { - String loopbackProp = System.getProperty("jdk.net.useFastTcpLoopback", "false"); - return loopbackProp.isEmpty() || Boolean.parseBoolean(loopbackProp); - } - // -- Socket operations -- private static native boolean isIPv6Available0(); @@ -491,7 +484,7 @@ static FileDescriptor socket() throws IOException { static FileDescriptor socket(ProtocolFamily family, boolean stream) throws IOException { boolean preferIPv6 = isIPv6Available() && (family != StandardProtocolFamily.INET); - return IOUtil.newFD(socket0(preferIPv6, stream, false, FAST_LOOPBACK)); + return IOUtil.newFD(socket0(preferIPv6, stream, false)); } static FileDescriptor serverSocket() { @@ -501,12 +494,11 @@ static FileDescriptor serverSocket() { static FileDescriptor serverSocket(ProtocolFamily family) { boolean preferIPv6 = isIPv6Available() && (family != StandardProtocolFamily.INET); - return IOUtil.newFD(socket0(preferIPv6, true, true, FAST_LOOPBACK)); + return IOUtil.newFD(socket0(preferIPv6, true, true)); } // Due to oddities SO_REUSEADDR on Windows reuse is ignored - private static native int socket0(boolean preferIPv6, boolean stream, boolean reuse, - boolean fastLoopback); + private static native int socket0(boolean preferIPv6, boolean stream, boolean reuse); public static void bind(FileDescriptor fd, InetAddress addr, int port) throws IOException @@ -797,8 +789,6 @@ static native int blockOrUnblock6(boolean block, FileDescriptor fd, byte[] group EXCLUSIVE_BIND = false; } - FAST_LOOPBACK = isFastTcpLoopbackRequested(); - IPV6_AVAILABLE = isIPv6Available0(); SO_REUSEPORT_AVAILABLE = isReusePortAvailable0(); } diff --git a/src/java.base/share/native/libnet/net_util.h b/src/java.base/share/native/libnet/net_util.h index a739cb3d2ccf..8983e51eb64f 100644 --- a/src/java.base/share/native/libnet/net_util.h +++ b/src/java.base/share/native/libnet/net_util.h @@ -164,9 +164,6 @@ NET_SetSockOpt(int fd, int level, int opt, const void *arg, int len); JNIEXPORT int JNICALL NET_Bind(int fd, SOCKETADDRESS *sa, int len); -JNIEXPORT jint JNICALL -NET_EnableFastTcpLoopback(int fd); - unsigned short in_cksum(unsigned short *addr, int len); jint NET_Wait(JNIEnv *env, jint fd, jint flags, jint timeout); diff --git a/src/java.base/unix/native/libnet/net_util_md.c b/src/java.base/unix/native/libnet/net_util_md.c index 054209342b15..45fb2c172b73 100644 --- a/src/java.base/unix/native/libnet/net_util_md.c +++ b/src/java.base/unix/native/libnet/net_util_md.c @@ -233,11 +233,6 @@ void NET_ThrowUnknownHostExceptionWithGaiError(JNIEnv *env, } } -JNIEXPORT jint JNICALL -NET_EnableFastTcpLoopback(int fd) { - return 0; -} - /** * See net_util.h for documentation */ diff --git a/src/java.base/unix/native/libnio/ch/Net.c b/src/java.base/unix/native/libnio/ch/Net.c index ad3f132f48ea..948646fb80c3 100644 --- a/src/java.base/unix/native/libnio/ch/Net.c +++ b/src/java.base/unix/native/libnio/ch/Net.c @@ -229,7 +229,7 @@ Java_sun_nio_ch_Net_canUseIPv6OptionsWithIPv4LocalAddress0(JNIEnv* env, jclass c JNIEXPORT jint JNICALL Java_sun_nio_ch_Net_socket0(JNIEnv *env, jclass cl, jboolean preferIPv6, - jboolean stream, jboolean reuse, jboolean ignored) + jboolean stream, jboolean reuse) { int fd; int type = (stream ? SOCK_STREAM : SOCK_DGRAM); diff --git a/src/java.base/windows/native/libnet/net_util_md.c b/src/java.base/windows/native/libnet/net_util_md.c index 6320c6b81121..75a22de5990a 100644 --- a/src/java.base/windows/native/libnet/net_util_md.c +++ b/src/java.base/windows/native/libnet/net_util_md.c @@ -29,9 +29,6 @@ #include "java_net_InetAddress.h" #include "java_net_SocketOptions.h" -// Taken from mstcpip.h in Windows SDK 8.0 or newer. -#define SIO_LOOPBACK_FAST_PATH _WSAIOW(IOC_VENDOR,16) - #ifndef IPTOS_TOS_MASK #define IPTOS_TOS_MASK 0x1e #endif @@ -393,25 +390,6 @@ void dumpAddr (char *str, void *addr) { } } -/** - * Enables SIO_LOOPBACK_FAST_PATH - */ -JNIEXPORT jint JNICALL -NET_EnableFastTcpLoopback(int fd) { - int enabled = 1; - DWORD result_byte_count = -1; - int result = WSAIoctl(fd, - SIO_LOOPBACK_FAST_PATH, - &enabled, - sizeof(enabled), - NULL, - 0, - &result_byte_count, - NULL, - NULL); - return result == SOCKET_ERROR ? WSAGetLastError() : 0; -} - int IsWindows10RS3OrGreater() { OSVERSIONINFOEXW osvi = { sizeof(osvi), 0, 0, 0, 0, {0}, 0, 0 }; diff --git a/src/java.base/windows/native/libnio/ch/Net.c b/src/java.base/windows/native/libnio/ch/Net.c index 36c0e4fa738f..892ffc3411c8 100644 --- a/src/java.base/windows/native/libnio/ch/Net.c +++ b/src/java.base/windows/native/libnio/ch/Net.c @@ -128,7 +128,7 @@ Java_sun_nio_ch_Net_canUseIPv6OptionsWithIPv4LocalAddress0(JNIEnv* env, jclass c JNIEXPORT jint JNICALL Java_sun_nio_ch_Net_socket0(JNIEnv *env, jclass cl, jboolean preferIPv6, - jboolean stream, jboolean reuse, jboolean fastLoopback) + jboolean stream, jboolean reuse) { SOCKET s; int domain = (preferIPv6) ? AF_INET6 : AF_INET; @@ -146,23 +146,6 @@ Java_sun_nio_ch_Net_socket0(JNIEnv *env, jclass cl, jboolean preferIPv6, setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, (const char *)&opt, sizeof(opt)); } - /* Enable SIO_LOOPBACK_FAST_PATH on TCP sockets if possible */ - if (stream && fastLoopback) { - static int loopback_available = 1; - if (loopback_available) { - int rv = NET_EnableFastTcpLoopback((jint)s); - if (rv) { - if (rv == WSAEOPNOTSUPP) { - loopback_available = 0; - } else { - NET_ThrowNew(env, rv, "fastLoopback"); - closesocket(s); - return IOS_THROWN; - } - } - } - } - /* Disable WSAECONNRESET errors for initially unconnected UDP sockets */ if (!stream) { setConnectionReset(s, FALSE); diff --git a/test/jdk/java/nio/channels/AsynchronousSocketChannel/StressLoopback.java b/test/jdk/java/nio/channels/AsynchronousSocketChannel/StressLoopback.java index 331da97845ae..ee4898b47250 100644 --- a/test/jdk/java/nio/channels/AsynchronousSocketChannel/StressLoopback.java +++ b/test/jdk/java/nio/channels/AsynchronousSocketChannel/StressLoopback.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -24,8 +24,6 @@ /* @test * @bug 6834246 6842687 * @summary Stress test connections through the loopback interface - * @run main StressLoopback - * @run main/othervm/timeout=480 -Djdk.net.useFastTcpLoopback StressLoopback * @key randomness */ diff --git a/test/micro/org/openjdk/bench/java/net/SocketChannelConnectionSetup.java b/test/micro/org/openjdk/bench/java/net/SocketChannelConnectionSetup.java index 652ed134c391..c7fd41c489e2 100644 --- a/test/micro/org/openjdk/bench/java/net/SocketChannelConnectionSetup.java +++ b/test/micro/org/openjdk/bench/java/net/SocketChannelConnectionSetup.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -94,14 +94,5 @@ public static void main(String[] args) throws RunnerException { .build(); new Runner(opt).run(); - - opt = new OptionsBuilder() - .include(org.openjdk.bench.java.net.SocketChannelConnectionSetup.class.getSimpleName()) - .jvmArgs("-Djdk.net.useFastTcpLoopback=true") - .warmupForks(1) - .forks(2) - .build(); - - new Runner(opt).run(); } } From de084f07e0c2ebe1936463a101790bacc6d5b371 Mon Sep 17 00:00:00 2001 From: Guanqiang Han Date: Tue, 20 Oct 2026 10:09:40 +0000 Subject: [PATCH 128/169] 8389578: [Valhalla] C2 crashes in OopFlow after implicit null-check conversion with a scalarized debug-info use Reviewed-by: qamai, thartmann --- src/hotspot/share/opto/lcm.cpp | 33 +++++--- ...licitNullCheckWithScalarizedDebugInfo.java | 75 +++++++++++++++++++ 2 files changed, 96 insertions(+), 12 deletions(-) create mode 100644 test/hotspot/jtreg/compiler/c2/TestImplicitNullCheckWithScalarizedDebugInfo.java diff --git a/src/hotspot/share/opto/lcm.cpp b/src/hotspot/share/opto/lcm.cpp index 833aa348b470..0c67d3965c97 100644 --- a/src/hotspot/share/opto/lcm.cpp +++ b/src/hotspot/share/opto/lcm.cpp @@ -325,21 +325,30 @@ void PhaseCFG::implicit_null_check(Block* block, Node *proj, Node *val, int allo } } - // Check ctrl input to see if the null-check dominates the memory op Block *cb = get_block_for_node(mach); - cb = cb->_idom; // Always hoist at least 1 block - if( !was_store ) { // Stores can be hoisted only one block - while( cb->_dom_depth > (block->_dom_depth + 1)) - cb = cb->_idom; // Hoist loads as far as we want - // The non-null-block should dominate the memory op, too. Live - // range spilling will insert a spill in the non-null-block if it is - // needs to spill the memory op for an implicit null check. - if (cb->_dom_depth == (block->_dom_depth + 1)) { - if (cb != not_null_block) continue; - cb = cb->_idom; + + if (was_store) { + // A store can only be moved from the immediate non-null successor. + if (cb != not_null_block) { + continue; } + } else { + // A load can be used here only if its block is dominated by the + // non-null successor. Check this explicitly because a candidate load + // may have been scheduled on the null path instead. + // For example, in JDK-8389578, loop opts did not recognize equivalent + // oop and value-type null_marker checks, leaving a redundant check. + // The load was protected by earlier null checks, but its only use + // was in the scalarized debug info of the redundant check's uncommon + // trap. This allowed the load to be scheduled on that check's null path. + if (!not_null_block->dominates(cb)) { + continue; + } + } + + if (not_null_block->_idom != block) { + continue; } - if( cb != block ) continue; // Found a memory user; see if it can be hoisted to check-block uint vidx = 0; // Capture index of value into memop diff --git a/test/hotspot/jtreg/compiler/c2/TestImplicitNullCheckWithScalarizedDebugInfo.java b/test/hotspot/jtreg/compiler/c2/TestImplicitNullCheckWithScalarizedDebugInfo.java new file mode 100644 index 000000000000..a2299365ae96 --- /dev/null +++ b/test/hotspot/jtreg/compiler/c2/TestImplicitNullCheckWithScalarizedDebugInfo.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @key stress + * @bug 8389578 + * @summary Test an implicit null check whose result is used by scalarized + * debug info on the null path. + * @enablePreview + * @run main ${test.main.class} + * @run main/othervm -Xcomp -XX:+UnlockDiagnosticVMOptions -XX:+StressGCM -XX:StressSeed=371 + * -XX:CompileCommand=compileonly,${test.main.class}::testImplicitNullCheck + * ${test.main.class} + */ + +public class TestImplicitNullCheckWithScalarizedDebugInfo { + static char c1 = 'A'; + static double d; + static Character c2 = '('; + static short s = -2; + static int i4; + + static int testImplicitNullCheck() { + if (--s != 0) { + c2.charValue(); + } else { + Byte.valueOf((byte) 1).shortValue(); + d++; + } + + char c3 = c1; + int res = 0; + int i1 = i4; + int i2 = i1 + 1; + int i3 = i1 + i2; + i4 = i3; + + char debugInfoPayload = c2; + boolean cond = ++c3 <= 1; + char c4 = cond ? Character.valueOf('r') : c2; + --s; + + res -= c1 > 1 ? 2 : 1; + for (byte b = 9; b >= 0; b--) { + long l = 2L << c3; + res |= l; + } + return res + i1 + i2 + i3; + } + + public static void main(String[] args) { + testImplicitNullCheck(); + } +} From 742e547d732611726d9363e39ec5479bc0c5b22d Mon Sep 17 00:00:00 2001 From: Thomas Schatzl Date: Tue, 20 Oct 2026 10:57:18 +0000 Subject: [PATCH 129/169] 8394619: G1: Clean up G1FreeRegionList Reviewed-by: ayang --- .../share/gc/g1/g1HeapRegionManager.cpp | 13 +- .../share/gc/g1/g1HeapRegionManager.hpp | 3 +- src/hotspot/share/gc/g1/g1HeapRegionSet.cpp | 15 +- src/hotspot/share/gc/g1/g1HeapRegionSet.hpp | 66 ++------ .../share/gc/g1/g1HeapRegionSet.inline.hpp | 145 +++++++----------- 5 files changed, 74 insertions(+), 168 deletions(-) diff --git a/src/hotspot/share/gc/g1/g1HeapRegionManager.cpp b/src/hotspot/share/gc/g1/g1HeapRegionManager.cpp index 83f62fd7bab3..160c93cdbc21 100644 --- a/src/hotspot/share/gc/g1/g1HeapRegionManager.cpp +++ b/src/hotspot/share/gc/g1/g1HeapRegionManager.cpp @@ -92,21 +92,10 @@ void G1HeapRegionManager::initialize(G1RegionToSpaceMapper* heap_storage, } G1HeapRegion* G1HeapRegionManager::allocate_free_region(G1HeapRegionType type, uint requested_node_index) { - G1HeapRegion* hr = nullptr; bool from_head = !type.is_young(); G1NUMA* numa = G1NUMA::numa(); - if (requested_node_index != G1NUMA::AnyNodeIndex && numa->is_enabled()) { - // Try to allocate with requested node index. - hr = _free_list.remove_region_with_node_index(from_head, requested_node_index); - } - - if (hr == nullptr) { - // If there's a single active node or we did not get a region from our requested node, - // try without requested node index. - hr = _free_list.remove_region(from_head); - } - + G1HeapRegion* hr = _free_list.remove_region(from_head, requested_node_index); if (hr != nullptr) { assert(hr->next() == nullptr, "Single region should not have next"); assert(is_available(hr->hrm_index()), "Must be committed"); diff --git a/src/hotspot/share/gc/g1/g1HeapRegionManager.hpp b/src/hotspot/share/gc/g1/g1HeapRegionManager.hpp index 8edfc99e3ef8..5764532e75d8 100644 --- a/src/hotspot/share/gc/g1/g1HeapRegionManager.hpp +++ b/src/hotspot/share/gc/g1/g1HeapRegionManager.hpp @@ -202,7 +202,8 @@ class G1HeapRegionManager: public CHeapObj { _free_list.add_ordered(list); } - // Allocate a free region with specific node index. If fails allocate with next node index. + // Allocate a free region on a specific node. If that fails, use the next available + // on another node. G1HeapRegion* allocate_free_region(G1HeapRegionType type, uint requested_node_index); // Allocate a humongous object from the free list diff --git a/src/hotspot/share/gc/g1/g1HeapRegionSet.cpp b/src/hotspot/share/gc/g1/g1HeapRegionSet.cpp index f710b64d7f29..d39eed88b262 100644 --- a/src/hotspot/share/gc/g1/g1HeapRegionSet.cpp +++ b/src/hotspot/share/gc/g1/g1HeapRegionSet.cpp @@ -131,14 +131,9 @@ void G1FreeRegionList::add_list_common_start(G1FreeRegionList* from_list) { } #ifdef ASSERT - G1FreeRegionListIterator iter(from_list); - while (iter.more_available()) { - G1HeapRegion* hr = iter.get_next(); - // In set_containing_set() we check that we either set the value - // from null to non-null or vice versa to catch bugs. So, we have - // to null it first before setting it to the value. - hr->set_containing_set(nullptr); - hr->set_containing_set(this); + for (G1HeapRegion* r = from_list->_head; r != nullptr; r = r->next()) { + r->set_containing_set(nullptr); + r->set_containing_set(this); } #endif // ASSERT } @@ -373,8 +368,8 @@ G1FreeRegionList::~G1FreeRegionList() { } } -G1FreeRegionList::NodeInfo::NodeInfo() : _numa(G1NUMA::numa()), _num_regions_on_node(nullptr), - _num_nodes(_numa->num_active_nodes()) { +G1FreeRegionList::NodeInfo::NodeInfo() : _num_regions_on_node(nullptr), + _num_nodes(G1NUMA::numa()->num_active_nodes()) { assert(UseNUMA, "Invariant"); _num_regions_on_node = NEW_C_HEAP_ARRAY(uint, _num_nodes, mtGC); diff --git a/src/hotspot/share/gc/g1/g1HeapRegionSet.hpp b/src/hotspot/share/gc/g1/g1HeapRegionSet.hpp index 6db3001b117f..41b1cf4bb82d 100644 --- a/src/hotspot/share/gc/g1/g1HeapRegionSet.hpp +++ b/src/hotspot/share/gc/g1/g1HeapRegionSet.hpp @@ -62,7 +62,6 @@ class G1HeapRegionSetChecker : public CHeapObj { // Base class for all the classes that represent heap region sets. It contains the // basic attributes that each set needs to maintain (e.g., number of regions) plus // any shared functionality (e.g., verification). - class G1HeapRegionSetBase { friend class VMStructs; @@ -105,7 +104,6 @@ class G1HeapRegionSetBase { virtual void verify(); void verify_start(); - void verify_next_region(G1HeapRegion* hr); void verify_end(); void verify_optional() { DEBUG_ONLY(verify();) } @@ -117,7 +115,6 @@ class G1HeapRegionSetBase { // explicitly tracked. It's helpful to group regions using such sets // so that we can reason about all the region groups in the heap using // the same interface (namely, the G1HeapRegionSetBase API). - class G1HeapRegionSet : public G1HeapRegionSetBase { public: G1HeapRegionSet(const char* name, G1HeapRegionSetChecker* checker): @@ -133,18 +130,9 @@ class G1HeapRegionSet : public G1HeapRegionSetBase { // sorted list. We should try to avoid doing operations that iterate over // such lists in performance critical paths. Typically we should // add / remove one region at a time or concatenate two lists. - -class G1FreeRegionListIterator; -class G1NUMA; - class G1FreeRegionList : public G1HeapRegionSetBase { - friend class G1FreeRegionListIterator; - -private: - // This class is only initialized if there are multiple active nodes. class NodeInfo : public CHeapObj { - G1NUMA* _numa; uint* _num_regions_on_node; uint _num_nodes; @@ -173,8 +161,8 @@ class G1FreeRegionList : public G1HeapRegionSetBase { static uint _unrealistically_large_num_regions; - inline G1HeapRegion* remove_from_head_impl(); - inline G1HeapRegion* remove_from_tail_impl(); + inline void unlink_from_list(G1HeapRegion* cur); + inline void remove_from_list(G1HeapRegion* cur); inline void increase_num_regions(uint node_index); inline void decrease_num_regions(uint node_index); @@ -184,9 +172,10 @@ class G1FreeRegionList : public G1HeapRegionSetBase { void add_list_common_end(G1FreeRegionList* from_list); void verify_region_to_remove(G1HeapRegion* curr, G1HeapRegion* next) NOT_DEBUG_RETURN; -protected: - // See the comment for G1HeapRegionSetBase::clear() - virtual void clear(); + + void clear(); + + G1HeapRegion* find_region_to_remove(bool from_head, uint requested_node_index); public: G1FreeRegionList(const char* name, G1HeapRegionSetChecker* checker = nullptr); @@ -209,11 +198,11 @@ class G1FreeRegionList : public G1HeapRegionSetBase { // Same restrictions as above, but adds the region last in the list. inline void add_to_tail(G1HeapRegion* region_to_add); - // Removes from head or tail based on the given argument. - G1HeapRegion* remove_region(bool from_head); - - G1HeapRegion* remove_region_with_node_index(bool from_head, - uint requested_node_index); + // Finds and removes the next fitting region from the free list from head or tail. + // If NUMA support is enabled, initially tries to match the result to be on the + // requested node. This search is limited by G1NUMA's maximum search depth; if no + // matching region is found or not using NUMA, just takes the first. + G1HeapRegion* remove_region(bool from_head, uint requested_node_index); // Merge two ordered lists. The result is also ordered. The order is // determined by hrm_index. @@ -234,40 +223,7 @@ class G1FreeRegionList : public G1HeapRegionSetBase { virtual void verify(); - using G1HeapRegionSetBase::num_regions; uint num_regions_on_node(uint node_index) const; }; -// Iterator class that provides a convenient way to iterate over the -// regions of a FreeRegionList. - -class G1FreeRegionListIterator : public StackObj { -private: - G1FreeRegionList* _list; - G1HeapRegion* _curr; - -public: - bool more_available() { - return _curr != nullptr; - } - - G1HeapRegion* get_next() { - assert(more_available(), - "get_next() should be called when more regions are available"); - - // If we are going to introduce a count in the iterator we should - // do the "cycle" check. - - G1HeapRegion* hr = _curr; - _list->verify_region(hr); - _curr = hr->next(); - return hr; - } - - G1FreeRegionListIterator(G1FreeRegionList* list) - : _list(list), - _curr(list->_head) { - } -}; - #endif // SHARE_GC_G1_G1HEAPREGIONSET_HPP diff --git a/src/hotspot/share/gc/g1/g1HeapRegionSet.inline.hpp b/src/hotspot/share/gc/g1/g1HeapRegionSet.inline.hpp index c18ac68e0648..a6074145aa81 100644 --- a/src/hotspot/share/gc/g1/g1HeapRegionSet.inline.hpp +++ b/src/hotspot/share/gc/g1/g1HeapRegionSet.inline.hpp @@ -120,116 +120,81 @@ inline void G1FreeRegionList::add_ordered(G1HeapRegion* hr) { increase_num_regions(hr->node_index()); } -inline G1HeapRegion* G1FreeRegionList::remove_from_head_impl() { - G1HeapRegion* result = _head; - _head = result->next(); - if (_head == nullptr) { - _tail = nullptr; +inline void G1FreeRegionList::unlink_from_list(G1HeapRegion* r) { + precond(r != nullptr); + // Splice the region out of the list. + G1HeapRegion* prev = r->prev(); + G1HeapRegion* next = r->next(); + if (prev == nullptr) { + _head = next; } else { - _head->set_prev(nullptr); + prev->set_next(next); } - result->set_next(nullptr); - return result; -} - -inline G1HeapRegion* G1FreeRegionList::remove_from_tail_impl() { - G1HeapRegion* result = _tail; - - _tail = result->prev(); - if (_tail == nullptr) { - _head = nullptr; + if (next == nullptr) { + _tail = prev; } else { - _tail->set_next(nullptr); + next->set_prev(prev); } - result->set_prev(nullptr); - return result; + r->set_prev(nullptr); + r->set_next(nullptr); } -inline G1HeapRegion* G1FreeRegionList::remove_region(bool from_head) { - check_mt_safety(); - verify_optional(); +inline void G1FreeRegionList::remove_from_list(G1HeapRegion* r) { + precond(r != nullptr); - if (is_empty()) { - return nullptr; - } - assert_free_region_list(num_regions() > 0 && _head != nullptr && _tail != nullptr, "invariant"); - - G1HeapRegion* hr; - - if (from_head) { - hr = remove_from_head_impl(); - } else { - hr = remove_from_tail_impl(); - } - - if (_last == hr) { + unlink_from_list(r); + if (_last == r) { _last = nullptr; } - // remove() will verify the region and check mt safety. - remove(hr); - - decrease_num_regions(hr->node_index()); - - return hr; + remove(r); + decrease_num_regions(r->node_index()); } -inline G1HeapRegion* G1FreeRegionList::remove_region_with_node_index(bool from_head, - uint requested_node_index) { - assert(UseNUMA, "Invariant"); - - const uint max_search_depth = G1NUMA::numa()->max_search_depth(); - G1HeapRegion* cur; - - // Find the region to use, searching from _head or _tail as requested. - size_t cur_depth = 0; - if (from_head) { - for (cur = _head; - cur != nullptr && cur_depth < max_search_depth; - cur = cur->next(), ++cur_depth) { - if (requested_node_index == cur->node_index()) { - break; +inline G1HeapRegion* G1FreeRegionList::find_region_to_remove(bool from_head, uint requested_node_index) { + G1HeapRegion* cur = nullptr; + + if (G1NUMA::numa()->is_enabled() && requested_node_index != G1NUMA::AnyNodeIndex) { + const uint max_search_depth = G1NUMA::numa()->max_search_depth(); + // Find the region to use, searching from _head or _tail as requested. + uint cur_depth = 0; + if (from_head) { + for (cur = _head; + cur != nullptr && cur_depth < max_search_depth; + cur = cur->next(), ++cur_depth) { + if (requested_node_index == cur->node_index()) { + return cur; + } } - } - } else { - for (cur = _tail; - cur != nullptr && cur_depth < max_search_depth; - cur = cur->prev(), ++cur_depth) { - if (requested_node_index == cur->node_index()) { - break; + } else { + for (cur = _tail; + cur != nullptr && cur_depth < max_search_depth; + cur = cur->prev(), ++cur_depth) { + if (requested_node_index == cur->node_index()) { + return cur; + } } } } + // Either we could not find a region on the correct NUMA node within the search depth, or + // we do not care about the node placement. In either case take the next region from the + // requested end of the list. + cur = from_head ? _head : _tail; + return cur; +} - // Didn't find a region to use. - if (cur == nullptr || cur_depth >= max_search_depth) { - return nullptr; - } - - // Splice the region out of the list. - G1HeapRegion* prev = cur->prev(); - G1HeapRegion* next = cur->next(); - if (prev == nullptr) { - _head = next; - } else { - prev->set_next(next); - } - if (next == nullptr) { - _tail = prev; - } else { - next->set_prev(prev); - } - cur->set_prev(nullptr); - cur->set_next(nullptr); +inline G1HeapRegion* G1FreeRegionList::remove_region(bool from_head, uint requested_node_index) { + check_mt_safety(); + verify_optional(); - if (_last == cur) { - _last = nullptr; + if (is_empty()) { + return nullptr; } + assert_free_region_list(num_regions() > 0 && _head != nullptr && _tail != nullptr, "invariant"); - remove(cur); - decrease_num_regions(cur->node_index()); - - return cur; + G1HeapRegion* r = find_region_to_remove(from_head, requested_node_index); + remove_from_list(r); + return r; } inline void G1FreeRegionList::NodeInfo::increase_num_regions(uint node_index) { From c6fda163b283ec7d1594e99acdbbc87caa292e14 Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Tue, 20 Oct 2026 11:09:35 +0000 Subject: [PATCH 130/169] 8392523: Test java/util/concurrent/forkjoin/AsyncShutdownNowInvokeAnyRace.java timed out then completed Reviewed-by: alanb, dholmes --- .../AsyncShutdownNowInvokeAnyRace.java | 67 ++++++++++--------- 1 file changed, 34 insertions(+), 33 deletions(-) diff --git a/test/jdk/java/util/concurrent/forkjoin/AsyncShutdownNowInvokeAnyRace.java b/test/jdk/java/util/concurrent/forkjoin/AsyncShutdownNowInvokeAnyRace.java index c48cf9495466..7aad90a414fc 100644 --- a/test/jdk/java/util/concurrent/forkjoin/AsyncShutdownNowInvokeAnyRace.java +++ b/test/jdk/java/util/concurrent/forkjoin/AsyncShutdownNowInvokeAnyRace.java @@ -82,6 +82,10 @@ public void testInvokeAny() throws Exception { ThreadLocalRandom rnd = ThreadLocalRandom.current(); int falseAlarms = 0; for (int rep = 1; rep < reps; rep++) { + + if (Thread.interrupted()) // Ensure that the test respects interruptions + throw new InterruptedException(); + ForkJoinPool pool = new ForkJoinPool(1); CountDownLatch pleaseShutdownNow = new CountDownLatch(1); int nTasks = rnd.nextInt(2, 5); @@ -106,40 +110,37 @@ public void testInvokeAny() throws Exception { } catch (Throwable t) { throw new AssertionError(t); } }; Future shutdownResult = CompletableFuture.runAsync(shutdown); - try { - try { - if (rnd.nextBoolean()) - pool.invokeAny(tasks, 10L, SECONDS); - else - pool.invokeAny(tasks); - throw new AssertionError("should throw"); - } catch (RejectedExecutionException re) { - falseAlarms++; - } catch (CancellationException re) { - } catch (ExecutionException ex) { - Throwable cause = ex.getCause(); - if (!(cause instanceof InterruptedException) && - !(cause instanceof CancellationException)) - throw ex; - } catch (TimeoutException ex) { - dumpTestThreads(); - int i = rep; - String detail = String.format( - "invokeAny timed out, " - + "nTasks=%d rep=%d threadsStarted=%d%n" - + "poolAtShutdownNow=%s%n" - + "poolAtTimeout=%s%n" - + "queuedTaskCount=%d queuedSubmissionCount=%d", - nTasks, i, threadsStarted.get(), - poolAtShutdownNow, - pool, - pool.getQueuedTaskCount(), - pool.getQueuedSubmissionCount()); - throw new AssertionError(detail, ex); - } - } finally { - pool.shutdown(); + + try (pool) { // Ensure that the pool is terminated after each iteration + if (rnd.nextBoolean()) + pool.invokeAny(tasks, 10L, SECONDS); + else + pool.invokeAny(tasks); + throw new AssertionError("should throw"); + } catch (RejectedExecutionException re) { + falseAlarms++; + } catch (CancellationException re) { + } catch (ExecutionException ex) { + Throwable cause = ex.getCause(); + if (!(cause instanceof InterruptedException) && + !(cause instanceof CancellationException)) + throw ex; + } catch (TimeoutException ex) { + dumpTestThreads(); + String detail = String.format( + "invokeAny timed out, " + + "nTasks=%d rep=%d threadsStarted=%d%n" + + "poolAtShutdownNow=%s%n" + + "poolAtTimeout=%s%n" + + "queuedTaskCount=%d queuedSubmissionCount=%d", + nTasks, rep, threadsStarted.get(), + poolAtShutdownNow, + pool, + pool.getQueuedTaskCount(), + pool.getQueuedSubmissionCount()); + throw new AssertionError(detail, ex); } + if (falseAlarms != 0) System.out.println("Premature shutdowns = " + falseAlarms); shutdownResult.get(); From ff39f9f71e0df36022880b2cc006e99f80a60a98 Mon Sep 17 00:00:00 2001 From: Jorn Vernee Date: Tue, 20 Oct 2026 12:10:07 +0000 Subject: [PATCH 131/169] 8394752: Rewrite Linker javadoc in terms of canonical layouts Reviewed-by: mdoerr, mcimadamore --- .../classes/java/lang/foreign/Linker.java | 130 +++++++++++------- 1 file changed, 79 insertions(+), 51 deletions(-) diff --git a/src/java.base/share/classes/java/lang/foreign/Linker.java b/src/java.base/share/classes/java/lang/foreign/Linker.java index 5c3821a14ded..30a7c24560ae 100644 --- a/src/java.base/share/classes/java/lang/foreign/Linker.java +++ b/src/java.base/share/classes/java/lang/foreign/Linker.java @@ -83,15 +83,17 @@ * * {@snippet lang = java: * Linker linker = Linker.nativeLinker(); + * MemoryLayout C_SIZE_T = linker.canonicalLayouts().get("size_t"); + * MemoryLayout C_POINTER = linker.canonicalLayouts().get("void*"); * MethodHandle strlen = linker.downcallHandle( * linker.defaultLookup().findOrThrow("strlen"), - * FunctionDescriptor.of(JAVA_LONG, ADDRESS) + * FunctionDescriptor.of(C_SIZE_T, C_POINTER) * ); * } * * Note how the native linker also provides access, via its {@linkplain #defaultLookup() default lookup}, * to the native functions defined by the C libraries loaded with the Java runtime. - * Above, the default lookup is used to search the address of the {@code strlen} native + * Above, the default lookup is used to search for the address of the {@code strlen} native * function. That address is then passed, along with a platform-dependent description * of the signature of the function expressed as a {@link FunctionDescriptor} (more on * that below) to the native linker's {@link #downcallHandle(MemorySegment, FunctionDescriptor, Option...)} @@ -129,7 +131,7 @@ * {@linkplain AddressLayout#targetLayout() target layout}. For instance, a pointer that * is known to point to a C {@code int[2]} array can be modeled as an address layout * whose target layout is a sequence layout whose element count is 2, and whose - * element type is {@link ValueLayout#JAVA_INT}. + * element type is the canonical layout for {@code int}. *

      * All native linker implementations are guaranteed to provide canonical layouts for the * following set of types: @@ -147,18 +149,17 @@ *

    • {@code void*}
    • *
    * As noted above, the specific canonical layout associated with each type can vary, - * depending on the data model supported by a given ABI. For instance, the C type - * {@code long} maps to the layout constant {@link ValueLayout#JAVA_LONG} on Linux/x64, - * but maps to the layout constant {@link ValueLayout#JAVA_INT} on Windows/x64. - * Similarly, the C type {@code size_t} maps to the layout constant - * {@link ValueLayout#JAVA_LONG} on 64-bit platforms, but maps to the layout constant - * {@link ValueLayout#JAVA_INT} on 32-bit platforms. + * depending on the data model supported by a given ABI. For instance, the canonical + * layout for the C type {@code long} is a {@link ValueLayout.OfLong} on Linux/x64, + * and a {@link ValueLayout.OfInt} on Windows/x64. + * Similarly, the canonical layout for the C type {@code size_t} is a + * {@link ValueLayout.OfLong} on 64-bit platforms, and a {@link ValueLayout.OfInt} + * on 32-bit platforms. *

    * A native linker typically does not provide canonical layouts for C's unsigned integral * types. Instead, they are modeled using the canonical layouts associated with their * corresponding signed integral types. For instance, the C type {@code unsigned long} - * maps to the layout constant {@link ValueLayout#JAVA_LONG} on Linux/x64, but maps to - * the layout constant {@link ValueLayout#JAVA_INT} on Windows/x64. + * maps to the canonical layout for {@code long}. *

    * The following table shows some examples of how C types are modeled in Linux/x64 * according to the "System V Application Binary Interface" @@ -169,71 +170,98 @@ * * * C type - * Layout + * Layout type * Java type + * Canonical layout name + * Alias in examples * * * * {@code bool} - * {@link ValueLayout#JAVA_BOOLEAN} + * {@link ValueLayout.OfBoolean} * {@code boolean} - * {@code char}
    {@code unsigned char} - * {@link ValueLayout#JAVA_BYTE} + * {@code bool} + * {@code C_BOOL} + * {@code char} + * {@link ValueLayout.OfByte} * {@code byte} - * {@code short}
    {@code unsigned short} - * {@link ValueLayout#JAVA_SHORT} + * {@code char} + * {@code C_CHAR} + * {@code short} + * {@link ValueLayout.OfShort} * {@code short} - * {@code int}
    {@code unsigned int} - * {@link ValueLayout#JAVA_INT} + * {@code short} + * {@code C_SHORT} + * {@code int} + * {@link ValueLayout.OfInt} + * {@code int} * {@code int} - * {@code long}
    {@code unsigned long} - * {@link ValueLayout#JAVA_LONG} + * {@code C_INT} + * {@code long} + * {@link ValueLayout.OfLong} + * {@code long} * {@code long} - * {@code long long}
    {@code unsigned long long} - * {@link ValueLayout#JAVA_LONG} + * {@code C_LONG} + * {@code long long} + * {@link ValueLayout.OfLong} * {@code long} + * {@code long long} + * {@code C_LONG_LONG} * {@code float} - * {@link ValueLayout#JAVA_FLOAT} + * {@link ValueLayout.OfFloat} * {@code float} + * {@code float} + * {@code C_FLOAT} * {@code double} - * {@link ValueLayout#JAVA_DOUBLE} + * {@link ValueLayout.OfDouble} + * {@code double} * {@code double} + * {@code C_DOUBLE} {@code size_t} - * {@link ValueLayout#JAVA_LONG} + * {@link ValueLayout.OfLong} * {@code long} + * {@code size_t} + * {@code C_SIZE_T} * {@code char*}, {@code int**}, {@code struct Point*} - * {@link ValueLayout#ADDRESS} + * {@link AddressLayout} * {@link MemorySegment} + * {@code void*} + * {@code C_POINTER} * {@code int (*ptr)[10]} * *

    - * ValueLayout.ADDRESS.withTargetLayout(
    - *     MemoryLayout.sequenceLayout(10,
    - *         ValueLayout.JAVA_INT)
    + * C_POINTER.withTargetLayout(
    + *     MemoryLayout.sequenceLayout(10, C_INT)
      * );
      * 
    * {@link MemorySegment} + * N/A + * N/A * struct Point { int x; long y; }; * *
      * MemoryLayout.structLayout(
    - *     ValueLayout.JAVA_INT.withName("x"),
    + *     C_INT.withName("x"),
      *     MemoryLayout.paddingLayout(4),
    - *     ValueLayout.JAVA_LONG.withName("y")
    + *     C_LONG.withName("y")
      * );
      * 
    * * {@link MemorySegment} + * N/A + * N/A * union Choice { float a; int b; } * *
      * MemoryLayout.unionLayout(
    - *     ValueLayout.JAVA_FLOAT.withName("a"),
    - *     ValueLayout.JAVA_INT.withName("b")
    + *     C_FLOAT.withName("a"),
    + *     C_INT.withName("b")
      * );
      * 
    * * {@link MemorySegment} + * N/A + * N/A * * *

    @@ -293,13 +321,13 @@ * Linker linker = Linker.nativeLinker(); * MethodHandle qsort = linker.downcallHandle( * linker.defaultLookup().findOrThrow("qsort"), - * FunctionDescriptor.ofVoid(ADDRESS, JAVA_LONG, JAVA_LONG, ADDRESS) + * FunctionDescriptor.ofVoid(C_POINTER, C_SIZE_T, C_SIZE_T, C_POINTER) * ); * } * - * As before, we use {@link ValueLayout#JAVA_LONG} to map the C type {@code size_t} type, - * and {@link ValueLayout#ADDRESS} for both the first pointer parameter (the array - * pointer) and the last parameter (the function pointer). + * We use the canonical layout for {@code "size_t"}, and the canonical layout for + * {@code "void*"} for both the first pointer parameter (the array pointer) and + * the last parameter (the function pointer). *

    * To invoke the {@code qsort} downcall handle obtained above, we need a function pointer * to be passed as the last parameter. That is, we need to create a function pointer out @@ -309,7 +337,7 @@ * {@snippet lang = java: * class Qsort { * static int qsortCompare(MemorySegment elem1, MemorySegment elem2) { - * return Integer.compare(elem1.get(JAVA_INT, 0), elem2.get(JAVA_INT, 0)); + * return Integer.compare(elem1.get(C_INT, 0), elem2.get(C_INT, 0)); * } * } * } @@ -317,9 +345,9 @@ * Now let's create a method handle for the comparator method defined above: * * {@snippet lang = java: - * FunctionDescriptor comparDesc = FunctionDescriptor.of(JAVA_INT, - * ADDRESS.withTargetLayout(JAVA_INT), - * ADDRESS.withTargetLayout(JAVA_INT)); + * FunctionDescriptor comparDesc = FunctionDescriptor.of(C_INT, + * C_POINTER.withTargetLayout(C_INT), + * C_POINTER.withTargetLayout(C_INT)); * MethodHandle comparHandle = MethodHandles.lookup() * .findStatic(Qsort.class, "qsortCompare", * comparDesc.toMethodType()); @@ -327,7 +355,7 @@ * * First, we create a function descriptor for the function pointer type. Since we know * that the parameters passed to the comparator method will be pointers to elements of - * a C {@code int[]} array, we can specify {@link ValueLayout#JAVA_INT} as the target + * a C {@code int[]} array, we can specify the canonical layout for {@code "int"} as the target * layout for the address layouts of both parameters. This will allow the comparator * method to access the contents of the array elements to be compared. We then * {@linkplain FunctionDescriptor#toMethodType() turn} that function descriptor into @@ -339,9 +367,9 @@ * {@snippet lang = java: * try (Arena arena = Arena.ofConfined()) { * MemorySegment comparFunc = linker.upcallStub(comparHandle, comparDesc, arena); - * MemorySegment array = arena.allocateFrom(JAVA_INT, 0, 9, 3, 4, 6, 5, 1, 8, 2, 7); + * MemorySegment array = arena.allocateFrom(C_INT, 0, 9, 3, 4, 6, 5, 1, 8, 2, 7); * qsort.invokeExact(array, 10L, 4L, comparFunc); - * int[] sorted = array.toArray(JAVA_INT); // [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ] + * int[] sorted = array.toArray(C_INT); // [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ] * } * } * @@ -384,12 +412,12 @@ * * MethodHandle malloc = linker.downcallHandle( * linker.defaultLookup().findOrThrow("malloc"), - * FunctionDescriptor.of(ADDRESS, JAVA_LONG) + * FunctionDescriptor.of(C_POINTER, C_SIZE_T) * ); * * MethodHandle free = linker.downcallHandle( * linker.defaultLookup().findOrThrow("free"), - * FunctionDescriptor.ofVoid(ADDRESS) + * FunctionDescriptor.ofVoid(C_POINTER) * ); * } * @@ -517,7 +545,7 @@ * Linker linker = Linker.nativeLinker(); * MethodHandle printf = linker.downcallHandle( * linker.defaultLookup().findOrThrow("printf"), - * FunctionDescriptor.of(JAVA_INT, ADDRESS, JAVA_INT, JAVA_INT, JAVA_INT), + * FunctionDescriptor.of(C_INT, C_POINTER, C_INT, C_INT, C_INT), * Linker.Option.firstVariadicArg(1) // first int is variadic * ); * } @@ -862,10 +890,10 @@ static Option captureCallState(String... capturedState) { return new LinkerOptions.CaptureCallState(mask); } - /** + /** * {@return a struct layout that represents the layout of the capture state - * segment that is passed to a downcall handle linked with - * {@link #captureCallState(String...)}} + * segment that is passed to a downcall handle linked with + * {@link #captureCallState(String...)}} *

    * The capture state layout is platform-dependent but is guaranteed to be * a {@linkplain StructLayout struct layout} containing only {@linkplain ValueLayout value layouts} From ee4d33c062189fff004fc120a5caf776b520c625 Mon Sep 17 00:00:00 2001 From: Yunbo Zhang Date: Tue, 20 Oct 2026 12:53:40 +0000 Subject: [PATCH 132/169] 8393007: G1: Waste allowance calculation may wrap around on 32 bit Reviewed-by: tschatzl, ayang --- src/hotspot/share/gc/g1/g1Policy.cpp | 3 ++- test/hotspot/jtreg/gc/g1/mixedgc/TestLogging.java | 14 +++++++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/hotspot/share/gc/g1/g1Policy.cpp b/src/hotspot/share/gc/g1/g1Policy.cpp index 5fdfdec67e03..dcad3bea8b24 100644 --- a/src/hotspot/share/gc/g1/g1Policy.cpp +++ b/src/hotspot/share/gc/g1/g1Policy.cpp @@ -47,6 +47,7 @@ #include "logging/log.hpp" #include "runtime/java.hpp" #include "runtime/mutexLocker.hpp" +#include "utilities/checkedCast.hpp" #include "utilities/debug.hpp" #include "utilities/growableArray.hpp" #include "utilities/pair.hpp" @@ -1431,7 +1432,7 @@ bool G1Policy::next_gc_should_be_mixed() const { } size_t G1Policy::allowed_waste_in_collection_set() const { - return G1HeapWastePercent * _g1h->capacity() / 100; + return checked_cast(static_cast(_g1h->capacity()) * G1HeapWastePercent / 100); } bool G1Policy::try_get_available_bytes_estimate(size_t& available_bytes) const { diff --git a/test/hotspot/jtreg/gc/g1/mixedgc/TestLogging.java b/test/hotspot/jtreg/gc/g1/mixedgc/TestLogging.java index b1d4a4ed5fc0..7c3ad69f831c 100644 --- a/test/hotspot/jtreg/gc/g1/mixedgc/TestLogging.java +++ b/test/hotspot/jtreg/gc/g1/mixedgc/TestLogging.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -33,6 +33,7 @@ /* * @test TestLogging + * @bug 8393007 * @summary Check that a mixed GC is reflected in the gc logs * @requires vm.gc.G1 * @requires vm.opt.MaxGCPauseMillis == "null" @@ -68,6 +69,7 @@ public static void main(String args[]) throws Exception { test("-Xlog:gc,gc+heap=debug"); // Test turns logging on by giving -Xlog:gc=debug flag test("-Xlog:gc=debug,gc+heap=debug"); + testWasteAllowance(); } private static void test(String vmFlag) throws Exception { @@ -78,6 +80,16 @@ private static void test(String vmFlag) throws Exception { output.shouldContain("Pause Young (Mixed)"); } + private static void testWasteAllowance() throws Exception { + // Multiplying a 64 MiB heap size by 100 overflows on 32-bit VMs. + OutputAnalyzer output = spawnMixedGCProvoker("-Xms64m", "-Xmx64m", + "-XX:G1HeapWastePercent=100", "-Xlog:gc,gc+ergo+cset=debug"); + System.out.println(output.getStdout()); + output.shouldHaveExitValue(0); + output.shouldContain("Pause Young (Mixed)"); + output.shouldMatch("Pruned .*\\(allowed 67108864\\)"); + } + /** * Method spawns MixedGCProvoker with addition flags set * From 7aedb92e9a32bd50c230d69f201464c9798e3275 Mon Sep 17 00:00:00 2001 From: Matthias Baesken Date: Tue, 20 Oct 2026 13:06:39 +0000 Subject: [PATCH 133/169] 8392561: [GCC static analyzer] potential dereference of NULL pointer xsdo in Java_sun_java2d_x11_XSurfaceData_isDrawableValid Reviewed-by: prr, lucy --- .../unix/native/common/java2d/x11/X11SurfaceData.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/java.desktop/unix/native/common/java2d/x11/X11SurfaceData.c b/src/java.desktop/unix/native/common/java2d/x11/X11SurfaceData.c index b134623a0bd1..565225006b0a 100644 --- a/src/java.desktop/unix/native/common/java2d/x11/X11SurfaceData.c +++ b/src/java.desktop/unix/native/common/java2d/x11/X11SurfaceData.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -176,6 +176,9 @@ Java_sun_java2d_x11_XSurfaceData_isDrawableValid(JNIEnv *env, jobject this) #ifndef HEADLESS X11SDOps *xsdo = X11SurfaceData_GetOps(env, this); + if (xsdo == NULL) { + return JNI_FALSE; + } AWT_LOCK(); if (xsdo->drawable != 0 || X11SD_InitWindow(env, xsdo) == SD_SUCCESS) { From d63b981776ec2258023f54d8c5bfdbf52b074f4f Mon Sep 17 00:00:00 2001 From: Jaikiran Pai Date: Tue, 20 Oct 2026 13:13:25 +0000 Subject: [PATCH 134/169] 8394746: java/net/SocketOption/OptionsTest.java fails for IP_TOS on Windows - Invalid argument: setsocketopt Reviewed-by: alanb --- .../windows/native/libnet/net_util_md.c | 12 +----- src/java.base/windows/native/libnio/ch/Net.c | 38 ++++++++++++++----- 2 files changed, 30 insertions(+), 20 deletions(-) diff --git a/src/java.base/windows/native/libnet/net_util_md.c b/src/java.base/windows/native/libnet/net_util_md.c index 75a22de5990a..52d5003b5063 100644 --- a/src/java.base/windows/native/libnet/net_util_md.c +++ b/src/java.base/windows/native/libnet/net_util_md.c @@ -283,17 +283,7 @@ JNIEXPORT int JNICALL NET_GetSockOpt(int s, int level, int optname, void *optval, int *optlen) { - int rv; - - if (level == IPPROTO_IPV6 && optname == IPV6_TCLASS) { - int *intopt = (int *)optval; - *intopt = 0; - *optlen = sizeof(*intopt); - return 0; - } - - rv = getsockopt(s, level, optname, optval, optlen); - + int rv = getsockopt(s, level, optname, optval, optlen); /* * IPPROTO_IP/IP_TOS is not supported on some Windows diff --git a/src/java.base/windows/native/libnio/ch/Net.c b/src/java.base/windows/native/libnio/ch/Net.c index 892ffc3411c8..12c1b9b3a2df 100644 --- a/src/java.base/windows/native/libnio/ch/Net.c +++ b/src/java.base/windows/native/libnio/ch/Net.c @@ -339,6 +339,13 @@ Java_sun_nio_ch_Net_getIntOption0(JNIEnv *env, jclass clazz, jobject fdo, char *arg; int arglen, n; + /** + * IPV6_TCLASS is not supported on Windows + */ + if (level == IPPROTO_IPV6 && opt == IPV6_TCLASS) { + return 0; + } + if (level == SOL_SOCKET && opt == SO_LINGER) { arg = (char *)&linger; arglen = sizeof(linger); @@ -348,11 +355,11 @@ Java_sun_nio_ch_Net_getIntOption0(JNIEnv *env, jclass clazz, jobject fdo, } /** - * HACK: IP_TOS is deprecated on Windows and querying the option + * IP_TOS is deprecated on Windows and querying the option * returns a protocol error. NET_GetSockOpt handles this and uses - * a fallback mechanism. Same applies to IPV6_TCLASS + * a fallback mechanism. */ - if ((level == IPPROTO_IP && opt == IP_TOS) || (level == IPPROTO_IPV6 && opt == IPV6_TCLASS)) { + if (level == IPPROTO_IP && opt == IP_TOS) { mayNeedConversion = JNI_TRUE; } @@ -380,6 +387,13 @@ Java_sun_nio_ch_Net_setIntOption0(JNIEnv *env, jclass clazz, jobject fdo, char *parg; int arglen, n; + /** + * IPV6_TCLASS is not supported on Windows + */ + if (level == IPPROTO_IPV6 && opt == IPV6_TCLASS) { + return; + } + if (level == SOL_SOCKET && opt == SO_LINGER) { parg = (char *)&linger; arglen = sizeof(linger); @@ -395,9 +409,15 @@ Java_sun_nio_ch_Net_setIntOption0(JNIEnv *env, jclass clazz, jobject fdo, arglen = sizeof(arg); } - if (level == IPPROTO_IPV6 && opt == IPV6_TCLASS) { - /* No op */ - return; + /** + * Setting IP_TOS option requires clearing the + * lowest bit (representing the "unused" bit) + * of the value. NET_SetSockOpt handles the + * value conversion as well as a fallback + * mechanism if the value cannot be set. + */ + if (level == IPPROTO_IP && opt == IP_TOS) { + mayNeedConversion = JNI_TRUE; } if (mayNeedConversion) { @@ -406,7 +426,7 @@ Java_sun_nio_ch_Net_setIntOption0(JNIEnv *env, jclass clazz, jobject fdo, n = setsockopt(fdval(env, fdo), level, opt, parg, arglen); } if (n == SOCKET_ERROR) - NET_ThrowNew(env, WSAGetLastError(), "setsocketopt"); + NET_ThrowNew(env, WSAGetLastError(), "setsockopt"); } JNIEXPORT jint JNICALL @@ -437,7 +457,7 @@ Java_sun_nio_ch_Net_joinOrDrop4(JNIEnv *env, jobject this, jboolean join, jobjec if (n == SOCKET_ERROR) { if (join && (WSAGetLastError() == WSAENOPROTOOPT)) return IOS_UNAVAILABLE; - NET_ThrowNew(env, WSAGetLastError(), "setsocketopt"); + NET_ThrowNew(env, WSAGetLastError(), "setsockopt"); } return 0; } @@ -524,7 +544,7 @@ Java_sun_nio_ch_Net_blockOrUnblock6(JNIEnv *env, jobject this, jboolean block, j int opt = (block) ? MCAST_BLOCK_SOURCE : MCAST_UNBLOCK_SOURCE; int n = setGroupSourceReqOption(env, fdo, opt, group, index, source); if (n == SOCKET_ERROR) { - NET_ThrowNew(env, WSAGetLastError(), "setsocketopt to block or unblock source"); + NET_ThrowNew(env, WSAGetLastError(), "setsockopt to block or unblock source"); } return 0; } From 2f4fdcbf90a0f31e55be425f512aa6173a4d42de Mon Sep 17 00:00:00 2001 From: Vicente Romero Date: Tue, 20 Oct 2026 14:19:07 +0000 Subject: [PATCH 135/169] 8339188: Javac should issue a compiler error when a value class overrides Object::finalize Reviewed-by: dlsmith, mcimadamore --- .../com/sun/tools/javac/comp/Check.java | 2 +- .../tools/javac/resources/compiler.properties | 4 +- .../valhalla/valuetypes/MyAbstractVal.jasm | 39 +++++++++++++++ .../runtime/valhalla/valuetypes/MyVal.jasm | 39 +++++++++++++++ .../valuetypes/TestFinalizableValues.java | 49 +++++++------------ .../Object/AbstractValueWithFinalizer.jasm | 34 +++++++++++++ test/jdk/java/lang/Object/ValueObjects.java | 36 +++++--------- .../java/lang/Object/ValueWithFinalizer.jasm | 34 +++++++++++++ .../AbstractValueClassWithFinalizeMethod.java | 32 ++++++++++++ .../ValueClassWithFinalizeMethod.java | 2 +- 10 files changed, 212 insertions(+), 59 deletions(-) create mode 100644 test/hotspot/jtreg/runtime/valhalla/valuetypes/MyAbstractVal.jasm create mode 100644 test/hotspot/jtreg/runtime/valhalla/valuetypes/MyVal.jasm create mode 100644 test/jdk/java/lang/Object/AbstractValueWithFinalizer.jasm create mode 100644 test/jdk/java/lang/Object/ValueWithFinalizer.jasm create mode 100644 test/langtools/tools/javac/diags/examples/AbstractValueClassWithFinalizeMethod.java diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java index 6d73f823d1b1..ded2164c9e71 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java @@ -2150,7 +2150,7 @@ void checkOverride(Env env, JCMethodDecl tree, MethodSymbol m) { } if (allowValueClasses && origin.isValueClass() && names.finalize.equals(m.name)) { if (m.overrides(syms.objectFinalize, origin, types, false)) { - log.warning(tree.pos(), Warnings.ValueFinalize); + log.error(tree.pos(), Errors.ValueFinalize); } } if (allowRecords && origin.isRecord()) { diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties index f676b7ae44d6..3669a2065a0b 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties @@ -4398,5 +4398,5 @@ compiler.err.enclosing.class.type.non.denotable=\ enclosing class type: {0}\n\ is non-denotable, try casting to a denotable type -compiler.warn.value.finalize=\ - value classes should not have a finalizer method, as it is not invoked +compiler.err.value.finalize=\ + value classes cannot have finalize methods diff --git a/test/hotspot/jtreg/runtime/valhalla/valuetypes/MyAbstractVal.jasm b/test/hotspot/jtreg/runtime/valhalla/valuetypes/MyAbstractVal.jasm new file mode 100644 index 000000000000..f2611b5fcc9f --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/valuetypes/MyAbstractVal.jasm @@ -0,0 +1,39 @@ +/* Javac reports an error when a value class declares a method overriding + * Object::finalize. The VM must still handle such class files correctly, so + * this class is supplied as jasm. It corresponds to the following source: + * + * abstract value class MyAbstractVal { + * static volatile boolean finalizerWasCalled = false; + * int i = 0; + * + * @SuppressWarnings("deprecation") + * protected void finalize() { + * finalizerWasCalled = true; + * } + * } + */ +abstract class MyAbstractVal version 72:65535 { + static volatile Field finalizerWasCalled:Z; + final strict Field i:I = 0; + + Method "":"()V" stack 2 locals 1 { + aload_0; + iconst_0; + putfield Field i:I; + aload_0; + invokespecial Method java/lang/Object."":"()V"; + return; + } + + protected Method finalize:"()V" stack 1 locals 1 { + iconst_1; + putstatic Field finalizerWasCalled:Z; + return; + } + + static Method "":"()V" stack 1 locals 0 { + iconst_0; + putstatic Field finalizerWasCalled:Z; + return; + } +} diff --git a/test/hotspot/jtreg/runtime/valhalla/valuetypes/MyVal.jasm b/test/hotspot/jtreg/runtime/valhalla/valuetypes/MyVal.jasm new file mode 100644 index 000000000000..fa1bf55c162b --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/valuetypes/MyVal.jasm @@ -0,0 +1,39 @@ +/* Javac reports an error when a value class declares a method overriding + * Object::finalize. The VM must still handle such class files correctly, so + * this class is supplied as jasm. It corresponds to the following source: + * + * value class MyVal { + * static volatile boolean finalizerWasCalled = false; + * int i = 0; + * + * @SuppressWarnings("deprecation") + * protected void finalize() { + * finalizerWasCalled = true; + * } + * } + */ +final class MyVal version 72:65535 { + static volatile Field finalizerWasCalled:Z; + final strict Field i:I = 0; + + Method "":"()V" stack 2 locals 1 { + aload_0; + iconst_0; + putfield Field i:I; + aload_0; + invokespecial Method java/lang/Object."":"()V"; + return; + } + + protected Method finalize:"()V" stack 1 locals 1 { + iconst_1; + putstatic Field finalizerWasCalled:Z; + return; + } + + static Method "":"()V" stack 1 locals 0 { + iconst_0; + putstatic Field finalizerWasCalled:Z; + return; + } +} diff --git a/test/hotspot/jtreg/runtime/valhalla/valuetypes/TestFinalizableValues.java b/test/hotspot/jtreg/runtime/valhalla/valuetypes/TestFinalizableValues.java index 95a958b8a4e5..5d9928767a41 100644 --- a/test/hotspot/jtreg/runtime/valhalla/valuetypes/TestFinalizableValues.java +++ b/test/hotspot/jtreg/runtime/valhalla/valuetypes/TestFinalizableValues.java @@ -25,7 +25,7 @@ * @test TestFinalizableValues * @library /test/lib * @enablePreview - * @compile TestFinalizableValues.java + * @compile MyVal.jasm MyAbstractVal.jasm TestFinalizableValues.java * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI TestFinalizableValues */ @@ -43,39 +43,24 @@ public class TestFinalizableValues { static WhiteBox WB = WhiteBox.getWhiteBox(); + /* + * MyVal and MyAbstractVal are supplied as MyVal.jasm and MyAbstractVal.jasm: both are + * classes that javac can no longer produce from source since JDK-8339188 forbids a + * value class from declaring a method that overrides Object::finalize. See the + * jasm files for the corresponding source. + */ public static class TestHelper { - static boolean finalizer1WasCalled = false; - static boolean finalizer2WasCalled = false; - - static value class MyVal { - static volatile boolean finalizerWasCalled = false; - int i = 0; - - @SuppressWarnings("deprecation") - protected void finalize() { - finalizerWasCalled = true; - } - } - - static abstract value class MyAbstractVal { - int i = 0; - - @SuppressWarnings("deprecation") - protected void finalize() { - finalizer1WasCalled = true; - } - } - static value class MyVal2 extends MyAbstractVal {} static class MyId extends MyAbstractVal {} static class MyId2 extends MyAbstractVal { + static boolean finalizerWasCalled = false; int i = 0; @SuppressWarnings("deprecation") protected void finalize() { - finalizer2WasCalled = true; + finalizerWasCalled = true; } } @@ -101,14 +86,14 @@ public static void main(String[] args) throws ClassNotFoundException { Thread.currentThread().interrupt(); } } - if (finalizer1WasCalled != expectFinalizer1) { + if (MyAbstractVal.finalizerWasCalled != expectFinalizer1) { throw new RuntimeException("Finalizer1 was " - + (finalizer1WasCalled ? "" : "not ") + + (MyAbstractVal.finalizerWasCalled ? "" : "not ") + "executed"); } - if (finalizer2WasCalled != expectFinalizer2) { + if (MyId2.finalizerWasCalled != expectFinalizer2) { throw new RuntimeException("Finalizer2 was " - + (finalizer2WasCalled ? "" : "not ") + + (MyId2.finalizerWasCalled ? "" : "not ") + "executed"); } } @@ -122,7 +107,7 @@ static void test(String classname, boolean expectRegistration, String... args) t Collections.addAll(argsList, "-XX:+IgnoreUnrecognizedVMOptions"); Collections.addAll(argsList, "-XX:+TraceFinalizerRegistration"); Collections.addAll(argsList, "TestFinalizableValues$TestHelper"); - Collections.addAll(argsList, "TestFinalizableValues$TestHelper$" + classname); + Collections.addAll(argsList, classname); Collections.addAll(argsList, args); ProcessBuilder pb = ProcessTools.createTestJavaProcessBuilder(argsList); OutputAnalyzer out = new OutputAnalyzer(pb.start()); @@ -137,8 +122,8 @@ static void test(String classname, boolean expectRegistration, String... args) t } public static void main(String[] args) throws IOException { test("MyVal", false, "false", "false"); - test("MyVal2", false, "false", "false"); - test("MyId", false, "false", "false"); - test("MyId2", true, "false", "true"); + test("TestFinalizableValues$TestHelper$MyVal2", false, "false", "false"); + test("TestFinalizableValues$TestHelper$MyId", false, "false", "false"); + test("TestFinalizableValues$TestHelper$MyId2", true, "false", "true"); } } diff --git a/test/jdk/java/lang/Object/AbstractValueWithFinalizer.jasm b/test/jdk/java/lang/Object/AbstractValueWithFinalizer.jasm new file mode 100644 index 000000000000..6cf239e73398 --- /dev/null +++ b/test/jdk/java/lang/Object/AbstractValueWithFinalizer.jasm @@ -0,0 +1,34 @@ +/* Javac reports an error when a value class declares a method overriding + * Object::finalize. The VM must still handle such class files correctly, so + * this class is supplied as jasm. It corresponds to the following source: + * + * abstract value class AbstractValueWithFinalizer { + * CountDownLatch latch; + * AbstractValueWithFinalizer(CountDownLatch latch) { + * this.latch = latch; + * } + * @Override + * protected void finalize() { + * latch.countDown(); + * } + * } + */ +abstract class AbstractValueWithFinalizer version 72:65535 { + final strict Field latch:"Ljava/util/concurrent/CountDownLatch;"; + + Method "":"(Ljava/util/concurrent/CountDownLatch;)V" stack 2 locals 2 { + aload_0; + aload_1; + putfield Field latch:"Ljava/util/concurrent/CountDownLatch;"; + aload_0; + invokespecial Method java/lang/Object."":"()V"; + return; + } + + protected Method finalize:"()V" stack 1 locals 1 { + aload_0; + getfield Field latch:"Ljava/util/concurrent/CountDownLatch;"; + invokevirtual Method java/util/concurrent/CountDownLatch.countDown:"()V"; + return; + } +} diff --git a/test/jdk/java/lang/Object/ValueObjects.java b/test/jdk/java/lang/Object/ValueObjects.java index 50122d1a401d..f2fcaeac2726 100644 --- a/test/jdk/java/lang/Object/ValueObjects.java +++ b/test/jdk/java/lang/Object/ValueObjects.java @@ -26,6 +26,7 @@ * @summary Basic test of Object methods on value objects * @enablePreview * @library /test/lib + * @compile ValueWithFinalizer.jasm AbstractValueWithFinalizer.jasm ValueObjects.java * @run junit ${test.main.class} */ @@ -102,22 +103,16 @@ class Holder { /** * Test that the finalize method on a value class is not invoked by the GC. + * + * ValueWithFinalizer is supplied as ValueWithFinalizer.jasm rather than a local value + * class here, since javac no longer allows a value class to declare a method that + * overrides Object::finalize (JDK-8339188). See the jasm file for the corresponding + * source. */ @Test void testValueClassFinalize() throws Exception { - value class V { - CountDownLatch latch; - V(CountDownLatch latch) { - this.latch = latch; - } - @Override - protected void finalize() { - latch.countDown(); - } - } - var latch = new TimeoutAdjustingLatch(); - var obj = new V(latch); + var obj = new ValueWithFinalizer(latch); obj = null; for (int i = 0; i < 3; i++) { System.gc(); @@ -128,20 +123,15 @@ protected void finalize() { /** * Test that the finalize method on an abstract value value is not invoked by the GC. + * + * AbstractValueWithFinalizer is supplied as AbstractValueWithFinalizer.jasm rather than + * a local abstract value class here, since javac no longer allows a value class to + * declare a method that overrides Object::finalize (JDK-8339188). See the jasm file for + * the corresponding source. */ @Test void testAbstractValueClassFinalize() throws Exception { - abstract value class AV { - CountDownLatch latch; - AV(CountDownLatch latch) { - this.latch = latch; - } - @Override - protected void finalize() { - latch.countDown(); - } - } - /*identity*/ class C extends AV { + /*identity*/ class C extends AbstractValueWithFinalizer { C(CountDownLatch latch) { super(latch); } diff --git a/test/jdk/java/lang/Object/ValueWithFinalizer.jasm b/test/jdk/java/lang/Object/ValueWithFinalizer.jasm new file mode 100644 index 000000000000..4ef9d09a53a9 --- /dev/null +++ b/test/jdk/java/lang/Object/ValueWithFinalizer.jasm @@ -0,0 +1,34 @@ +/* Javac reports an error when a value class declares a method overriding + * Object::finalize. The VM must still handle such class files correctly, so + * this class is supplied as jasm. It corresponds to the following source: + * + * value class ValueWithFinalizer { + * CountDownLatch latch; + * ValueWithFinalizer(CountDownLatch latch) { + * this.latch = latch; + * } + * @Override + * protected void finalize() { + * latch.countDown(); + * } + * } + */ +final class ValueWithFinalizer version 72:65535 { + final strict Field latch:"Ljava/util/concurrent/CountDownLatch;"; + + Method "":"(Ljava/util/concurrent/CountDownLatch;)V" stack 2 locals 2 { + aload_0; + aload_1; + putfield Field latch:"Ljava/util/concurrent/CountDownLatch;"; + aload_0; + invokespecial Method java/lang/Object."":"()V"; + return; + } + + protected Method finalize:"()V" stack 1 locals 1 { + aload_0; + getfield Field latch:"Ljava/util/concurrent/CountDownLatch;"; + invokevirtual Method java/util/concurrent/CountDownLatch.countDown:"()V"; + return; + } +} diff --git a/test/langtools/tools/javac/diags/examples/AbstractValueClassWithFinalizeMethod.java b/test/langtools/tools/javac/diags/examples/AbstractValueClassWithFinalizeMethod.java new file mode 100644 index 000000000000..367b6c68229a --- /dev/null +++ b/test/langtools/tools/javac/diags/examples/AbstractValueClassWithFinalizeMethod.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +// key: compiler.err.value.finalize +// key: compiler.note.preview.filename +// key: compiler.note.preview.recompile +// key: compiler.warn.has.been.deprecated.for.removal +// options: --enable-preview -source ${jdk.version} + +abstract value class AbstractValueClassWithFinalizeMethod { + protected void finalize() throws Throwable { } +} diff --git a/test/langtools/tools/javac/diags/examples/ValueClassWithFinalizeMethod.java b/test/langtools/tools/javac/diags/examples/ValueClassWithFinalizeMethod.java index 9e651b7f0b6a..33abd050708c 100644 --- a/test/langtools/tools/javac/diags/examples/ValueClassWithFinalizeMethod.java +++ b/test/langtools/tools/javac/diags/examples/ValueClassWithFinalizeMethod.java @@ -21,7 +21,7 @@ * questions. */ -// key: compiler.warn.value.finalize +// key: compiler.err.value.finalize // key: compiler.note.preview.filename // key: compiler.note.preview.recompile // key: compiler.warn.has.been.deprecated.for.removal From 644cd392f2af7fa2f64c55b5e3e1988a1810133c Mon Sep 17 00:00:00 2001 From: Vladimir Kozlov Date: Tue, 20 Oct 2026 15:08:47 +0000 Subject: [PATCH 136/169] 8394831: C2: SIGSEGV in PhaseOutput::fill_buffer when running -version with -XX:+PrintExceptionHandlers -XX:+PrintFrameConverterAssembly Reviewed-by: shade, chagedorn --- src/hotspot/share/ci/ciEnv.cpp | 2 +- src/hotspot/share/opto/c2compiler.cpp | 3 ++ src/hotspot/share/opto/compile.cpp | 9 ++--- src/hotspot/share/opto/compile.hpp | 5 +++ src/hotspot/share/opto/doCall.cpp | 2 +- src/hotspot/share/opto/graphKit.cpp | 2 +- src/hotspot/share/opto/output.cpp | 7 ++-- src/hotspot/share/opto/parse1.cpp | 3 +- .../c2/TestPrintFrameConverterAssembly.java | 38 +++++++++++++++++++ 9 files changed, 57 insertions(+), 14 deletions(-) create mode 100644 test/hotspot/jtreg/compiler/c2/TestPrintFrameConverterAssembly.java diff --git a/src/hotspot/share/ci/ciEnv.cpp b/src/hotspot/share/ci/ciEnv.cpp index 65f9eec967eb..4b649c5dd482 100644 --- a/src/hotspot/share/ci/ciEnv.cpp +++ b/src/hotspot/share/ci/ciEnv.cpp @@ -1275,7 +1275,7 @@ void ciEnv::register_method(ciMethod* target, assert(!method->is_synchronized() || nm->has_monitors(), ""); #if INCLUDE_CDS - if (task()->is_aot_compile()) { + if (is_aot_compile()) { #ifdef ASSERT LogStreamHandle(Trace, aot, codecache, nmethod) log; if (log.is_enabled()) { diff --git a/src/hotspot/share/opto/c2compiler.cpp b/src/hotspot/share/opto/c2compiler.cpp index e836629cfbb5..30089b426e44 100644 --- a/src/hotspot/share/opto/c2compiler.cpp +++ b/src/hotspot/share/opto/c2compiler.cpp @@ -146,7 +146,9 @@ void C2Compiler::compile_method(ciEnv* env, ciMethod* target, int entry_bci, boo bool do_locks_coarsening = EliminateLocks; bool do_superword = UseSuperWord; bool do_stringopts = OptimizeStringConcat; + bool aot_compile = env->is_aot_compile(); bool for_aot_preload = (task->compile_reason() == CompileTask::Reason_AOTCompileForPreload); + assert(!for_aot_preload || aot_compile, "sanity"); assert(!for_aot_preload || (ClassInitBarrierMode > 0), "sanity"); while (!env->failing()) { @@ -160,6 +162,7 @@ void C2Compiler::compile_method(ciEnv* env, ciMethod* target, int entry_bci, boo do_locks_coarsening, do_superword, do_stringopts, + aot_compile, for_aot_preload, install_code); Compile C(env, target, entry_bci, options, directive); diff --git a/src/hotspot/share/opto/compile.cpp b/src/hotspot/share/opto/compile.cpp index 63ed6ed8f394..00220a68a215 100644 --- a/src/hotspot/share/opto/compile.cpp +++ b/src/hotspot/share/opto/compile.cpp @@ -606,7 +606,7 @@ void Compile::print_compile_messages() { if( PrintOpto ) { if (is_osr_compilation()) { tty->print("[OSR]"); - } else if (env()->task()->is_aot_compile()) { + } else if (is_aot_compile()) { if (for_aot_preload()) { tty->print("[PRE]"); } else { @@ -649,9 +649,8 @@ void Compile::print_ideal_ir(const char* compile_phase_name) const { NoSafepointVerifier nsv; ttyLocker ttyl; if (xtty != nullptr) { - CompileTask* task = env()->task(); - bool aot_comp = task->is_aot_load_or_compile(); - bool aot_preload_comp = task->is_aot_preload_or_compile(); + bool aot_comp = is_aot_compile(); + bool aot_preload_comp = for_aot_preload(); xtty->head("ideal compile_id='%d'%s compile_phase='%s'", compile_id(), is_osr_compilation() ? " compile_kind='osr'" : @@ -786,7 +785,7 @@ Compile::Compile(ciEnv* ci_env, ciMethod* target, int osr_bci, set_print_intrinsics(directive->PrintIntrinsicsOption); set_has_irreducible_loop(true); // conservative until build_loop_tree() reset it - if ((ProfileTraps && !ci_env->is_aot_compile()) || + if ((ProfileTraps && !is_aot_compile()) || AOTCodeCache::is_using_code()) { // Make sure the method being compiled gets its own MDO, // so we can at least track the decompile_count(). diff --git a/src/hotspot/share/opto/compile.hpp b/src/hotspot/share/opto/compile.hpp index 008c6bd9a84b..d2306909d360 100644 --- a/src/hotspot/share/opto/compile.hpp +++ b/src/hotspot/share/opto/compile.hpp @@ -189,6 +189,7 @@ class Options { const bool _do_locks_coarsening; // Do locks coarsening const bool _do_superword; // Do SuperWord const bool _do_stringopts; // Do StringOpts + const bool _aot_compile; // AOT code compilation const bool _for_aot_preload; // Generate AOT code for preload (before Java method execution), // include class init barriers const bool _install_code; // Install the code that was compiled @@ -201,6 +202,7 @@ class Options { bool do_locks_coarsening, bool do_superword, bool do_stringopts, + bool aot_compile, bool for_aot_preload, bool install_code) : _subsume_loads(subsume_loads), @@ -211,6 +213,7 @@ class Options { _do_locks_coarsening(do_locks_coarsening), _do_superword(do_superword), _do_stringopts(do_stringopts), + _aot_compile(aot_compile), _for_aot_preload(for_aot_preload), _install_code(install_code) { } @@ -225,6 +228,7 @@ class Options { /* do_lock_coarsening = */ false, /* do_superword = */ true, /* do_stringopts = */ false, + /* aot_compile = */ false, /* for_aot_preload = */ false, /* install_code = */ true ); @@ -612,6 +616,7 @@ class Compile : public Phase { bool do_superword() const { return _options._do_superword; } bool do_stringopts() const { return _options._do_stringopts; } + bool is_aot_compile() const { return _options._aot_compile; } bool do_clinit_barriers() const { return _options._for_aot_preload; } bool for_aot_preload() const { return _options._for_aot_preload; } diff --git a/src/hotspot/share/opto/doCall.cpp b/src/hotspot/share/opto/doCall.cpp index 28a727202d27..6f3c78a126f3 100644 --- a/src/hotspot/share/opto/doCall.cpp +++ b/src/hotspot/share/opto/doCall.cpp @@ -542,7 +542,7 @@ bool Parse::can_not_compile_call_site(ciMethod *dest_method, ciInstanceKlass* kl if (!holder_klass->is_being_initialized() && !holder_klass->is_initialized() && !holder_klass->is_interface()) { - if (C->env()->task()->is_aot_compile()) { + if (C->is_aot_compile()) { ResourceMark rm; log_debug(aot, compilation)("Emitting uncommon trap (cannot compile call site) in AOT code for %s", holder_klass->name()->as_klass_external_name()); } diff --git a/src/hotspot/share/opto/graphKit.cpp b/src/hotspot/share/opto/graphKit.cpp index a812f5e7b825..73f69aeafbab 100644 --- a/src/hotspot/share/opto/graphKit.cpp +++ b/src/hotspot/share/opto/graphKit.cpp @@ -3508,7 +3508,7 @@ void GraphKit::clinit_barrier(ciInstanceKlass* ik, ciMethod* context) { } else if (ik->is_initialized()) { return; // no barrier needed } else { - if (C->env()->task()->is_aot_compile()) { + if (C->is_aot_compile()) { ResourceMark rm; log_debug(aot, compilation)("Emitting uncommon trap (clinit barrier) in AOT code for %s", ik->name()->as_klass_external_name()); } diff --git a/src/hotspot/share/opto/output.cpp b/src/hotspot/share/opto/output.cpp index 6f480f754545..e1b995c4bc87 100644 --- a/src/hotspot/share/opto/output.cpp +++ b/src/hotspot/share/opto/output.cpp @@ -1919,15 +1919,14 @@ void PhaseOutput::fill_buffer(C2_MacroAssembler* masm, uint* blk_starts) { // This output goes directly to the tty, not the compiler log. // To enable tools to match it up with the compilation activity, // be sure to tag this tty output with the compile ID. + bool aot_comp = C->is_aot_compile(); + bool aot_preload_comp = C->for_aot_preload(); if (xtty != nullptr) { - CompileTask* task = C->env()->task(); - bool aot_comp = task->is_aot_load_or_compile(); - bool aot_preload_comp = task->is_aot_preload_or_compile(); xtty->head("opto_assembly compile_id='%d'%s", C->compile_id(), C->is_osr_compilation() ? " compile_kind='osr'" : (aot_preload_comp ? " compile_kind='AP'" : (aot_comp ? " compile_kind='A'" : ""))); } - const char* is_aot = C->env()->is_aot_compile() ? (C->for_aot_preload() ? "(AP) " : "(A) -") : "-----"; + const char* is_aot = aot_comp ? (aot_preload_comp ? "(AP) " : "(A) -") : "-----"; if (C->method() != nullptr) { tty->print_cr("----------------------- MetaData before Compile_id = %d %s-------------------", C->compile_id(), is_aot); tty->print_raw(method_metadata_str.freeze()); diff --git a/src/hotspot/share/opto/parse1.cpp b/src/hotspot/share/opto/parse1.cpp index 984ad391dc08..c7710947a307 100644 --- a/src/hotspot/share/opto/parse1.cpp +++ b/src/hotspot/share/opto/parse1.cpp @@ -1351,8 +1351,7 @@ static int scale_limit(int64_t limit) { } void Parse::count_aot_code_calls() { - bool is_aot_compilation = C->env()->is_aot_compile(); - if (UseAOTCodeCounters && (depth() == 1) && is_aot_compilation) { + if (UseAOTCodeCounters && (depth() == 1) && C->is_aot_compile()) { // Clear out dead values from the debug info in following runtime call kill_dead_locals(); diff --git a/test/hotspot/jtreg/compiler/c2/TestPrintFrameConverterAssembly.java b/test/hotspot/jtreg/compiler/c2/TestPrintFrameConverterAssembly.java new file mode 100644 index 000000000000..d10cdb239467 --- /dev/null +++ b/test/hotspot/jtreg/compiler/c2/TestPrintFrameConverterAssembly.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8394831 + * @requires vm.debug == true & vm.compiler2.enabled + * @summary Run with -XX:+PrintFrameConverterAssembly in debug builds. + * + * @run main/othervm -XX:-DisplayVMOutput -XX:+PrintFrameConverterAssembly + * -XX:+PrintExceptionHandlers ${test.main.class} + */ +package compiler.c2; + +public class TestPrintFrameConverterAssembly { + public static void main(String[] args) { + } +} From ecadb8fff3030b9b82214eae17d5011bf4991593 Mon Sep 17 00:00:00 2001 From: Dan Heidinga Date: Tue, 20 Oct 2026 16:51:42 +0000 Subject: [PATCH 137/169] 8391308: Remove ClassLoader.classes vector and addClass call Co-authored-by: Coleen Phillimore Reviewed-by: dholmes, sspitsyn --- .../share/classfile/loaderConstraints.cpp | 22 +-- .../share/classfile/loaderConstraints.hpp | 4 +- .../share/classfile/systemDictionary.cpp | 17 +- src/hotspot/share/memory/universe.cpp | 8 - src/hotspot/share/memory/universe.hpp | 1 - src/hotspot/share/prims/jvmti.xml | 14 +- src/hotspot/share/prims/jvmtiTagMap.cpp | 53 ++++++ .../share/classes/java/lang/ClassLoader.java | 15 +- .../ClassLoaderDefinedClassesTest.java | 152 ++++++++++++++++++ .../ClassLoader/ClassLoaderTest.java | 86 ++++++++++ .../ClassLoader/libClassLoaderTest.cpp | 96 +++++++++++ 11 files changed, 412 insertions(+), 56 deletions(-) create mode 100644 test/hotspot/jtreg/serviceability/HeapDump/ClassLoaderDefinedClassesTest.java create mode 100644 test/hotspot/jtreg/serviceability/jvmti/FollowReferences/ClassLoader/ClassLoaderTest.java create mode 100644 test/hotspot/jtreg/serviceability/jvmti/FollowReferences/ClassLoader/libClassLoaderTest.cpp diff --git a/src/hotspot/share/classfile/loaderConstraints.cpp b/src/hotspot/share/classfile/loaderConstraints.cpp index 13c1ff7319dd..bebe7c53e551 100644 --- a/src/hotspot/share/classfile/loaderConstraints.cpp +++ b/src/hotspot/share/classfile/loaderConstraints.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -442,22 +442,16 @@ InstanceKlass* LoaderConstraintTable::find_constrained_klass(Symbol* name, return nullptr; } -// Removes a class that was added to the table then class loading subsequently failed for this class, -// so we don't have a dangling pointer to InstanceKlass in the LoaderConstraintTable. -void LoaderConstraintTable::remove_failed_loaded_klass(InstanceKlass* klass, - ClassLoaderData* loader) { +// Checks that a class that failed to load wasn't added to the table. +void LoaderConstraintTable::check_failed_loaded_klass(InstanceKlass* klass, + ClassLoaderData* loader) { +#ifdef ASSERT MutexLocker ml(SystemDictionary_lock); Symbol* name = klass->name(); - LoaderConstraint *p = find_loader_constraint(name, loader); - if (p != nullptr && p->klass() != nullptr && p->klass() == klass) { - // If this is the klass in the constraint, the error was OOM from the ClassLoader.addClass() call. - // Other errors during loading (eg. constraint violations) will not have added this klass. - log_info(class, loader, constraints)("removing klass %s: failed to load", name->as_C_string()); - // We only null out the class, since the constraint for the class name for this loader is still valid as - // it was added when checking signature loaders for a method or field resolution. - p->set_klass(nullptr); - } + LoaderConstraint* p = find_loader_constraint(name, loader); + assert (p == nullptr || p->klass() != klass, "pointer to class that failed to load"); +#endif } void LoaderConstraintTable::merge_loader_constraints(Symbol* class_name, diff --git a/src/hotspot/share/classfile/loaderConstraints.hpp b/src/hotspot/share/classfile/loaderConstraints.hpp index 8153d9c28fd7..0e40781f7764 100644 --- a/src/hotspot/share/classfile/loaderConstraints.hpp +++ b/src/hotspot/share/classfile/loaderConstraints.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -59,7 +59,7 @@ class LoaderConstraintTable : public AllStatic { // Class loader constraints static bool check_or_update(InstanceKlass* k, ClassLoaderData* loader, Symbol* name); - static void remove_failed_loaded_klass(InstanceKlass* k, ClassLoaderData* loader); + static void check_failed_loaded_klass(InstanceKlass* k, ClassLoaderData* loader); static void purge_loader_constraints(); static void print_table_statistics(outputStream* st); diff --git a/src/hotspot/share/classfile/systemDictionary.cpp b/src/hotspot/share/classfile/systemDictionary.cpp index 78d29717d830..65ff8ec57bf1 100644 --- a/src/hotspot/share/classfile/systemDictionary.cpp +++ b/src/hotspot/share/classfile/systemDictionary.cpp @@ -1564,19 +1564,6 @@ void SystemDictionary::define_instance_class(InstanceKlass* k, Handle class_load // which will require a token to perform the define class check_constraints(k, loader_data, true, CHECK); - // Register class just loaded with class loader (placed in ArrayList) - // Note we do this before updating the dictionary, as this can - // fail with an OutOfMemoryError (if it does, we will *not* put this - // class in the dictionary and will not update the class hierarchy). - // JVMTI FollowReferences needs to find the classes this way. - if (k->class_loader() != nullptr) { - methodHandle m(THREAD, Universe::loader_addClass_method()); - JavaValue result(T_VOID); - JavaCallArguments args(class_loader); - args.push_oop(Handle(THREAD, k->java_mirror())); - JavaCalls::call(&result, m, &args, CHECK); - } - JFR_ONLY(Jfr::on_definition(k, THREAD);) // Add to class hierarchy, and do possible deoptimizations. @@ -1688,8 +1675,8 @@ InstanceKlass* SystemDictionary::find_or_define_instance_class(Symbol* class_nam assert(defined_k != nullptr, "Should have a klass if there's no exception"); k->class_loader_data()->add_to_deallocate_list(k); } else if (HAS_PENDING_EXCEPTION) { - // Remove this InstanceKlass from the LoaderConstraintTable if added. - LoaderConstraintTable::remove_failed_loaded_klass(k, class_loader_data(class_loader)); + // Validate that this InstanceKlass is not present in the LoaderConstraintTable. + LoaderConstraintTable::check_failed_loaded_klass(k, class_loader_data(class_loader)); assert(defined_k == nullptr, "Should not have a klass if there's an exception"); k->class_loader_data()->add_to_deallocate_list(k); } diff --git a/src/hotspot/share/memory/universe.cpp b/src/hotspot/share/memory/universe.cpp index b523c90681dd..29ff059dd692 100644 --- a/src/hotspot/share/memory/universe.cpp +++ b/src/hotspot/share/memory/universe.cpp @@ -111,7 +111,6 @@ class LatestMethodCache { }; static LatestMethodCache _finalizer_register_cache; // Finalizer.register() -static LatestMethodCache _loader_addClass_cache; // ClassLoader.addClass() static LatestMethodCache _throw_illegal_access_error_cache; // Unsafe.throwIllegalAccessError() static LatestMethodCache _throw_no_such_method_error_cache; // Unsafe.throwNoSuchMethodError() static LatestMethodCache _do_stack_walk_cache; // AbstractStackWalker.doStackWalk() @@ -1082,7 +1081,6 @@ Method* LatestMethodCache::get_method() { } Method* Universe::finalizer_register_method() { return _finalizer_register_cache.get_method(); } -Method* Universe::loader_addClass_method() { return _loader_addClass_cache.get_method(); } Method* Universe::throw_illegal_access_error() { return _throw_illegal_access_error_cache.get_method(); } Method* Universe::throw_no_such_method_error() { return _throw_no_such_method_error_cache.get_method(); } Method* Universe::do_stack_walk_method() { return _do_stack_walk_cache.get_method(); } @@ -1112,12 +1110,6 @@ void Universe::initialize_known_methods(JavaThread* current) { "throwNoSuchMethodError", vmSymbols::void_method_signature(), true); - // Set up method for registering loaded classes in class loader vector - _loader_addClass_cache.init(current, - vmClasses::ClassLoader_klass(), - "addClass", - vmSymbols::class_void_signature(), false); - // Set up method for stack walking _do_stack_walk_cache.init(current, vmClasses::AbstractStackWalker_klass(), diff --git a/src/hotspot/share/memory/universe.hpp b/src/hotspot/share/memory/universe.hpp index ab0cc10316b5..71f483b55063 100644 --- a/src/hotspot/share/memory/universe.hpp +++ b/src/hotspot/share/memory/universe.hpp @@ -248,7 +248,6 @@ class Universe: AllStatic { static uintx the_array_interfaces_bitmap() { return _the_array_interfaces_bitmap; } static Method* finalizer_register_method(); - static Method* loader_addClass_method(); static Method* throw_illegal_access_error(); static Method* throw_no_such_method_error(); static Method* do_stack_walk_method(); diff --git a/src/hotspot/share/prims/jvmti.xml b/src/hotspot/share/prims/jvmti.xml index 9fa08096688b..8a5a1fc27a1b 100644 --- a/src/hotspot/share/prims/jvmti.xml +++ b/src/hotspot/share/prims/jvmti.xml @@ -3549,6 +3549,13 @@ err = (*jvmti)->Deallocate(jvmti, stack_info); describe the kind of reference being reported. +

    + Implementation Note: + In the HotSpot VM implementation, a reference from a class loader to each + non-hidden class defined by the class loader is reported with reference kind + . +

    + - Heap root reference: other heap root reference. + Other reference from an object to another object, or other heap root reference. @@ -4374,12 +4381,15 @@ class C2 extends C1 implements I2 { Points to the tag of the referrer object, or points to the zero if the referrer object is not tagged. - Null if the referrer in not an object (that is, + Null if the referrer is not an object (that is, this callback is reporting a heap root). To set the tag value to be associated with the referrer object the agent sets the jlong pointed to by the parameter. If this callback is reporting a reference from an object to itself, referrer_tag_ptr == tag_ptr. + For , this parameter can be + null or non-null, depending on whether the callback is reporting a heap + root reference or a reference from an object. diff --git a/src/hotspot/share/prims/jvmtiTagMap.cpp b/src/hotspot/share/prims/jvmtiTagMap.cpp index 03d6c15cfbd1..0889976ff5c6 100644 --- a/src/hotspot/share/prims/jvmtiTagMap.cpp +++ b/src/hotspot/share/prims/jvmtiTagMap.cpp @@ -22,6 +22,7 @@ * */ +#include "classfile/classLoaderData.hpp" #include "classfile/classLoaderDataGraph.hpp" #include "classfile/javaClasses.inline.hpp" #include "classfile/javaStackTraceClasses.hpp" @@ -1856,6 +1857,7 @@ class CallbackInvoker : AllStatic { // functions to report references static inline bool report_array_element_reference(const JvmtiHeapwalkObject& referrer, const JvmtiHeapwalkObject& referree, jint index); + static inline bool report_other_reference(const JvmtiHeapwalkObject& referrer, const JvmtiHeapwalkObject& referree); static inline bool report_class_reference(const JvmtiHeapwalkObject& referrer, const JvmtiHeapwalkObject& referree); static inline bool report_class_loader_reference(const JvmtiHeapwalkObject& referrer, const JvmtiHeapwalkObject& referree); static inline bool report_signers_reference(const JvmtiHeapwalkObject& referrer, const JvmtiHeapwalkObject& referree); @@ -2459,6 +2461,15 @@ inline bool CallbackInvoker::report_field_reference(const JvmtiHeapwalkObject& r } } +inline bool CallbackInvoker::report_other_reference(const JvmtiHeapwalkObject& referrer, const JvmtiHeapwalkObject& referree) { + // Reporting classes from the class loader is not supported for old Heap 1.0 functions. + if (is_basic_heap_walk()) { + return true; + } else { + return invoke_advanced_object_reference_callback(JVMTI_HEAP_REFERENCE_OTHER, referrer, referree, -1); + } +} + // report an array referencing an element object inline bool CallbackInvoker::report_constant_pool_reference(const JvmtiHeapwalkObject& referrer, const JvmtiHeapwalkObject& referree, jint index) { if (is_basic_heap_walk()) { @@ -2785,8 +2796,11 @@ class VM_HeapWalkOperation: public VM_Operation { inline bool iterate_over_flat_array(const JvmtiHeapwalkObject& o); inline bool iterate_over_type_array(const JvmtiHeapwalkObject& o); inline bool iterate_over_class(const JvmtiHeapwalkObject& o); + inline bool iterate_over_class_loader(const JvmtiHeapwalkObject& o); inline bool iterate_over_object(const JvmtiHeapwalkObject& o); + class CollectDeclaredKlasses; + // root collection inline bool collect_simple_roots(); inline bool collect_stack_roots(); @@ -3086,6 +3100,43 @@ inline bool VM_HeapWalkOperation::iterate_over_class(const JvmtiHeapwalkObject& return true; } +class VM_HeapWalkOperation::CollectDeclaredKlasses : public KlassClosure { + GrowableArray* _klasses; + public: + CollectDeclaredKlasses(GrowableArray* klasses) : _klasses(klasses) {} + void do_klass(Klass* k) { + if (k->is_instance_klass() && InstanceKlass::cast(k)->is_loaded() && !k->is_hidden()) { + _klasses->push(k); + } + } +}; + +inline bool VM_HeapWalkOperation::iterate_over_class_loader(const JvmtiHeapwalkObject& o) { + assert(!o.is_flat(), "ClassLoaderKlass object cannot be flattened"); + bool res = iterate_over_object(o); + // For a class loader, iterate over the loaded classes also to maintain compatibility + // for when ClassLoader had a vector of loaded classes. + ClassLoaderData* data = java_lang_ClassLoader::loader_data_acquire(o.obj()); + + if (res && data != nullptr) { + ResourceMark rm; + GrowableArray* klasses = new GrowableArray(10); + CollectDeclaredKlasses itr(klasses); + data->classes_do(&itr); + + for (int i = 0; i < klasses->length(); i++) { + Klass* k = klasses->at(i); + JvmtiHeapwalkObject m(k->java_mirror()); + // Pretend the classes are referred indirectly by the class loader. They are + // root objects, so make them other references. + if (!CallbackInvoker::report_other_reference(o, m)) { + return false; + } + } + } + return res; +} + // an object references a class and its instance fields // (static fields are ignored here as we report these as // references from the class). @@ -3324,6 +3375,8 @@ bool VM_HeapWalkOperation::visit(const JvmtiHeapwalkObject& o) { // a java.lang.Class return iterate_over_class(o); } + } else if (java_lang_ClassLoader::is_instance(o.obj())) { + return iterate_over_class_loader(o); } else { // we report stack references only when initial object is not specified // (in the case we start from heap roots which include platform thread stack references) diff --git a/src/java.base/share/classes/java/lang/ClassLoader.java b/src/java.base/share/classes/java/lang/ClassLoader.java index fdb5a80e2a2a..e21def8cfbe4 100644 --- a/src/java.base/share/classes/java/lang/ClassLoader.java +++ b/src/java.base/share/classes/java/lang/ClassLoader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2019, Azul Systems, Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -310,23 +310,12 @@ static boolean isRegistered(Class c) { // Shared among all packages with unsigned classes private static final Certificate[] nocerts = new Certificate[0]; - // The classes loaded by this class loader. The only purpose of this table - // is to keep the classes from being GC'ed until the loader is GC'ed. - private final ArrayList> classes = new ArrayList<>(); - // The "default" domain. Set as the default ProtectionDomain on newly // created classes. private final ProtectionDomain defaultDomain = new ProtectionDomain(new CodeSource(null, (Certificate[]) null), null, this, null); - // Invoked by the VM to record every loaded class with this loader. - void addClass(Class c) { - synchronized (classes) { - classes.add(c); - } - } - // The packages defined in this class loader. Each package name is // mapped to its corresponding NamedPackage object. // @@ -2646,8 +2635,6 @@ private void resetArchivedStates() { } reinitObjectField("package2certs", new ConcurrentHashMap<>()); - classes.clear(); - classes.trimToSize(); classLoaderValueMap = null; } } diff --git a/test/hotspot/jtreg/serviceability/HeapDump/ClassLoaderDefinedClassesTest.java b/test/hotspot/jtreg/serviceability/HeapDump/ClassLoaderDefinedClassesTest.java new file mode 100644 index 000000000000..c320298bc8d6 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/HeapDump/ClassLoaderDefinedClassesTest.java @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.io.File; +import java.lang.ref.Reference; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import jdk.test.lib.Asserts; +import jdk.test.lib.JDKToolLauncher; +import jdk.test.lib.apps.LingeredApp; +import jdk.test.lib.process.ProcessTools; +import jdk.test.lib.hprof.model.JavaClass; +import jdk.test.lib.hprof.model.Snapshot; +import jdk.test.lib.hprof.parser.Reader; + +import java.lang.classfile.ClassFile; +import java.lang.constant.ClassDesc; + +import static java.lang.classfile.ClassFile.*; +import static java.lang.constant.ConstantDescs.*; + +/* + * @test + * @bug 8391308 + * @summary Verifies heap dump contains a reference to a java.lang.Class instance from its ClassLoader + * @library /test/lib + * @run driver ClassLoaderDefinedClassesTest + */ +class ClassLoaderDefinedClassesTarg extends LingeredApp { + + static class MyLoader extends ClassLoader { + byte[] bytes = ClassFile.of().build(ClassDesc.of("Test"), clb -> + clb.withFlags(ACC_PUBLIC) + .withMethodBody(INIT_NAME, MTD_void, ACC_PUBLIC, cob -> + cob.aload(0) + .invokespecial(CD_Object, INIT_NAME, MTD_void) + .return_()) + ); + + @Override + public Class findClass(String name) throws ClassNotFoundException { + if (name.equals("Test")) { + return defineClass(name, bytes, 0, bytes.length); + } + throw new ClassNotFoundException(name); + } + } + + public static void main(String[] args) { + MyLoader ldr = new MyLoader(); + Class test; + try { + test = ldr.loadClass("Test"); + } catch (ClassNotFoundException cnfe) { + throw new RuntimeException("Test not loaded"); + } + LingeredApp.main(args); + Reference.reachabilityFence(ldr); + } +} + +public class ClassLoaderDefinedClassesTest { + + public static void main(String[] args) throws Exception { + File dumpFile = new File("Myheapdump.hprof"); + createDump(dumpFile, args); + verifyDump(dumpFile); + } + + private static void createDump(File dumpFile, String[] extraOptions) throws Exception { + LingeredApp theApp = null; + try { + theApp = new ClassLoaderDefinedClassesTarg(); + + List extraVMArgs = new ArrayList<>(); + extraVMArgs.addAll(Arrays.asList(extraOptions)); + LingeredApp.startApp(theApp, extraVMArgs.toArray(new String[0])); + + //jcmd GC.heap_dump + JDKToolLauncher launcher = JDKToolLauncher + .createUsingTestJDK("jcmd") + .addToolArg(Long.toString(theApp.getPid())) + .addToolArg("GC.heap_dump") + .addToolArg("-overwrite") + .addToolArg(dumpFile.getAbsolutePath()); + Process p = ProcessTools.startProcess("jcmd", new ProcessBuilder(launcher.getCommand())); + // If something goes wrong with heap dumping most likely we'll get crash of the target VM. + while (!p.waitFor(5, TimeUnit.SECONDS)) { + if (!theApp.getProcess().isAlive()) { + log("ERROR: target VM died, killing jcmd..."); + p.destroyForcibly(); + throw new Exception("Target VM died"); + } + } + + if (p.exitValue() != 0) { + throw new Exception("Jcmd exited with code " + p.exitValue()); + } + } finally { + LingeredApp.stopApp(theApp); + } + } + + private static void verifyDump(File dumpFile) throws Exception { + Asserts.assertTrue(dumpFile.exists(), "Heap dump file not found."); + + log("Reading " + dumpFile + "..."); + try (Snapshot snapshot = Reader.readFile(dumpFile.getPath(), true, 0)) { + log("Resolving snapshot..."); + snapshot.resolve(true); + log("Snapshot resolved."); + + JavaClass loaderClass = + snapshot.findClass("ClassLoaderDefinedClassesTarg$MyLoader"); + JavaClass testClass = snapshot.findClass("Test"); + + Asserts.assertNotNull(loaderClass, "MyLoader class missing from dump"); + Asserts.assertNotNull(testClass, "Test class missing from dump"); + // Assert that the Class has a field for the class loader. + Asserts.assertSame(testClass.getLoader(), + loaderClass.getInstances(false).nextElement()); + } + } + + private static void log(Object s) { + System.out.println(s); + } + +} diff --git a/test/hotspot/jtreg/serviceability/jvmti/FollowReferences/ClassLoader/ClassLoaderTest.java b/test/hotspot/jtreg/serviceability/jvmti/FollowReferences/ClassLoader/ClassLoaderTest.java new file mode 100644 index 000000000000..89ced9952f12 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/FollowReferences/ClassLoader/ClassLoaderTest.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8391308 + * @library /test/lib + * @requires vm.jvmti + * @summary The test verifies heap walking API (FollowReferences) reports classes + * when starting from the class loader. + * @run main/othervm/native -agentlib:ClassLoaderTest ClassLoaderTest + */ + +import java.lang.classfile.ClassFile; +import java.lang.constant.ClassDesc; +import java.lang.ref.Reference; +import jdk.test.lib.Asserts; + +import static java.lang.classfile.ClassFile.*; +import static java.lang.constant.ConstantDescs.*; + +public class ClassLoaderTest { + + static class MyLoader extends ClassLoader { + byte[] bytes = ClassFile.of().build(ClassDesc.of("Test"), clb -> + clb.withFlags(ACC_PUBLIC) + .withMethodBody(INIT_NAME, MTD_void, ACC_PUBLIC, cob -> + cob.aload(0) + .invokespecial(CD_Object, INIT_NAME, MTD_void) + .return_()) + ); + + @Override + public Class findClass(String name) throws ClassNotFoundException { + if (name.equals("Test")) { + return defineClass(name, bytes, 0, bytes.length); + } + throw new ClassNotFoundException(name); + } + } + + static { + System.loadLibrary("ClassLoaderTest"); + } + + private static native boolean targetReachedFrom(ClassLoader loader, Class target); + private static native boolean targetKindIsOther(); + + public static void main(String[] args) { + MyLoader ldr = new MyLoader(); + Class test; + try { + test = ldr.loadClass("Test"); + } catch (ClassNotFoundException cnfe) { + throw new RuntimeException("Test not loaded"); + } + + Asserts.assertTrue(targetReachedFrom(ldr, test), + "FollowReferences starting at MyLoader should have reached Test.class"); + + Asserts.assertTrue(targetKindIsOther(), + "FollowReferences should have reported Test.class as target kind OTHER"); + + Reference.reachabilityFence(ldr); + } +} diff --git a/test/hotspot/jtreg/serviceability/jvmti/FollowReferences/ClassLoader/libClassLoaderTest.cpp b/test/hotspot/jtreg/serviceability/jvmti/FollowReferences/ClassLoader/libClassLoaderTest.cpp new file mode 100644 index 000000000000..2f9fd36ddb8c --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/FollowReferences/ClassLoader/libClassLoaderTest.cpp @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include +#include +#include "jvmti.h" +#include "jvmti_common.hpp" + +extern "C" { + +static jvmtiEnv* jvmti = nullptr; + +JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *jvm, char *options, void *reserved) { + jint res = jvm->GetEnv((void **)&jvmti, JVMTI_VERSION_1_1); + if (res != JNI_OK || jvmti == nullptr) { + LOG("jvm->GetEnv failed\n"); + return JNI_ERR; + } + jvmtiCapabilities caps; + memset(&caps, 0, sizeof(caps)); + caps.can_tag_objects = 1; + jvmtiError err = jvmti->AddCapabilities(&caps); + check_jvmti_error(err, "AddCapabilities"); + return JNI_OK; +} + +static constexpr jlong TARGET_TAG = 0x1234; +static long target_kind; +static bool target_seen; + +static jint JNICALL reference_callback( + jvmtiHeapReferenceKind kind, + const jvmtiHeapReferenceInfo* info, + jlong class_tag, + jlong referrer_class_tag, + jlong size, + jlong* tag_ptr, + jlong* referrer_tag_ptr, + jint length, + void* user_data) { + if (*tag_ptr == TARGET_TAG) { + target_seen = true; + target_kind = kind; + LOG("Reached tagged Test.class, reference kind: %d\n", kind); + } + return JVMTI_VISIT_OBJECTS; +} + +JNIEXPORT jboolean JNICALL +Java_ClassLoaderTest_targetReachedFrom( + JNIEnv* env, jclass, jobject loader, jclass target) { + target_seen = false; + + check_jvmti_error(jvmti->SetTag(target, TARGET_TAG), "SetTag target"); + + jvmtiHeapCallbacks callbacks = {}; + callbacks.heap_reference_callback = reference_callback; + + check_jvmti_error( + jvmti->FollowReferences(0, // do not filter tagged objects + nullptr, // no class filter + loader, // initial object + &callbacks, + nullptr), + "FollowReferences"); + + check_jvmti_error(jvmti->SetTag(target, 0), "clear target tag"); + return target_seen; +} + +JNIEXPORT jboolean JNICALL +Java_ClassLoaderTest_targetKindIsOther(JNIEnv* env, jclass) { + return target_kind == JVMTI_HEAP_REFERENCE_OTHER; +} + +} From 83f5ecbdf96d502a0069fe6f30d094b32f7c6017 Mon Sep 17 00:00:00 2001 From: Shiv Shah Date: Tue, 20 Oct 2026 17:42:15 +0000 Subject: [PATCH 138/169] 8394785: compiler/whitebox/StressNMethodRelocation.java fails with -XX:CompileThresholdScaling=0.01 Reviewed-by: kvn, mhaessig --- .../jtreg/compiler/whitebox/StressNMethodRelocation.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/hotspot/jtreg/compiler/whitebox/StressNMethodRelocation.java b/test/hotspot/jtreg/compiler/whitebox/StressNMethodRelocation.java index efcc9f03fb91..b3f04b4af4fc 100644 --- a/test/hotspot/jtreg/compiler/whitebox/StressNMethodRelocation.java +++ b/test/hotspot/jtreg/compiler/whitebox/StressNMethodRelocation.java @@ -180,6 +180,11 @@ public void invoke(byte[] num1, byte[] num2, byte[] result) throws Exception { } public void compileWithC2() throws Exception { + // The enqueue below is dropped while a compile the policy queued + // on its own is still in the queue, so wait for that one first + while (WHITE_BOX.isMethodQueuedForCompilation(method)) { + Thread.onSpinWait(); + } WHITE_BOX.enqueueMethodForCompilation(method, C2_LEVEL); while (WHITE_BOX.isMethodQueuedForCompilation(method)) { Thread.onSpinWait(); From 8992ee72a1fb3a5cd9e8413e58d74ec9f5e51f64 Mon Sep 17 00:00:00 2001 From: Shiv Shah Date: Tue, 20 Oct 2026 17:42:43 +0000 Subject: [PATCH 139/169] 8394787: compiler/c1/CanonicalizeArrayLength.java times out with -XX:CompileThresholdScaling=0.01 Reviewed-by: kvn, mhaessig --- .../jtreg/compiler/c1/CanonicalizeArrayLength.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/test/hotspot/jtreg/compiler/c1/CanonicalizeArrayLength.java b/test/hotspot/jtreg/compiler/c1/CanonicalizeArrayLength.java index 30f1aab00474..ba903044ae9f 100644 --- a/test/hotspot/jtreg/compiler/c1/CanonicalizeArrayLength.java +++ b/test/hotspot/jtreg/compiler/c1/CanonicalizeArrayLength.java @@ -27,21 +27,25 @@ * @summary C1 crashes in Canonicalizer::do_ArrayLength() after fix for JDK-8150102 * * @run main/othervm -XX:+IgnoreUnrecognizedVMOptions -XX:+UnlockDiagnosticVMOptions - * -XX:CompileThreshold=100 -XX:+TieredCompilation -XX:TieredStopAtLevel=1 + * -XX:CompileThreshold=100 -XX:CompileThresholdScaling=1.0 + * -XX:+TieredCompilation -XX:TieredStopAtLevel=1 * -XX:-BackgroundCompilation * compiler.c1.CanonicalizeArrayLength * @run main/othervm -XX:+IgnoreUnrecognizedVMOptions -XX:+UnlockDiagnosticVMOptions - * -XX:CompileThreshold=100 -XX:+TieredCompilation -XX:TieredStopAtLevel=3 + * -XX:CompileThreshold=100 -XX:CompileThresholdScaling=1.0 + * -XX:+TieredCompilation -XX:TieredStopAtLevel=3 * -XX:-BackgroundCompilation * -XX:+PatchALot * compiler.c1.CanonicalizeArrayLength * @run main/othervm -XX:+IgnoreUnrecognizedVMOptions -XX:+UnlockDiagnosticVMOptions - * -XX:CompileThreshold=100 -XX:+TieredCompilation -XX:TieredStopAtLevel=1 + * -XX:CompileThreshold=100 -XX:CompileThresholdScaling=1.0 + * -XX:+TieredCompilation -XX:TieredStopAtLevel=1 * -XX:-BackgroundCompilation * -XX:ScavengeRootsInCode=0 * compiler.c1.CanonicalizeArrayLength * @run main/othervm -XX:+IgnoreUnrecognizedVMOptions -XX:+UnlockDiagnosticVMOptions - * -XX:CompileThreshold=100 -XX:+TieredCompilation -XX:TieredStopAtLevel=1 + * -XX:CompileThreshold=100 -XX:CompileThresholdScaling=1.0 + * -XX:+TieredCompilation -XX:TieredStopAtLevel=1 * -XX:-BackgroundCompilation -XX:ScavengeRootsInCode=1 * compiler.c1.CanonicalizeArrayLength */ From 78837b1958bc03c27570009e5f6a907aaa7135b3 Mon Sep 17 00:00:00 2001 From: Shiv Shah Date: Tue, 20 Oct 2026 17:43:41 +0000 Subject: [PATCH 140/169] 8394786: compiler/compilercontrol/commands/CompileLevelPrintTest.java fails with -XX:CompileThresholdScaling=0.01 Reviewed-by: kvn, mhaessig --- .../compilercontrol/commands/CompileLevelPrintTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/hotspot/jtreg/compiler/compilercontrol/commands/CompileLevelPrintTest.java b/test/hotspot/jtreg/compiler/compilercontrol/commands/CompileLevelPrintTest.java index 1f4562defa53..4baf003c9221 100644 --- a/test/hotspot/jtreg/compiler/compilercontrol/commands/CompileLevelPrintTest.java +++ b/test/hotspot/jtreg/compiler/compilercontrol/commands/CompileLevelPrintTest.java @@ -30,6 +30,8 @@ * @requires vm.compMode != "Xint" & vm.flavor == "server" * & (vm.opt.TieredStopAtLevel == 4 | vm.opt.TieredStopAtLevel == null) * & (vm.opt.CompilationMode == "normal" | vm.opt.CompilationMode == null) + * & vm.opt.CompileThresholdScaling == null + * & vm.opt.CompileThreshold == null * @library /test/lib * @build jdk.test.whitebox.WhiteBox * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox From ec7278835b94b397bdce134f37e184e8d0051e1d Mon Sep 17 00:00:00 2001 From: Shiv Shah Date: Tue, 20 Oct 2026 17:44:09 +0000 Subject: [PATCH 141/169] 8394784: compiler/exceptions/OptimizeImplicitExceptions.java fails with -XX:CompileThresholdScaling=0.01 Reviewed-by: kvn, mhaessig --- .../jtreg/compiler/exceptions/OptimizeImplicitExceptions.java | 1 + 1 file changed, 1 insertion(+) diff --git a/test/hotspot/jtreg/compiler/exceptions/OptimizeImplicitExceptions.java b/test/hotspot/jtreg/compiler/exceptions/OptimizeImplicitExceptions.java index 15d4e150d906..c3ad5e903057 100644 --- a/test/hotspot/jtreg/compiler/exceptions/OptimizeImplicitExceptions.java +++ b/test/hotspot/jtreg/compiler/exceptions/OptimizeImplicitExceptions.java @@ -28,6 +28,7 @@ * * @requires vm.compiler2.enabled & vm.compMode != "Xcomp" * @requires vm.opt.DeoptimizeALot != true + * @requires vm.opt.CompileThresholdScaling == null & vm.opt.CompileThreshold == null * * @library /test/lib * @build jdk.test.whitebox.WhiteBox From dfae1e532a1308542c8316fb6414d22424c3d0e1 Mon Sep 17 00:00:00 2001 From: Naoto Sato Date: Tue, 20 Oct 2026 18:14:50 +0000 Subject: [PATCH 142/169] 8393882: Unchecked lengths in I18N binary-data parsers can cause oversized array allocations Reviewed-by: jlu --- .../jdk/internal/icu/impl/UBiDiProps.java | 29 +++-- .../sun/text/RuleBasedBreakIterator.java | 22 +++- .../sun/util/calendar/ZoneInfoFile.java | 17 ++- .../icu/UBiDiPropsDataValidation.java | 103 ++++++++++++++++ .../BreakIteratorDataValidation.java | 74 ++++++++++++ .../calendar/ZoneRulesDataValidation.java | 110 ++++++++++++++++++ 6 files changed, 343 insertions(+), 12 deletions(-) create mode 100644 test/jdk/jdk/internal/icu/UBiDiPropsDataValidation.java create mode 100644 test/jdk/sun/text/resources/BreakIterator/BreakIteratorDataValidation.java create mode 100644 test/jdk/sun/util/calendar/ZoneRulesDataValidation.java diff --git a/src/java.base/share/classes/jdk/internal/icu/impl/UBiDiProps.java b/src/java.base/share/classes/jdk/internal/icu/impl/UBiDiProps.java index b83bc8e941f9..e3bf10cafe69 100644 --- a/src/java.base/share/classes/jdk/internal/icu/impl/UBiDiProps.java +++ b/src/java.base/share/classes/jdk/internal/icu/impl/UBiDiProps.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -66,8 +66,8 @@ private void readData(ByteBuffer bytes) throws IOException { // read indexes[] int i, count; count=bytes.getInt(); - if(count bytes.remaining() / Integer.BYTES) { + throw new IOException("Invalid index count in " + DATA_FILE_NAME); } indexes=new int[count]; @@ -88,22 +88,31 @@ private void readData(ByteBuffer bytes) throws IOException { // read mirrors[] count=indexes[IX_MIRROR_LENGTH]; - if(count>0) { - mirrors=new int[count]; - for(i=0; i bytes.remaining() / Integer.BYTES) { + throw new IOException("Invalid mirrors length in " + DATA_FILE_NAME); + } + mirrors=new int[count]; + for(i=0; i bytes.remaining()) { + throw new IOException("Invalid joining-group length in " + DATA_FILE_NAME); + } + count = (int)jgArrayLength; jgArray=new byte[count]; for(i=0; i bytes.remaining()) { + throw new IOException("Invalid joining-group 2 length in " + DATA_FILE_NAME); + } + count = (int)jgArrayLength; jgArray2=new byte[count]; for(i=0; i 1024) { + throw new StreamCorruptedException( + "Invalid standard transition count: " + stdSize); + } long[] stdTrans = new long[stdSize]; for (int i = 0; i < stdSize; i++) { stdTrans[i] = readEpochSec(in); @@ -323,6 +330,10 @@ public static ZoneInfo getZoneInfo(DataInput in, String zoneId) throws Exception stdOffsets[i] = readOffset(in); } int savSize = in.readInt(); + if (savSize < 0 || savSize > 1024) { + throw new StreamCorruptedException( + "Invalid saving count: " + savSize); + } long[] savTrans = new long[savSize]; for (int i = 0; i < savSize; i++) { savTrans[i] = readEpochSec(in); @@ -332,6 +343,10 @@ public static ZoneInfo getZoneInfo(DataInput in, String zoneId) throws Exception savOffsets[i] = readOffset(in); } int ruleSize = in.readByte(); + if (ruleSize < 0 || ruleSize > 16) { + throw new StreamCorruptedException( + "Invalid rule size: " + ruleSize); + } ZoneOffsetTransitionRule[] rules = new ZoneOffsetTransitionRule[ruleSize]; for (int i = 0; i < ruleSize; i++) { rules[i] = new ZoneOffsetTransitionRule(in); diff --git a/test/jdk/jdk/internal/icu/UBiDiPropsDataValidation.java b/test/jdk/jdk/internal/icu/UBiDiPropsDataValidation.java new file mode 100644 index 000000000000..3b8acf563bb6 --- /dev/null +++ b/test/jdk/jdk/internal/icu/UBiDiPropsDataValidation.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +/* + * @test + * @bug 8393882 + * @summary Tests the validation of the ICU UBiDiProps data + * @modules java.base/jdk.internal.icu.impl:+open + * @run junit UBiDiPropsDataValidation + */ + +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.nio.ByteBuffer; + +import jdk.internal.icu.impl.ICUBinary; +import jdk.internal.icu.impl.UBiDiProps; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.junit.Assert.assertThrows; + +public class UBiDiPropsDataValidation { + // Constants copied from UBiDiProps.java + private static final String DATA_FILE_NAME = + "/jdk/internal/icu/impl/data/icudata/ubidi.icu"; + private static final int FMT = 0x42694469; + private static final int IX_MIRROR_LENGTH = 3; + private static final int IX_JG_LIMIT = 5; + private static final int IX_JG_LIMIT2 = 7; + // end of copied constants + + private static final ByteBuffer ORIGINAL = ICUBinary.getRequiredData(DATA_FILE_NAME); + private static final int SIZE = ORIGINAL.remaining(); + private static final int INDEXES_START; + static { + try { + ICUBinary.readHeader(ORIGINAL, FMT, null); + } catch (IOException ioe) { + throw new ExceptionInInitializerError(ioe); + } + INDEXES_START = ORIGINAL.position(); + } + + @ParameterizedTest + @ValueSource (ints = {0, IX_MIRROR_LENGTH, IX_JG_LIMIT, IX_JG_LIMIT2}) + void invalidDataSizeTest(int index) throws IOException { + ORIGINAL.position(0); + ByteBuffer data = ByteBuffer.allocate(SIZE) + .order(ORIGINAL.order()) + .put(ORIGINAL) + .flip(); + data.position(INDEXES_START); + + // Test with Integer.MAX_VALUE + data.putInt(INDEXES_START + index * Integer.BYTES, Integer.MAX_VALUE); + if (index == IX_JG_LIMIT || index == IX_JG_LIMIT2) { + // set start to 0 + data.putInt(INDEXES_START + (index - 1) * Integer.BYTES, 0); + } + data.position(0); + assertThrows(IOException.class, () -> readTestData(data)); + + // Test with a negative size + data.putInt(INDEXES_START + index * Integer.BYTES, -1); + data.position(0); + assertThrows(IOException.class, () -> readTestData(data)); + } + + private static void readTestData(ByteBuffer data) throws Throwable { + var constructor = UBiDiProps.class.getDeclaredConstructor(); + constructor.setAccessible(true); + var target = constructor.newInstance(); + var readData = UBiDiProps.class.getDeclaredMethod( + "readData", ByteBuffer.class); + readData.setAccessible(true); + + try { + readData.invoke(target, data); + } catch (InvocationTargetException e) { + throw e.getCause(); + } + } +} diff --git a/test/jdk/sun/text/resources/BreakIterator/BreakIteratorDataValidation.java b/test/jdk/sun/text/resources/BreakIterator/BreakIteratorDataValidation.java new file mode 100644 index 000000000000..319790fee3c6 --- /dev/null +++ b/test/jdk/sun/text/resources/BreakIterator/BreakIteratorDataValidation.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +/* + * @test + * @bug 8393882 + * @summary Tests the validation of the BreakIterator data + * @modules java.base/sun.text + * @run junit BreakIteratorDataValidation + */ + +import java.nio.ByteBuffer; +import java.util.MissingResourceException; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import sun.text.RuleBasedBreakIterator; + +import static org.junit.Assert.assertThrows; + +public class BreakIteratorDataValidation { + private static final int DATASIZE = + 48 // size including checksum + + 1 // one endState, avoiding zero division with endStates.length + + 1_024; // BMPindices + @ParameterizedTest + @ValueSource (ints = {12, 16, 20, 24, 28, 32, 36}) + void hugeDataSizeTest(int index) { + byte[] data = ByteBuffer.allocate(DATASIZE) + .put(new byte[]{'B', 'I', 'd', 'a', 't', 'a', 0, 1}) + .putInt(DATASIZE - 12) // totalDataSize + .putInt(20, 1) // endStates size + .putInt(index, Integer.MAX_VALUE) // huge table length + .array(); + + assertThrows("Malformed data was accepted", + MissingResourceException.class, + () -> new RuleBasedBreakIterator("test", data)); + } + + @ParameterizedTest + @ValueSource (ints = {12, 16, 20, 24, 28, 32, 36}) + void negativeDataSizeTest(int index) { + byte[] data = ByteBuffer.allocate(DATASIZE) + .put(new byte[]{'B', 'I', 'd', 'a', 't', 'a', 0, 1}) + .putInt(DATASIZE - 12) // totalDataSize + .putInt(20, 1) // endStates size + .putInt(index, -1) // negative data length + .array(); + + assertThrows("Malformed data was accepted", + MissingResourceException.class, + () -> new RuleBasedBreakIterator("test", data)); + } +} diff --git a/test/jdk/sun/util/calendar/ZoneRulesDataValidation.java b/test/jdk/sun/util/calendar/ZoneRulesDataValidation.java new file mode 100644 index 000000000000..04b16e8c6d14 --- /dev/null +++ b/test/jdk/sun/util/calendar/ZoneRulesDataValidation.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +/* + * @test + * @bug 8393882 + * @summary Tests the validation of the ZoneRules data + * @modules java.base/sun.util.calendar + * @run junit ZoneRulesDataValidation + */ + +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.io.IOException; +import java.io.StreamCorruptedException; +import java.nio.ByteBuffer; +import java.util.List; +import sun.util.calendar.ZoneInfoFile; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.junit.Assert.assertThrows; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +public class ZoneRulesDataValidation { + private static List testOffsets() { + return List.of( + Arguments.of(1, true, 1_024), // stdOffsets + Arguments.of(6, true, 1_024), // savOffsets + Arguments.of(11, false, 16) // ruleSize + ); + } + + private static ByteBuffer validData() { + return ByteBuffer.allocate(12) + .put((byte)1) // ZoneRules format + .putInt(0) // stdSize + .put((byte)0) // stdOffsets[0]: UTC + .putInt(0) // savSize + .put((byte)0) // savOffsets[0]: UTC + .put((byte)0); // ruleSize + } + + @Test + void invalidTypeTest() throws IOException { + var data = validData(); + data.put(0, (byte)0); + final var in = new DataInputStream(new ByteArrayInputStream(data.array())); + assertThrows(StreamCorruptedException.class, () -> ZoneInfoFile.getZoneInfo(in, "Test")); + in.close(); + } + + @ParameterizedTest + @MethodSource("testOffsets") + void tooLargeDataSizeTest(int offset, boolean isInt, int max) throws IOException { + var data = validData(); + if (isInt) { + data.putInt(offset, max + 1); + } else { + data.put(offset, (byte)(max + 1)); + } + final var in = new DataInputStream(new ByteArrayInputStream(data.array())); + assertThrows(StreamCorruptedException.class, () -> ZoneInfoFile.getZoneInfo(in, "Test")); + in.close(); + } + + @ParameterizedTest + @MethodSource("testOffsets") + void negativeDataSizeTest(int offset, boolean isInt) throws IOException { + var data = validData(); + if (isInt) { + data.putInt(offset, -1); + } else { + data.put(offset, (byte)(-1)); + } + data.position(0); + final var in = new DataInputStream(new ByteArrayInputStream(data.array())); + assertThrows(StreamCorruptedException.class, () -> ZoneInfoFile.getZoneInfo(in, "Test")); + in.close(); + } + + @Test + void validDataSizeTest() throws IOException { + final var in = new DataInputStream(new ByteArrayInputStream(validData().array())); + assertDoesNotThrow(() -> ZoneInfoFile.getZoneInfo(in, "Test")); + in.close(); + } +} From 448dd77455c6d88a26a500a37892ec63c52d7239 Mon Sep 17 00:00:00 2001 From: Chris Plummer Date: Tue, 20 Oct 2026 20:57:02 +0000 Subject: [PATCH 143/169] 8394982: Problem list sun/tools/jhsdb/HeapDumpTestWithActiveProcess.java due to JDK-8394812 Reviewed-by: dholmes --- test/jdk/ProblemList-enable-preview.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/jdk/ProblemList-enable-preview.txt b/test/jdk/ProblemList-enable-preview.txt index 962cd33c979b..84aa30f843d8 100644 --- a/test/jdk/ProblemList-enable-preview.txt +++ b/test/jdk/ProblemList-enable-preview.txt @@ -34,3 +34,5 @@ ############################################################################# # Value Objects failures start here: + +sun/tools/jhsdb/HeapDumpTestWithActiveProcess.java 8394812 generic-all \ No newline at end of file From e049dd2114bc4794bdec8edd3f9d159983834a6d Mon Sep 17 00:00:00 2001 From: William Kemper Date: Tue, 20 Oct 2026 21:47:52 +0000 Subject: [PATCH 144/169] 8394388: GenShen: Enhance WhiteBox API to support old and young collections Reviewed-by: ruili, shade, kdnilsen --- .../heuristics/shenandoahYoungHeuristics.hpp | 3 + .../gc/shenandoah/shenandoahController.hpp | 2 +- .../gc/shenandoah/shenandoahGeneration.hpp | 13 ++ .../shenandoahGenerationalControlThread.cpp | 113 ++++++++- .../shenandoahGenerationalControlThread.hpp | 12 + .../shenandoah/shenandoahGenerationalHeap.cpp | 5 + .../shenandoah/shenandoahGenerationalHeap.hpp | 2 + .../gc/shenandoah/shenandoahOldGeneration.hpp | 13 ++ src/hotspot/share/prims/whitebox.cpp | 42 ++++ .../shenandoah/generational/TestHelpers.java | 133 +++++++++++ .../generational/TestWhiteBoxOldMarkGC.java | 219 ++++++++++++++++++ .../generational/TestWhiteBoxYoungGC.java | 120 ++++++++++ test/lib/jdk/test/whitebox/WhiteBox.java | 9 + 13 files changed, 679 insertions(+), 7 deletions(-) create mode 100644 test/hotspot/jtreg/gc/shenandoah/generational/TestHelpers.java create mode 100644 test/hotspot/jtreg/gc/shenandoah/generational/TestWhiteBoxOldMarkGC.java create mode 100644 test/hotspot/jtreg/gc/shenandoah/generational/TestWhiteBoxYoungGC.java diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahYoungHeuristics.hpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahYoungHeuristics.hpp index 723fb631e755..c8304ac1836f 100644 --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahYoungHeuristics.hpp +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahYoungHeuristics.hpp @@ -41,6 +41,9 @@ class ShenandoahYoungHeuristics : public ShenandoahGenerationalHeuristics { bool should_start_gc() override; + // Young collections can never unload classes + bool can_unload_classes() override { return false; } + size_t bytes_of_allocation_runway_before_gc_trigger(size_t young_regions_to_be_reclaimed); protected: diff --git a/src/hotspot/share/gc/shenandoah/shenandoahController.hpp b/src/hotspot/share/gc/shenandoah/shenandoahController.hpp index b7e22db9b04d..3f462e6b8773 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahController.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahController.hpp @@ -148,7 +148,7 @@ class ShenandoahController: public ConcurrentGCThread { static const char* collector_phase_to_string(ShenandoahCollectorPhase phase); protected: - void handle_requested_gc(GCCause::Cause cause); + virtual void handle_requested_gc(GCCause::Cause cause); // Used at the end of a cycle to clear allocation failure and notify alloc waiters virtual void clear_allocation_failure_and_notify_waiters(); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.hpp b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.hpp index 9bbba38d3638..fd6b69990d18 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.hpp @@ -32,6 +32,7 @@ #include "gc/shenandoah/shenandoahLock.hpp" #include "gc/shenandoah/shenandoahMarkingContext.hpp" #include "memory/allocation.hpp" +#include "runtime/atomic.hpp" class ShenandoahCollectionSet; class ShenandoahHeap; @@ -55,6 +56,10 @@ class ShenandoahGeneration : public CHeapObj, public ShenandoahSpaceInfo { // Bytes reserved within this generation to hold evacuated objects from the collection set size_t _evacuation_reserve; + // Read by mutators, written by control thread. This records the id of the last gc cycle + // to collect this generation. + Atomic _completed_gc_id; + protected: ShenandoahFreeSet* _free_set; ShenandoahHeuristics* _heuristics; @@ -68,6 +73,14 @@ class ShenandoahGeneration : public CHeapObj, public ShenandoahSpaceInfo { uint max_workers); ~ShenandoahGeneration(); + void update_completed_gc_id(size_t gc_id) { + _completed_gc_id.store_relaxed(gc_id); + } + + size_t completed_gc_id() const { + return _completed_gc_id.load_relaxed(); + } + inline bool is_young() const { return _type == YOUNG; } inline bool is_old() const { return _type == OLD; } inline bool is_global() const { return _type == GLOBAL || _type == NON_GEN; } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.cpp index 42609cbce723..5db519df1efa 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.cpp @@ -165,11 +165,11 @@ ShenandoahGenerationalControlThread::GCMode ShenandoahGenerationalControlThread: return stw_full; } -void ShenandoahGenerationalControlThread::prepare_for_explicit_gc(ShenandoahGCRequest &request) const { - request.generation = _heap->global_generation(); - request.generation->heuristics()->record_requested_gc(); - request.generation->heuristics()->log_trigger("GC Request (%s)", GCCause::to_string(request.cause)); - _heap->set_unload_classes(request.generation->heuristics()->can_unload_classes()); +void ShenandoahGenerationalControlThread::prepare_for_explicit_gc(ShenandoahGCRequest& request) const { + ShenandoahHeuristics* heuristics = request.generation->heuristics(); + heuristics->record_requested_gc(); + heuristics->log_trigger("GC Request (%s)", GCCause::to_string(request.cause)); + _heap->set_unload_classes(heuristics->can_unload_classes()); } ShenandoahGenerationalControlThread::GCMode ShenandoahGenerationalControlThread::prepare_for_concurrent_gc(ShenandoahGCRequest &request) const { @@ -360,12 +360,18 @@ void ShenandoahGenerationalControlThread::service_concurrent_old_cycle(const She switch (original_state) { case ShenandoahOldGeneration::IDLE: old_generation->transition_to(ShenandoahOldGeneration::MARKING); + set_gc_mode(bootstrapping_old); + + // This is the logical gc id for the old collection. The old cycle itself might be + // interrupted many times and each iteration will generate a new gc id, but the one + // we record at the start will span all the interruptions. + old_generation->record_collection_start(get_gc_id()); + // Configure the young generation's concurrent mark to put objects in // old regions into the concurrent mark queues associated with the old // generation. The young cycle will run as normal except that rather than // ignore old references it will mark and enqueue them in the old concurrent // task queues, but it will not traverse them. - young_generation->set_old_gen_task_queues(old_generation->task_queues()); service_concurrent_cycle(young_generation, request.cause, true); _heap->process_gc_stats(); @@ -422,6 +428,9 @@ void ShenandoahGenerationalControlThread::resume_concurrent_old_cycle(Shenandoah if (alloc_stall_count() == 0) { _heap->notify_gc_progress(); } + + // For old GC, record that it has completed the gc it started + generation->update_completed_gc_id(generation->started_gc_id()); generation->heuristics()->record_concurrent_completion(); _heap->shenandoah_policy()->record_success_old(); @@ -460,6 +469,7 @@ void ShenandoahGenerationalControlThread::service_concurrent_cycle(ShenandoahGen // In the generational mode, we don't use global heuristics to trigger global cycles, so // direct heuristic signals to the young heuristics + generation->update_completed_gc_id(get_gc_id()); _heap->young_generation()->heuristics()->record_concurrent_completion(); _heap->shenandoah_policy()->record_success_concurrent(get_gc_id(), generation->is_young(), gc.abbreviated()); @@ -509,6 +519,7 @@ void ShenandoahGenerationalControlThread::service_stw_full_cycle(GCCause::Cause gc.collect(cause); // Notify threads waiting for GC and/or memory that a full cycle has completed + _heap->global_generation()->update_completed_gc_id(get_gc_id()); notify_gc_waiters(); clear_allocation_failure_and_notify_waiters(); } @@ -604,6 +615,96 @@ bool ShenandoahGenerationalControlThread::preempt_old_marking(ShenandoahGenerati return generation->is_young() && _allow_old_preemption.try_unset(); } +bool ShenandoahGenerationalControlThread::wait_for_gc_cycle(GCCause::Cause cause, ShenandoahGeneration* generation, uint timeout_seconds) { + if (generation->is_old()) { + return wait_for_old_gc_cycle(cause, static_cast(generation), timeout_seconds); + } + + // Make sure we have at least one complete GC cycle before unblocking + // from the requested GC. + // + // This is especially important for weak references cleanup and/or native + // resources (e.g. DirectByteBuffers) machinery: when the GC request + // comes very late in the already running cycle, it would miss lots of new + // opportunities for cleanup that were made available before the caller + // requested the GC. + MonitorLocker ml(&_gc_waiters_lock); + const size_t entry_gc_id = get_gc_id(); + while (generation->completed_gc_id() <= entry_gc_id && !should_terminate()) { + // Make requests to run cycles until at least one is completed + notify_control_thread(cause, generation); + ml.wait(); + } + return generation->completed_gc_id() > entry_gc_id; +} + +bool ShenandoahGenerationalControlThread::wait_for_old_gc_cycle(GCCause::Cause cause, ShenandoahOldGeneration* generation, uint timeout_seconds) { + const double start = os::elapsedTime(); + const double deadline = start + timeout_seconds; + MonitorLocker ml(&_gc_waiters_lock); + const size_t entry_old_gc_id = generation->started_gc_id(); + const size_t entry_gc_id = get_gc_id(); + generation->heuristics()->log_trigger("WhiteBox Old Mark"); + while (generation->completed_gc_id() <= entry_old_gc_id && !should_terminate()) { + // Make requests to run cycles until at least one old mark cycle is completed after this method was called + if (os::elapsedTime() > deadline) { + log_info(gc, thread)("Giving up on running an old gc after: %.3f seconds", os::elapsedTime() - start); + log_info(gc, thread)("Entry gc id: %zu, current id: %zu, entry old gc: %zu, last completed: %zu", + entry_gc_id, get_gc_id(), entry_old_gc_id, generation->completed_gc_id()); + return false; + } + + { + // Take control lock for gc mode. Old gc state is changed only when the gc mode is not none. + MonitorLocker controller(&_control_lock, Mutex::_no_safepoint_check_flag); + + if (gc_mode() == none) { + switch (generation->state()) { + case ShenandoahOldGeneration::IDLE: + // Note: we must only submit a request if the old generation is IDLE + notify_control_thread(controller, cause, generation); + break; + case ShenandoahOldGeneration::MARKING: + case ShenandoahOldGeneration::FILLING: + // do nothing, let the existing cycle finish + break; + case ShenandoahOldGeneration::EVACUATING: + case ShenandoahOldGeneration::EVACUATING_AFTER_GLOBAL: + // Expedite the mixed collections to finish the old cycle + generation->heuristics()->log_trigger("WhiteBox Old Expedite Mixed"); + notify_control_thread(controller, cause, _heap->young_generation()); + break; + default: + ShouldNotReachHere(); + } + } + } + + ml.wait(100); + } + + // check actual condition in case vm was stopped and thread was terminated + return generation->completed_gc_id() > entry_old_gc_id; +} + +void ShenandoahGenerationalControlThread::handle_requested_gc(GCCause::Cause cause) { + // For normal requested GCs (System.gc) we want to block the caller. However, + // for whitebox requested GC, we want to initiate the GC and return immediately. + // The whitebox caller thread will arrange for itself to wait until the GC notifies + // it that has reached the requested breakpoint (phase in the GC). + if (cause == GCCause::_wb_breakpoint) { + notify_control_thread(cause, ShenandoahHeap::heap()->global_generation()); + return; + } + + // We can only run concurrent young GCs. If the request is to run a young gc, but + // explicit invokes concurrent is disabled, you get a global/full GC. + ShenandoahGeneration* generation = cause == GCCause::_wb_young_gc && ExplicitGCInvokesConcurrent + ? ShenandoahHeap::heap()->young_generation() + : ShenandoahHeap::heap()->global_generation(); + wait_for_gc_cycle(cause, generation); +} + const char* ShenandoahGenerationalControlThread::gc_mode_name(GCMode mode) { switch (mode) { case none: return "idle"; diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.hpp b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.hpp index f59f7514cf62..51cefa728d5f 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.hpp @@ -76,6 +76,9 @@ class ShenandoahGenerationalControlThread: public ShenandoahController { // Return true if the request to start a concurrent GC for the given generation succeeded. bool request_concurrent_gc(ShenandoahGeneration* generation); + // Visible for white box API to start an old cycle + bool wait_for_gc_cycle(GCCause::Cause cause, ShenandoahGeneration* generation, uint timeout_seconds = 10); + // Returns the current state of the control thread GCMode gc_mode() const { return _gc_mode; @@ -90,6 +93,10 @@ class ShenandoahGenerationalControlThread: public ShenandoahController { // Will also schedule an immediate full gc if old evacuations failed void clear_allocation_failure_and_notify_waiters() override; + + // Handle whitebox requests to run old or young cycles + void handle_requested_gc(GCCause::Cause cause) override; + private: // Executes one GC cycle @@ -131,6 +138,11 @@ class ShenandoahGenerationalControlThread: public ShenandoahController { // Print table for young region ages if log is enabled void maybe_print_young_region_ages() const; + + // Specialized function that waits for one complete old gc cycle. Note that this may invoke + // young cycles in order to complete mixed evacuations of an existent old gc before starting + // another. + bool wait_for_old_gc_cycle(GCCause::Cause cause, ShenandoahOldGeneration* generation, uint timeout_seconds); }; #endif // SHARE_GC_SHENANDOAH_SHENANDOAHGENERATIONALCONTROLTHREAD_HPP diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp index 411c58ef0a6b..a54eed890b50 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp @@ -119,6 +119,11 @@ void ShenandoahGenerationalHeap::initialize_heuristics() { _old_generation->initialize_heuristics(mode()); } +bool ShenandoahGenerationalHeap::wait_for_old_collection(uint timeout_seconds) { + const auto gen_ctrl_thread = static_cast(_control_thread); + return gen_ctrl_thread->wait_for_gc_cycle(GCCause::_shenandoah_concurrent_gc, old_generation(), timeout_seconds); +} + void ShenandoahGenerationalHeap::initialize_serviceability() { assert(mode()->is_generational(), "Only for the generational mode"); _young_gen_memory_pool = new ShenandoahYoungGenMemoryPool(this); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.hpp b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.hpp index 6aff6351e433..b7932d32223a 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.hpp @@ -44,6 +44,8 @@ class ShenandoahGenerationalHeap : public ShenandoahHeap { void initialize_heuristics() override; void post_initialize_heuristics() override; + bool wait_for_old_collection(uint timeout_seconds); + static ShenandoahGenerationalHeap* heap() { assert(ShenandoahCardBarrier, "Should have card barrier to use generational heap"); CollectedHeap* heap = Universe::heap(); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.hpp b/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.hpp index 47165b2278cd..5ac87ffce743 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.hpp @@ -31,6 +31,7 @@ #include "gc/shenandoah/shenandoahGenerationalHeap.hpp" #include "gc/shenandoah/shenandoahPadding.hpp" #include "gc/shenandoah/shenandoahSharedVariables.hpp" +#include "runtime/atomic.hpp" class LogStream; class ShenandoahScanRemembered; @@ -84,6 +85,10 @@ class ShenandoahOldGeneration : public ShenandoahGeneration { // True if old regions may be safely traversed by the remembered set scan. bool _is_parsable; + // Read by mutators, written by control thread. This is a logical id for an old generation collection. + // It is stable across interruptions by young GCs. + Atomic _started_gc_id; + bool coalesce_and_fill(); public: @@ -332,6 +337,14 @@ class ShenandoahOldGeneration : public ShenandoahGeneration { size_t free_unaffiliated_regions() const override; size_t get_affiliated_region_count() const override; size_t max_capacity() const override; + + void record_collection_start(size_t gc_id) { + _started_gc_id.store_relaxed(gc_id); + } + + size_t started_gc_id() const { + return _started_gc_id.load_relaxed(); + } }; diff --git a/src/hotspot/share/prims/whitebox.cpp b/src/hotspot/share/prims/whitebox.cpp index 6438839aeafb..4c2dc0596aba 100644 --- a/src/hotspot/share/prims/whitebox.cpp +++ b/src/hotspot/share/prims/whitebox.cpp @@ -122,6 +122,12 @@ #if INCLUDE_SERIALGC #include "gc/serial/serialHeap.hpp" #endif // INCLUDE_SERIALGC +#if INCLUDE_SHENANDOAHGC +#include "gc/shenandoah/shenandoahGenerationalHeap.hpp" +#include "gc/shenandoah/shenandoahHeap.hpp" +#include "gc/shenandoah/shenandoahHeapRegion.hpp" +#include "gc/shenandoah/shenandoahOldGeneration.hpp" +#endif // INCLUDE_SHENANDOAHGC #if INCLUDE_ZGC #include "gc/z/zAddress.inline.hpp" #include "gc/z/zHeap.inline.hpp" @@ -694,6 +700,37 @@ WB_END #endif // INCLUDE_G1GC +#if INCLUDE_SHENANDOAHGC + +WB_ENTRY(jint, WB_ShenandoahRegionSize(JNIEnv* env, jobject o)) + if (UseShenandoahGC) { + return ShenandoahHeapRegion::region_size_bytes_jint(); + } + THROW_MSG_0(vmSymbols::java_lang_UnsupportedOperationException(), "WB_ShenandoahRegionSize: Shenandoah GC is not enabled"); +WB_END + +WB_ENTRY(jint, WB_ShenandoahRegionCount(JNIEnv* env, jobject o)) + if (UseShenandoahGC) { + return static_cast(ShenandoahHeap::heap()->num_regions()); + } + THROW_MSG_0(vmSymbols::java_lang_UnsupportedOperationException(), "WB_ShenandoahRegionCount: Shenandoah GC is not enabled"); +WB_END + +WB_ENTRY(jlong, WB_ShenandoahOldMarkGC(JNIEnv* env, jobject o, jint timeout_seconds)) + if (UseShenandoahGC && ShenandoahHeap::heap()->mode()->is_generational()) { + if (timeout_seconds < 0) { + THROW_MSG_0(vmSymbols::java_lang_IllegalArgumentException(), "WB_ShenandoahOldMarkGC: Timeout must be non-negative"); + } + if (ShenandoahGenerationalHeap::heap()->wait_for_old_collection(timeout_seconds)) { + return ShenandoahHeap::heap()->old_generation()->completed_gc_id(); + } + THROW_MSG_0(vmSymbols::java_lang_IllegalStateException(), "WB_ShenandoahOldMarkGC: Could not complete old GC cycle"); + } + THROW_MSG_0(vmSymbols::java_lang_UnsupportedOperationException(), "WB_ShenandoahOldMarkGC: Generational mode for Shenandoah GC is not enabled"); +WB_END + +#endif // INCLUDE_SHENANDOAHGC + // Alloc memory using the test memory tag so that we can use that to see if // NMT picks it up correctly WB_ENTRY(jlong, WB_NMTMalloc(JNIEnv* env, jobject o, jlong size)) @@ -2984,6 +3021,11 @@ static JNINativeMethod methods[] = { {CC"g1MemoryNodeIds", CC"()[I", (void*)&WB_G1MemoryNodeIds }, {CC"g1GetMixedGCInfo", CC"(I)[J", (void*)&WB_G1GetMixedGCInfo }, #endif // INCLUDE_G1GC +#if INCLUDE_SHENANDOAHGC + {CC"shenandoahRegionSize", CC"()I", (void*)&WB_ShenandoahRegionSize }, + {CC"shenandoahRegionCount", CC"()I", (void*)&WB_ShenandoahRegionCount }, + {CC"shenandoahOldMarkGC", CC"(I)J", (void*)&WB_ShenandoahOldMarkGC }, +#endif // INCLUDE_SHENANDOAHGC {CC"NMTMalloc", CC"(J)J", (void*)&WB_NMTMalloc }, {CC"NMTMallocWithPseudoStack", CC"(JI)J", (void*)&WB_NMTMallocWithPseudoStack}, {CC"NMTMallocWithPseudoStackAndType", CC"(JII)J", (void*)&WB_NMTMallocWithPseudoStackAndType}, diff --git a/test/hotspot/jtreg/gc/shenandoah/generational/TestHelpers.java b/test/hotspot/jtreg/gc/shenandoah/generational/TestHelpers.java new file mode 100644 index 000000000000..e7006b4788f0 --- /dev/null +++ b/test/hotspot/jtreg/gc/shenandoah/generational/TestHelpers.java @@ -0,0 +1,133 @@ +/* + * Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +package gc.shenandoah.generational; + +import java.io.Closeable; +import java.io.IOException; +import java.lang.management.GarbageCollectorMXBean; +import java.lang.management.ManagementFactory; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import javax.management.Notification; +import javax.management.NotificationEmitter; +import javax.management.NotificationListener; +import javax.management.openmbean.CompositeData; + +import com.sun.management.GarbageCollectionNotificationInfo; +import jdk.test.whitebox.WhiteBox; + +class TestHelpers { + interface GcNotificationListener { + void handleNotification(GarbageCollectionNotificationInfo info); + } + + public static void logEvent(GarbageCollectionNotificationInfo event) { + System.out.println(event.getGcAction() + ", " + event.getGcCause() + ", " + event.getGcName()); + } + + static class ScopedListener implements Closeable { + private final NotificationListener handle; + private final CountDownLatch sawSystemGC; + + ScopedListener() { + this(TestHelpers::logEvent); + } + + ScopedListener(final GcNotificationListener gcListener) { + sawSystemGC = new CountDownLatch(1); + handle = (notification, handback) -> { + if (isCollectorNotification(notification)) { + GarbageCollectionNotificationInfo info = GarbageCollectionNotificationInfo.from((CompositeData) notification.getUserData()); + if (isSystemGC(info)) { + sawSystemGC.countDown(); + } + + gcListener.handleNotification(info); + } + }; + + TestHelpers.subscribeToCollectorNotifications(handle); + } + + @Override + public void close() throws IOException { + try { + System.gc(); + + if (!sawSystemGC.await(30, TimeUnit.SECONDS)) { + throw new RuntimeException("Timed out waiting for end of gc event stream"); + } + + TestHelpers.unsubscribeToCollectorNotifications(handle); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + + private static final WhiteBox WB = WhiteBox.getWhiteBox(); + + static void promote(List objects) { + final int maxTries = 5; + int tries = 0; + while (!allAreInOld(objects) && tries < maxTries) { + WB.fullGC(); + ++tries; + } + + assert allAreInOld(objects) : "Not all objects were promoted"; + } + + static boolean allAreInOld(List objects) { + for (Object obj : objects) { + if (!WB.isObjectInOldGen(obj)) { + return false; + } + } + return true; + } + + static boolean isCollectorNotification(Notification n) { + return n.getType().equals(GarbageCollectionNotificationInfo.GARBAGE_COLLECTION_NOTIFICATION); + } + + static void subscribeToCollectorNotifications(NotificationListener listener) { + for (GarbageCollectorMXBean b : ManagementFactory.getGarbageCollectorMXBeans()) { + ((NotificationEmitter) b).addNotificationListener(listener, null, null); + } + } + + static void unsubscribeToCollectorNotifications(NotificationListener listener) throws Exception { + for (GarbageCollectorMXBean b : ManagementFactory.getGarbageCollectorMXBeans()) { + ((NotificationEmitter) b).removeNotificationListener(listener, null, null); + } + } + + static boolean isSystemGC(GarbageCollectionNotificationInfo info) { + return info.getGcCause().contains("System.gc"); + } +} diff --git a/test/hotspot/jtreg/gc/shenandoah/generational/TestWhiteBoxOldMarkGC.java b/test/hotspot/jtreg/gc/shenandoah/generational/TestWhiteBoxOldMarkGC.java new file mode 100644 index 000000000000..114aa4169b9d --- /dev/null +++ b/test/hotspot/jtreg/gc/shenandoah/generational/TestWhiteBoxOldMarkGC.java @@ -0,0 +1,219 @@ +/* + * Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +/* + * @test id=old + * @bug 8394388 + * @summary Test that WhiteBox::shenandoahOldMarkGC is robust + * @requires vm.gc.Shenandoah + * @requires vm.flagless + * @library /test/lib / + * @build jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run driver gc.shenandoah.generational.TestWhiteBoxOldMarkGC old + */ + +/* + * @test id=mixed + * @bug 8394388 + * @summary Test that WhiteBox::shenandoahOldMarkGC will expedite mixed + * evacuations to finish an extent OldGC. + * @requires vm.gc.Shenandoah + * @requires vm.flagless + * @library /test/lib / + * @build jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run driver gc.shenandoah.generational.TestWhiteBoxOldMarkGC mixed + */ + +/* + * @test id=cancel + * @bug 8394388 + * @summary Test that WhiteBox::shenandoahOldMarkGC will timeout and fail when + * the old mark is canceled by a full GC. + * @requires vm.gc.Shenandoah + * @requires vm.flagless + * @library /test/lib / + * @build jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run driver gc.shenandoah.generational.TestWhiteBoxOldMarkGC cancel + */ +package gc.shenandoah.generational; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; +import jdk.test.whitebox.WhiteBox; + +import com.sun.management.GarbageCollectionNotificationInfo; + +import static gc.shenandoah.generational.TestHelpers.promote; + +public class TestWhiteBoxOldMarkGC { + public static final int OBJECT_COUNT = 2048; + + public static void main(String[] argv) throws Exception { + switch (argv[0]) { + case "old" -> Old.runTest(); + case "mixed" -> Mixed.runTest(); + case "cancel" -> Cancelled.runTest(); + default -> throw new IllegalArgumentException("Argument should be: old|mixed|cancel"); + } + } + + public static class Old { + private static void runTest() throws Exception { + OutputAnalyzer output = ProcessTools.executeLimitedTestJava( + getCommandLineArguments(Old.class.getName()) + ); + + output.shouldContain("WhiteBox Old Mark"); + output.shouldNotContain("WhiteBox Old Expedite Mixed"); + output.shouldHaveExitValue(0); + } + + public static void main(String[] argv) { + testConsecutiveOld(); + } + + private static void testConsecutiveOld() { + final WhiteBox WB = WhiteBox.getWhiteBox(); + long one = WB.shenandoahOldMarkGC(); + long two = WB.shenandoahOldMarkGC(); + if (one == two) { + throw new RuntimeException("Old GC did not advance GC ID"); + } + } + } + + public static class Mixed { + private static void runTest() throws Exception { + OutputAnalyzer output = ProcessTools.executeLimitedTestJava( + getCommandLineArguments(Mixed.class.getName()) + ); + + output.shouldContain("WhiteBox Old Mark"); + output.shouldContain("WhiteBox Old Expedite Mixed"); + output.shouldHaveExitValue(0); + } + + public static void main(String[] argv) { + testMixed(); + } + + private static void testMixed() { + final WhiteBox WB = WhiteBox.getWhiteBox(); + + var objects = new ArrayList<>(OBJECT_COUNT); + for (int i = 0; i < OBJECT_COUNT; ++i) { + objects.add(new Object()); + } + + promote(objects); + + // Release half the objects in a way that avoids 100% free regions + for (int i = 0; i < OBJECT_COUNT; ++i) { + if (i % 2 == 0) { + objects.set(i, null); + } + } + + long one = WB.shenandoahOldMarkGC(); + long two = WB.shenandoahOldMarkGC(); + if (one == two) { + throw new RuntimeException("Old GC did not advance GC ID"); + } + } + } + + // TODO: Old mark cancellation is broken. Fixed by JDK-8387790. + // The test makes no assertions because we cannot readily test the old + // mark cancellation case. + public static class Cancelled { + private static void runTest() throws Exception { + OutputAnalyzer output = ProcessTools.executeLimitedTestJava( + getCommandLineArguments( + TestWhiteBoxOldMarkGC.Cancelled.class.getName() + ) + ); + output.shouldHaveExitValue(0); + } + + public static void main(String[] argv) throws Exception { + testCancelledOldGc(); + } + + private static void testCancelledOldGc() throws InterruptedException { + final WhiteBox WB = WhiteBox.getWhiteBox(); + + // Using a linked list here to intentionally slow down old marking + var objects = new LinkedList<>(); + for (int i = 0; i < OBJECT_COUNT * 100; ++i) { + objects.add(new Object()); + } + + promote(objects); + + int i = 0; + for (var iter = objects.listIterator(); iter.hasNext(); iter.next()) { + if (++i % 2 == 0) { + iter.remove(); + } + } + + final var fullGC = new Thread(WB::fullGC); + try (var _ = new TestHelpers.ScopedListener()) { + fullGC.start(); + WB.shenandoahOldMarkGC(5); + + fullGC.join(10000); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + private static String[] getCommandLineArguments(String... additional) { + var defaultArguments = new String[] { + "-Xbootclasspath/a:.", + "-XX:+UnlockDiagnosticVMOptions", "-XX:+WhiteBoxAPI", + "-XX:+UnlockExperimentalVMOptions", + "-XX:+UseShenandoahGC", "-XX:ShenandoahGCMode=generational", + "-Xlog:gc*", "-Xmx128M", "-ea", + "-XX:ShenandoahGenerationalMinTenuringAge=1", + "-XX:ShenandoahGenerationalMaxTenuringAge=1", + }; + var arguments = new String[defaultArguments.length + additional.length]; + System.arraycopy(defaultArguments, 0, arguments, 0, defaultArguments.length); + System.arraycopy(additional, 0, arguments, defaultArguments.length, additional.length); + return arguments; + } +} diff --git a/test/hotspot/jtreg/gc/shenandoah/generational/TestWhiteBoxYoungGC.java b/test/hotspot/jtreg/gc/shenandoah/generational/TestWhiteBoxYoungGC.java new file mode 100644 index 000000000000..52e656bb102b --- /dev/null +++ b/test/hotspot/jtreg/gc/shenandoah/generational/TestWhiteBoxYoungGC.java @@ -0,0 +1,120 @@ +/* + * Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +/* + * @test id=young + * @bug 8394388 + * @summary Test that WhiteBox::youngGC is robust + * @requires vm.gc.Shenandoah + * @requires vm.flagless + * @library /test/lib / + * @build jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run driver gc.shenandoah.generational.TestWhiteBoxYoungGC young + */ + +/* + * @test id=not-concurrent + * @bug 8394388 + * @summary Test that WhiteBox::youngGC falls back to global with -XX:-ExplicitGCInvokesConcurrent + * @requires vm.gc.Shenandoah + * @requires vm.flagless + * @library /test/lib / + * @build jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run driver gc.shenandoah.generational.TestWhiteBoxYoungGC not-concurrent + */ +package gc.shenandoah.generational; + +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; +import jdk.test.whitebox.WhiteBox; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.LinkedList; + +import static gc.shenandoah.generational.TestHelpers.promote; + +public class TestWhiteBoxYoungGC { + public static void main(String[] argv) throws Exception { + switch (argv[0]) { + case "young" -> Young.runTest(); + case "not-concurrent" -> NotConcurrent.runTest(); + default -> throw new IllegalArgumentException("Argument should be: old|mixed|cancel"); + } + } + + public static class Young { + private static void runTest() throws Exception { + OutputAnalyzer output = ProcessTools.executeLimitedTestJava( + getCommandLineArguments(Young.class.getName()) + ); + + output.shouldContain("WhiteBox Initiated Young GC"); + output.shouldHaveExitValue(0); + } + + public static void main(String[] argv) { + final WhiteBox WB = WhiteBox.getWhiteBox(); + WB.youngGC(); + WB.youngGC(); + } + } + + public static class NotConcurrent { + private static void runTest() throws Exception { + OutputAnalyzer output = ProcessTools.executeLimitedTestJava( + getCommandLineArguments( + "-XX:-ExplicitGCInvokesConcurrent", + NotConcurrent.class.getName()) + ); + + output.shouldNotContain("WhiteBox Initiated Global GC"); + output.shouldHaveExitValue(0); + } + + public static void main(String[] argv) { + final WhiteBox WB = WhiteBox.getWhiteBox(); + WB.youngGC(); + WB.youngGC(); + } + } + + private static String[] getCommandLineArguments(String... additional) { + var defaultArguments = new String[] { + "-Xbootclasspath/a:.", + "-XX:+UnlockDiagnosticVMOptions", "-XX:+WhiteBoxAPI", + "-XX:+UnlockExperimentalVMOptions", + "-XX:+UseShenandoahGC", "-XX:ShenandoahGCMode=generational", + "-Xlog:gc*", "-Xmx128M", "-ea", + "-XX:ShenandoahGenerationalMinTenuringAge=1", + "-XX:ShenandoahGenerationalMaxTenuringAge=1", + }; + var arguments = new String[defaultArguments.length + additional.length]; + System.arraycopy(defaultArguments, 0, arguments, 0, defaultArguments.length); + System.arraycopy(additional, 0, arguments, defaultArguments.length, additional.length); + return arguments; + } +} diff --git a/test/lib/jdk/test/whitebox/WhiteBox.java b/test/lib/jdk/test/whitebox/WhiteBox.java index 1c40fef15284..c871dc6b6c47 100644 --- a/test/lib/jdk/test/whitebox/WhiteBox.java +++ b/test/lib/jdk/test/whitebox/WhiteBox.java @@ -336,6 +336,15 @@ public Object[] parseCommandLine(String commandline, char delim, Dia */ public native long[] g1GetMixedGCInfo(int liveness); + // Shenandoah + public native int shenandoahRegionSize(); + public native int shenandoahRegionCount(); + public native long shenandoahOldMarkGC(int timeoutSeconds); + public long shenandoahOldMarkGC() { + final int TIMEOUT_SECONDS = 10; + return shenandoahOldMarkGC(TIMEOUT_SECONDS); + } + // NMT public native long NMTMalloc(long size); public native void NMTFree(long mem); From 5f977991fc2dac17ede03e6d06771cabfe93e963 Mon Sep 17 00:00:00 2001 From: Shawn Emery Date: Wed, 21 Oct 2026 00:04:33 +0000 Subject: [PATCH 145/169] 8386459: implKyberAddPolyJava() should return the result parameter, not through the first operand Reviewed-by: vpaprotski, ascarpino --- src/java.base/share/classes/com/sun/crypto/provider/ML_KEM.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/java.base/share/classes/com/sun/crypto/provider/ML_KEM.java b/src/java.base/share/classes/com/sun/crypto/provider/ML_KEM.java index 5335357f8b97..f71a3f8ea6a8 100644 --- a/src/java.base/share/classes/com/sun/crypto/provider/ML_KEM.java +++ b/src/java.base/share/classes/com/sun/crypto/provider/ML_KEM.java @@ -1211,7 +1211,7 @@ static int implKyberAddPoly(short[] result, short[] a, short[] b) { static void implKyberAddPolyJava(short[] result, short[] a, short[] b) { for (int m = 0; m < ML_KEM_N; m++) { int r = a[m] + b[m] + ML_KEM_Q; // This makes r > - ML_KEM_Q - a[m] = (short) r; + result[m] = (short) r; } } From 2b2ccfe95d61add507bc227d27679543b9de370e Mon Sep 17 00:00:00 2001 From: Albert Mingkun Yang Date: Wed, 21 Oct 2026 00:46:35 +0000 Subject: [PATCH 146/169] 8394910: Remove redundant initial_refcount parameter from PartialArrayState Reviewed-by: tschatzl --- .../share/gc/shared/partialArraySplitter.inline.hpp | 2 +- src/hotspot/share/gc/shared/partialArrayState.cpp | 11 ++++------- src/hotspot/share/gc/shared/partialArrayState.hpp | 4 ++-- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/hotspot/share/gc/shared/partialArraySplitter.inline.hpp b/src/hotspot/share/gc/shared/partialArraySplitter.inline.hpp index ba0ba4346bd3..dc702072f3f9 100644 --- a/src/hotspot/share/gc/shared/partialArraySplitter.inline.hpp +++ b/src/hotspot/share/gc/shared/partialArraySplitter.inline.hpp @@ -54,7 +54,7 @@ PartialArraySplitter::start(Queue* queue, if (end < length) { TASKQUEUE_STATS_ONLY(_stats.inc_split();) PartialArrayState* state = - _allocator.allocate(array, end, length, chunk_size, 1); + _allocator.allocate(array, end, length, chunk_size); enqueue(queue, state, 1); } return Claim{array, 0, end}; diff --git a/src/hotspot/share/gc/shared/partialArrayState.cpp b/src/hotspot/share/gc/shared/partialArrayState.cpp index aaa820349c74..81d80b71ceb0 100644 --- a/src/hotspot/share/gc/shared/partialArrayState.cpp +++ b/src/hotspot/share/gc/shared/partialArrayState.cpp @@ -35,18 +35,16 @@ PartialArrayState::PartialArrayState(objArrayOop array, size_t index, size_t length, - size_t chunk_size, - size_t initial_refcount) + size_t chunk_size) : _array(array), _length(length), _chunk_size(chunk_size), _index(index), - _refcount(initial_refcount) + _refcount(1) { assert(index <= length, "precondition"); assert(chunk_size > 0, "precondition"); assert((length - index) % chunk_size == 0, "whole chunks remain"); - assert(initial_refcount > 0, "must have a task reference"); } void PartialArrayState::add_references(size_t count) { @@ -81,8 +79,7 @@ PartialArrayStateAllocator::~PartialArrayStateAllocator() { PartialArrayState* PartialArrayStateAllocator::allocate(objArrayOop array, size_t index, size_t length, - size_t chunk_size, - size_t initial_refcount) { + size_t chunk_size) { void* p; FreeListEntry* head = _free_list; if (head == nullptr) { @@ -92,7 +89,7 @@ PartialArrayState* PartialArrayStateAllocator::allocate(objArrayOop array, head->~FreeListEntry(); p = head; } - return ::new (p) PartialArrayState(array, index, length, chunk_size, initial_refcount); + return ::new (p) PartialArrayState(array, index, length, chunk_size); } void PartialArrayStateAllocator::release(PartialArrayState* state) { diff --git a/src/hotspot/share/gc/shared/partialArrayState.hpp b/src/hotspot/share/gc/shared/partialArrayState.hpp index 04852ae741eb..302d73ae879c 100644 --- a/src/hotspot/share/gc/shared/partialArrayState.hpp +++ b/src/hotspot/share/gc/shared/partialArrayState.hpp @@ -66,7 +66,7 @@ class PartialArrayState { PartialArrayState(objArrayOop array, size_t index, size_t length, - size_t chunk_size, size_t initial_refcount); + size_t chunk_size); // Increment the ref-count by count before publishing that many tasks to a queue. void add_references(size_t count); @@ -121,7 +121,7 @@ class PartialArrayStateAllocator : public CHeapObj { PartialArrayState* allocate(objArrayOop array, size_t index, size_t length, - size_t chunk_size, size_t initial_refcount); + size_t chunk_size); // Decrement the state's ref-count by one. If it reaches zero, recycle on // this allocator's free-list. The state must belong to the same manager. From 145e171a7a00930ff95158d5d777acd6bfddc997 Mon Sep 17 00:00:00 2001 From: Jasmine Karthikeyan Date: Wed, 21 Oct 2026 01:27:14 +0000 Subject: [PATCH 147/169] 8394230: C2: Unexpected long CountBits nodes in SuperWord truncation Reviewed-by: thartmann, qamai --- src/hotspot/share/opto/superword.cpp | 3 ++ .../vectorization/TestSubwordTruncation.java | 51 ++++++++++++++++++- 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/src/hotspot/share/opto/superword.cpp b/src/hotspot/share/opto/superword.cpp index 3c03b492b0ff..9ab788a344eb 100644 --- a/src/hotspot/share/opto/superword.cpp +++ b/src/hotspot/share/opto/superword.cpp @@ -2516,12 +2516,15 @@ static bool can_subword_truncate(Node* in, const Type* type) { case Op_RotateRight: case Op_RotateLeft: case Op_PopCountI: + case Op_PopCountL: case Op_ReverseBytesS: case Op_ReverseBytesUS: case Op_ReverseBytesI: case Op_ReverseI: case Op_CountLeadingZerosI: + case Op_CountLeadingZerosL: case Op_CountTrailingZerosI: + case Op_CountTrailingZerosL: case Op_IsFiniteF: case Op_IsFiniteD: case Op_IsInfiniteF: diff --git a/test/hotspot/jtreg/compiler/vectorization/TestSubwordTruncation.java b/test/hotspot/jtreg/compiler/vectorization/TestSubwordTruncation.java index 0a80a59efd43..9f5a90f2e03b 100644 --- a/test/hotspot/jtreg/compiler/vectorization/TestSubwordTruncation.java +++ b/test/hotspot/jtreg/compiler/vectorization/TestSubwordTruncation.java @@ -29,7 +29,7 @@ /* * @test - * @bug 8350177 8362171 8369881 8342095 8380988 + * @bug 8350177 8362171 8369881 8342095 8380988 8394230 * @summary Ensure that truncation of subword vectors produces correct results * @library /test/lib / * @run driver compiler.vectorization.TestSubwordTruncation @@ -70,6 +70,16 @@ static Object[] setupCharArray() { return new Object[] { arr }; } + @Setup + static Object[] setupLongArray() { + long[] arr = new long[SIZE]; + for (int i = 0; i < SIZE; i++) { + arr[i] = G.next().longValue(); + } + + return new Object[] { arr }; + } + // Shorts @Test @@ -557,6 +567,45 @@ public Object[] testCastII() { return new Object[] { bytes }; } + @Test + @IR(counts = { IRNode.POPCOUNT_L, ">0" }) + @Arguments(setup = "setupLongArray") + public Object[] testPopCountL(long[] in) { + byte[] res = new byte[SIZE]; + + for (int i = 0; i < SIZE; i++) { + res[i] = (byte) Long.bitCount(in[i]); + } + + return new Object[] { res }; + } + + @Test + @IR(counts = { IRNode.COUNT_LEADING_ZEROS_L, ">0" }) + @Arguments(setup = "setupLongArray") + public Object[] testCountLeadingZerosL(long[] in) { + byte[] res = new byte[SIZE]; + + for (int i = 0; i < SIZE; i++) { + res[i] = (byte) Long.numberOfLeadingZeros(in[i]); + } + + return new Object[] { res }; + } + + @Test + @IR(counts = { IRNode.COUNT_TRAILING_ZEROS_L, ">0" }) + @Arguments(setup = "setupLongArray") + public Object[] testCountTrailingZerosL(long[] in) { + byte[] res = new byte[SIZE]; + + for (int i = 0; i < SIZE; i++) { + res[i] = (byte) Long.numberOfTrailingZeros(in[i]); + } + + return new Object[] { res }; + } + public static void main(String[] args) { TestFramework.run(); } From fd09c382c41e9798c518639f345d9ce85f3fd1c9 Mon Sep 17 00:00:00 2001 From: Quan Anh Mai Date: Wed, 21 Oct 2026 02:56:15 +0000 Subject: [PATCH 148/169] 8394736: C2: Trap profile pollution can disable Preconditions.checkIndex intrinsification Reviewed-by: vlivanov, kvn --- src/hotspot/share/opto/library_call.cpp | 17 ++- .../compiler/intrinsics/TestCheckIndex.java | 18 +-- .../intrinsics/TestPollutedCheckIndex.java | 125 ++++++++++++++++++ .../TestLoopNestTooManyTraps.java | 2 +- .../rangechecks/TestLongRangeCheck.java | 7 - 5 files changed, 147 insertions(+), 22 deletions(-) create mode 100644 test/hotspot/jtreg/compiler/intrinsics/TestPollutedCheckIndex.java diff --git a/src/hotspot/share/opto/library_call.cpp b/src/hotspot/share/opto/library_call.cpp index c7da58aeb863..1cc670a135f7 100644 --- a/src/hotspot/share/opto/library_call.cpp +++ b/src/hotspot/share/opto/library_call.cpp @@ -1202,21 +1202,26 @@ bool LibraryCallKit::inline_countPositives() { return true; } +// +// int checkIndex(int index, int length, +// BiFunction, X> oobef) +// +// long checkIndex(long index, long length, +// BiFunction, X> oobef) bool LibraryCallKit::inline_preconditions_checkIndex(BasicType bt) { Node* index = argument(0); Node* length = bt == T_INT ? argument(1) : argument(2); - if (too_many_traps(Deoptimization::Reason_intrinsic) || too_many_traps(Deoptimization::Reason_range_check)) { - return false; - } // check that length is positive Node* len_pos_cmp = _gvn.transform(CmpNode::make(length, integercon(0, bt), bt)); Node* len_pos_bol = _gvn.transform(new BoolNode(len_pos_cmp, BoolTest::ge)); { + // Let the intepreter throw the exception since the actual exception that needs throwing is the + // result of oobef, which is not trivial BuildCutout unless(this, len_pos_bol, PROB_MAX); uncommon_trap(Deoptimization::Reason_intrinsic, - Deoptimization::Action_make_not_entrant); + Deoptimization::Action_none); } if (stopped()) { @@ -1244,10 +1249,12 @@ bool LibraryCallKit::inline_preconditions_checkIndex(BasicType bt) { } set_control(_gvn.transform(new IfTrueNode(rc))); { + // Let the intepreter throw the exception since the actual exception that needs throwing is the + // result of oobef, which is not trivial PreserveJVMState pjvms(this); set_control(_gvn.transform(new IfFalseNode(rc))); uncommon_trap(Deoptimization::Reason_range_check, - Deoptimization::Action_make_not_entrant); + Deoptimization::Action_none); } if (stopped()) { diff --git a/test/hotspot/jtreg/compiler/intrinsics/TestCheckIndex.java b/test/hotspot/jtreg/compiler/intrinsics/TestCheckIndex.java index d0c67fc7f151..5f0347ca4454 100644 --- a/test/hotspot/jtreg/compiler/intrinsics/TestCheckIndex.java +++ b/test/hotspot/jtreg/compiler/intrinsics/TestCheckIndex.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, Red Hat, Inc. All rights reserved. + * Copyright (c) 2020, 2026, Red Hat, Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -85,16 +85,16 @@ public static void main(String[] args) throws NoSuchMethodException { throw new RuntimeException("exception not thrown"); } catch (IndexOutOfBoundsException ioobe) { } - if (WHITE_BOX.isMethodCompiled(m1)) { - throw new RuntimeException("should have deoptimized"); + if (!WHITE_BOX.isMethodCompiled(m1)) { + throw new RuntimeException("should still be compiled"); } try { test2(0, -10); throw new RuntimeException("exception not thrown"); } catch (IndexOutOfBoundsException ioobe) { } - if (WHITE_BOX.isMethodCompiled(m2)) { - throw new RuntimeException("should have deoptimized"); + if (!WHITE_BOX.isMethodCompiled(m2)) { + throw new RuntimeException("should still be compiled"); } try { @@ -102,16 +102,16 @@ public static void main(String[] args) throws NoSuchMethodException { throw new RuntimeException("exception not thrown"); } catch (IndexOutOfBoundsException ioobe) { } - if (WHITE_BOX.isMethodCompiled(m3)) { - throw new RuntimeException("should have deoptimized"); + if (!WHITE_BOX.isMethodCompiled(m3)) { + throw new RuntimeException("should still be compiled"); } try { test4(42, 10); throw new RuntimeException("exception not thrown"); } catch (IndexOutOfBoundsException ioobe) { } - if (WHITE_BOX.isMethodCompiled(m4)) { - throw new RuntimeException("should have deoptimized"); + if (!WHITE_BOX.isMethodCompiled(m4)) { + throw new RuntimeException("should still be compiled"); } } diff --git a/test/hotspot/jtreg/compiler/intrinsics/TestPollutedCheckIndex.java b/test/hotspot/jtreg/compiler/intrinsics/TestPollutedCheckIndex.java new file mode 100644 index 000000000000..baa9fe9000f3 --- /dev/null +++ b/test/hotspot/jtreg/compiler/intrinsics/TestPollutedCheckIndex.java @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package compiler.intrinsics; + +import compiler.lib.ir_framework.*; + +import java.lang.foreign.Arena; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; +import java.util.Objects; + +/* + * @test + * @bug 8394736 + * @summary When a call to Objects::checkIndex fails and hits the uncommon trap, the compiler + * should still intrinsify Preconditions::checkIndex at all other calls to + * Objects::checkIndex. + * @library /test/lib / + * @run driver ${test.main.class} + */ +public class TestPollutedCheckIndex { + private static final MemorySegment SEGMENT = Arena.global().allocate(Integer.BYTES); + + public static void main(String[] args) { + var framework = new TestFramework(); + framework.setDefaultWarmup(1); + // Ensure the stability of the tests + framework.addFlags("-XX:-CompactStrings", + "-XX:CompileCommand=dontinline,*::outOfBoundsCheckIndex", + "-XX:CompileCommand=inline,java.lang.String::*", + "-XX:CompileCommand=inline,java.lang.StringUTF16::*"); + framework.addScenarios(new Scenario(0)); + framework.addScenarios(new Scenario(1, "-XX:PerMethodTrapLimit=0")); + framework.start(); + } + + private static void polluteInt() { + try { + Objects.checkIndex(1, 0); + throw new RuntimeException(); + } catch (IndexOutOfBoundsException _) {} + } + + private static void polluteLong() { + try { + Objects.checkIndex(1L, 0L); + throw new RuntimeException(); + } catch (IndexOutOfBoundsException _) {} + } + + private static void polluteString() { + try { + // This trap pollutes String::checkIndex, preventing Preconditions::checkIndex from + // intrinsifying from there + int _ = "".codePointAt(1); + throw new RuntimeException(); + } catch (IndexOutOfBoundsException _) {} + } + + private static void polluteMemorySegment() { + try { + SEGMENT.set(ValueLayout.JAVA_INT_UNALIGNED, 4, 0); + throw new RuntimeException(); + } catch (IndexOutOfBoundsException _) {} + } + + @Test + @IR(counts = {IRNode.RANGE_CHECK_TRAP, "1"}) + private static int testInt(int idx, int limit) { + return Objects.checkIndex(idx, limit); + } + + @Test + @IR(counts = {IRNode.RANGE_CHECK_TRAP, "1"}) + private static long testLong(long idx, long limit) { + return Objects.checkIndex(idx, limit); + } + + @Test + @IR(counts = {IRNode.RANGE_CHECK_TRAP, "1"}) + private static char testString(String str, int idx) { + return str.charAt(idx); + } + + @Test + @IR(counts = {IRNode.RANGE_CHECK_TRAP, "1"}) + private static int testMemorySegment(MemorySegment ms, long offset) { + return ms.get(ValueLayout.JAVA_INT_UNALIGNED, offset); + } + + @Run(test = {"testInt", "testLong", "testString", "testMemorySegment"}) + public void run() { + for (int i = 0; i < 20000; i++) { + polluteInt(); + polluteLong(); + polluteString(); + polluteMemorySegment(); + } + + testInt(0, 1); + testLong(0, 1); + testString("a", 0); + testMemorySegment(SEGMENT, 0); + } +} diff --git a/test/hotspot/jtreg/compiler/longcountedloops/TestLoopNestTooManyTraps.java b/test/hotspot/jtreg/compiler/longcountedloops/TestLoopNestTooManyTraps.java index c2008a2cccb0..8a1f11d213b8 100644 --- a/test/hotspot/jtreg/compiler/longcountedloops/TestLoopNestTooManyTraps.java +++ b/test/hotspot/jtreg/compiler/longcountedloops/TestLoopNestTooManyTraps.java @@ -60,7 +60,7 @@ public static void main(String[] args) throws NoSuchMethodException { test1(10000, 1000); } catch (IndexOutOfBoundsException ioobe) { } - if (j <= 1) { + if (j == 0) { if (WHITE_BOX.isMethodCompiled(methodTest1)) { throw new RuntimeException("test1 should have deoptimized at iteration " + j); } diff --git a/test/hotspot/jtreg/compiler/rangechecks/TestLongRangeCheck.java b/test/hotspot/jtreg/compiler/rangechecks/TestLongRangeCheck.java index 1c4fbb8cc9c0..6f8233bc70aa 100644 --- a/test/hotspot/jtreg/compiler/rangechecks/TestLongRangeCheck.java +++ b/test/hotspot/jtreg/compiler/rangechecks/TestLongRangeCheck.java @@ -95,7 +95,6 @@ private static void test(String method, long start, long stop, long length, long throw new RuntimeException("unexpected exception"); } } - assertIsNotCompiled(m); m = newClassLoader().loadClass("TestLongRangeCheck").getDeclaredMethod(method, long.class, long.class, long.class, long.class); m.invoke(null, start, stop, length, offset); // run once so all classes are loaded @@ -112,7 +111,6 @@ private static void test(String method, long start, long stop, long length, long throw new RuntimeException("unexpected exception"); } } - assertIsNotCompiled(m); m = newClassLoader().loadClass("TestLongRangeCheck").getDeclaredMethod(method, long.class, long.class, long.class, long.class); m.invoke(null, start, stop, length, offset); // run once so all classes are loaded @@ -128,7 +126,6 @@ private static void test(String method, long start, long stop, long length, long throw new RuntimeException("unexpected exception"); } } - assertIsNotCompiled(m); } private static void testOverflow(String method, long start, long stop, long length, long offset0, long offset1) throws Exception { @@ -146,7 +143,6 @@ private static void testOverflow(String method, long start, long stop, long leng throw new RuntimeException("unexpected exception"); } } - assertIsNotCompiled(m); } private static void testConditional(String method, long start, long stop, long length, long offset0, long offset1, long start1, long stop1) throws Exception { @@ -167,7 +163,6 @@ private static void testConditional(String method, long start, long stop, long l throw new RuntimeException("unexpected exception"); } } - assertIsNotCompiled(m); } if (stop1 != stop) { @@ -185,7 +180,6 @@ private static void testConditional(String method, long start, long stop, long l throw new RuntimeException("unexpected exception"); } } - assertIsNotCompiled(m); } m = newClassLoader().loadClass("TestLongRangeCheck").getDeclaredMethod(method, long.class, long.class, long.class, long.class, long.class, long.class); @@ -328,7 +322,6 @@ public static void main(String[] args) throws Exception { throw new RuntimeException("unexpected exception"); } } - assertIsNotCompiled(m); } } From 6c98dd4b002d78f3a50a7c648d91026356a3a8b4 Mon Sep 17 00:00:00 2001 From: Dingli Zhang Date: Wed, 21 Oct 2026 03:23:15 +0000 Subject: [PATCH 149/169] 8394822: RISC-V: CRC32/CRC32C intrinsic results are not sign-extended Reviewed-by: fyang, ayang --- .../cpu/riscv/c1_LIRAssembler_riscv.cpp | 1 + src/hotspot/cpu/riscv/stubGenerator_riscv.cpp | 2 + .../c2/riscv64/TestCRCSignExtension.java | 126 ++++++++++++++++++ 3 files changed, 129 insertions(+) create mode 100644 test/hotspot/jtreg/compiler/c2/riscv64/TestCRCSignExtension.java diff --git a/src/hotspot/cpu/riscv/c1_LIRAssembler_riscv.cpp b/src/hotspot/cpu/riscv/c1_LIRAssembler_riscv.cpp index a47b2e98b277..f73e41bcb4ba 100644 --- a/src/hotspot/cpu/riscv/c1_LIRAssembler_riscv.cpp +++ b/src/hotspot/cpu/riscv/c1_LIRAssembler_riscv.cpp @@ -1704,6 +1704,7 @@ void LIR_Assembler::emit_updatecrc32(LIR_OpUpdateCRC32* op) { __ zext(crc, crc, 32); __ update_byte_crc32(crc, val, res); __ notr(res, crc); // ~crc + __ sext(res, res, 32); } void LIR_Assembler::check_conflict(ciKlass* exact_klass, intptr_t current_klass, diff --git a/src/hotspot/cpu/riscv/stubGenerator_riscv.cpp b/src/hotspot/cpu/riscv/stubGenerator_riscv.cpp index d2a5b98b79c9..9793dddb68be 100644 --- a/src/hotspot/cpu/riscv/stubGenerator_riscv.cpp +++ b/src/hotspot/cpu/riscv/stubGenerator_riscv.cpp @@ -7838,6 +7838,7 @@ static const int64_t right_3_bits = right_n_bits(3); __ kernel_crc32(crc, buf, len, c_rarg3, c_rarg4, c_rarg5, c_rarg6, // tmp's for tables c_rarg7, t2, t3, t4, t5, t6); // misc tmps + __ sext(crc, crc, 32); __ leave(); // required for proper stackwalking of RuntimeStub frame __ ret(); @@ -7875,6 +7876,7 @@ static const int64_t right_3_bits = right_n_bits(3); c_rarg3, c_rarg4, // byte_table, clmul_table c_rarg5, c_rarg6, c_rarg7, // accum_hi, k1, k2 t2, t3, t4); // scratch1, scratch2, fold_end + __ sext(crc, crc, 32); __ leave(); // required for proper stackwalking of RuntimeStub frame __ ret(); diff --git a/test/hotspot/jtreg/compiler/c2/riscv64/TestCRCSignExtension.java b/test/hotspot/jtreg/compiler/c2/riscv64/TestCRCSignExtension.java new file mode 100644 index 000000000000..6457bcdacf86 --- /dev/null +++ b/test/hotspot/jtreg/compiler/c2/riscv64/TestCRCSignExtension.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2026, Institute of Software, Chinese Academy of Sciences. + * All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test id=c1 + * @bug 8394822 + * @summary Verify sign extension of RISC-V CRC32 and CRC32C intrinsic results + * @requires os.arch == "riscv64" & vm.compiler1.enabled + * @modules java.base/java.util.zip:+open + * @run main/othervm -Xbatch -XX:+UnlockDiagnosticVMOptions -XX:+UseCRC32Intrinsics -XX:+UseCRC32CIntrinsics + * -XX:+TieredCompilation -XX:TieredStopAtLevel=1 -XX:CompileThreshold=1000 + * -XX:CompileCommand=compileonly,compiler.c2.riscv64.TestCRCSignExtension::test* + * compiler.c2.riscv64.TestCRCSignExtension + */ + +/* + * @test id=c2 + * @bug 8394822 + * @summary Verify sign extension of RISC-V CRC32 and CRC32C intrinsic results + * @requires os.arch == "riscv64" & vm.compiler2.enabled + * @modules java.base/java.util.zip:+open + * @run main/othervm -Xbatch -XX:+UnlockDiagnosticVMOptions -XX:+UseCRC32Intrinsics -XX:+UseCRC32CIntrinsics + * -XX:-TieredCompilation -XX:CompileThreshold=1000 + * -XX:CompileCommand=compileonly,compiler.c2.riscv64.TestCRCSignExtension::test* + * compiler.c2.riscv64.TestCRCSignExtension + */ + +package compiler.c2.riscv64; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.nio.charset.StandardCharsets; +import java.util.zip.CRC32; +import java.util.zip.CRC32C; + +public class TestCRCSignExtension { + private static final MethodHandle CRC32_UPDATE; + private static final MethodHandle CRC32_UPDATE_BYTES; + private static final MethodHandle CRC32C_UPDATE_BYTES; + + static { + try { + var lookup32 = MethodHandles.privateLookupIn(CRC32.class, MethodHandles.lookup()); + var lookup32c = MethodHandles.privateLookupIn(CRC32C.class, MethodHandles.lookup()); + var byteType = MethodType.methodType(int.class, int.class, int.class); + var arrayType = MethodType.methodType(int.class, int.class, byte[].class, int.class, int.class); + CRC32_UPDATE = lookup32.findStatic(CRC32.class, "update", byteType); + CRC32_UPDATE_BYTES = lookup32.findStatic(CRC32.class, "updateBytes0", arrayType); + CRC32C_UPDATE_BYTES = lookup32c.findStatic(CRC32C.class, "updateBytes", arrayType); + } catch (ReflectiveOperationException e) { + throw new ExceptionInInitializerError(e); + } + } + + // Compare before a field store or an interpreted return can normalize the result. + static boolean testUpdateByte(int b, int expected) throws Throwable { + // Fresh CRC32 state passed to the private native. + return (int) CRC32_UPDATE.invokeExact(0, b) == expected; + } + + static boolean testUpdateBytesCRC32(byte[] data, int expected) throws Throwable { + return (int) CRC32_UPDATE_BYTES.invokeExact(0, data, 0, data.length) == expected; + } + + static boolean testUpdateBytesCRC32C(byte[] data, int expected) throws Throwable { + // A fresh CRC32C starts from the raw state 0xffffffff. + return (int) CRC32C_UPDATE_BYTES.invokeExact(0xffffffff, data, 0, data.length) == expected; + } + + static void check(boolean ok, int i, String what) { + if (!ok) { + throw new RuntimeException(what + " mismatch at iteration " + i); + } + } + + public static void main(String[] args) throws Throwable { + // One input per polarity, each chosen so CRC32 and the raw CRC32C state agree, so a single + // pair of arrays covers both stubs. + byte[] clear = "b".getBytes(StandardCharsets.US_ASCII); // bit 31 clear for both + byte[] set = "e".getBytes(StandardCharsets.US_ASCII); // bit 31 set for both + + // Enough iterations to reach -XX:CompileThreshold and compile the test methods with C1 or C2. + for (int i = 0; i < 2_000; i++) { + // CRC32.update(int): C1's inline updateCRC32. Its single byte is the array's only byte, + // so the expected value equals the CRC32 array one below. + // python3 -c "import zlib; print(hex(zlib.crc32(bytes([ord('b')]), 0)))" # 0x71beeff9 + check(testUpdateByte('b', 0x71beeff9), i, "CRC32.update(int) bit31 clear"); + // python3 -c "import zlib; print(hex(zlib.crc32(bytes([ord('e')]), 0)))" # 0xefda7a5a + check(testUpdateByte('e', 0xefda7a5a), i, "CRC32.update(int) bit31 set"); + + // updateBytesCRC32 stub. + // python3 -c "import zlib; print(hex(zlib.crc32(b'b')))" # 0x71beeff9 + check(testUpdateBytesCRC32(clear, 0x71beeff9), i, "CRC32.update(byte[]) bit31 clear"); + // python3 -c "import zlib; print(hex(zlib.crc32(b'e')))" # 0xefda7a5a + check(testUpdateBytesCRC32(set, 0xefda7a5a), i, "CRC32.update(byte[]) bit31 set"); + + // updateBytesCRC32C stub; the raw state is the complement of getValue(). + // python3 -c "from crc32c import crc32c; print(hex((~crc32c(b'b')) & 0xffffffff))" # 0x2d7f4f3b + check(testUpdateBytesCRC32C(clear, 0x2d7f4f3b), i, "CRC32C.update(byte[]) bit31 clear"); + // python3 -c "from crc32c import crc32c; print(hex((~crc32c(b'e')) & 0xffffffff))" # 0xf9b52bd0 + check(testUpdateBytesCRC32C(set, 0xf9b52bd0), i, "CRC32C.update(byte[]) bit31 set"); + } + } +} From e48cc5f341ac3032622086cf88ee9b7fad0b85ac Mon Sep 17 00:00:00 2001 From: Dingli Zhang Date: Wed, 21 Oct 2026 03:25:20 +0000 Subject: [PATCH 150/169] 8394902: RISC-V: Add vector rearrange support for small lane count vectors Reviewed-by: gcao, fyang --- src/hotspot/cpu/riscv/riscv_v.ad | 9 ------- .../vectorapi/VectorRearrangeTest.java | 24 +++++++++---------- 2 files changed, 12 insertions(+), 21 deletions(-) diff --git a/src/hotspot/cpu/riscv/riscv_v.ad b/src/hotspot/cpu/riscv/riscv_v.ad index f7951a4a2d12..f3e3a7c47ab8 100644 --- a/src/hotspot/cpu/riscv/riscv_v.ad +++ b/src/hotspot/cpu/riscv/riscv_v.ad @@ -101,15 +101,6 @@ source %{ return false; } break; - case Op_VectorLoadShuffle: - case Op_VectorRearrange: - // vlen >= 4 is required, because min vector size for byte is 4 on riscv, - // VectorLoadShuffle is from byte to X, so it requires vlen >= 4. - // VectorRearrange depends on VectorLoadShuffle, so it also requires vlen >= 4. - if (vlen < 4) { - return false; - } - break; case Op_MulReductionVI: case Op_MulReductionVL: // When vlen < 4, our log2(vlen) implementation does not help to gain performance improvement. diff --git a/test/hotspot/jtreg/compiler/vectorapi/VectorRearrangeTest.java b/test/hotspot/jtreg/compiler/vectorapi/VectorRearrangeTest.java index f2d172b88881..eb900fe0056c 100644 --- a/test/hotspot/jtreg/compiler/vectorapi/VectorRearrangeTest.java +++ b/test/hotspot/jtreg/compiler/vectorapi/VectorRearrangeTest.java @@ -23,8 +23,8 @@ /** * @test - * @bug 8350463 - * @summary AArch64: Add vector rearrange support for small lane count vectors + * @bug 8350463 8394902 + * @summary Test vector rearrange support for small lane count vectors * @modules jdk.incubator.vector * @library /test/lib / * @@ -103,7 +103,7 @@ public class VectorRearrangeTest { } @Test - @IR(counts = {IRNode.REARRANGE_VB, IRNode.VECTOR_SIZE_8, " >0 "}, applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}) + @IR(counts = {IRNode.REARRANGE_VB, IRNode.VECTOR_SIZE_8, " >0 "}, applyIfCPUFeatureOr = {"avx", "true", "asimd", "true", "rvv", "true"}) public void rearrange_byte64() { VectorShuffle shuffle = VectorShuffle.fromArray(bspec64, indexes[2], 0); for (int i = 0; i < LENGTH; i += bspec64.length()) { @@ -123,7 +123,7 @@ public void rearrange_byte64_verify() { } @Test - @IR(counts = {IRNode.REARRANGE_VB, IRNode.VECTOR_SIZE_16, " >0 "}, applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}) + @IR(counts = {IRNode.REARRANGE_VB, IRNode.VECTOR_SIZE_16, " >0 "}, applyIfCPUFeatureOr = {"avx", "true", "asimd", "true", "rvv", "true"}) public void rearrange_byte128() { VectorShuffle shuffle = VectorShuffle.fromArray(bspec128, indexes[3], 0); for (int i = 0; i < LENGTH; i += bspec128.length()) { @@ -143,7 +143,7 @@ public void rearrange_byte128_verify() { } @Test - @IR(counts = {IRNode.REARRANGE_VS, IRNode.VECTOR_SIZE_4, " >0 "}, applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}) + @IR(counts = {IRNode.REARRANGE_VS, IRNode.VECTOR_SIZE_4, " >0 "}, applyIfCPUFeatureOr = {"avx", "true", "asimd", "true", "rvv", "true"}) public void rearrange_short64() { VectorShuffle shuffle = VectorShuffle.fromArray(sspec64, indexes[1], 0); for (int i = 0; i < LENGTH; i += sspec64.length()) { @@ -163,7 +163,7 @@ public void rearrange_short64_verify() { } @Test - @IR(counts = {IRNode.REARRANGE_VS, IRNode.VECTOR_SIZE_8, " >0 "}, applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}) + @IR(counts = {IRNode.REARRANGE_VS, IRNode.VECTOR_SIZE_8, " >0 "}, applyIfCPUFeatureOr = {"avx", "true", "asimd", "true", "rvv", "true"}) public void rearrange_short128() { VectorShuffle shuffle = VectorShuffle.fromArray(sspec128, indexes[2], 0); for (int i = 0; i < LENGTH; i += sspec128.length()) { @@ -183,7 +183,7 @@ public void rearrange_short128_verify() { } @Test - @IR(counts = {IRNode.REARRANGE_VI, IRNode.VECTOR_SIZE_2, " >0 "}, applyIfCPUFeature = {"asimd", "true"}) + @IR(counts = {IRNode.REARRANGE_VI, IRNode.VECTOR_SIZE_2, " >0 "}, applyIfCPUFeatureOr = {"asimd", "true", "rvv", "true"}) public void rearrange_int64() { VectorShuffle shuffle = VectorShuffle.fromArray(ispec64, indexes[0], 0); for (int i = 0; i < LENGTH; i += ispec64.length()) { @@ -203,7 +203,7 @@ public void rearrange_int64_verify() { } @Test - @IR(counts = {IRNode.REARRANGE_VI, IRNode.VECTOR_SIZE_4, " >0 "}, applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}) + @IR(counts = {IRNode.REARRANGE_VI, IRNode.VECTOR_SIZE_4, " >0 "}, applyIfCPUFeatureOr = {"avx", "true", "asimd", "true", "rvv", "true"}) public void rearrange_int128() { VectorShuffle shuffle = VectorShuffle.fromArray(ispec128, indexes[1], 0); for (int i = 0; i < LENGTH; i += ispec128.length()) { @@ -223,7 +223,7 @@ public void rearrange_int128_verify() { } @Test - @IR(counts = {IRNode.REARRANGE_VL, IRNode.VECTOR_SIZE_2, " >0 "}, applyIfCPUFeature = {"asimd", "true"}) + @IR(counts = {IRNode.REARRANGE_VL, IRNode.VECTOR_SIZE_2, " >0 "}, applyIfCPUFeatureOr = {"asimd", "true", "rvv", "true"}) public void rearrange_long128() { VectorShuffle shuffle = VectorShuffle.fromArray(lspec128, indexes[0], 0); for (int i = 0; i < LENGTH; i += lspec128.length()) { @@ -243,7 +243,7 @@ public void rearrange_long128_verify() { } @Test - @IR(counts = {IRNode.REARRANGE_VF, IRNode.VECTOR_SIZE_2, " >0 "}, applyIfCPUFeature = {"asimd", "true"}) + @IR(counts = {IRNode.REARRANGE_VF, IRNode.VECTOR_SIZE_2, " >0 "}, applyIfCPUFeatureOr = {"asimd", "true", "rvv", "true"}) public void rearrange_float64() { VectorShuffle shuffle = VectorShuffle.fromArray(fspec64, indexes[0], 0); for (int i = 0; i < LENGTH; i += fspec64.length()) { @@ -263,7 +263,7 @@ public void rearrange_float64_verify() { } @Test - @IR(counts = {IRNode.REARRANGE_VF, IRNode.VECTOR_SIZE_4, " >0 "}, applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}) + @IR(counts = {IRNode.REARRANGE_VF, IRNode.VECTOR_SIZE_4, " >0 "}, applyIfCPUFeatureOr = {"avx", "true", "asimd", "true", "rvv", "true"}) public void rearrange_float128() { VectorShuffle shuffle = VectorShuffle.fromArray(fspec128, indexes[1], 0); for (int i = 0; i < LENGTH; i += fspec128.length()) { @@ -283,7 +283,7 @@ public void rearrange_float128_verify() { } @Test - @IR(counts = {IRNode.REARRANGE_VD, IRNode.VECTOR_SIZE_2, " >0 "}, applyIfCPUFeature = {"asimd", "true"}) + @IR(counts = {IRNode.REARRANGE_VD, IRNode.VECTOR_SIZE_2, " >0 "}, applyIfCPUFeatureOr = {"asimd", "true", "rvv", "true"}) public void rearrange_double128() { VectorShuffle shuffle = VectorShuffle.fromArray(dspec128, indexes[0], 0); for (int i = 0; i < LENGTH; i += dspec128.length()) { From ba5e0a5b846a9cebf6d89bf5536dc0a342cd31ca Mon Sep 17 00:00:00 2001 From: Jan Lahoda Date: Wed, 21 Oct 2026 05:47:27 +0000 Subject: [PATCH 151/169] 8393065: Wrong bytecode generated for a value class constructor referring to another instance of the same class Reviewed-by: vromero --- .../tools/javac/comp/LocalProxyVarsGen.java | 5 +++- .../LocalProxyVariablesRuntime.java | 25 +++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/LocalProxyVarsGen.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/LocalProxyVarsGen.java index 517dd9a89e35..960d204e4deb 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/LocalProxyVarsGen.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/LocalProxyVarsGen.java @@ -59,6 +59,8 @@ import com.sun.tools.javac.jvm.Target; import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.tree.JCTree.JCFieldAccess; +import com.sun.tools.javac.tree.JCTree.JCIdent; import com.sun.tools.javac.tree.JCTree.JCStatement; import com.sun.tools.javac.tree.TreeInfo; import com.sun.tools.javac.tree.TreeScanner; @@ -315,7 +317,8 @@ public void visitIdent(JCTree.JCIdent tree) { @Override public void visitSelect(JCTree.JCFieldAccess tree) { super.visitSelect(tree); - if (ctorPrologue && fieldToLocalMap.get(tree.sym) != null) { + if (ctorPrologue && fieldToLocalMap.get(tree.sym) != null && + TreeInfo.isThisOrSelectorDotThis(tree.selected)) { result = make.at(md).Ident(fieldToLocalMap.get(tree.sym)); rollback.put(result, tree); } else { diff --git a/test/langtools/tools/javac/valhalla/value-objects/LocalProxyVariablesRuntime.java b/test/langtools/tools/javac/valhalla/value-objects/LocalProxyVariablesRuntime.java index 42e33c7e445b..8e612a20b3ad 100644 --- a/test/langtools/tools/javac/valhalla/value-objects/LocalProxyVariablesRuntime.java +++ b/test/langtools/tools/javac/valhalla/value-objects/LocalProxyVariablesRuntime.java @@ -23,6 +23,7 @@ /** * @test + * @bug 8393065 * @enablePreview * @compile LocalProxyVariablesRuntime.java * @run main LocalProxyVariablesRuntime @@ -37,6 +38,8 @@ public static void main(String... args) { new Value3(0); new Value4(); new Value4(0); + new ValueCopy(new ValueCopy(1), 1, false); + new ValueCopy(new ValueCopy(2), 2, '0'); } private static value class Value1 { @@ -95,4 +98,26 @@ public Value4() {} public Value4(int x) {} } + + private static value class ValueCopy { + private final int value; + + public ValueCopy(int value) { + this.value = value; + } + + public ValueCopy(ValueCopy original, int expected, boolean b) { + this.value = original.value; + if (value != expected) { + throw new IllegalStateException("Expected: " + expected + ", got: " + value); + } + } + + public ValueCopy(ValueCopy original, int expected, char c) { + (this).value = original.value; + if (value != expected) { + throw new IllegalStateException("Expected: " + expected + ", got: " + value); + } + } + } } From 77656b94a9126d8fb7b2b90362a47625a44fb188 Mon Sep 17 00:00:00 2001 From: Albert Mingkun Yang Date: Wed, 21 Oct 2026 06:15:46 +0000 Subject: [PATCH 152/169] 8392766: RISC-V: Remove redundant la in TemplateTable::fast_binaryswitch Reviewed-by: gcao, fyang --- src/hotspot/cpu/riscv/templateTable_riscv.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/hotspot/cpu/riscv/templateTable_riscv.cpp b/src/hotspot/cpu/riscv/templateTable_riscv.cpp index beb85c02e9fb..02ad154bf652 100644 --- a/src/hotspot/cpu/riscv/templateTable_riscv.cpp +++ b/src/hotspot/cpu/riscv/templateTable_riscv.cpp @@ -2182,7 +2182,6 @@ void TemplateTable::fast_binaryswitch() { __ load_unsigned_byte(t0, Address(temp, 0)); __ add(xbcp, xbcp, j); - __ la(xbcp, Address(xbcp, 0)); __ dispatch_only(vtos, /*generate_poll*/true); // default case -> j = default offset @@ -2195,7 +2194,6 @@ void TemplateTable::fast_binaryswitch() { __ load_unsigned_byte(t0, Address(temp, 0)); __ add(xbcp, xbcp, j); - __ la(xbcp, Address(xbcp, 0)); __ dispatch_only(vtos, /*generate_poll*/true); } From f08cc50666486d8ba1652bc3cee23230171aa437 Mon Sep 17 00:00:00 2001 From: Jan Lahoda Date: Wed, 21 Oct 2026 07:36:10 +0000 Subject: [PATCH 153/169] 8391830: javac incorrectly considers types disjoint in instanceof when symbols are incomplete Reviewed-by: vromero --- .../com/sun/tools/javac/code/Symbol.java | 2 +- .../tools/javac/cast/IncompleteSymbol.java | 102 ++++++++++++++++++ 2 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 test/langtools/tools/javac/cast/IncompleteSymbol.java diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symbol.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symbol.java index 79773ac252f5..37fc5e7e9203 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symbol.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symbol.java @@ -416,7 +416,7 @@ public boolean isStrictInstance() { } public boolean isInterface() { - return (flags_field & INTERFACE) != 0; + return (flags() & INTERFACE) != 0; } public boolean isAbstract() { diff --git a/test/langtools/tools/javac/cast/IncompleteSymbol.java b/test/langtools/tools/javac/cast/IncompleteSymbol.java new file mode 100644 index 000000000000..96a5e7ebd1df --- /dev/null +++ b/test/langtools/tools/javac/cast/IncompleteSymbol.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8391830 + * @summary Verify that casting conversion works properly even for interfaces + * whose symbols where not completed yet. + * @library /tools/lib + * @modules + * jdk.compiler/com.sun.tools.javac.api + * jdk.compiler/com.sun.tools.javac.main + * @build toolbox.ToolBox toolbox.JavacTask + * @run junit IncompleteSymbol + */ + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import toolbox.JavacTask; +import toolbox.ToolBox; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; + +public class IncompleteSymbol { + Path base; + ToolBox tb = new ToolBox(); + + @Test + void testIncompleteSymbol() throws Exception { + Path classes = base.resolve("classes"); + Files.createDirectories(classes); + new JavacTask(tb) + .outdir(classes) + .sources(""" + package p; + public sealed interface A permits B {} + """, + """ + package p; + public sealed interface B extends A permits C {} + """, + """ + package p; + public abstract class X {} + """, + """ + package p; + public final class C extends X implements B {} + """) + .run() + .writeAll(); + new JavacTask(tb) + .outdir(classes) + .classpath(classes) + .sources(""" + package q; + + import p.A; + import p.X; + + class Test { + void test(A a) { + if (a instanceof X x) { } + } + } + """) + .run() + .writeAll(); + } + + @BeforeEach + public void setUp(TestInfo info) { + base = Paths.get(".") + .resolve(info.getTestMethod() + .orElseThrow() + .getName()); + } +} From a0785e62626f6b4e9a8bf7e3ca1bbd2c4b6f2133 Mon Sep 17 00:00:00 2001 From: Casper Norrbin Date: Wed, 21 Oct 2026 08:25:33 +0000 Subject: [PATCH 154/169] 8391648: Use a single non-oop field list for identity class layouts Reviewed-by: fparain, matsaave, coleenp --- .../share/classfile/fieldLayoutBuilder.cpp | 119 ++++++++++-------- .../share/classfile/fieldLayoutBuilder.hpp | 18 +-- 2 files changed, 75 insertions(+), 62 deletions(-) diff --git a/src/hotspot/share/classfile/fieldLayoutBuilder.cpp b/src/hotspot/share/classfile/fieldLayoutBuilder.cpp index d55adf8093f3..643f6f5767f5 100644 --- a/src/hotspot/share/classfile/fieldLayoutBuilder.cpp +++ b/src/hotspot/share/classfile/fieldLayoutBuilder.cpp @@ -187,19 +187,14 @@ bool LayoutRawBlock::fit(int size, int alignment) { FieldGroup::FieldGroup(int contended_group) : _next(nullptr), - _small_primitive_fields(nullptr), - _big_primitive_fields(nullptr), + _non_oop_fields(nullptr), _oop_fields(nullptr), _contended_group(contended_group) {} // -1 means no contended group, 0 means default contended group void FieldGroup::add_primitive_field(int idx, BasicType type) { int size = type2aelembytes(type); LayoutRawBlock* block = new LayoutRawBlock(idx, LayoutRawBlock::REGULAR, size, size /* alignment == size for primitive types */); - if (size >= heapOopSize) { - add_to_big_primitive_list(block); - } else { - add_to_small_primitive_list(block); - } + add_to_non_oop_list(block); } void FieldGroup::add_oop_field(int idx) { @@ -218,35 +213,20 @@ void FieldGroup::add_flat_field(int idx, ValueKlass* vk, LayoutKind lk) { LayoutRawBlock* block = new LayoutRawBlock(idx, LayoutRawBlock::FLAT, size, alignment); block->set_value_klass(vk); block->set_layout_kind(lk); - if (block->size() >= heapOopSize) { - add_to_big_primitive_list(block); - } else { - assert(!vk->contains_oops(), "Size of Value klass with oops should be >= heapOopSize"); - add_to_small_primitive_list(block); - } + add_to_non_oop_list(block); } void FieldGroup::sort_by_size() { - if (_small_primitive_fields != nullptr) { - _small_primitive_fields->sort(LayoutRawBlock::compare_size_inverted); - } - if (_big_primitive_fields != nullptr) { - _big_primitive_fields->sort(LayoutRawBlock::compare_size_inverted); + if (_non_oop_fields != nullptr) { + _non_oop_fields->sort(LayoutRawBlock::compare_size_inverted); } } -void FieldGroup::add_to_small_primitive_list(LayoutRawBlock* block) { - if (_small_primitive_fields == nullptr) { - _small_primitive_fields = new GrowableArray(INITIAL_LIST_SIZE); +void FieldGroup::add_to_non_oop_list(LayoutRawBlock* block) { + if (_non_oop_fields == nullptr) { + _non_oop_fields = new GrowableArray(INITIAL_LIST_SIZE); } - _small_primitive_fields->append(block); -} - -void FieldGroup::add_to_big_primitive_list(LayoutRawBlock* block) { - if (_big_primitive_fields == nullptr) { - _big_primitive_fields = new GrowableArray(INITIAL_LIST_SIZE); - } - _big_primitive_fields->append(block); + _non_oop_fields->append(block); } FieldLayout::FieldLayout(GrowableArray* field_info, Array* value_field_info_array, ConstantPool* cp) : @@ -310,18 +290,41 @@ LayoutRawBlock* FieldLayout::first_field_block() { return block; } -// Insert a set of fields into a layout. -// For each field, search for an empty slot able to fit the field +// Return the first non-oop field smaller than a heap oop. +int FieldGroup::first_small_field_index() const { + if (_non_oop_fields == nullptr) { + return 0; + } + + int index = 0; + while (index < _non_oop_fields->length() && _non_oop_fields->at(index)->size() >= heapOopSize) { + index++; + } + return index; +} + +// Insert fields from a list into a layout. add() inserts the whole list, +// while add_range() inserts a half-open range of the list. +// For each selected field, search for an empty slot able to fit the field // (satisfying both size and alignment requirements), if none is found, // add the field at the end of the layout. // Fields cannot be inserted before the block specified in the "start" argument void FieldLayout::add(GrowableArray* list, LayoutRawBlock* start) { - if (list == nullptr) return; - if (start == nullptr) start = this->_start; + if (list == nullptr) { + return; + } + add_range(list, 0, list->length(), start); +} + +void FieldLayout::add_range(GrowableArray* list, int from, int to, LayoutRawBlock* start) { + assert(list != nullptr, "sanity"); + assert(0 <= from && from <= to && to <= list->length(), "sanity"); + + if (start == nullptr) start = _start; bool last_search_success = false; int last_size = 0; int last_alignment = 0; - for (int i = 0; i < list->length(); i ++) { + for (int i = from; i < to; i ++) { LayoutRawBlock* b = list->at(i); LayoutRawBlock* cursor = nullptr; LayoutRawBlock* candidate = nullptr; @@ -983,14 +986,14 @@ LayoutRawBlock* FieldLayoutBuilder::insert_contended_padding(LayoutRawBlock* slo // Computation of regular classes layout is an evolution of the previous default layout // (FieldAllocationStyle 1): -// - primitive fields (both primitive types and flat value types) are allocated +// - non-oop fields (both primitive types and flat value types) are allocated // first (from the biggest to the smallest) // - oop fields are allocated, either in existing gaps or at the end of // the layout. We allocate oops in a single block to have a single oop map entry. // - if the super class ended with an oop, we lead with oops. That will cause the // trailing oop map entry of the super class and the oop map entry of this class // to be folded into a single entry later. Correspondingly, if the super class -// ends with a primitive field, we gain nothing by leading with oops; therefore +// ends with a non-oop field, we gain nothing by leading with oops; therefore // we let oop fields trail, thus giving future derived classes the chance to apply // the same trick. void FieldLayoutBuilder::compute_regular_layout() { @@ -1011,11 +1014,9 @@ void FieldLayoutBuilder::compute_regular_layout() { if (_super_ends_with_oop) { _layout->add(_root_group->oop_fields()); - _layout->add(_root_group->big_primitive_fields()); - _layout->add(_root_group->small_primitive_fields()); + _layout->add(_root_group->non_oop_fields()); } else { - _layout->add(_root_group->big_primitive_fields()); - _layout->add(_root_group->small_primitive_fields()); + _layout->add(_root_group->non_oop_fields()); _layout->add(_root_group->oop_fields()); } @@ -1030,8 +1031,7 @@ void FieldLayoutBuilder::compute_regular_layout() { start = padding; } - _layout->add(cg->big_primitive_fields(), start); - _layout->add(cg->small_primitive_fields(), start); + _layout->add(cg->non_oop_fields(), start); _layout->add(cg->oop_fields(), start); need_tail_padding = true; } @@ -1041,10 +1041,9 @@ void FieldLayoutBuilder::compute_regular_layout() { insert_contended_padding(_layout->last_block()); } - // Warning: IntanceMirrorKlass expects static oops to be allocated first + // InstanceMirrorKlass expects static oop fields to be allocated first. _static_layout->add_contiguously(_static_fields->oop_fields()); - _static_layout->add(_static_fields->big_primitive_fields()); - _static_layout->add(_static_fields->small_primitive_fields()); + _static_layout->add(_static_fields->non_oop_fields()); epilogue(); } @@ -1056,10 +1055,10 @@ void FieldLayoutBuilder::compute_regular_layout() { // of value classes is to be embedded into other containers, it is critical // to keep their size as small as possible. For this reason, the allocation // strategy is: -// - big primitive fields (primitive types and flat value types larger -// than an oop) are allocated first (from the biggest to the smallest) +// - non-oop fields at least as large as a heap oop are allocated first +// (from the biggest to the smallest) // - then oop fields -// - then small primitive fields (from the biggest to the smallest) +// - then non-oop fields smaller than a heap oop (from the biggest to the smallest) // void FieldLayoutBuilder::compute_value_class_layout() { @@ -1149,9 +1148,21 @@ void FieldLayoutBuilder::compute_value_class_layout() { } } - _layout->add(_root_group->big_primitive_fields()); + // The non-oop fields are sorted in decreasing size order. Split the list around + // heapOopSize so oop fields can be placed between large and small fields. + GrowableArray* non_oop_fields = _root_group->non_oop_fields(); + int split_index = 0; + + if (non_oop_fields != nullptr) { + split_index = _root_group->first_small_field_index(); + _layout->add_range(non_oop_fields, 0, split_index); + } + _layout->add(_root_group->oop_fields()); - _layout->add(_root_group->small_primitive_fields()); + + if (non_oop_fields != nullptr) { + _layout->add_range(non_oop_fields, split_index, non_oop_fields->length()); + } LayoutRawBlock* first_field = _layout->first_field_block(); if (first_field != nullptr) { @@ -1309,10 +1320,9 @@ void FieldLayoutBuilder::compute_value_class_layout() { _payload_size_in_bytes = null_free_atomic_layout_size_in_bytes(); } } - // Warning:: InstanceMirrorKlass expects static oops to be allocated first + // InstanceMirrorKlass expects static oop fields to be allocated first. _static_layout->add_contiguously(_static_fields->oop_fields()); - _static_layout->add(_static_fields->big_primitive_fields()); - _static_layout->add(_static_fields->small_primitive_fields()); + _static_layout->add(_static_fields->non_oop_fields()); generate_acmp_maps(); epilogue(); @@ -1352,7 +1362,8 @@ void FieldLayoutBuilder::register_embedded_oops(OopMapBlocksBuilder* nonstatic_o nonstatic_oop_maps->add(b->offset(), 1); } } - register_embedded_oops_from_list(nonstatic_oop_maps, group->big_primitive_fields()); + // A non-oop block can be a flat field that contains embedded oops. + register_embedded_oops_from_list(nonstatic_oop_maps, group->non_oop_fields()); } static int insert_segment(GrowableArray* map, int offset, int size, int last_idx) { diff --git a/src/hotspot/share/classfile/fieldLayoutBuilder.hpp b/src/hotspot/share/classfile/fieldLayoutBuilder.hpp index e1594255515d..3f4c868da8be 100644 --- a/src/hotspot/share/classfile/fieldLayoutBuilder.hpp +++ b/src/hotspot/share/classfile/fieldLayoutBuilder.hpp @@ -132,16 +132,16 @@ class LayoutRawBlock : public ResourceObj { // A Field group represents a set of fields that have to be allocated together, // this is the way the @Contended annotation is supported. -// Inside a FieldGroup, fields are sorted based on their kind: primitive, -// oop, or flat. +// Fields are separated into oop fields and non-oop field blocks. +// Non-oop blocks include primitive fields and flat fields. +// Flat fields can themselves contain embedded oops. // class FieldGroup : public ResourceObj { private: FieldGroup* _next; - GrowableArray* _small_primitive_fields; - GrowableArray* _big_primitive_fields; + GrowableArray* _non_oop_fields; GrowableArray* _oop_fields; int _contended_group; static const int INITIAL_LIST_SIZE = 16; @@ -151,10 +151,11 @@ class FieldGroup : public ResourceObj { FieldGroup* next() const { return _next; } void set_next(FieldGroup* next) { _next = next; } - GrowableArray* small_primitive_fields() const { return _small_primitive_fields; } - GrowableArray* big_primitive_fields() const { return _big_primitive_fields; } + GrowableArray* non_oop_fields() const { return _non_oop_fields; } GrowableArray* oop_fields() const { return _oop_fields; } int contended_group() const { return _contended_group; } + // Requires the non-oop fields to be sorted in decreasing size order. + int first_small_field_index() const; void add_primitive_field(int idx, BasicType type); void add_oop_field(int idx); @@ -162,8 +163,7 @@ class FieldGroup : public ResourceObj { void add_block(LayoutRawBlock** list, LayoutRawBlock* block); void sort_by_size(); private: - void add_to_small_primitive_list(LayoutRawBlock* block); - void add_to_big_primitive_list(LayoutRawBlock* block); + void add_to_non_oop_list(LayoutRawBlock* block); }; // The FieldLayout class represents a set of fields organized @@ -232,6 +232,8 @@ class FieldLayout : public ResourceObj { LayoutRawBlock* first_field_block(); void add(GrowableArray* list, LayoutRawBlock* start = nullptr); + // Add the fields in the half-open range [from, to). + void add_range(GrowableArray* list, int from, int to, LayoutRawBlock* start = nullptr); void add_field_at_offset(LayoutRawBlock* blocks, int offset, LayoutRawBlock* start = nullptr); void add_contiguously(GrowableArray* list, LayoutRawBlock* start = nullptr); LayoutRawBlock* insert_field_block(LayoutRawBlock* slot, LayoutRawBlock* block); From 0ae62a1cf77fa31e18da72350357e5a37be63051 Mon Sep 17 00:00:00 2001 From: Albert Mingkun Yang Date: Wed, 21 Oct 2026 10:39:32 +0000 Subject: [PATCH 155/169] 8394912: Parallel: Remove redundant arg of PSPromotionManager::push_objArray Reviewed-by: tschatzl --- src/hotspot/share/gc/parallel/psPromotionManager.cpp | 4 +--- src/hotspot/share/gc/parallel/psPromotionManager.hpp | 2 +- src/hotspot/share/gc/parallel/psPromotionManager.inline.hpp | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/hotspot/share/gc/parallel/psPromotionManager.cpp b/src/hotspot/share/gc/parallel/psPromotionManager.cpp index 1d9e42529a26..628935ba4d53 100644 --- a/src/hotspot/share/gc/parallel/psPromotionManager.cpp +++ b/src/hotspot/share/gc/parallel/psPromotionManager.cpp @@ -270,9 +270,7 @@ void PSPromotionManager::process_array_chunk(PartialArrayState* state, bool stol process_array_chunk(claim._array, claim._start, claim._end); } -void PSPromotionManager::push_objArray(oop old_obj, oop new_obj) { - assert(old_obj->is_forwarded(), "precondition"); - assert(old_obj->forwardee() == new_obj, "precondition"); +void PSPromotionManager::push_objArray(oop new_obj) { precond(new_obj->is_array_with_oops()); objArrayOop to_array = objArrayOop(new_obj); diff --git a/src/hotspot/share/gc/parallel/psPromotionManager.hpp b/src/hotspot/share/gc/parallel/psPromotionManager.hpp index 287808429d3a..dd91fccd27c7 100644 --- a/src/hotspot/share/gc/parallel/psPromotionManager.hpp +++ b/src/hotspot/share/gc/parallel/psPromotionManager.hpp @@ -100,7 +100,7 @@ class PSPromotionManager { void process_array_chunk(PartialArrayState* state, bool stolen); void process_array_chunk(objArrayOop obj, size_t start, size_t end); - void push_objArray(oop old_obj, oop new_obj); + void push_objArray(oop new_obj); inline void promotion_trace_event(oop new_obj, Klass* klass, size_t obj_size, uint age, bool tenured, diff --git a/src/hotspot/share/gc/parallel/psPromotionManager.inline.hpp b/src/hotspot/share/gc/parallel/psPromotionManager.inline.hpp index 2f7fb7cd970e..fa2f1809db23 100644 --- a/src/hotspot/share/gc/parallel/psPromotionManager.inline.hpp +++ b/src/hotspot/share/gc/parallel/psPromotionManager.inline.hpp @@ -299,7 +299,7 @@ inline oop PSPromotionManager::copy_unmarked_to_survivor_space(oop o, // So, the is_array_with_oops test would be very infrequent. if (new_obj_size > _min_array_size_for_chunking && new_obj->is_array_with_oops()) { - push_objArray(o, new_obj); + push_objArray(new_obj); } else { // we'll just push its contents push_contents(new_obj, klass); From db2463a63a3d783d59308b107c1d0d0d9898dc38 Mon Sep 17 00:00:00 2001 From: wanglu Date: Wed, 21 Oct 2026 10:44:47 +0000 Subject: [PATCH 156/169] 8394824: [ASAN]tier1 Clhsdb core-dump SA tests fail on ASAN+fastdebug builds Reviewed-by: dholmes, sspitsyn, shade --- test/hotspot/jtreg/serviceability/sa/ClhsdbCDSCore.java | 1 + test/hotspot/jtreg/serviceability/sa/ClhsdbFindPC.java | 2 ++ test/hotspot/jtreg/serviceability/sa/ClhsdbPmap.java | 1 + test/hotspot/jtreg/serviceability/sa/ClhsdbPstack.java | 1 + 4 files changed, 5 insertions(+) diff --git a/test/hotspot/jtreg/serviceability/sa/ClhsdbCDSCore.java b/test/hotspot/jtreg/serviceability/sa/ClhsdbCDSCore.java index e6a6e55c7f74..eb5d59792c4c 100644 --- a/test/hotspot/jtreg/serviceability/sa/ClhsdbCDSCore.java +++ b/test/hotspot/jtreg/serviceability/sa/ClhsdbCDSCore.java @@ -27,6 +27,7 @@ * @summary Test the clhsdb commands 'printall', 'jstack' on a CDS enabled corefile. * @requires vm.hasSA * @requires !vm.ubsan + * @requires !vm.asan * @requires vm.gc != "Z" * @requires vm.cds * @requires vm.flavor == "server" diff --git a/test/hotspot/jtreg/serviceability/sa/ClhsdbFindPC.java b/test/hotspot/jtreg/serviceability/sa/ClhsdbFindPC.java index 821ce1356e5d..8222ca8b9539 100644 --- a/test/hotspot/jtreg/serviceability/sa/ClhsdbFindPC.java +++ b/test/hotspot/jtreg/serviceability/sa/ClhsdbFindPC.java @@ -51,6 +51,7 @@ * @summary Test the clhsdb 'findpc' command with Xcomp on core file * @requires vm.hasSA * @requires !vm.ubsan + * @requires !vm.asan * @requires vm.gc != "Z" * @requires vm.compMode != "Xcomp" * @requires vm.compiler1.enabled @@ -77,6 +78,7 @@ * @summary Test the clhsdb 'findpc' command w/o Xcomp on core file * @requires vm.hasSA * @requires !vm.ubsan + * @requires !vm.asan * @requires vm.gc != "Z" * @requires vm.compiler1.enabled * @library /test/lib diff --git a/test/hotspot/jtreg/serviceability/sa/ClhsdbPmap.java b/test/hotspot/jtreg/serviceability/sa/ClhsdbPmap.java index 4079ee6f2b23..b8042a65f17e 100644 --- a/test/hotspot/jtreg/serviceability/sa/ClhsdbPmap.java +++ b/test/hotspot/jtreg/serviceability/sa/ClhsdbPmap.java @@ -47,6 +47,7 @@ * @summary Test clhsdb pmap command on a core file * @requires vm.hasSA * @requires !vm.ubsan + * @requires !vm.asan * @requires vm.gc != "Z" * @library /test/lib * @run main/othervm/timeout=480 ClhsdbPmap true diff --git a/test/hotspot/jtreg/serviceability/sa/ClhsdbPstack.java b/test/hotspot/jtreg/serviceability/sa/ClhsdbPstack.java index 8f06cf755188..1d64f9687b4b 100644 --- a/test/hotspot/jtreg/serviceability/sa/ClhsdbPstack.java +++ b/test/hotspot/jtreg/serviceability/sa/ClhsdbPstack.java @@ -47,6 +47,7 @@ * @summary Test clhsdb pstack command on a core file * @requires vm.hasSA * @requires !vm.ubsan + * @requires !vm.asan * @requires vm.gc != "Z" * @library /test/lib * @run main/othervm/timeout=480 ClhsdbPstack true From 7cc7df7a5cb9106af7a6f9d4017b54f7b36a982a Mon Sep 17 00:00:00 2001 From: David Briemann Date: Wed, 21 Oct 2026 10:46:11 +0000 Subject: [PATCH 157/169] 8392768: PPC64: Optimize narrow oop constant loads for 31-bit compressed oop ranges Reviewed-by: mdoerr, rrich --- src/hotspot/cpu/ppc/c2_init_ppc.cpp | 8 ++- src/hotspot/cpu/ppc/macroAssembler_ppc.cpp | 66 ++++++------------- src/hotspot/cpu/ppc/macroAssembler_ppc.hpp | 14 +++- .../cpu/ppc/macroAssembler_ppc.inline.hpp | 16 ++--- src/hotspot/cpu/ppc/ppc.ad | 24 +++++++ 5 files changed, 68 insertions(+), 60 deletions(-) diff --git a/src/hotspot/cpu/ppc/c2_init_ppc.cpp b/src/hotspot/cpu/ppc/c2_init_ppc.cpp index 3c524f3b80e9..d4174192fe91 100644 --- a/src/hotspot/cpu/ppc/c2_init_ppc.cpp +++ b/src/hotspot/cpu/ppc/c2_init_ppc.cpp @@ -1,6 +1,6 @@ /* - * Copyright (c) 2000, 2025, Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2012, 2024 SAP SE. All rights reserved. + * Copyright (c) 2000, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2026 SAP SE. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,6 +23,7 @@ * */ +#include "asm/macroAssembler.hpp" #include "opto/compile.hpp" #include "opto/node.hpp" #include "runtime/globals.hpp" @@ -34,4 +35,7 @@ void Compile::pd_compiler2_init() { guarantee(CodeEntryAlignment >= InteriorEntryAlignment, ""); +#ifdef _LP64 + MacroAssembler::initialize_compressed_oops_fit_in_31_bits(); +#endif } diff --git a/src/hotspot/cpu/ppc/macroAssembler_ppc.cpp b/src/hotspot/cpu/ppc/macroAssembler_ppc.cpp index a40c618ee9c3..c58c1b248742 100644 --- a/src/hotspot/cpu/ppc/macroAssembler_ppc.cpp +++ b/src/hotspot/cpu/ppc/macroAssembler_ppc.cpp @@ -202,36 +202,27 @@ address MacroAssembler::get_address_of_calculate_address_from_global_toc_at(addr } #ifdef _LP64 -// Patch compressed oops or klass constants. -// Assembler sequence is -// 1) compressed oops: -// lis rx = const.hi -// ori rx = rx | const.lo -// 2) compressed klass: -// lis rx = const.hi -// clrldi rx = rx & 0xFFFFffff // clearMS32b, optional -// ori rx = rx | const.lo -// Clrldi will be passed by. -address MacroAssembler::patch_set_narrow_oop(address a, address bound, narrowOop data) { +bool MacroAssembler::_compressed_oops_fit_in_31_bits = false; + +void MacroAssembler::initialize_compressed_oops_fit_in_31_bits() { + if (UseCompressedOops) { + address max_oop = align_down(CompressedOops::end() - 1, MinObjAlignmentInBytes); + narrowOop max_encoded_oop = CompressedOops::encode(cast_to_oop(max_oop)); + _compressed_oops_fit_in_31_bits = Assembler::is_uimm((jlong)max_encoded_oop, 31); + } +} + +// Patch compressed oop constants. +// The relocation points to the ori of an adjacent lis/ori pair. +// +// lis rx = const.hi +// ori rx = rx | const.lo +address MacroAssembler::patch_set_narrow_oop(address a, address lower_bound, narrowOop data) { assert(UseCompressedOops, "Should only patch compressed oops"); const address inst2_addr = a; - const int inst2 = *(int *)inst2_addr; - - // The relocation points to the second instruction, the ori, - // and the ori reads and writes the same register dst. - const int dst = inv_rta_field(inst2); - assert(is_ori(inst2) && inv_rs_field(inst2) == dst, "must be ori reading and writing dst"); - // Now, find the preceding addis which writes to dst. - int inst1 = 0; - address inst1_addr = inst2_addr - BytesPerInstWord; - bool inst1_found = false; - while (inst1_addr >= bound) { - inst1 = *(int *)inst1_addr; - if (is_lis(inst1) && inv_rs_field(inst1) == dst) { inst1_found = true; break; } - inst1_addr -= BytesPerInstWord; - } - assert(inst1_found, "inst is not lis"); + assert(is_set_narrow_oop(inst2_addr, lower_bound), "must be lis/ori narrow oop constant"); + const address inst1_addr = inst2_addr - BytesPerInstWord; uint32_t data_value = CompressedOops::narrow_oop_value(data); int xc = (data_value >> 16) & 0xffff; @@ -243,27 +234,12 @@ address MacroAssembler::patch_set_narrow_oop(address a, address bound, narrowOop } // Get compressed oop constant. -narrowOop MacroAssembler::get_narrow_oop(address a, address bound) { +narrowOop MacroAssembler::get_narrow_oop(address a, address lower_bound) { assert(UseCompressedOops, "Should only patch compressed oops"); const address inst2_addr = a; - const int inst2 = *(int *)inst2_addr; - - // The relocation points to the second instruction, the ori, - // and the ori reads and writes the same register dst. - const int dst = inv_rta_field(inst2); - assert(is_ori(inst2) && inv_rs_field(inst2) == dst, "must be ori reading and writing dst"); - // Now, find the preceding lis which writes to dst. - int inst1 = 0; - address inst1_addr = inst2_addr - BytesPerInstWord; - bool inst1_found = false; - - while (inst1_addr >= bound) { - inst1 = *(int *) inst1_addr; - if (is_lis(inst1) && inv_rs_field(inst1) == dst) { inst1_found = true; break;} - inst1_addr -= BytesPerInstWord; - } - assert(inst1_found, "inst is not lis"); + assert(is_set_narrow_oop(inst2_addr, lower_bound), "must be lis/ori narrow oop constant"); + const address inst1_addr = inst2_addr - BytesPerInstWord; uint xl = ((unsigned int) (get_imm(inst2_addr, 0) & 0xffff)); uint xh = (((get_imm(inst1_addr, 0)) & 0xffff) << 16); diff --git a/src/hotspot/cpu/ppc/macroAssembler_ppc.hpp b/src/hotspot/cpu/ppc/macroAssembler_ppc.hpp index d336ef81dd70..1bb1290ff252 100644 --- a/src/hotspot/cpu/ppc/macroAssembler_ppc.hpp +++ b/src/hotspot/cpu/ppc/macroAssembler_ppc.hpp @@ -131,11 +131,19 @@ class MacroAssembler: public Assembler { static address get_address_of_calculate_address_from_global_toc_at(address a, address addr); #ifdef _LP64 + private: + static bool _compressed_oops_fit_in_31_bits; + + public: + static void initialize_compressed_oops_fit_in_31_bits(); + inline static bool compressed_oops_fit_in_31_bits() { + return _compressed_oops_fit_in_31_bits; + } // Patch narrow oop constant. - inline static bool is_set_narrow_oop(address a, address bound); + inline static bool is_set_narrow_oop(address a, address lower_bound); // Returns address of first instruction in sequence. - static address patch_set_narrow_oop(address a, address bound, narrowOop data); - static narrowOop get_narrow_oop(address a, address bound); + static address patch_set_narrow_oop(address a, address lower_bound, narrowOop data); + static narrowOop get_narrow_oop(address a, address lower_bound); #endif inline static bool is_load_const_at(address a); diff --git a/src/hotspot/cpu/ppc/macroAssembler_ppc.inline.hpp b/src/hotspot/cpu/ppc/macroAssembler_ppc.inline.hpp index cdeb8527beaa..440b8e72dbec 100644 --- a/src/hotspot/cpu/ppc/macroAssembler_ppc.inline.hpp +++ b/src/hotspot/cpu/ppc/macroAssembler_ppc.inline.hpp @@ -137,7 +137,7 @@ inline bool MacroAssembler::is_calculate_address_from_global_toc_at(address a, a #ifdef _LP64 // Detect narrow oop constants. -inline bool MacroAssembler::is_set_narrow_oop(address a, address bound) { +inline bool MacroAssembler::is_set_narrow_oop(address a, address lower_bound) { const address inst2_addr = a; const int inst2 = *(int *)a; // The relocation points to the second instruction, the ori. @@ -147,15 +147,11 @@ inline bool MacroAssembler::is_set_narrow_oop(address a, address bound) { const int dst = inv_rta_field(inst2); if (inv_rs_field(inst2) != dst) return false; - // Now, find the preceding addis which writes to dst. - int inst1 = 0; - address inst1_addr = inst2_addr - BytesPerInstWord; - while (inst1_addr >= bound) { - inst1 = *(int *) inst1_addr; - if (is_lis(inst1) && inv_rs_field(inst1) == dst) return true; - inst1_addr -= BytesPerInstWord; - } - return false; + // The preceding instruction must be the lis which writes dst. + const address inst1_addr = inst2_addr - BytesPerInstWord; + if (inst1_addr < lower_bound) return false; + const int inst1 = *(int *) inst1_addr; + return is_lis(inst1) && inv_rs_field(inst1) == dst; } #endif diff --git a/src/hotspot/cpu/ppc/ppc.ad b/src/hotspot/cpu/ppc/ppc.ad index 1126d8884618..a3c36163e466 100644 --- a/src/hotspot/cpu/ppc/ppc.ad +++ b/src/hotspot/cpu/ppc/ppc.ad @@ -3776,6 +3776,15 @@ operand immN() %{ interface(CONST_INTER); %} +operand immN_31bit() %{ + predicate(MacroAssembler::compressed_oops_fit_in_31_bits()); + match(ConN); + + op_cost(5); + format %{ %} + interface(CONST_INTER); +%} + // nullptr Pointer Immediate operand immN_0() %{ predicate(n->get_narrowcon() == 0); @@ -5678,6 +5687,21 @@ instruct loadBase(iRegLdst dst) %{ ins_pipe(pipe_class_default); %} +instruct loadConN_31bit(iRegNdst dst, immN_31bit src) %{ + match(Set dst src); + ins_cost(DEFAULT_COST*2); + + format %{ "LIS/ORI $dst, $src" %} + size(8); + ins_encode %{ + AddressLiteral addrlit = __ constant_oop_address((jobject)$src$$constant); + __ lis($dst$$Register, 0); // Will get patched. + __ relocate(addrlit.rspec(), /*compressed format*/ 1); + __ ori($dst$$Register, $dst$$Register, 0); // Will get patched. + %} + ins_pipe(pipe_class_default); +%} + instruct loadConN(iRegNdst dst, immN src) %{ match(Set dst src); ins_cost(DEFAULT_COST*3); From 4f394d3e8701c730dcb303519d1fbc7f3deeedf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Lund=C3=A9n?= Date: Wed, 21 Oct 2026 12:46:36 +0000 Subject: [PATCH 158/169] 8393716: C2: Add PrintIdealNodeCount as a compile command Reviewed-by: chagedorn, mchevalier --- .../share/compiler/compilerDirectives.hpp | 4 +- src/hotspot/share/compiler/compilerOracle.hpp | 1 + src/hotspot/share/opto/compile.cpp | 8 +- .../CompileCommandPrintIdealNodeCount.java | 98 +++++++++++++++++++ 4 files changed, 106 insertions(+), 5 deletions(-) create mode 100644 test/hotspot/jtreg/compiler/print/CompileCommandPrintIdealNodeCount.java diff --git a/src/hotspot/share/compiler/compilerDirectives.hpp b/src/hotspot/share/compiler/compilerDirectives.hpp index ea709c6897ce..5137b31ce76f 100644 --- a/src/hotspot/share/compiler/compilerDirectives.hpp +++ b/src/hotspot/share/compiler/compilerDirectives.hpp @@ -90,7 +90,9 @@ NOT_PRODUCT(cflags(PrintIdeal, bool, PrintIdeal, PrintIdeal)) \ NOT_PRODUCT(cflags(PhasePrintLevel, intx, PrintPhaseLevel, PhasePrintLevel)) \ NOT_PRODUCT(cflags(IGVPrintLevel, intx, PrintIdealGraphLevel, IGVPrintLevel)) \ cflags(IncrementalInlineForceCleanup, bool, IncrementalInlineForceCleanup, IncrementalInlineForceCleanup) \ - cflags(MaxNodeLimit, intx, MaxNodeLimit, MaxNodeLimit) + cflags(MaxNodeLimit, intx, MaxNodeLimit, MaxNodeLimit) \ +NOT_PRODUCT(cflags(PrintIdealNodeCount, bool, PrintIdealNodeCount, PrintIdealNodeCount)) \ + #define compilerdirectives_c2_string_flags(cflags) \ NOT_PRODUCT(cflags(TraceAutoVectorization, ccstrlist, "", TraceAutoVectorization)) \ NOT_PRODUCT(cflags(TraceMergeStores, ccstrlist, "", TraceMergeStores)) \ diff --git a/src/hotspot/share/compiler/compilerOracle.hpp b/src/hotspot/share/compiler/compilerOracle.hpp index 35cceea32cd3..b5b571e58eaa 100644 --- a/src/hotspot/share/compiler/compilerOracle.hpp +++ b/src/hotspot/share/compiler/compilerOracle.hpp @@ -95,6 +95,7 @@ NOT_PRODUCT(option(TraceMergeStores, "TraceMergeStores", Ccstrlist)) \ option(CloneMapDebug, "CloneMapDebug", Bool) \ option(IncrementalInlineForceCleanup, "IncrementalInlineForceCleanup", Bool) \ option(MaxNodeLimit, "MaxNodeLimit", Intx) \ +NOT_PRODUCT(option(PrintIdealNodeCount, "PrintIdealNodeCount", Bool)) \ option(DontAOTCompile, "DontAOTCompile", Bool) \ option(DontPreload, "DontPreload", Bool) \ option(IgnoreAOTCompiled, "IgnoreAOTCompiled", Bool) \ diff --git a/src/hotspot/share/opto/compile.cpp b/src/hotspot/share/opto/compile.cpp index 00220a68a215..63edde39f72b 100644 --- a/src/hotspot/share/opto/compile.cpp +++ b/src/hotspot/share/opto/compile.cpp @@ -1294,7 +1294,7 @@ uint Compile::count_live_nodes_by_graph_walk() { void Compile::print_missing_nodes() { // Return if CompileLog is null and PrintIdealNodeCount is false. - if ((_log == nullptr) && (! PrintIdealNodeCount)) { + if ((_log == nullptr) && !directive()->PrintIdealNodeCountOption) { return; } @@ -1325,7 +1325,7 @@ void Compile::print_missing_nodes() { if (_log != nullptr) { _log->elem("mismatched_node_info node_idx='%d' type='both live and dead'", i); } - if (PrintIdealNodeCount) { + if (directive()->PrintIdealNodeCountOption) { // Print the log message to tty tty->print_cr("mismatched_node idx='%d' both live and dead'", i); useful.at(i)->dump(); @@ -1336,7 +1336,7 @@ void Compile::print_missing_nodes() { if (_log != nullptr) { _log->elem("mismatched_node_info node_idx='%d' type='neither live nor dead'", i); } - if (PrintIdealNodeCount) { + if (directive()->PrintIdealNodeCountOption) { // Print the log message to tty tty->print_cr("mismatched_node idx='%d' type='neither live nor dead'", i); } @@ -5358,7 +5358,7 @@ Compile::TracePhase::~TracePhase() { return; // timing code, not stressing bailouts. } #ifdef ASSERT - if (PrintIdealNodeCount) { + if (_compile->directive()->PrintIdealNodeCountOption) { tty->print_cr("phase name='%s' nodes='%d' live='%d' live_graph_walk='%d'", phase_name(), _compile->unique(), _compile->live_nodes(), _compile->count_live_nodes_by_graph_walk()); } diff --git a/test/hotspot/jtreg/compiler/print/CompileCommandPrintIdealNodeCount.java b/test/hotspot/jtreg/compiler/print/CompileCommandPrintIdealNodeCount.java new file mode 100644 index 000000000000..3d0e550f2bcf --- /dev/null +++ b/test/hotspot/jtreg/compiler/print/CompileCommandPrintIdealNodeCount.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8393716 + * @summary Checks that -XX:CompileCommand=PrintIdealNodeCount,... works + * @requires vm.debug & vm.compiler2.enabled & vm.flagless + * @library /test/lib + * @run driver ${test.main.class} + */ + +package compiler.print; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; + +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; + +public class CompileCommandPrintIdealNodeCount { + private static final String PARSE_NODE_COUNTS = "phase name='parse' nodes='[0-9]+' live='[0-9]+' live_graph_walk='[0-9]+'"; + + public static void main(String[] args) throws Exception { + // No PrintIdealNodeCount prints nothing + List testOptions = new ArrayList<>(); + test(testOptions) + .shouldHaveExitValue(0) + .shouldNotMatch(PARSE_NODE_COUNTS); + + testOptions = new ArrayList<>(); + testOptions.add("-XX:CompileCommand=PrintIdealNodeCount," + TestMain.class.getName() + "::method1"); + OutputAnalyzer result = test(testOptions); + result.shouldHaveExitValue(0); + int matches = result.matchersForAllMatchingLinesStdout(Pattern.compile(PARSE_NODE_COUNTS)).size(); + int expected_matches = 1; + if (matches != expected_matches) { + throw new RuntimeException("Unexpected number of matching lines: " + matches + ", expected " + expected_matches); + } + + testOptions = new ArrayList<>(); + testOptions.add("-XX:CompileCommand=PrintIdealNodeCount," + TestMain.class.getName() + "::*"); + result = test(testOptions); + result.shouldHaveExitValue(0); + matches = result.matchersForAllMatchingLinesStdout(Pattern.compile(PARSE_NODE_COUNTS)).size(); + expected_matches = 2; + if (matches != expected_matches) { + throw new RuntimeException("Unexpected number of matching lines: " + matches + ", expected " + expected_matches); + } + } + + private static OutputAnalyzer test(List testOptions) throws Exception { + List options = new ArrayList<>(); + options.add("-Xcomp"); + options.add("-Xbatch"); + options.add("-XX:-TieredCompilation"); + options.add("-XX:CompileCommand=compileonly," + TestMain.class.getName() + "::method*"); + options.addAll(testOptions); + options.add(TestMain.class.getName()); + return ProcessTools.executeLimitedTestJava(options); + } + + public static class TestMain { + public static void main(String[] args) { + method1(); + method2(); + } + + public static int method1() { + return 1; + } + + public static int method2() { + return 2; + } + } +} From d544fca92aab371187df86fbbf3b473abdab25b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20=C3=96sterlund?= Date: Wed, 21 Oct 2026 14:09:08 +0000 Subject: [PATCH 159/169] 8380289: Implement JEP 545: Faster Startup and Warmup with ZGC & JEP 546: Adaptive Heap Sizing for ZGC MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Axel Boldt-Christmas Co-authored-by: Joel Sikström Reviewed-by: stefank, aboldtch --- src/hotspot/os/bsd/gc/z/zAdaptiveHeap_bsd.cpp | 81 ++ src/hotspot/os/bsd/gc/z/zLargePages_bsd.cpp | 7 +- src/hotspot/os/bsd/gc/z/zNUMA_bsd.cpp | 4 +- .../bsd/gc/z/zPhysicalMemoryBacking_bsd.cpp | 8 +- .../bsd/gc/z/zPhysicalMemoryBacking_bsd.hpp | 6 +- .../os/linux/gc/z/zAdaptiveHeap_linux.cpp | 172 +++ .../os/linux/gc/z/zLargePages_linux.cpp | 87 +- src/hotspot/os/linux/gc/z/zNUMA_linux.cpp | 21 +- .../gc/z/zPhysicalMemoryBacking_linux.cpp | 113 +- .../gc/z/zPhysicalMemoryBacking_linux.hpp | 10 +- src/hotspot/os/linux/os_linux.cpp | 3 + src/hotspot/os/linux/os_linux.hpp | 8 +- .../os/windows/gc/z/zAdaptiveHeap_windows.cpp | 66 + .../os/windows/gc/z/zLargePages_windows.cpp | 7 +- src/hotspot/os/windows/gc/z/zNUMA_windows.cpp | 4 +- .../gc/z/zPhysicalMemoryBacking_windows.cpp | 8 +- .../gc/z/zPhysicalMemoryBacking_windows.hpp | 6 +- src/hotspot/share/gc/shared/gcArguments.cpp | 6 +- .../share/gc/shared/jvmFlagConstraintsGC.hpp | 6 +- .../gc/shenandoah/shenandoahArguments.cpp | 6 + .../gc/shenandoah/shenandoahArguments.hpp | 1 + .../share/gc/z/jvmFlagConstraintsZ.cpp | 58 + .../share/gc/z/jvmFlagConstraintsZ.hpp | 40 + src/hotspot/share/gc/z/vmStructs_z.hpp | 2 +- src/hotspot/share/gc/z/zAdaptiveHeap.cpp | 1246 +++++++++++++++++ src/hotspot/share/gc/z/zAdaptiveHeap.hpp | 225 +++ .../share/gc/z/zAdaptiveHeap.inline.hpp | 52 + src/hotspot/share/gc/z/zArguments.cpp | 109 +- src/hotspot/share/gc/z/zArguments.hpp | 1 + src/hotspot/share/gc/z/zCollectedHeap.cpp | 6 +- src/hotspot/share/gc/z/zDirector.cpp | 105 +- src/hotspot/share/gc/z/zDriver.cpp | 22 +- src/hotspot/share/gc/z/zDriver.hpp | 6 + src/hotspot/share/gc/z/zGeneration.cpp | 37 +- src/hotspot/share/gc/z/zGlobals.hpp | 12 + src/hotspot/share/gc/z/zHeap.cpp | 44 +- src/hotspot/share/gc/z/zHeap.hpp | 10 +- src/hotspot/share/gc/z/zHeuristics.cpp | 2 +- src/hotspot/share/gc/z/zLargePages.cpp | 9 +- src/hotspot/share/gc/z/zLargePages.hpp | 11 +- src/hotspot/share/gc/z/zLargePages.inline.hpp | 10 + src/hotspot/share/gc/z/zLock.hpp | 10 + src/hotspot/share/gc/z/zLock.inline.hpp | 15 + src/hotspot/share/gc/z/zMappedCache.hpp | 6 +- .../share/gc/z/zMappedCache.inline.hpp | 33 + src/hotspot/share/gc/z/zMemoryWorker.cpp | 1122 +++++++++++++++ src/hotspot/share/gc/z/zMemoryWorker.hpp | 116 ++ src/hotspot/share/gc/z/zNUMA.cpp | 4 +- src/hotspot/share/gc/z/zNUMA.hpp | 7 +- src/hotspot/share/gc/z/zNUMA.inline.hpp | 10 +- src/hotspot/share/gc/z/zPageAllocator.cpp | 971 ++++++++++--- src/hotspot/share/gc/z/zPageAllocator.hpp | 195 ++- .../share/gc/z/zPageAllocator.inline.hpp | 22 +- .../share/gc/z/zPhysicalMemoryManager.cpp | 36 +- .../share/gc/z/zPhysicalMemoryManager.hpp | 11 +- src/hotspot/share/gc/z/zRange.hpp | 3 +- src/hotspot/share/gc/z/zRange.inline.hpp | 10 +- .../share/gc/z/zReferenceProcessor.cpp | 13 +- src/hotspot/share/gc/z/zServiceability.cpp | 13 +- src/hotspot/share/gc/z/zServiceability.hpp | 3 +- src/hotspot/share/gc/z/zStat.cpp | 296 +++- src/hotspot/share/gc/z/zStat.hpp | 67 +- src/hotspot/share/gc/z/zTask.cpp | 28 +- src/hotspot/share/gc/z/zTask.hpp | 12 +- src/hotspot/share/gc/z/zUncommitter.cpp | 32 +- src/hotspot/share/gc/z/zUncommitter.hpp | 4 +- src/hotspot/share/gc/z/zUtils.hpp | 16 +- src/hotspot/share/gc/z/zUtils.inline.hpp | 8 + .../share/gc/z/zVirtualMemoryManager.cpp | 13 +- src/hotspot/share/gc/z/zWorkers.cpp | 8 +- src/hotspot/share/gc/z/zWorkers.hpp | 4 +- src/hotspot/share/gc/z/z_globals.hpp | 12 + src/hotspot/share/runtime/arguments.hpp | 1 + .../share/runtime/globals_extension.hpp | 14 +- src/java.base/share/man/java.md | 12 + .../sun/jvm/hotspot/gc/z/ZPageAllocator.java | 7 +- .../hotspot/gtest/gc/z/test_zAdaptiveHeap.cpp | 182 +++ test/hotspot/jtreg/ProblemList-Virtual.txt | 1 + .../gc/arguments/TestSoftMaxHeapSizeFlag.java | 6 +- .../gc/z/TestAdaptiveHeapSizingMode.java | 131 ++ test/hotspot/jtreg/gc/z/TestUncommit.java | 5 +- .../TestOptionsWithRangesDynamic.java | 6 +- .../CollectionUsageThreshold.java | 16 +- .../event/gc/detailed/TestZUncommitEvent.java | 4 +- 84 files changed, 5596 insertions(+), 526 deletions(-) create mode 100644 src/hotspot/os/bsd/gc/z/zAdaptiveHeap_bsd.cpp create mode 100644 src/hotspot/os/linux/gc/z/zAdaptiveHeap_linux.cpp create mode 100644 src/hotspot/os/windows/gc/z/zAdaptiveHeap_windows.cpp create mode 100644 src/hotspot/share/gc/z/jvmFlagConstraintsZ.cpp create mode 100644 src/hotspot/share/gc/z/jvmFlagConstraintsZ.hpp create mode 100644 src/hotspot/share/gc/z/zAdaptiveHeap.cpp create mode 100644 src/hotspot/share/gc/z/zAdaptiveHeap.hpp create mode 100644 src/hotspot/share/gc/z/zAdaptiveHeap.inline.hpp create mode 100644 src/hotspot/share/gc/z/zMappedCache.inline.hpp create mode 100644 src/hotspot/share/gc/z/zMemoryWorker.cpp create mode 100644 src/hotspot/share/gc/z/zMemoryWorker.hpp create mode 100644 test/hotspot/gtest/gc/z/test_zAdaptiveHeap.cpp create mode 100644 test/hotspot/jtreg/gc/z/TestAdaptiveHeapSizingMode.java diff --git a/src/hotspot/os/bsd/gc/z/zAdaptiveHeap_bsd.cpp b/src/hotspot/os/bsd/gc/z/zAdaptiveHeap_bsd.cpp new file mode 100644 index 000000000000..2ed9d8f558ad --- /dev/null +++ b/src/hotspot/os/bsd/gc/z/zAdaptiveHeap_bsd.cpp @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include "gc/z/zAdaptiveHeap.hpp" +#include "runtime/os.inline.hpp" +#include "utilities/debug.hpp" + +void ZAdaptiveHeap::pd_machine_memory_info(ZMachineMemoryInfo& info) { + info._physical_memory = os::Machine::physical_memory(); + info._is_valid = os::Machine::available_memory(info._available_memory); +} + +bool ZAdaptiveHeap::pd_machine_elapsed_system_cpu_time(ZSystemCpuTime& value) { +#ifdef __APPLE__ + const int processor_count = os::processor_count(); + + if (processor_count <= 0) { + return false; + } + + mach_msg_type_number_t count = HOST_CPU_LOAD_INFO_COUNT; + host_cpu_load_info_data_t load_data; + + kern_return_t ret = host_statistics(mach_host_self(), HOST_CPU_LOAD_INFO, (host_info_t)&load_data, &count); + if (ret != KERN_SUCCESS) { + assert(false, "This should never happen"); + return false; + } + + // The CPU ticks counters are 32-bit and may experience wrapping behaviour. + natural_t ticks = load_data.cpu_ticks[CPU_STATE_USER] + + load_data.cpu_ticks[CPU_STATE_NICE] + + load_data.cpu_ticks[CPU_STATE_SYSTEM]; + + value._elapsed_time = double(ticks) / CLK_TCK; + value._processor_count = double(processor_count); + return true; +#else + Unimplemented(); +#endif +} + +bool ZAdaptiveHeap::pd_container_elapsed_system_cpu_time(ZSystemCpuTime& value) { + ShouldNotReachHere(); +} + +bool ZAdaptiveHeap::pd_machine_compressed_memory(physical_memory_size_type& value) { +#ifdef __APPLE__ + mach_msg_type_number_t count = HOST_VM_INFO64_COUNT; + vm_statistics64_data_t vmstat; + kern_return_t kerr = host_statistics64(mach_host_self(), HOST_VM_INFO64, + (host_info64_t)&vmstat, &count); + assert(kerr == KERN_SUCCESS, + "host_statistics64 failed - check mach_host_self() and count"); + if (kerr == KERN_SUCCESS) { + value = vmstat.compressor_page_count * os::vm_page_size(); + return true; + } +#endif + return false; +} diff --git a/src/hotspot/os/bsd/gc/z/zLargePages_bsd.cpp b/src/hotspot/os/bsd/gc/z/zLargePages_bsd.cpp index 0c42d7790886..11218d651839 100644 --- a/src/hotspot/os/bsd/gc/z/zLargePages_bsd.cpp +++ b/src/hotspot/os/bsd/gc/z/zLargePages_bsd.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,6 +23,11 @@ #include "gc/z/zLargePages.hpp" #include "runtime/globals.hpp" +#include "utilities/debug.hpp" + +bool ZLargePages::pd_collapse(void* addr, size_t bytes) { + ShouldNotCallThis(); +} void ZLargePages::pd_initialize() { if (UseLargePages) { diff --git a/src/hotspot/os/bsd/gc/z/zNUMA_bsd.cpp b/src/hotspot/os/bsd/gc/z/zNUMA_bsd.cpp index 3acaa9ab8f94..c7046b806c20 100644 --- a/src/hotspot/os/bsd/gc/z/zNUMA_bsd.cpp +++ b/src/hotspot/os/bsd/gc/z/zNUMA_bsd.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -28,6 +28,8 @@ void ZNUMA::pd_initialize() { _enabled = false; + _node_count = 1; + _bound_node_count = 1; _count = !FLAG_IS_DEFAULT(ZFakeNUMA) ? ZFakeNUMA : 1; diff --git a/src/hotspot/os/bsd/gc/z/zPhysicalMemoryBacking_bsd.cpp b/src/hotspot/os/bsd/gc/z/zPhysicalMemoryBacking_bsd.cpp index 861fda7a71d7..f80604cf764d 100644 --- a/src/hotspot/os/bsd/gc/z/zPhysicalMemoryBacking_bsd.cpp +++ b/src/hotspot/os/bsd/gc/z/zPhysicalMemoryBacking_bsd.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -92,7 +92,7 @@ bool ZPhysicalMemoryBacking::is_initialized() const { return _initialized; } -void ZPhysicalMemoryBacking::warn_commit_limits(size_t max_capacity) const { +void ZPhysicalMemoryBacking::warn_commit_limits(size_t expected_capacity, size_t max_capacity) const { // Does nothing } @@ -178,3 +178,7 @@ void ZPhysicalMemoryBacking::unmap(zaddress_unsafe addr, size_t size) const { fatal("Failed to map memory (%s)", err.to_string()); } } + +void ZPhysicalMemoryBacking::collapse(zaddress_unsafe addr, size_t size) const { + ShouldNotReachHere(); +} diff --git a/src/hotspot/os/bsd/gc/z/zPhysicalMemoryBacking_bsd.hpp b/src/hotspot/os/bsd/gc/z/zPhysicalMemoryBacking_bsd.hpp index 9fa64fb51bd3..a8cbdfc9ad9e 100644 --- a/src/hotspot/os/bsd/gc/z/zPhysicalMemoryBacking_bsd.hpp +++ b/src/hotspot/os/bsd/gc/z/zPhysicalMemoryBacking_bsd.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -38,13 +38,15 @@ class ZPhysicalMemoryBacking { bool is_initialized() const; - void warn_commit_limits(size_t max_capacity) const; + void warn_commit_limits(size_t expected_capacity, size_t max_capacity) const; size_t commit(zbacking_offset offset, size_t length, uint32_t numa_id) const; size_t uncommit(zbacking_offset offset, size_t length) const; void map(zaddress_unsafe addr, size_t size, zbacking_offset offset) const; void unmap(zaddress_unsafe addr, size_t size) const; + + void collapse(zaddress_unsafe addr, size_t size) const; }; #endif // OS_BSD_GC_Z_ZPHYSICALMEMORYBACKING_BSD_HPP diff --git a/src/hotspot/os/linux/gc/z/zAdaptiveHeap_linux.cpp b/src/hotspot/os/linux/gc/z/zAdaptiveHeap_linux.cpp new file mode 100644 index 000000000000..869b51f7b018 --- /dev/null +++ b/src/hotspot/os/linux/gc/z/zAdaptiveHeap_linux.cpp @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include "gc/shared/gcLogPrecious.hpp" +#include "gc/z/zAdaptiveHeap.hpp" +#include "gc/z/zNUMA.inline.hpp" +#include "jvm_io.h" +#include "logging/log.hpp" +#include "os_linux.hpp" +#include "osContainer_linux.hpp" +#include "runtime/os.inline.hpp" +#include "utilities/debug.hpp" +#include "utilities/globalDefinitions.hpp" + +void ZAdaptiveHeap::pd_machine_memory_info(ZMachineMemoryInfo& info) { + info._physical_memory = 0; + info._available_memory = 0; + info._is_valid = false; + + if (!ZNUMA::is_bound()) { + info._physical_memory = os::Machine::physical_memory(); + info._is_valid = os::Machine::available_memory(info._available_memory); + return; + } + + // In the case where the user has bound the process and -UseNUMA is provided, + // we must still gather information from all bound nodes. Even though our memory + // management does not try to do any NUMA optimizations when managing with memory. + assert(!ZNUMA::is_enabled() || ZNUMA::bound_count() == ZNUMA::count(), + "UseNUMA implies that bound_count(%u) == count(%u)", ZNUMA::bound_count(), ZNUMA::count()); + + for (uint32_t numa_id = 0; numa_id < ZNUMA::bound_count(); numa_id++) { + const int node = ZNUMA::numa_id_to_node(numa_id); + char path[128]; + jio_snprintf(path, sizeof(path), "/sys/devices/system/node/node%d/meminfo", node); + + physical_memory_size_type node_physical_memory = 0; + physical_memory_size_type node_available_memory = 0; + bool found_mem_total = false; + bool found_mem_available = false; + bool found_mem_free = false; + bool found_active_file = false; + bool found_inactive_file = false; + bool found_sreclaimable = false; + + FILE* f = os::fopen(path, "r"); + if (f) { + char line[256]; + while (fgets(line, sizeof(line), f) != nullptr) { + int n = -1; + physical_memory_size_type read_value = 0; + if (sscanf(line, "Node %d MemTotal: " PHYS_MEM_TYPE_FORMAT " kB", &n, &read_value) == 2) { + node_physical_memory = read_value * K; + found_mem_total = true; + } else if (sscanf(line, "Node %d MemAvailable: " PHYS_MEM_TYPE_FORMAT " kB", &n, &read_value) == 2) { + // If the Kernel has an approximation of MemAvailable, use it + node_available_memory = read_value * K; + found_mem_available = true; + } else if (!found_mem_available && sscanf(line, "Node %d MemFree: " PHYS_MEM_TYPE_FORMAT " kB", &n, &read_value) == 2) { + node_available_memory += read_value * K; + found_mem_free = true; + } else if (!found_mem_available && sscanf(line, "Node %d Active(file): " PHYS_MEM_TYPE_FORMAT " kB", &n, &read_value) == 2) { + node_available_memory += read_value * K; + found_active_file = true; + } else if (!found_mem_available && sscanf(line, "Node %d Inactive(file): " PHYS_MEM_TYPE_FORMAT " kB", &n, &read_value) == 2) { + node_available_memory += read_value * K; + found_inactive_file = true; + } else if (!found_mem_available && sscanf(line, "Node %d SReclaimable: " PHYS_MEM_TYPE_FORMAT " kB", &n, &read_value) == 2) { + node_available_memory += read_value * K; + found_sreclaimable = true; + } + + if (found_mem_total && (found_mem_available || (found_mem_free && found_active_file && found_inactive_file && found_sreclaimable))) { + break; + } + } + + fclose(f); + + if (!found_mem_total) { + // Some Kernels might not have "MemTotal" available in the node-specific file. + // Fall back to numa_node_size64 if that's the case. + long long res = os::Linux::numa_node_size64(node, nullptr); + if (res != -1) { + node_physical_memory = (physical_memory_size_type)res; + found_mem_total = true; + } + } + } + + if (!(found_mem_total && (found_mem_available || (found_mem_free && found_active_file && found_inactive_file && found_sreclaimable)))) { + static bool runs_once = [&]() { + log_warning_p(gc, heap)("Failed to read one of the NUMA-node specific values: " + "MemTotal: %d, MemAvailable: %d, MemFree: %d, Active(file): %d, Inactive(file): %d, SReclaimable: %d", + found_mem_total, found_mem_available, found_mem_free, found_active_file, found_inactive_file, found_sreclaimable); + return true; + }(); + assert(false, "This should not happen"); + + info._physical_memory = os::Machine::physical_memory(); + info._is_valid = os::Machine::available_memory(info._available_memory); + return; + } + + info._physical_memory += node_physical_memory; + info._available_memory += node_available_memory; + } + + info._is_valid = true; +} + +bool ZAdaptiveHeap::pd_machine_elapsed_system_cpu_time(ZSystemCpuTime& value) { + os::Linux::CPUPerfTicks ticks; + + if (!os::Linux::get_tick_information(&ticks, -1)) { + return false; + } + + const int online_cpus = checked_cast(::sysconf(_SC_NPROCESSORS_ONLN)); + + if (online_cpus == -1) { + assert(false, "This should never happen"); + return false; + } + assert(online_cpus > 0, "Unespected online CPU count: %d", online_cpus); + + const uint64_t sum = ticks.used + ticks.usedKernel; + value._elapsed_time = double(sum) / os::Posix::clock_tics_per_second(); + value._processor_count = double(online_cpus); + return true; +} + +bool ZAdaptiveHeap::pd_container_elapsed_system_cpu_time(ZSystemCpuTime& value) { + precond(os::is_containerized()); + uint64_t result; + double processors; + // os::Container::processor_count does not account for the processors + // availiable in the container, but for the process. This can give a scew as + // cpu_usage_in_micros is the whole container. + if (OSContainer::cpu_usage_in_micros(result) && + os::Container::processor_count(processors)) { + value._elapsed_time = double(result) / MICROUNITS; + value._processor_count = processors; + return true; + } + + return false; +} + +bool ZAdaptiveHeap::pd_machine_compressed_memory(physical_memory_size_type& value) { + return false; +} diff --git a/src/hotspot/os/linux/gc/z/zLargePages_linux.cpp b/src/hotspot/os/linux/gc/z/zLargePages_linux.cpp index c9f99a4b6db4..76b5b38c5a34 100644 --- a/src/hotspot/os/linux/gc/z/zLargePages_linux.cpp +++ b/src/hotspot/os/linux/gc/z/zLargePages_linux.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -21,13 +21,78 @@ * questions. */ +#include "gc/z/zAdaptiveHeap.inline.hpp" #include "gc/z/zLargePages.hpp" #include "hugepages.hpp" #include "os_linux.hpp" #include "runtime/globals.hpp" +#include "runtime/globals_extension.hpp" + +#include + +// Define MADV_COLLAPSE here so we can build HotSpot on old systems. +#define MADV_COLLAPSE_value 25 +#ifndef MADV_COLLAPSE +#define MADV_COLLAPSE MADV_COLLAPSE_value +#else + // Sanity-check our assumed default value if we build with a new enough libc. + static_assert(MADV_COLLAPSE == MADV_COLLAPSE_value); +#endif + +bool ZLargePages::pd_collapse(void* addr, size_t bytes) { + // When MADV_COLLAPSE races with THP khugepaged, you sometimes get + // EAGAIN. We just do it again then. + // Note: mlock, mlockall, DMA / GUP with FOLL_PIN/FOLL_LONGTERM can + // can all cause EAGAIN to be persistent. We treat this as a + // user error, and allow a calling thread to spin forever here. + // We may need to evaluate potential live-locking here in the future. + for (;;) { + const int result = ::madvise(addr, bytes, MADV_COLLAPSE); + if (result == 0) { + return true; + } + if (result == -1 && errno == EAGAIN) { + continue; + } + + return false; + } +} + +static bool madv_collapse_available() { + const size_t size = 2 * M; + void* const res = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + + if (res == MAP_FAILED) { + return false; + } + + assert(size >= os::vm_page_size(), "Unexpected page size"); + os::pretouch_memory(res, (void*)(((char*)res) + os::vm_page_size())); + + const bool result = ZLargePages::pd_collapse(res, size); + + munmap(res, size); + + return result; +} void ZLargePages::pd_initialize() { + // Evaluate if we should move madv collapse logic to os::Linux / HugePages so + // that validate_thps_configured can log the warning correctly if shmem THP + // is disabled. + // Maybe combined with a general GC callback. Let os::Linux / HugePages contain + // the OS logic, let the GC do the extra log warnings as part of the + // validation callback. + const auto can_collapse = []() { + return ZMemoryHeating && madv_collapse_available(); + }; + if (os::Linux::thp_requested()) { + if (can_collapse()) { + _state = Collapse; + return; + } // Check if the OS config turned off transparent huge pages for shmem. _os_enforced_transparent_mode = HugePages::shmem_thp_info().is_disabled(); _state = _os_enforced_transparent_mode ? Disabled : Transparent; @@ -35,7 +100,25 @@ void ZLargePages::pd_initialize() { } if (UseLargePages) { - _state = Explicit; + if (!ZAdaptiveHeapSizing || ZAdaptiveHeap::explicit_max_capacity()) { + _state = Explicit; + return; + } + + log_warning(gc, init)("UseLargePages requires a max heap size to be set (-Xmx) when running with Adaptive Heap Sizing. " + "Disabling the use of explicit large pages for the heap"); + } + + // Because os::Linux::large_page_init does not set the origin, we cannot tell + // whether the user explicitly disabled large pages or explicitly requested + // large pages which the OS configuration did not support. + // Look into adding a similar cached user request as os::Linux::thp_requested + // for UseLargePages. + const bool large_pages_explicitly_disabled = !FLAG_IS_DEFAULT(UseLargePages) && !UseLargePages; + const bool thp_explicitly_disabled = !FLAG_IS_DEFAULT(UseTransparentHugePages) && !UseTransparentHugePages; + + if (!large_pages_explicitly_disabled && !thp_explicitly_disabled && can_collapse()) { + _state = Collapse; return; } diff --git a/src/hotspot/os/linux/gc/z/zNUMA_linux.cpp b/src/hotspot/os/linux/gc/z/zNUMA_linux.cpp index 3245d07caf9f..f44d4bac7f4f 100644 --- a/src/hotspot/os/linux/gc/z/zNUMA_linux.cpp +++ b/src/hotspot/os/linux/gc/z/zNUMA_linux.cpp @@ -102,7 +102,6 @@ class ZNUMAConverter { public: void initialize() { precond(!_initialized); - precond(UseNUMA); populate_id_mappings(); populate_node_mappings(); @@ -140,15 +139,23 @@ static ZNUMAConverter z_numa_converter; void ZNUMA::pd_initialize() { _enabled = UseNUMA; - if (UseNUMA) { + const int configured_nodes = os::Linux::numa_num_configured_nodes(); + if (configured_nodes > 0) { z_numa_converter.initialize(); - _count = z_numa_converter.count(); + + _node_count = integer_cast(configured_nodes); + _bound_node_count = z_numa_converter.count(); } else { - // UseNUMA and is_faked() are mutually excluded in zArguments.cpp. - _count = !FLAG_IS_DEFAULT(ZFakeNUMA) - ? ZFakeNUMA - : 1; // No NUMA nodes + _node_count = 1; + _bound_node_count = 1; } + + // UseNUMA and is_faked() are mutually excluded in zArguments.cpp. + _count = UseNUMA + ? _bound_node_count + : !FLAG_IS_DEFAULT(ZFakeNUMA) + ? ZFakeNUMA + : 1; // No NUMA nodes } uint32_t ZNUMA::id() { diff --git a/src/hotspot/os/linux/gc/z/zPhysicalMemoryBacking_linux.cpp b/src/hotspot/os/linux/gc/z/zPhysicalMemoryBacking_linux.cpp index 28159ae48016..f6b92d746f3b 100644 --- a/src/hotspot/os/linux/gc/z/zPhysicalMemoryBacking_linux.cpp +++ b/src/hotspot/os/linux/gc/z/zPhysicalMemoryBacking_linux.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -22,6 +22,7 @@ */ #include "gc/shared/gcLogPrecious.hpp" +#include "gc/z/zAdaptiveHeap.inline.hpp" #include "gc/z/zAddress.inline.hpp" #include "gc/z/zArray.inline.hpp" #include "gc/z/zErrno.hpp" @@ -35,6 +36,8 @@ #include "hugepages.hpp" #include "logging/log.hpp" #include "os_linux.hpp" +#include "runtime/globals.hpp" +#include "runtime/globals_extension.hpp" #include "runtime/init.hpp" #include "runtime/os.hpp" #include "runtime/safefetch.hpp" @@ -116,6 +119,8 @@ static const char* ZPreferredHugetlbfsMountpoints[] = { static int z_fallocate_hugetlbfs_attempts = 3; static bool z_fallocate_supported = true; +static size_t z_max_map_count = 0; + ZPhysicalMemoryBacking::ZPhysicalMemoryBacking(size_t max_capacity) : _fd(-1), _filesystem(0), @@ -151,6 +156,14 @@ ZPhysicalMemoryBacking::ZPhysicalMemoryBacking(size_t max_capacity) _block_size = buf.f_bsize; _available = buf.f_bavail * _block_size; + // Note that the available space on a tmpfs or a hugetlbfs filesystem + // will be zero if no size limit was specified when it was mounted. + if (_available == 0) { + log_info_p(gc, init)("Available space on backing filesystem: N/A"); + } else { + log_info_p(gc, init)("Available space on backing filesystem: %zuM", _available / M); + } + log_info_p(gc, init)("Heap Backing Filesystem: %s (" UINT64_FORMAT_X ")", is_tmpfs() ? ZFILESYSTEM_TMPFS : is_hugetlbfs() ? ZFILESYSTEM_HUGETLBFS : "other", _filesystem); @@ -192,6 +205,9 @@ ZPhysicalMemoryBacking::ZPhysicalMemoryBacking(size_t max_capacity) return; } + // Should we try to use fallocate + z_fallocate_supported = ZLargePages::should_try_fallocate(); + // Successfully initialized _initialized = true; } @@ -308,12 +324,9 @@ void ZPhysicalMemoryBacking::warn_available_space(size_t max_capacity) const { // will be zero if no size limit was specified when it was mounted. if (_available == 0) { // No size limit set, skip check - log_info_p(gc, init)("Available space on backing filesystem: N/A"); return; } - log_info_p(gc, init)("Available space on backing filesystem: %zuM", _available / M); - // Warn if the filesystem doesn't currently have enough space available to hold // the max heap size. The max heap size will be capped if we later hit this limit // when trying to expand the heap. @@ -328,7 +341,11 @@ void ZPhysicalMemoryBacking::warn_available_space(size_t max_capacity) const { } } -void ZPhysicalMemoryBacking::warn_max_map_count(size_t max_capacity) const { +void ZPhysicalMemoryBacking::compute_max_map_count() const { + if (z_max_map_count != 0) { + return; + } + const char* const filename = ZFILENAME_PROC_MAX_MAP_COUNT; FILE* const file = os::fopen(filename, "r"); if (file == nullptr) { @@ -337,38 +354,52 @@ void ZPhysicalMemoryBacking::warn_max_map_count(size_t max_capacity) const { return; } - size_t actual_max_map_count = 0; - const int result = fscanf(file, "%zu", &actual_max_map_count); + const int result = fscanf(file, "%zu", &z_max_map_count); fclose(file); if (result != 1) { // Failed to read file, skip check log_debug_p(gc, init)("Failed to read %s", filename); return; } +} + +void ZPhysicalMemoryBacking::warn_max_map_count(size_t expected_capacity, size_t max_capacity) const { + // In the worst case, ZGC needs 2 mappings per granule. The reason is that if the + // fragmentation is at maximum, the mappings will be interleaved between being + // backed by memory vs not being backed by memory. + const size_t mappings_per_granule = 2; + const size_t expected_mappings = (expected_capacity / ZGranuleSize) * mappings_per_granule; + const size_t max_mappings = (max_capacity / ZGranuleSize) * mappings_per_granule; // The required max map count is impossible to calculate exactly since subsystems // other than ZGC are also creating memory mappings, and we have no control over that. // However, ZGC tends to create the most mappings and dominate the total count. - // In the worst cases, ZGC will map each granule three times, i.e. once per heap view. - // We speculate that we need another 20% to allow for non-ZGC subsystems to map memory. - const size_t required_max_map_count = (max_capacity / ZGranuleSize) * 3 * 1.2; - if (actual_max_map_count < required_max_map_count) { - log_warning_p(gc)("***** WARNING! INCORRECT SYSTEM CONFIGURATION DETECTED! *****"); - log_warning_p(gc)("The system limit on number of memory mappings per process might be too low for the given"); - log_warning_p(gc)("max Java heap size (%zuM). Please adjust %s to allow for at", - max_capacity / M, filename); - log_warning_p(gc)("least %zu mappings (current limit is %zu). Continuing execution " - "with the current", required_max_map_count, actual_max_map_count); - log_warning_p(gc)("limit could lead to a premature OutOfMemoryError being thrown, due to failure to map memory."); + const size_t required_max_map_count = expected_mappings + expected_mappings / 5; + if (z_max_map_count >= required_max_map_count) { + return; } + + const size_t recommended_max_map_count = max_mappings + max_mappings / 5; + + const char* const filename = ZFILENAME_PROC_MAX_MAP_COUNT; + log_warning_p(gc)("***** WARNING! INCORRECT SYSTEM CONFIGURATION DETECTED! *****"); + log_warning_p(gc)("The system limit on number of memory mappings per process might be too low for the"); + log_warning_p(gc)("Java heap size (%zuM). Please adjust %s to allow for at", + expected_capacity / M, filename); + log_warning_p(gc)("least %zu mappings (current limit is %zu). Continuing execution " + "with the current", recommended_max_map_count, z_max_map_count); + log_warning_p(gc)("limit could lead to a premature OutOfMemoryError being thrown, due to failure to map memory."); } -void ZPhysicalMemoryBacking::warn_commit_limits(size_t max_capacity) const { +void ZPhysicalMemoryBacking::warn_commit_limits(size_t expected_capacity, size_t max_capacity) const { // Warn if available space is too low - warn_available_space(max_capacity); + warn_available_space(expected_capacity); + + // Compute max map count the first time (during bootstrapping) + compute_max_map_count(); // Warn if max map count is too low - warn_max_map_count(max_capacity); + warn_max_map_count(expected_capacity, max_capacity); } bool ZPhysicalMemoryBacking::is_tmpfs() const { @@ -513,21 +544,24 @@ ZErrno ZPhysicalMemoryBacking::fallocate_fill_hole(zbacking_offset offset, size_ // some point touch these segments, otherwise we can not punch hole in them. // Also note that we need to use compat mode when using transparent huge pages, // since we need to use madvise(2) on the mapping before the page is allocated. - if (z_fallocate_supported && !ZLargePages::is_enabled()) { - const ZErrno err = fallocate_fill_hole_syscall(offset, length); - if (!err) { - // Success - return 0; - } + if (z_fallocate_supported) { + precond(ZLargePages::should_try_fallocate()); + + const ZErrno err = fallocate_fill_hole_syscall(offset, length); + + if (!err) { + // Success + return 0; + } - if (err != ENOSYS && err != EOPNOTSUPP) { - // Failed - return err; - } + if (err != ENOSYS && err != EOPNOTSUPP) { + // Failed + return err; + } - // Not supported - log_debug_p(gc)("Falling back to fallocate() compatibility mode"); - z_fallocate_supported = false; + // Not supported + log_debug_p(gc)("Falling back to fallocate() compatibility mode"); + z_fallocate_supported = false; } return fallocate_fill_hole_compat(offset, length); @@ -593,6 +627,8 @@ ZErrno ZPhysicalMemoryBacking::fallocate(bool punch_hole, zbacking_offset offset return err; } +static Atomic warned_failed_commit{false}; + bool ZPhysicalMemoryBacking::commit_inner(zbacking_offset offset, size_t length) const { log_trace(gc, heap)("Committing memory: %zuM-%zuM (%zuM)", untype(offset) / M, untype(to_zbacking_offset_end(offset, length)) / M, length / M); @@ -615,8 +651,11 @@ bool ZPhysicalMemoryBacking::commit_inner(zbacking_offset offset, size_t length) goto retry; } + if (!warned_failed_commit.exchange(true, memory_order_relaxed)) { + log_error_p(gc)("Failed to commit memory (%s)", err.to_string()); + } + // Failed - log_error_p(gc)("Failed to commit memory (%s)", err.to_string()); return false; } @@ -705,3 +744,7 @@ void ZPhysicalMemoryBacking::unmap(zaddress_unsafe addr, size_t size) const { fatal("Failed to map memory (%s)", err.to_string()); } } + +void ZPhysicalMemoryBacking::collapse(zaddress_unsafe addr, size_t size) const { + ZLargePages::pd_collapse((void*)untype(addr), size); +} diff --git a/src/hotspot/os/linux/gc/z/zPhysicalMemoryBacking_linux.hpp b/src/hotspot/os/linux/gc/z/zPhysicalMemoryBacking_linux.hpp index 4b083d1c79c3..f192ba0dc286 100644 --- a/src/hotspot/os/linux/gc/z/zPhysicalMemoryBacking_linux.hpp +++ b/src/hotspot/os/linux/gc/z/zPhysicalMemoryBacking_linux.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -31,14 +31,14 @@ class ZErrno; class ZPhysicalMemoryBacking { private: int _fd; - size_t _size; uint64_t _filesystem; size_t _block_size; size_t _available; bool _initialized; void warn_available_space(size_t max_capacity) const; - void warn_max_map_count(size_t max_capacity) const; + void compute_max_map_count() const; + void warn_max_map_count(size_t expected_capacity, size_t max_capacity) const; int create_mem_fd(const char* name) const; int create_file_fd(const char* name) const; @@ -67,13 +67,15 @@ class ZPhysicalMemoryBacking { bool is_initialized() const; - void warn_commit_limits(size_t max_capacity) const; + void warn_commit_limits(size_t expected_capacity, size_t max_capacity) const; size_t commit(zbacking_offset offset, size_t length, uint32_t numa_id) const; size_t uncommit(zbacking_offset offset, size_t length) const; void map(zaddress_unsafe addr, size_t size, zbacking_offset offset) const; void unmap(zaddress_unsafe addr, size_t size) const; + + void collapse(zaddress_unsafe addr, size_t size) const; }; #endif // OS_LINUX_GC_Z_ZPHYSICALMEMORYBACKING_LINUX_HPP diff --git a/src/hotspot/os/linux/os_linux.cpp b/src/hotspot/os/linux/os_linux.cpp index 1d66aa90dc68..9bfe4091fa57 100644 --- a/src/hotspot/os/linux/os_linux.cpp +++ b/src/hotspot/os/linux/os_linux.cpp @@ -3352,6 +3352,8 @@ bool os::Linux::libnuma_init() { libnuma_dlsym(handle, "numa_num_configured_nodes"))); set_numa_available(CAST_TO_FN_PTR(numa_available_func_t, libnuma_dlsym(handle, "numa_available"))); + set_numa_node_size64(CAST_TO_FN_PTR(numa_node_size64_func_t, + libnuma_dlsym(handle, "numa_node_size64"))); set_numa_tonode_memory(CAST_TO_FN_PTR(numa_tonode_memory_func_t, libnuma_dlsym(handle, "numa_tonode_memory"))); set_numa_interleave_memory(CAST_TO_FN_PTR(numa_interleave_memory_func_t, @@ -3641,6 +3643,7 @@ os::Linux::numa_node_to_cpus_func_t os::Linux::_numa_node_to_cpus; os::Linux::numa_node_to_cpus_v2_func_t os::Linux::_numa_node_to_cpus_v2; os::Linux::numa_max_node_func_t os::Linux::_numa_max_node; os::Linux::numa_num_configured_nodes_func_t os::Linux::_numa_num_configured_nodes; +os::Linux::numa_node_size64_func_t os::Linux::_numa_node_size64; os::Linux::numa_available_func_t os::Linux::_numa_available; os::Linux::numa_tonode_memory_func_t os::Linux::_numa_tonode_memory; os::Linux::numa_interleave_memory_func_t os::Linux::_numa_interleave_memory; diff --git a/src/hotspot/os/linux/os_linux.hpp b/src/hotspot/os/linux/os_linux.hpp index 51a10be1e5d4..99d5fa71f9b5 100644 --- a/src/hotspot/os/linux/os_linux.hpp +++ b/src/hotspot/os/linux/os_linux.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -209,6 +209,7 @@ class os::Linux { typedef int (*numa_node_to_cpus_v2_func_t)(int node, void *mask); typedef int (*numa_max_node_func_t)(void); typedef int (*numa_num_configured_nodes_func_t)(void); + typedef long long (*numa_node_size64_func_t)(int node, long long* freep); typedef int (*numa_available_func_t)(void); typedef int (*numa_tonode_memory_func_t)(void *start, size_t size, int node); typedef void (*numa_interleave_memory_func_t)(void *start, size_t size, unsigned long *nodemask); @@ -231,6 +232,7 @@ class os::Linux { static numa_node_to_cpus_v2_func_t _numa_node_to_cpus_v2; static numa_max_node_func_t _numa_max_node; static numa_num_configured_nodes_func_t _numa_num_configured_nodes; + static numa_node_size64_func_t _numa_node_size64; static numa_available_func_t _numa_available; static numa_tonode_memory_func_t _numa_tonode_memory; static numa_interleave_memory_func_t _numa_interleave_memory; @@ -260,6 +262,7 @@ class os::Linux { static void set_numa_node_to_cpus_v2(numa_node_to_cpus_v2_func_t func) { _numa_node_to_cpus_v2 = func; } static void set_numa_max_node(numa_max_node_func_t func) { _numa_max_node = func; } static void set_numa_num_configured_nodes(numa_num_configured_nodes_func_t func) { _numa_num_configured_nodes = func; } + static void set_numa_node_size64(numa_node_size64_func_t func) { _numa_node_size64 = func; } static void set_numa_available(numa_available_func_t func) { _numa_available = func; } static void set_numa_tonode_memory(numa_tonode_memory_func_t func) { _numa_tonode_memory = func; } static void set_numa_interleave_memory(numa_interleave_memory_func_t func) { _numa_interleave_memory = func; } @@ -301,6 +304,9 @@ class os::Linux { static int numa_num_configured_nodes() { return _numa_num_configured_nodes != nullptr ? _numa_num_configured_nodes() : -1; } + static long long numa_node_size64(int node, long long* freep) { + return _numa_node_size64 != nullptr ? _numa_node_size64(node, freep) : -1; + } static int numa_available() { return _numa_available != nullptr ? _numa_available() : -1; } static int numa_tonode_memory(void *start, size_t size, int node) { return _numa_tonode_memory != nullptr ? _numa_tonode_memory(start, size, node) : -1; diff --git a/src/hotspot/os/windows/gc/z/zAdaptiveHeap_windows.cpp b/src/hotspot/os/windows/gc/z/zAdaptiveHeap_windows.cpp new file mode 100644 index 000000000000..eb706c323625 --- /dev/null +++ b/src/hotspot/os/windows/gc/z/zAdaptiveHeap_windows.cpp @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include "gc/z/zAdaptiveHeap.hpp" +#include "runtime/os.inline.hpp" + +#include +#include + +void ZAdaptiveHeap::pd_machine_memory_info(ZMachineMemoryInfo& info) { + info._physical_memory = os::Machine::physical_memory(); + info._is_valid = os::Machine::available_memory(info._available_memory); +} + +bool ZAdaptiveHeap::pd_machine_elapsed_system_cpu_time(ZSystemCpuTime& value) { + const int processor_count = os::processor_count(); + + if (processor_count <= 0 || processor_count > 64) { + // GetSystemTimes is not accurate on systems with more than 64 cores. + return false; + } + + FILETIME idle, kernel, user; + if (GetSystemTimes(&idle, &kernel, &user) == 0) { + assert(false, "this should not fail"); + return false; + } + + // Kernel time includes idle time + jlong ticks = jlong_from(user.dwHighDateTime, user.dwLowDateTime) + + jlong_from(kernel.dwHighDateTime, kernel.dwLowDateTime) - + jlong_from(idle.dwHighDateTime, idle.dwLowDateTime); + + // Ticks are 100 ns + value._elapsed_time = double(ticks) / 1e7; + value._processor_count = double(processor_count); + return true; +} + +bool ZAdaptiveHeap::pd_container_elapsed_system_cpu_time(ZSystemCpuTime& value) { + ShouldNotReachHere(); +} + +bool ZAdaptiveHeap::pd_machine_compressed_memory(physical_memory_size_type& value) { + return false; +} diff --git a/src/hotspot/os/windows/gc/z/zLargePages_windows.cpp b/src/hotspot/os/windows/gc/z/zLargePages_windows.cpp index dcd178da8212..70aaa3447a1b 100644 --- a/src/hotspot/os/windows/gc/z/zLargePages_windows.cpp +++ b/src/hotspot/os/windows/gc/z/zLargePages_windows.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,6 +25,11 @@ #include "gc/z/zLargePages.hpp" #include "gc/z/zSyscall_windows.hpp" #include "runtime/globals.hpp" +#include "utilities/debug.hpp" + +bool ZLargePages::pd_collapse(void* addr, size_t bytes) { + ShouldNotCallThis(); +} void ZLargePages::pd_initialize() { if (UseLargePages) { diff --git a/src/hotspot/os/windows/gc/z/zNUMA_windows.cpp b/src/hotspot/os/windows/gc/z/zNUMA_windows.cpp index e2bd68035844..2a49e58b656d 100644 --- a/src/hotspot/os/windows/gc/z/zNUMA_windows.cpp +++ b/src/hotspot/os/windows/gc/z/zNUMA_windows.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -28,6 +28,8 @@ void ZNUMA::pd_initialize() { _enabled = false; + _node_count = 1; + _bound_node_count = 1; _count = !FLAG_IS_DEFAULT(ZFakeNUMA) ? ZFakeNUMA : 1; diff --git a/src/hotspot/os/windows/gc/z/zPhysicalMemoryBacking_windows.cpp b/src/hotspot/os/windows/gc/z/zPhysicalMemoryBacking_windows.cpp index b18abd7e9792..15f1a846de6d 100644 --- a/src/hotspot/os/windows/gc/z/zPhysicalMemoryBacking_windows.cpp +++ b/src/hotspot/os/windows/gc/z/zPhysicalMemoryBacking_windows.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -226,7 +226,7 @@ bool ZPhysicalMemoryBacking::is_initialized() const { return true; } -void ZPhysicalMemoryBacking::warn_commit_limits(size_t max_capacity) const { +void ZPhysicalMemoryBacking::warn_commit_limits(size_t expected_capacity, size_t max_capacity) const { // Does nothing } @@ -258,3 +258,7 @@ void ZPhysicalMemoryBacking::unmap(zaddress_unsafe addr, size_t size) const { _impl->unmap(addr, size); } + +void ZPhysicalMemoryBacking::collapse(zaddress_unsafe addr, size_t size) const { + ShouldNotReachHere(); +} diff --git a/src/hotspot/os/windows/gc/z/zPhysicalMemoryBacking_windows.hpp b/src/hotspot/os/windows/gc/z/zPhysicalMemoryBacking_windows.hpp index 91d59f496098..3e20c655c785 100644 --- a/src/hotspot/os/windows/gc/z/zPhysicalMemoryBacking_windows.hpp +++ b/src/hotspot/os/windows/gc/z/zPhysicalMemoryBacking_windows.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -40,13 +40,15 @@ class ZPhysicalMemoryBacking { bool is_initialized() const; - void warn_commit_limits(size_t max_capacity) const; + void warn_commit_limits(size_t expected_capacity, size_t max_capacity) const; size_t commit(zbacking_offset offset, size_t length, uint32_t numa_id); size_t uncommit(zbacking_offset offset, size_t length); void map(zaddress_unsafe addr, size_t size, zbacking_offset offset) const; void unmap(zaddress_unsafe addr, size_t size) const; + + void collapse(zaddress_unsafe addr, size_t size) const; }; #endif // OS_WINDOWS_GC_Z_ZPHYSICALMEMORYBACKING_WINDOWS_HPP diff --git a/src/hotspot/share/gc/shared/gcArguments.cpp b/src/hotspot/share/gc/shared/gcArguments.cpp index ec3d758c00a6..569072845f67 100644 --- a/src/hotspot/share/gc/shared/gcArguments.cpp +++ b/src/hotspot/share/gc/shared/gcArguments.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2017, Red Hat, Inc. and/or its affiliates. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -279,10 +279,6 @@ void GCArguments::initialize_heap_flags_and_sizes() { } } - if (FLAG_IS_DEFAULT(SoftMaxHeapSize)) { - FLAG_SET_ERGO(SoftMaxHeapSize, MaxHeapSize); - } - FLAG_SET_ERGO(MinHeapDeltaBytes, align_up(MinHeapDeltaBytes, SpaceAlignment)); if (checked_cast(ObjectAlignmentInBytes) > GCCardSizeInBytes) { diff --git a/src/hotspot/share/gc/shared/jvmFlagConstraintsGC.hpp b/src/hotspot/share/gc/shared/jvmFlagConstraintsGC.hpp index 1d2f45397aa7..236388b14dc4 100644 --- a/src/hotspot/share/gc/shared/jvmFlagConstraintsGC.hpp +++ b/src/hotspot/share/gc/shared/jvmFlagConstraintsGC.hpp @@ -34,6 +34,9 @@ #if INCLUDE_PARALLELGC #include "gc/parallel/jvmFlagConstraintsParallel.hpp" #endif +#if INCLUDE_ZGC +#include "gc/z/jvmFlagConstraintsZ.hpp" +#endif /* * Here we have GC arguments constraints functions, which are called automatically @@ -76,6 +79,7 @@ JVMFlag::Error MaxPLABSizeBounds(const char* name, size_t value, bool verbose); #define GC_CONSTRAINTS(f) \ SHARED_GC_CONSTRAINTS(f) \ G1GC_ONLY(G1_GC_CONSTRAINTS(f)) \ - PARALLELGC_ONLY(PARALLEL_GC_CONSTRAINTS(f)) + PARALLELGC_ONLY(PARALLEL_GC_CONSTRAINTS(f)) \ + ZGC_ONLY(Z_GC_CONSTRAINTS(f)) #endif // SHARE_GC_SHARED_JVMFLAGCONSTRAINTSGC_HPP diff --git a/src/hotspot/share/gc/shenandoah/shenandoahArguments.cpp b/src/hotspot/share/gc/shenandoah/shenandoahArguments.cpp index 87594c85a591..8b67bea82488 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahArguments.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahArguments.cpp @@ -240,6 +240,12 @@ void ShenandoahArguments::initialize_alignments() { } } +void ShenandoahArguments::initialize_heap_flags_and_sizes() { + GCArguments::initialize_heap_flags_and_sizes(); + + FLAG_SET_ERGO_IF_DEFAULT(SoftMaxHeapSize, MaxHeapSize); +} + CollectedHeap* ShenandoahArguments::create_heap() { if (strcmp(ShenandoahGCMode, "generational") != 0) { // Not generational diff --git a/src/hotspot/share/gc/shenandoah/shenandoahArguments.hpp b/src/hotspot/share/gc/shenandoah/shenandoahArguments.hpp index bc73d9a2d128..4dbb0ce561fa 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahArguments.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahArguments.hpp @@ -32,6 +32,7 @@ class CollectedHeap; class ShenandoahArguments : public GCArguments { private: virtual void initialize_alignments(); + virtual void initialize_heap_flags_and_sizes(); virtual void initialize(); virtual size_t conservative_max_heap_alignment(); diff --git a/src/hotspot/share/gc/z/jvmFlagConstraintsZ.cpp b/src/hotspot/share/gc/z/jvmFlagConstraintsZ.cpp new file mode 100644 index 000000000000..65ec8564da88 --- /dev/null +++ b/src/hotspot/share/gc/z/jvmFlagConstraintsZ.cpp @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "gc/shared/gc_globals.hpp" +#include "gc/z/jvmFlagConstraintsZ.hpp" +#include "gc/z/zAdaptiveHeap.inline.hpp" +#include "runtime/flags/jvmFlag.hpp" + +#include + +JVMFlag::Error ZGCIntensityConstraintFunc(double value, bool verbose) { + if (!UseZGC) { + // There is no constraint when not using ZGC. + return JVMFlag::Error::SUCCESS; + } + + if (!g_isfinite(value)) { + JVMFlag::printError(verbose, + "ZGCIntensityConstraint (%f) must be finite.\n", + value); + return JVMFlag::Error::VIOLATES_CONSTRAINT; + } + + if (ZAdaptiveHeapSizing && value == 0.0) { + JVMFlag::printError(verbose, + "Cannot dynamically switch ZGCIntensity off.\n"); + return JVMFlag::Error::VIOLATES_CONSTRAINT; + } + + if (!ZAdaptiveHeapSizing && value != 0.0) { + JVMFlag::printError(verbose, + "Cannot dynamically switch ZGCIntensity on.\n"); + return JVMFlag::Error::VIOLATES_CONSTRAINT; + } + + return JVMFlag::Error::SUCCESS; +} diff --git a/src/hotspot/share/gc/z/jvmFlagConstraintsZ.hpp b/src/hotspot/share/gc/z/jvmFlagConstraintsZ.hpp new file mode 100644 index 000000000000..b3d8f1574078 --- /dev/null +++ b/src/hotspot/share/gc/z/jvmFlagConstraintsZ.hpp @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_GC_Z_JVMFLAGCONSTRAINTSZ_HPP +#define SHARE_GC_Z_JVMFLAGCONSTRAINTSZ_HPP + +#include "runtime/flags/jvmFlag.hpp" +#include "utilities/globalDefinitions.hpp" + +#define Z_GC_CONSTRAINTS(f) \ + \ + /* Adaptive Heap Sizing Constraints */ \ + f(double, ZGCIntensityConstraintFunc) \ + /* */ + +Z_GC_CONSTRAINTS(DECLARE_CONSTRAINT) + + +#endif // SHARE_GC_Z_JVMFLAGCONSTRAINTSZ_HPP diff --git a/src/hotspot/share/gc/z/vmStructs_z.hpp b/src/hotspot/share/gc/z/vmStructs_z.hpp index de32b964a512..d8d4818df714 100644 --- a/src/hotspot/share/gc/z/vmStructs_z.hpp +++ b/src/hotspot/share/gc/z/vmStructs_z.hpp @@ -90,7 +90,7 @@ typedef ZValue ZPerNUMAZPartition; nonstatic_field(ZPage, _virtual, const ZVirtualMemory) \ volatile_nonstatic_field(ZPage, _top, zoffset_end) \ \ - nonstatic_field(ZPageAllocator, _max_capacity, const size_t) \ + nonstatic_field(ZPageAllocator, _static_max_capacity, const size_t) \ nonstatic_field(ZPageAllocator, _partitions, ZPerNUMAZPartition) \ \ static_field(ZNUMA, _count, uint32_t) \ diff --git a/src/hotspot/share/gc/z/zAdaptiveHeap.cpp b/src/hotspot/share/gc/z/zAdaptiveHeap.cpp new file mode 100644 index 000000000000..cea3549521d9 --- /dev/null +++ b/src/hotspot/share/gc/z/zAdaptiveHeap.cpp @@ -0,0 +1,1246 @@ +/* + * Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include "gc/shared/gc_globals.hpp" +#include "gc/shared/gcLogPrecious.hpp" +#include "gc/z/zAdaptiveHeap.hpp" +#include "gc/z/zAdaptiveHeap.inline.hpp" +#include "gc/z/zDriver.hpp" +#include "gc/z/zGlobals.hpp" +#include "gc/z/zHeap.inline.hpp" +#include "gc/z/zLock.inline.hpp" +#include "gc/z/zStat.hpp" +#include "logging/log.hpp" +#include "runtime/atomicAccess.hpp" +#include "runtime/globals_extension.hpp" +#include "runtime/os.hpp" +#include "utilities/debug.hpp" +#include "utilities/globalDefinitions.hpp" + +#include +#include + +bool ZAdaptiveHeap::_explicit_max_capacity; +bool ZAdaptiveHeap::_initialized; +bool ZAdaptiveHeap::_initialized_generation_data; +ZAdaptiveHeap::ZIntensitySmoother ZAdaptiveHeap::_gc_intensities; + +Atomic ZAdaptiveHeap::_young_to_old_gc_time(0.0); +Atomic ZAdaptiveHeap::_accumulated_young_gc_time(0.0); +Atomic ZAdaptiveHeap::_accumulated_mem_worker_time(0.0); +Atomic ZAdaptiveHeap::_avg_mem_worker_cpu_overhead(0.0); +ZAdaptiveHeap::ZGenerationOverhead ZAdaptiveHeap::_young_data; +ZAdaptiveHeap::ZGenerationOverhead ZAdaptiveHeap::_old_data; +Atomic ZAdaptiveHeap::_initial_young_worker_cap; + +// Calculate progression from 0 to 1 going down from a less critical availability to a more critical availability +static double calculate_progression(double availability, double from, double to) { + return 1.0 - (availability - to) / (from - to); +} + +void ZAdaptiveHeap::ZIntensitySmoother::initialize() { + _lock = new ZLock(); +} + +double ZAdaptiveHeap::ZIntensitySmoother::record_and_smooth_gc_intensity(double scaled_gc_intensity) { + precond(_lock != nullptr); + ZLocker locker(_lock); + _gc_intensities.add(scaled_gc_intensity); + return MAX2(_gc_intensities.avg(), scaled_gc_intensity); +} + +void ZAdaptiveHeap::initialize(bool explicit_max_capacity) { + precond(!_initialized); + _explicit_max_capacity = explicit_max_capacity; + if (ZAdaptiveHeapSizing) { + _gc_intensities.initialize(); + } + _initialized = true; +} + +void ZAdaptiveHeap::initialize_generation_data() { + precond(_initialized); + precond(!_initialized_generation_data); + + { // Setup initial machine_elapsed_system_cpu_time + ZSystemCpuTime machine_system_time_now; + const bool has_machine_system_time_now = machine_elapsed_system_cpu_time(machine_system_time_now); + if (has_machine_system_time_now) { + _young_data._last_machine_system_time = machine_system_time_now._elapsed_time; + _old_data._last_machine_system_time = machine_system_time_now._elapsed_time; + _young_data._machine_processor_count = machine_system_time_now._processor_count; + _old_data._machine_processor_count = machine_system_time_now._processor_count; + } + _young_data._has_last_machine_system_time = has_machine_system_time_now; + _old_data._has_last_machine_system_time = has_machine_system_time_now; + } + + { // Setup initial container_elapsed_system_cpu_time + ZSystemCpuTime container_system_time_now; + const bool has_container_system_time = container_elapsed_system_cpu_time(container_system_time_now); + if (has_container_system_time) { + _young_data._last_container_system_time = container_system_time_now._elapsed_time; + _old_data._last_container_system_time = container_system_time_now._elapsed_time; + _young_data._container_processor_count = container_system_time_now._processor_count; + _old_data._container_processor_count = container_system_time_now._processor_count; + } + _young_data._has_last_container_system_time = has_container_system_time; + _old_data._has_last_container_system_time = has_container_system_time; + } + + { // Setup initial os::elapsed_process_cpu_time + double process_time_now; + const bool has_process_time = os::elapsed_process_cpu_time(process_time_now); + if (has_process_time) { + _young_data._last_process_time = process_time_now; + _old_data._last_process_time = process_time_now; + } + _young_data._has_last_process_time = has_process_time; + _old_data._has_last_process_time = has_process_time; + } + + { // Setup initial os::elapsedTime + const double time_now = os::elapsedTime(); + _young_data._last_time = time_now; + _old_data._last_time = time_now; + } + + _initialized_generation_data = true; +} + +ZMachineMemoryInfo ZAdaptiveHeap::machine_memory_info() { + ZMachineMemoryInfo info = {}; + + // Used to "latch" on to whatever validity we initially observe and make sure + // that the same validity is continued to be observed moving forward. Observing + // different validity during execution can lead to really bad heuristics. + struct ZBooleanStabilityRAII { + ZMachineMemoryInfo& _info; + ZBooleanStabilityRAII(ZMachineMemoryInfo& info) : _info(info) {} + ~ZBooleanStabilityRAII() { + static bool is_valid = _info._is_valid; + if (_info._is_valid != is_valid) { + log_warning(gc)("The data source for machine_memory_info is unstable (expected _is_valid to be %s, got %s). " + "This behavior is unsupported for Adaptive Heap Sizing. Please run without Adaptive Heap Sizing using -XX:-ZAdaptiveHeapSizing/-XX:ZGCIntensity=0", + BOOL_TO_STR(is_valid), BOOL_TO_STR(_info._is_valid)); + os::exit(1); + } + } + } is_valid_stability(info); + + pd_machine_memory_info(info); + return info; +} + +physical_memory_size_type ZAdaptiveHeap::machine_physical_memory() { + return machine_memory_info()._physical_memory; +} + +double ZAdaptiveHeap::young_to_old_gc_time() { + return _young_to_old_gc_time.load_relaxed(); +} + +uint ZAdaptiveHeap::initial_young_worker_cap() { + uint capacity = _initial_young_worker_cap.load_relaxed(); + if (capacity == 0) { + // Not yet set; use one - there are barely any objects early on anyway + return 1; + } + + return capacity; +} + +// For large machines we estimate their wait probability as if they have 128 +// processors available. This means that for machines with more than 128 +// available processors we may overestimate the impact of loads above 75%. +static constexpr int ErlangCMaxProcessors = 128; + +static double* generate_factorials() { + static double result[ErlangCMaxProcessors + 1]; + result[0] = 1.0; + for (uint64_t i = 1; i <= ErlangCMaxProcessors; ++i) { + result[i] = result[i - 1] * (double)i; + } + return result; +} + +// Probability to have to wait in a queue given c servers and rho queue utilization +static double erlang_c(int c, double rho) { + // Cap c at ErlangCMaxProcessors to avoid problems with double precision. + c = MIN2(c, ErlangCMaxProcessors); + + static double* const factorials = generate_factorials(); + + const double unutilized_reciprocal = 1.0 / (1.0 - rho); + const double offered_load = rho * c; + const double nominator = unutilized_reciprocal * pow(offered_load, double(c)) / factorials[c]; + + double sum = 0.0; + for (int k = 0; k < c; ++k) { + sum += pow(offered_load, double(k)) / factorials[k]; + } + + const double denominator = nominator + sum; + + return nominator / denominator; +} + +static double cpu_latency_factor(int c, double rho, double unluckyness) { + const double prob_join_queue = erlang_c(c, rho); + + // P99 is approximately 100x more likely to join the queue + const double p99_prob_join_queue = MIN2(prob_join_queue * unluckyness, 1.0); + const double p99_latency_factor = p99_prob_join_queue / (1.0 - rho); + + return 1.0 + p99_latency_factor; +} + +ZMemoryPressureMetrics ZAdaptiveHeap::memory_pressure_metrics() { + precond(ZAdaptiveHeapSizing); + const double unscaled_gc_intensity = clamp(AtomicAccess::load(&ZGCIntensity), MinZGCIntensity, MaxZGCIntensity); + + const ZMachineMemoryInfo machine_memory_info = ZAdaptiveHeap::machine_memory_info(); + + const physical_memory_size_type machine_max_memory = machine_memory_info._physical_memory; + + const physical_memory_size_type machine_used_memory = machine_memory_info._is_valid + ? machine_memory_info._physical_memory - machine_memory_info._available_memory + : os::rss(); // Approximation for faulty os + + // The concerning threshold is after which memory utilization we start trying + // harder to keep the memory down. There are multiple reasons for letting the GC + // run hotter: + // 1) We want to maintain some headroom on the machine so that we can deal with + // spikes without getting allocation stalls. + // 2) It's good to let the OS keep some file system cache memory around + // 3) On systems that compress used memory, using compressed memory is not a + // free lunch as it leads to page faults that compress and decompress memory. + // This is extra painful for a tracing GC to traverse. + const double machine_compression_rate = machine_memory_compression_ratio(machine_used_memory, machine_max_memory); + + // When the machine memory usage is fluctuating and isn't stable, it's good to + // increase the headroom to the memory limits, otherwise there is a higher risk + // of an allocation stall. + const double machine_memory_instability = ZStatSystemMemoryUsage::machine_memory_stability(); + + const double memory_uncertainty = MAX2(machine_memory_instability, machine_compression_rate); + const double machine_concerning_threshold = MIN2(ZMemoryConcerningThreshold + memory_uncertainty, 1.0); + + const double machine_concerning_vs_high_diff = ZMemoryConcerningThreshold - ZMemoryHighThreshold; + const double machine_high_threshold = machine_concerning_threshold - machine_concerning_vs_high_diff; + + const double machine_critical_threshold = ZMemoryCriticalThreshold; + + const double far_avoid = 1.0 - ZMemoryConcerningThreshold; + const double medium_avoid = 1.0 - ZMemoryHighThreshold; + const double near_avoid = 1.0 - ZMemoryCriticalThreshold; + + physical_memory_size_type container_max_memory; + physical_memory_size_type container_used_memory; + double container_concerning_threshold; + double container_high_threshold; + double container_critical_threshold; + + const bool can_read_container_memory = os::is_containerized() && os::Container::used_memory(container_used_memory); + bool has_container_limit = false; + if (can_read_container_memory) { + physical_memory_size_type container_critical_memory; + + // Allocation stalls at critical levels + if (os::Container::memory_limit(container_max_memory)) { + has_container_limit = true; + } else { + container_max_memory = machine_max_memory; + } + + // Exponential increase in pressure up to critical + physical_memory_size_type container_high_memory; + if (os::Container::memory_throttle_limit(container_high_memory)) { + container_max_memory = MIN2(container_max_memory, container_high_memory); + container_critical_memory = container_max_memory; + container_high_memory = physical_memory_size_type(MIN2(container_high_memory, container_critical_memory) * (medium_avoid / near_avoid)); + has_container_limit = true; + } else { + container_critical_memory = physical_memory_size_type(container_max_memory * near_avoid); + container_high_memory = physical_memory_size_type(container_critical_memory * (medium_avoid / near_avoid)); + } + + // Linear increase in pressure up to intensity squared + physical_memory_size_type container_min_memory; + if (os::Container::memory_soft_limit(container_min_memory)) { + container_min_memory = MIN2(container_min_memory, physical_memory_size_type(container_high_memory * (far_avoid / medium_avoid))); + has_container_limit = true; + } else { + container_min_memory = physical_memory_size_type(container_high_memory * (far_avoid / medium_avoid)); + } + + const auto memory_threshold = [](physical_memory_size_type used_memory, physical_memory_size_type max_memory) { + return max_memory == 0 ? 1.0 : 1.0 - (double)used_memory / (double)max_memory; + }; + + container_critical_threshold = memory_threshold(container_critical_memory, container_max_memory); + container_high_threshold = memory_threshold(container_high_memory, container_max_memory); + container_concerning_threshold = memory_threshold(container_min_memory, container_max_memory); + + // Adjust the threshold by how unstable the memory usage in the system is + const double container_memory_instability = ZStatSystemMemoryUsage::container_memory_stability(); + const double instability_adjustment = MIN2(container_memory_instability, 1.0 - container_concerning_threshold); + container_concerning_threshold += instability_adjustment; + container_high_threshold += instability_adjustment; + } + + const bool is_containerized = can_read_container_memory && has_container_limit; + + if (!is_containerized) { + container_max_memory = machine_max_memory; + container_used_memory = machine_used_memory; + container_concerning_threshold = machine_concerning_threshold; + container_high_threshold = machine_high_threshold; + container_critical_threshold = machine_critical_threshold; + } + + return { + unscaled_gc_intensity, + is_containerized, + { + machine_used_memory, + machine_max_memory, + machine_concerning_threshold, + machine_high_threshold, + machine_critical_threshold + }, + { + container_used_memory, + container_max_memory, + container_concerning_threshold, + container_high_threshold, + container_critical_threshold + } + }; +} + +static double system_memory_pressure(const ZSystemMemoryPressureMetrics& metrics, double unscaled_gc_intensity) { + const physical_memory_size_type available_memory = metrics.available_memory(); + + // The remaining memory reserve of the machine + const double availability = metrics.available_fraction(); + + // A number indicating how much the memory pressure should grow as the + // memory unavailability grows + const double pressure_rate = MAX2(unscaled_gc_intensity, 2.0); + + const double concerning = metrics._concerning_threshold; + const double high = metrics._high_threshold; + const double critical = metrics._critical_threshold; + + if (availability < high) { + // When memory pressure is "high", we exponentially scale up memory pressure, + // from the already "high" pressure induced by "concerning" memory pressure. + const double progression = calculate_progression(availability, high, critical); + const double exponent = 1.0 + progression; + + // Returns a value between pressure_rate and pressure_rate^3 (the exact exponent + // depends on the values of high and critical, but will not go above 3) + return pow(pressure_rate, exponent); + } + + if (availability < concerning) { + // When memory pressure is "concerning", we quadratically scale up memory + // pressure to the "high" pressure (i.e., unscaled GC intensity). + const double progression = calculate_progression(availability, concerning, high); + const double progression_squared = progression * progression; + const double pressure_factor = (pressure_rate - 1.0) * progression_squared; + + // Returns a value between 1 and pressure_rate + return 1.0 + pressure_factor; + } + + return 1.0; +} + +double ZAdaptiveHeap::compute_memory_pressure(const ZMemoryPressureMetrics& metrics) { + const double machine_mem_pressure = system_memory_pressure(metrics._machine, metrics._unscaled_gc_intensity); + + if (!metrics._is_containerized) { + return machine_mem_pressure; + } + + const double container_mem_pressure = system_memory_pressure(metrics._container, metrics._unscaled_gc_intensity); + return MAX2(machine_mem_pressure, container_mem_pressure); +} + +static bool is_system_memory_pressure_concerning(const ZSystemMemoryPressureMetrics& metrics) { + const double availability = metrics.available_fraction(); + + return availability < metrics._concerning_threshold; +} + +bool ZAdaptiveHeap::is_memory_pressure_concerning(const ZMemoryPressureMetrics& metrics) { + if (is_system_memory_pressure_concerning(metrics._machine)) { + return true; + } + + if (metrics._is_containerized) { + return is_system_memory_pressure_concerning(metrics._container); + } + + return false; +} + +static bool is_system_memory_pressure_high(const ZSystemMemoryPressureMetrics& metrics) { + const double availability = metrics.available_fraction(); + + return availability < metrics._high_threshold; +} + +bool ZAdaptiveHeap::is_memory_pressure_high(const ZMemoryPressureMetrics& metrics) { + if (is_system_memory_pressure_high(metrics._machine)) { + return true; + } + + if (metrics._is_containerized) { + return is_system_memory_pressure_high(metrics._container); + } + + return false; +} + +static bool is_system_memory_pressure_critical(const ZSystemMemoryPressureMetrics& metrics) { + const double availability = metrics.available_fraction(); + + return availability < metrics._critical_threshold; +} + +bool ZAdaptiveHeap::is_memory_pressure_critical(const ZMemoryPressureMetrics& metrics) { + if (is_system_memory_pressure_critical(metrics._machine)) { + return true; + } + + if (metrics._is_containerized) { + return is_system_memory_pressure_critical(metrics._container); + } + + return false; +} + +void ZAdaptiveHeap::sample_generation_data(ZGenerationOverhead& generation_data) { + + { // Sample machine_elapsed_system_cpu_time + ZSystemCpuTime machine_system_time_now; + const bool has_machine_system_time = machine_elapsed_system_cpu_time(machine_system_time_now); + if (has_machine_system_time) { + if (generation_data._has_last_machine_system_time) { + const double machine_system_time_last = generation_data._last_machine_system_time; + const double machine_system_time = machine_system_time_now._elapsed_time - machine_system_time_last; + generation_data._machine_system_times.add(machine_system_time); + } + + generation_data._last_machine_system_time = machine_system_time_now._elapsed_time; + generation_data._machine_processor_count = machine_system_time_now._processor_count; + } + generation_data._has_last_machine_system_time = has_machine_system_time; + } + + { // Sample container_elapsed_system_cpu_time + ZSystemCpuTime container_system_time_now; + const bool has_container_system_time = container_elapsed_system_cpu_time(container_system_time_now); + if (has_container_system_time) { + if (generation_data._has_last_container_system_time) { + const double container_system_time_last = generation_data._last_container_system_time; + const double container_system_time = container_system_time_now._elapsed_time - container_system_time_last; + generation_data._container_system_times.add(container_system_time); + } + + generation_data._last_container_system_time = container_system_time_now._elapsed_time; + generation_data._container_processor_count = container_system_time_now._processor_count; + } + generation_data._has_last_container_system_time = has_container_system_time; + } + + { // Sample os::elapsed_process_cpu_time + double process_time_now; + const bool has_process_time = os::elapsed_process_cpu_time(process_time_now); + if (has_process_time) { + if (generation_data._has_last_process_time) { + const double process_time_last = generation_data._last_process_time; + const double process_time = process_time_now - process_time_last; + generation_data._process_times.add(process_time); + } + + generation_data._last_process_time = process_time_now; + } + generation_data._has_last_process_time = has_process_time; + } + + { // Sample os::elapsedTime + const double time_now = os::elapsedTime(); + const double time_last = generation_data._last_time; + const double time_since_last = time_now - time_last; + generation_data._last_time = time_now; + generation_data._gc_times_since_last.add(time_since_last); + } +} + +ZCpuPressureMetrics ZAdaptiveHeap::cpu_pressure_metrics(ZGenerationId generation) { + precond(_initialized_generation_data); + const bool is_young = generation == ZGenerationId::young; + ZGenerationOverhead& generation_data = is_young ? _young_data : _old_data; + + // Time metrics + // Note that the system time might have poor accuracy early on; it typically + // has 100 ms granularity. So take it with a large grain of salt early on... + sample_generation_data(generation_data); + + // Processor metrics + const double machine_ncpus = generation_data._has_last_machine_system_time + ? generation_data._machine_processor_count + : double(os::processor_count()); + const bool has_container_cpu_metrics = generation_data._has_last_container_system_time; + const double container_ncpus = generation_data._container_processor_count; + const bool has_container_cpu_capacity_limit = + has_container_cpu_metrics && + container_ncpus < double(machine_ncpus); + + // Processors used by this process + const double ncpus = has_container_cpu_metrics ? container_ncpus : double(machine_ncpus); + + // Cycle metrics + ZStatCycleStats cycle_stats = ZGeneration::generation(generation)->stat_cycle()->stats(); + const double gc_time = cycle_stats._last_total_vtime + (is_young ? 0.0 : _accumulated_young_gc_time.load_relaxed()); + generation_data._gc_times.add(gc_time); + const double mem_worker_time = is_young ? ZStatMemoryWorkers::get_and_reset_vtime() : _accumulated_mem_worker_time.load_relaxed(); + generation_data._mem_worker_times.add(mem_worker_time); + + const double avg_gc_time = generation_data._gc_times.avg(); + const double avg_time_since_last = generation_data._gc_times_since_last.avg(); + const double avg_machine_system_time = generation_data._machine_system_times.avg(); + const double avg_container_system_time = generation_data._container_system_times.avg(); + + // Process times + const double process_time = generation_data._process_times.last(); + const double avg_process_time = generation_data._process_times.avg(); + const double avg_generation_gc_cpu_overhead = percent_of(avg_gc_time, avg_process_time) / 100.0; + const double generation_gc_cpu_overhead = percent_of(gc_time, process_time) / 100.0; + + const double avg_machine_process_cpu_load = clamp((avg_process_time / avg_time_since_last) / machine_ncpus, 0.0, 1.0); + const double avg_machine_system_cpu_load = clamp((avg_machine_system_time / avg_time_since_last) / machine_ncpus, 0.0, 1.0); + const double avg_container_process_cpu_load = has_container_cpu_metrics + ? clamp((avg_process_time / avg_time_since_last) / ncpus, 0.0, 1.0) + : avg_machine_process_cpu_load; + const double avg_container_system_cpu_load = has_container_cpu_metrics + ? clamp((avg_container_system_time / avg_time_since_last) / ncpus, 0.0, 1.0) + : avg_machine_system_cpu_load; + + // Account for the overhead of old generation collections when evaluating + // the heap efficiency for young generation collections. + const double avg_total_gc_cpu_overhead = MIN2(avg_generation_gc_cpu_overhead / (is_young ? _young_to_old_gc_time.load_relaxed() : 1.0), 1.0); + + const double avg_mem_worker_time = generation_data._mem_worker_times.avg(); + const double avg_mem_worker_cpu_overhead = percent_of(avg_mem_worker_time, avg_gc_time); + + return { + has_container_cpu_metrics, + has_container_cpu_capacity_limit, + generation_gc_cpu_overhead, + avg_generation_gc_cpu_overhead, + avg_total_gc_cpu_overhead, + avg_time_since_last, + avg_process_time, + gc_time, + mem_worker_time, + avg_mem_worker_cpu_overhead, + { + machine_ncpus, + avg_machine_process_cpu_load, + avg_machine_system_cpu_load + }, + { + ncpus, + avg_container_process_cpu_load, + avg_container_system_cpu_load + } + }; +} + +static double mem_urgency_scaled_cpu_pressure(const ZSystemMemoryPressureMetrics& mem_metrics, double cpu_pressure) { + if (cpu_pressure >= 1.0) { + return cpu_pressure; + } + + // As memory pressure gets high, heap must be pressed down + if (is_system_memory_pressure_high(mem_metrics)) { + return 1.0; + } + + // As memory pressure gets higher, heap must be pressed down + if (!is_system_memory_pressure_concerning(mem_metrics)) { + return cpu_pressure; + } + + // Calculate concerning progression towards high + const double availability = mem_metrics.available_fraction(); + const double progression = calculate_progression(availability, mem_metrics._concerning_threshold, mem_metrics._high_threshold); + + // Scale back to 1 as we approach high mem pressure + return cpu_pressure + (1.0 - cpu_pressure) * progression; +} + +static double compute_cpu_vs_memory_pressure(const ZSystemMemoryPressureMetrics& mem_metrics, const ZSystemCpuPressureMetrics& cpu_metrics, physical_memory_size_type process_used_memory) { + const double process_memory_usage_ratio = clamp(double(process_used_memory) / double(mem_metrics._used_memory), 0.0, 1.0); + + const double process_cpu_usage_ratio = cpu_metrics._avg_process_load / cpu_metrics._avg_system_load; + + // The GC intensity is scaled by the relationship of how many of the system's + // used bytes belong to this process compared to how many of the used system + // CPU ticks belong to this process. For a single application deployment this + // has effectively no effect, while for a multi process deployment, processes + // that are unproportionately memory bloated compared to other processes will + // rebalance themselves better to provide more memory for other processes. + const double process_cpu_pressure = process_memory_usage_ratio - process_cpu_usage_ratio; + + // The GC intensity is scaled by what portion of system CPU resources are being + // used. As CPU utilization of the machine gets higher, there will be more + // fighting between mutator threads for CPU time, affecting latencies. + // This concern is ignored here - this heuristic is purely reasoning about + // the CPU vs memory resource availabilities. A separate cpu_vs_latency + // calculation estimates the latency impact of too high CPU, and adjusts. + const double system_cpu_usage = cpu_metrics._avg_system_load; + const double system_memory_usage = mem_metrics.used_fraction(); + const double system_cpu_pressure = system_memory_usage - system_cpu_usage; + + // Balance the forces of resource share imbalance across processes with the + // forces of system level resource usage imbalance. + const double cpu_vs_memory_pressure = 1.0 + clamp(process_cpu_pressure + system_cpu_pressure, -0.2, 2.0); + + // Make sure that as memory availability drops, memory pressure starts to + // dominate the overall GC intensity; without memory the JVM dies. + return mem_urgency_scaled_cpu_pressure(mem_metrics, cpu_vs_memory_pressure); +} + +static double compute_cpu_vs_memory_pressure(const ZMemoryPressureMetrics& mem_metrics, const ZCpuPressureMetrics& cpu_metrics, physical_memory_size_type process_used_memory) { + const double machine_cpu_pressure = compute_cpu_vs_memory_pressure(mem_metrics._machine, cpu_metrics._machine, process_used_memory); + + if (!mem_metrics._is_containerized && !cpu_metrics._has_container_cpu_capacity_limit) { + return machine_cpu_pressure; + } + + const double container_cpu_pressure = compute_cpu_vs_memory_pressure(mem_metrics._container, cpu_metrics._container, process_used_memory); + return ::sqrt(container_cpu_pressure * machine_cpu_pressure); +} + +static double compute_cpu_vs_latency_pressure(const ZSystemCpuPressureMetrics& machine_cpu_metrics) { + // Approximate latency risks on the machine level using an M/M/c queue system. + // This allows us to calculate by what factor tail latency will be affected by + // the CPU pressure. Rather than looking at the current CPU pressure, we look + // ahead a bit so we deal with latency problems proactively - before they arise, + // instead of post mortem. + // This type of M/M/c calculations make sense on a system level, but for the + // container level, things work differently, and CPU vs memory is considered + // purely as a resource balancing exercise, which still helps latency as well. + double rho = MIN2(machine_cpu_metrics._avg_system_load + 0.05, 0.99); + const int processor_count = MAX2(1, int(machine_cpu_metrics._processor_count)); + const double p99_response_time_scaling = cpu_latency_factor(processor_count, rho, 100.0); + return 1.0 / MIN2(p99_response_time_scaling, 5.0); +} + +static double compute_cpu_vs_latency_pressure(const ZMemoryPressureMetrics& mem_metrics, const ZCpuPressureMetrics& cpu_metrics) { + const double machine_cpu_vs_latency_pressure = compute_cpu_vs_latency_pressure(cpu_metrics._machine); + + // Make sure that as machine memory availability drops, memory pressure starts to + // dominate the overall GC intensity; without memory the JVM dies. + const double machine_scaled_pressure = mem_urgency_scaled_cpu_pressure(mem_metrics._machine, machine_cpu_vs_latency_pressure); + + if (!mem_metrics._is_containerized && !cpu_metrics._has_container_cpu_capacity_limit) { + return machine_scaled_pressure; + } + + // Make sure that as container availability drops, memory pressure starts to + // dominate the overall GC intensity; without memory the JVM dies. + // Note that using the machine CPU vs latency pressure here is intentional; + // the probability of getting placed in a run queue is on machine level, + // not on the container level. + const double container_scaled_pressure = mem_urgency_scaled_cpu_pressure(mem_metrics._container, machine_cpu_vs_latency_pressure); + + return sqrt(machine_scaled_pressure * container_scaled_pressure); +} + +ZResourcePressure ZAdaptiveHeap::compute_pressures(const ZMemoryPressureMetrics& mem_metrics, const ZCpuPressureMetrics& cpu_metrics, size_t projected_process_used_memory) { + precond(ZAdaptiveHeapSizing); + + const double mem_pressure = compute_memory_pressure(mem_metrics); + const double cpu_vs_memory_pressure = compute_cpu_vs_memory_pressure(mem_metrics, cpu_metrics, projected_process_used_memory); + const double cpu_vs_latency_pressure = compute_cpu_vs_latency_pressure(mem_metrics, cpu_metrics); + + // Use sqrt to compute geometric mean + const double cpu_pressure = ::sqrt(cpu_vs_memory_pressure * cpu_vs_latency_pressure); + + // The combined forces of memory vs CPU. The one force... TO RULE THEM ALL!! + const double resource_pressure = mem_pressure * cpu_pressure; + + const double scaled_gc_intensity = mem_metrics._unscaled_gc_intensity * resource_pressure; + + return { + scaled_gc_intensity, + cpu_pressure, + mem_pressure, + cpu_vs_memory_pressure, + cpu_vs_latency_pressure + }; +} + +double ZAdaptiveHeap::smoothed_gc_intensity(double scaled_gc_intensity) { + precond(_initialized); + return _gc_intensities.record_and_smooth_gc_intensity(scaled_gc_intensity); +} + +bool ZAdaptiveHeap::machine_elapsed_system_cpu_time(ZSystemCpuTime& value) { + return pd_machine_elapsed_system_cpu_time(value); +} + +bool ZAdaptiveHeap::container_elapsed_system_cpu_time(ZSystemCpuTime& value) { + if (os::is_containerized()) { + return pd_container_elapsed_system_cpu_time(value); + } + + return false; +} + +double ZAdaptiveHeap::machine_memory_compression_ratio(physical_memory_size_type machine_used_memory, + physical_memory_size_type machine_max_memory) { + physical_memory_size_type machine_compressed_memory; + if (pd_machine_compressed_memory(machine_compressed_memory)) { + machine_compressed_memory = MIN2(machine_compressed_memory, machine_used_memory); + } else { + machine_compressed_memory = 0; + } + + return double(machine_compressed_memory) / double(machine_max_memory); +} + +static double compute_target_gc_interval(const ZSystemMemoryPressureMetrics& mem_metrics, + double gc_intensity, + size_t process_used_memory) { + // As memory pressure gets high, heap must be pressed down + if (is_system_memory_pressure_critical(mem_metrics)) { + return 0.0; + } + + // High GC frequencies lead to extra overheads such as barrier storms and + // frequent cache invalidation of the heap. + // Therefore, we add a factor that ensures there is at least some social + // distancing between GCs, even when the GC overhead is small. The size of + // the factor scales with the level of load induced on the machine, as well + // as the scaled and unscaled GC intensities. + const double gc_intensity_factor = 5.0 / gc_intensity; + + // As the process starts dominating the available system memory, linearly + // tone down the memory bloating. + const double other_processes_memory = clamp(1.0 - mem_metrics.fraction_of_max(process_used_memory), 0.0, 1.0); + const double process_progression = calculate_progression(other_processes_memory, + 1.0, + mem_metrics._critical_threshold); + + const double gc_interval = gc_intensity_factor * (1.0 - process_progression); + + if (is_system_memory_pressure_concerning(mem_metrics)) { + // As memory availability drops low in the system, we want to further prevent + // memory bloating due to the interval target with another linear factor. If + // the only memory culprit is the current process, then the progression will + // now turn quadratic, which should help reduce the memory bloating further. + + const double system_availability = mem_metrics.available_fraction(); + const double system_progression = calculate_progression(system_availability, + mem_metrics._concerning_threshold, + mem_metrics._critical_threshold); + + return gc_interval * (1.0 - system_progression); + } + + return gc_interval; +} + +static double compute_target_gc_interval(const ZMemoryPressureMetrics& mem_metrics, + double gc_intensity, + size_t process_used_memory) { + const double machine_target_gc_interval = compute_target_gc_interval(mem_metrics._machine, + gc_intensity, + process_used_memory); + + if (!mem_metrics._is_containerized) { + return machine_target_gc_interval; + } + + const double container_target_gc_interval = compute_target_gc_interval(mem_metrics._container, + gc_intensity, + process_used_memory); + return MIN2(machine_target_gc_interval, container_target_gc_interval); +} + +// Logistic function, produces values in the range 0 - 1 in an S shape +static double sigmoid_function(double value) { + return 1.0 / (1.0 + pow(M_E, -value)); +} + +// This function smoothens out measured error signals to make the incremental heap +// sizing converge better. During an initial warmup period, a more aggressive function +// is used, which doesn't try to reduce the error signals. This reduces the number of +// early GCs before the system has had any chance to converge to a stable heap size. +static double smoothing_function(double value, double warmness) { + const double sigmoid = sigmoid_function(value); + const double aggressive = MIN2(MAX2(sigmoid, 0.5 + value), 2.0); + + return sigmoid * warmness + aggressive * (1.0 - warmness); +} + +size_t ZAdaptiveHeap::compute_heap_size(const ZHeapResizeMetrics& heap_metrics, ZGenerationId generation) { + precond(ZAdaptiveHeapSizing); + + const bool is_major = Thread::current() == ZDriver::major(); + const GCCause::Cause cause = is_major ? ZDriver::major()->gc_cause() : ZDriver::minor()->gc_cause(); + const bool is_heap_anti_pressure_gc = cause == GCCause::_z_proactive; + const bool is_heap_pressure_gc = cause == GCCause::_z_allocation_rate || + cause == GCCause::_z_high_usage || + cause == GCCause::_z_warmup; + + if (!is_heap_anti_pressure_gc && !is_heap_pressure_gc) { + // If this GC isn't heuristically triggered, don't resize or learn anything + return heap_metrics._heuristic_max_capacity; + } + + // System memory load + const ZMemoryPressureMetrics mem_metrics = memory_pressure_metrics(); + ZStatSystemMemoryUsage::record(mem_metrics); + + // System CPU load + const ZCpuPressureMetrics cpu_metrics = cpu_pressure_metrics(generation); + if (generation == ZGenerationId::young) { + _avg_mem_worker_cpu_overhead.store_relaxed(cpu_metrics._avg_mem_worker_cpu_overhead * ZAdaptiveHeap::young_to_old_gc_time()); + } else { + _avg_mem_worker_cpu_overhead.store_relaxed(cpu_metrics._avg_mem_worker_cpu_overhead); + } + + // Heap size metrics + const size_t soft_max_capacity = heap_metrics._soft_max_capacity; + const size_t current_max_capacity = heap_metrics._current_max_capacity; + const size_t heuristic_max_capacity = heap_metrics._heuristic_max_capacity; + const size_t capacity = heap_metrics._capacity; + const size_t static_min_capacity = heap_metrics._static_min_capacity; + const size_t used = heap_metrics._used; + + if (is_heap_anti_pressure_gc) { + // The GC is bored. The impact of shrinking should not cost a considerable amount of + // CPU, or we would not get here. + const size_t selected_capacity = MAX2(size_t(double(heuristic_max_capacity) * 0.95), used); + return clamp(align_down(selected_capacity, ZGranuleSize), static_min_capacity, current_max_capacity); + } + + const ZStatCycleStats cycle_stats = ZGeneration::generation(generation)->stat_cycle()->stats(); + + const double warmup_time_seconds = 3.0; + const double warmness = MIN2(os::elapsedTime(), warmup_time_seconds) / warmup_time_seconds; + const double warmness_squared = warmness * warmness; + + const size_t process_used_memory = os::rss(); + const size_t process_non_heap_memory = process_used_memory > capacity ? process_used_memory - capacity : 0; + const size_t projected_process_used_memory = heuristic_max_capacity + process_non_heap_memory; + + // Calculate the GC pressure that scales the rest of the heuristics + ZResourcePressure pressures = compute_pressures(mem_metrics, cpu_metrics, projected_process_used_memory); + + const double mem_pressure = pressures._mem_pressure; + const double avg_time_since_last = cpu_metrics._avg_gc_interval; + const double gc_intensity = smoothed_gc_intensity(pressures._scaled_gc_intensity); + + // Calculate the heuristic lower bound for the heuristic heap + const double alloc_rate = heap_metrics._alloc_rate; + // Since a GC cycle is obviously round, we can estimate the minimum bytes due to + // a particular allocation rate and GC intensity by calculating GC intensity * pi. + const double alloc_rate_window = warmness_squared / (gc_intensity * M_PI); + const size_t alloc_rate_window_bytes = size_t(alloc_rate * alloc_rate_window); + const size_t heuristic_low = align_down(size_t(double(MAX2(size_t(double(used) * 1.1), alloc_rate_window_bytes)) / mem_pressure), ZGranuleSize); + + const size_t upper_bound = MIN2(soft_max_capacity, current_max_capacity); + const size_t lower_bound = clamp(heuristic_low, static_min_capacity, upper_bound); + + // We want strictly more GC activity on systems with proactive memory + // compression (Windows and MacOS). Otherwise, memory bloating leads to + // memory compression and decompression, which wastes more CPU than just + // doing more GC instead. + const double compression_gc_penalty = 1.0 MACOS_ONLY(* 2.0) WINDOWS_ONLY(* 2.0); + + // When GC intensity is 10, the implication is that we want ZConcurrentWorkersCPUShare + // of the process CPU to be spent on doing GC when the process uses 100% of the + // available CPU cores. The ConcGCThreads sizing by default goes up to a maximum + // of ZConcurrentWorkersCPUShare of the available cores. So all ConcGCThreads + // would be running back to back then. + const double normalised_gc_intensity = gc_intensity / 10; + const double target_cpu_overhead = ZConcurrentWorkersCPUShare * normalised_gc_intensity * compression_gc_penalty; + + // Save some breadcrumbs to the director to not use more conc GC threads + // than we need to run back to back GC at the target GC CPU overhead limit. + // It is better to let concurrent heap expansion run. + const double avg_process_time = cpu_metrics._avg_process_time; + const double avg_process_cpus = avg_process_time / avg_time_since_last; + const double high_target_workers = avg_process_cpus * target_cpu_overhead; + const uint initial_young_worker_cap = (uint)clamp(ceil(high_target_workers * 1.5), 1.0, (double)ZYoungGCThreads); + _initial_young_worker_cap.store_relaxed(initial_young_worker_cap); + + const double upper_cpu_overhead = MAX2(cpu_metrics._avg_total_gc_cpu_overhead, cpu_metrics._generation_gc_cpu_overhead); + const double upper_cpu_overhead_error = upper_cpu_overhead - target_cpu_overhead; + + const double lower_cpu_overhead = MIN2(cpu_metrics._avg_total_gc_cpu_overhead, cpu_metrics._generation_gc_cpu_overhead); + const double lower_cpu_overhead_error = lower_cpu_overhead - target_cpu_overhead; + + const double target_gc_interval = compute_target_gc_interval(mem_metrics, gc_intensity, process_used_memory) / compression_gc_penalty; + const double gc_interval_error = MAX2(target_gc_interval - avg_time_since_last, target_gc_interval - cpu_metrics._avg_gc_interval); + + const double upper_error_signal = MAX2(upper_cpu_overhead_error, gc_interval_error); + const double lower_error_signal = MAX2(lower_cpu_overhead_error, gc_interval_error); + + const bool is_young = generation == ZGenerationId::young; + + if (is_young) { + const double accumulated_young_gc_time = _accumulated_young_gc_time.load_relaxed(); + const double new_accumulated_young_gc_time = accumulated_young_gc_time + cpu_metrics._gc_time; + // The only race is with the old collection end's exchange bellow. So if the + // first CAS fails, the second must invariantly succeed. + const bool result = _accumulated_young_gc_time.compare_set(accumulated_young_gc_time, new_accumulated_young_gc_time, memory_order_relaxed) || + _accumulated_young_gc_time.compare_set(0.0, cpu_metrics._gc_time, memory_order_relaxed); + assert(result, "Should have succeded, unexpected _accumulated_young_gc_time mutation, expected: %f or 0.0, current: %f", + accumulated_young_gc_time, _accumulated_young_gc_time.load_relaxed()); + + const double accumulated_mem_worker_time = _accumulated_mem_worker_time.load_relaxed(); + const double new_accumulated_mem_worker_time = accumulated_mem_worker_time + cpu_metrics._mem_worker_time; + const bool result2 = _accumulated_mem_worker_time.compare_set(accumulated_mem_worker_time, new_accumulated_mem_worker_time, memory_order_relaxed) || + _accumulated_mem_worker_time.compare_set(0.0, cpu_metrics._mem_worker_time, memory_order_relaxed); + assert(result2, "Should have succeded, unexpected _accumulated_mem_worker_time mutation, expected: %f or 0.0, current: %f", + accumulated_mem_worker_time, _accumulated_mem_worker_time.load_relaxed()); + } else { + const double accumulated_young_gc_time = _accumulated_young_gc_time.exchange(0.0, memory_order_relaxed); + const double young_to_old_gc_time = accumulated_young_gc_time / (accumulated_young_gc_time + cycle_stats._last_total_vtime); + _young_to_old_gc_time.store_relaxed(young_to_old_gc_time); + + _accumulated_mem_worker_time.exchange(0.0, memory_order_relaxed); + } + + const double upper_smoothened_error = smoothing_function(upper_error_signal, warmness); + const double upper_correction_factor = upper_smoothened_error + 0.5; + + const double lower_smoothened_error = smoothing_function(lower_error_signal, warmness); + const double lower_correction_factor = lower_smoothened_error + 0.5; + + const size_t upper_scaled_capacity = align_up(size_t(double(heuristic_max_capacity) * upper_correction_factor), ZGranuleSize); + const size_t lower_scaled_capacity = align_up(size_t(double(heuristic_max_capacity) * lower_correction_factor), ZGranuleSize); + + // Instead of increasing the heap by an aggressive amount, reduce the memory + // availability by said amount as we get closer to the current max capacity. + // This ensures the heap increase slows down as we approach the max capacity, + // similar to when a space ship is docking at a station. + const size_t remaining_capacity = capacity >= current_max_capacity ? 0 : current_max_capacity - capacity; + const size_t upper_scaled_max_capacity = upper_correction_factor <= 1.0 + ? upper_scaled_capacity + : align_up(heuristic_max_capacity + size_t(double(remaining_capacity) * (upper_correction_factor - 1.0)), ZGranuleSize); + const size_t lower_scaled_max_capacity = lower_correction_factor <= 1.0 + ? lower_scaled_capacity + : align_up(heuristic_max_capacity + size_t(double(remaining_capacity) * (lower_correction_factor - 1.0)), ZGranuleSize); + + const size_t upper_suggested_capacity = MIN2(upper_scaled_capacity, upper_scaled_max_capacity); + const size_t lower_suggested_capacity = MIN2(lower_scaled_capacity, lower_scaled_max_capacity); + + const size_t upper_bounded_capacity = clamp(upper_suggested_capacity, lower_bound, upper_bound); + const size_t lower_bounded_capacity = clamp(lower_suggested_capacity, lower_bound, upper_bound); + + // Grow if we experience short term *and* long term pressure on the heap + const bool should_grow = lower_bounded_capacity > heuristic_max_capacity && upper_bounded_capacity > heuristic_max_capacity; + // Shrink if we experience short term *and* long term reverse pressure on the heap + const bool should_shrink = lower_bounded_capacity < heuristic_max_capacity && upper_bounded_capacity < heuristic_max_capacity; + + const double cpu_pressure = pressures._cpu_pressure; + const double cpu_vs_memory_pressure = pressures._cpu_vs_memory_pressure; + const double cpu_vs_latency_pressure = pressures._cpu_vs_latency_pressure; + + if (mem_metrics._is_containerized) { + log_info(gc, load)("Container: System Memory Load: %.1f%%, Process Memory Load: %.1f%%, Heap Memory Load: %.1f%%", + double(mem_metrics._container._used_memory) / double(mem_metrics._container._max_memory) * 100.0, + double(projected_process_used_memory) / double(mem_metrics._container._max_memory) * 100.0, + double(heuristic_max_capacity) / double(mem_metrics._container._max_memory) * 100.0); + } + + if (cpu_metrics._has_container_cpu_metrics) { + log_info(gc, load)("Container: System CPU Load: %.1f%%, Process CPU Load: %.1f%%, GC CPU Load: %.1f%%", + cpu_metrics._container._avg_system_load * 100.0, cpu_metrics._container._avg_process_load * 100.0, + cpu_metrics._avg_total_gc_cpu_overhead * cpu_metrics._container._avg_process_load * 100.0); + } + + log_info(gc, load)("Machine: System Memory Load: %.1f%%, Process Memory Load: %.1f%%, Heap Memory Load: %.1f%%", + double(mem_metrics._machine._used_memory) / double(mem_metrics._machine._max_memory) * 100.0, + double(projected_process_used_memory) / double(mem_metrics._machine._max_memory) * 100.0, + double(heuristic_max_capacity) / double(mem_metrics._machine._max_memory) * 100.0); + + log_info(gc, load)("Machine: System CPU Load: %.1f%%, Process CPU Load: %.1f%%, GC CPU Load: %.1f%%", + cpu_metrics._machine._avg_system_load * 100.0, cpu_metrics._machine._avg_process_load * 100.0, + cpu_metrics._avg_total_gc_cpu_overhead * cpu_metrics._machine._avg_process_load * 100.0); + + if (ZAdaptiveHeapSizing) { + log_info(gc, heap)("Process GC CPU Overhead: %.1f%%, Target Process GC CPU Overhead: %.1f%%", + cpu_metrics._avg_total_gc_cpu_overhead * 100.0, target_cpu_overhead * 100.0); + log_debug(gc, heap)("Memory Worker CPU Overhead: %.1f%% of GC CPU Time", + cpu_metrics._avg_mem_worker_cpu_overhead); + + log_debug(gc, heap)("System CPU Pressure: %.1f, System Memory Pressure: %.1f", + cpu_pressure, mem_pressure); + log_debug(gc, heap)("System CPU vs Memory Pressure: %.1f, System CPU vs Latency Pressure: %.1f", + cpu_vs_memory_pressure, cpu_vs_latency_pressure); + log_info(gc, heap)("GC Intensity: %.1f, Resource Pressure Scaling: %.1f", + gc_intensity, gc_intensity / mem_metrics._unscaled_gc_intensity); + + log_debug(gc, heap)("GC Interval: %.3fs, Target Minimum: %.3fs", + avg_time_since_last, target_gc_interval); + log_debug(gc, heap)("Target heap lower bound: %zuM, upper bound: %zuM", + lower_bound / M, upper_bound / M); + log_debug(gc, heap)("Suggested capacity range: %zuM - %zuM, heuristic capacity: %zuM", + lower_suggested_capacity / M, upper_suggested_capacity / M, heuristic_max_capacity / M); + } + + if (should_grow) { + if (capacity < heuristic_max_capacity) { + // We have not yet finished growing initiated in a previous GC, because the throughput of GCs + // is higher than the throughput of growing. The control system needs to feel the impact of the + // previous growth request before growing further. Therefore, we decide not to change the + // heuristic max capacity in this case. + return heuristic_max_capacity; + } + + const size_t selected_capacity = MIN2(upper_bounded_capacity, lower_bounded_capacity); + const size_t capacity_resize = selected_capacity - heuristic_max_capacity; + + log_debug(gc, heap)("Updated heuristic max capacity: %zuM (%.3f%%), current capacity: %zuM", + selected_capacity / M, double(selected_capacity) / double(heuristic_max_capacity) * 100.0 - 100.0, capacity / M); + + log_info(gc, heap)("Heap Increase %zuM (%.1f%%)", capacity_resize / M, double(capacity_resize) / double(heuristic_max_capacity) * 100.0); + + return selected_capacity; + } else if (should_shrink) { + // We want to shrink slower than we grow; by splitting the proposed shrinking into a fraction, + // we get a slower tail of shrinking, which avoids unnecessary fluctuations up and down. + const size_t shrinking_fraction = 5; + + const size_t proposed_selected_capacity = MAX2(upper_bounded_capacity, lower_bounded_capacity); + const size_t capacity_resize = align_up((heuristic_max_capacity - proposed_selected_capacity) / shrinking_fraction, ZGranuleSize); + + const size_t selected_capacity = heuristic_max_capacity - capacity_resize; + + log_debug(gc, heap)("Updated heuristic max capacity: %zuM (%.3f%%), current capacity: %zuM", + selected_capacity / M, double(selected_capacity) / double(heuristic_max_capacity) * 100.0 - 100.0, capacity / M); + + log_info(gc, heap)("Heap Decrease %zuM (%.1f%%)", capacity_resize / M, double(capacity_resize) / double(heuristic_max_capacity) * 100.0); + + return selected_capacity; + } + + return heuristic_max_capacity; +} + +uint64_t ZAdaptiveHeap::urgent_uncommit_rate() { + return 64; // MB per second +} + +uint64_t ZAdaptiveHeap::no_uncommit_delay() { + return std::numeric_limits::max(); +} + +uint64_t ZAdaptiveHeap::critical_uncommit_delay() { + return 0; +} + +static uint64_t system_uncommit_delay(const ZSystemMemoryPressureMetrics& metrics, size_t capacity, size_t uncommit_granule, double avg_mem_worker_cpu_overhead) { + const double capacity_scaling = clamp(double(capacity) / double(metrics._used_memory), 0.05, 1.0); + const double worker_scaling = clamp(1.0 - avg_mem_worker_cpu_overhead, 0.05, 1.0); + const double rate_scaling = capacity_scaling * worker_scaling; + + // The remaining memory reserve of the system + const double available_fraction = metrics.available_fraction(); + + // If we are critically low on memory, aggressively free up memory + if (available_fraction <= metrics._critical_threshold) { + return ZAdaptiveHeap::critical_uncommit_delay(); + } + + // If we aren't low on memory, disable timer based uncommit; let + // the GC heuristics guide the heap down instead, as part of the + // natural control system. + if (available_fraction > metrics._concerning_threshold) { + return ZAdaptiveHeap::no_uncommit_delay(); + } + + const double uncommit_granule_mb = double(uncommit_granule) / M; + const double scaled_urgent_rate = ZAdaptiveHeap::urgent_uncommit_rate() * rate_scaling; + const uint64_t urgent_delay = uncommit_granule_mb * MILLIUNITS / scaled_urgent_rate; + + // If we aren't using a high amount memory, uncommit memory rather slowly + // and let the GC heuristics do most of the heavy lifting + if (available_fraction > metrics._high_threshold) { + // The memory pressure is concerning but not high; gradually siphon the + // heap to potential other JVMs that may be under more pressure, allowing + // them to grow. + // Progression until critical uncommitting starts + const double progression = calculate_progression(available_fraction, metrics._concerning_threshold, metrics._high_threshold); + + if (ZUncommitDelay == 0) { + // With no ZUncommitDelay, simply use the urgent delay + return urgent_delay; + } + + // Scale the uncommit interval by memory urgency, so the pace of uncommitting + // ramps up as the machine resources gets exhausted. + const double highest_rate = double(ZAdaptiveHeap::urgent_uncommit_rate()); + const double lowest_rate = highest_rate / ZUncommitDelay; + const double interpolated_rate = lowest_rate * (1.0 - progression) + highest_rate * progression; + const double scaled_rate = interpolated_rate * rate_scaling; + return uncommit_granule_mb * MILLIUNITS / scaled_rate; + } + + // We use a policy where the uncommit delay drops off fairly quickly + // as the memory pressure gets "high" to let uncommitting react before + // the next GC, but still without being brutal. + // When the memory availability becomes critical, more brutal uncommitting + // will commence. + + // Progression until critical uncommitting starts + const double progression = calculate_progression(available_fraction, metrics._high_threshold, metrics._critical_threshold); + + // Scale the uncommit interval by memory urgency, so the pace of uncommitting + // ramps up as the machine resources gets exhausted. + return uint64_t(urgent_delay * (1.0 - progression)); +} + +// How long to wait until it is time to uncommit memory. This goes towards +// infinity when there is no concerning memory pressure, then from 64 MB per +// ZUncommitDelay 64 MB per second when high, and eventually 0 when critically low. +uint64_t ZAdaptiveHeap::uncommit_delay(size_t uncommit_granule) { + precond(ZAdaptiveHeapSizing); + + const ZMemoryPressureMetrics mem_metrics = memory_pressure_metrics(); + ZStatSystemMemoryUsage::record(mem_metrics); + + const size_t capacity = ZHeap::heap()->capacity(); + const double avg_mem_worker_cpu_overhead = _avg_mem_worker_cpu_overhead.load_relaxed() / 100.0; + + const uint64_t machine_uncommit_delay = system_uncommit_delay(mem_metrics._machine, capacity, uncommit_granule, avg_mem_worker_cpu_overhead); + + if (!mem_metrics._is_containerized) { + return machine_uncommit_delay; + } + + const uint64_t container_uncommit_delay = system_uncommit_delay(mem_metrics._container, capacity, uncommit_granule, avg_mem_worker_cpu_overhead); + return MIN2(machine_uncommit_delay, container_uncommit_delay); +} + +uint64_t ZAdaptiveHeap::soft_ref_delay() { + ZStatHeap* const stats = ZGeneration::old()->stat_heap(); + // Young generation should have mostly transient state; + // consider it as basically free. + const size_t old_used_reloc_end = stats->used_generation_at_relocate_end(); + const size_t target_capacity = MAX2(ZHeap::heap()->heuristic_max_capacity(), old_used_reloc_end); + const size_t free_heap = target_capacity - old_used_reloc_end; + + const uint64_t explicit_delay = free_heap / M * uint64_t(SoftRefLRUPolicyMSPerMB); + + if (!ZAdaptiveHeapSizing) { + // Use the good old policy we all know and love so much when adaptive heap + // sizing is not in use. + return explicit_delay; + } + + // With adaptive heap sizing, there is a risk for a feedback loop when the amount + // of free memory decides how long soft references survive. More soft references + // will lead to the heap growing, hence creating more free memory and suddenly + // letting soft references live for longer. In order to cut this feedback loop, + // a more involved policy is used. + // + // The more involved strategy scales the delay with the time it would take for the + // heap to get filled up by old generation allocations multiplied by a scaled + // variation of SoftRefLRUPolicyMSPerMB. The scaling is more aggressive than linear + // by computing the nth root of SoftRefLRUPolicyMSPerMB, where n is some memory + // pressure. + + // Scale the delay by the old generation allocation rate; the faster it fills up, + // the more rapidly we need to prune soft references + const double avg_time_since_last = _old_data._gc_times_since_last.avg(); + const size_t old_live = stats->live_at_mark_end(); + const size_t old_used = ZHeap::heap()->used_old(); + const double old_allocated = old_used - old_live; + const double old_alloc_rate = MAX2(percent_of(old_allocated / M, avg_time_since_last) / 100.0, 1.0); + + const double time_to_old_oom = double(free_heap) / M / old_alloc_rate; + + const double free_ratio = double(target_capacity) / double(free_heap); + + const double mem_pressure = free_ratio; + + // Scale the SoftRefLRUPolicyMSPerMB as an nth root where n is the memory pressure. + // The reason for using the nth root is that it might not necessarily be that + // linearly decreasing the interval with memory pressure yields linearly more + // soft references being cleared. It rather depends on the access frequency. + // If they get accessed very frequently, then it's likely that no soft reference + // get cleared at all, until the interval is made *very* small. Therefore, the + // more aggressive nth root is used. + const uint64_t scaled_interval = uint64_t(pow(SoftRefLRUPolicyMSPerMB, 1.0 / mem_pressure)); + + // Compute the potentially more aggressive delay that cuts the feedback loop + const uint64_t implicit_delay = uint64_t(time_to_old_oom * double(scaled_interval)); + + // If the new policy yields earlier cut off points, then use that. Otherwise, + // we still use the more relaxed policy to cut off soft references when they + // have not been used for unreasonably long. While we could keep them around + // forever, it might also be a bit pointless. + const uint64_t delay = MIN2(implicit_delay, explicit_delay); + + log_info(gc, ref)("Soft ref timeout: %.3fs", double(delay) / MILLIUNITS); + + LogTarget(Debug, gc, ref) lt; + if (lt.is_enabled()) { + LogStream ls(lt); + + ls.print_cr("Soft ref time to old generation OOM: %.3fs", time_to_old_oom); + ls.print_cr("Soft ref explicit timeout: %.3fs", double(explicit_delay) / MILLIUNITS); + ls.print_cr("Soft ref implicit timeout: %.3fs", double(implicit_delay) / MILLIUNITS); + ls.print_cr("Soft ref memory pressure: %.3f", mem_pressure); + } + + return delay; +} + +void ZAdaptiveHeap::print() { + const char* status; + if (!ZAdaptiveHeapSizing) { + status = "Fixed"; + } else { + if (explicit_max_capacity() || MinHeapSize != DefaultMinHeapSize) { + status = "Adaptive (Explicit Boundaries)"; + } else { + status = "Adaptive (Implicit Boundaries)"; + } + } + log_info_p(gc, init)("Heap Sizing: %s", status); +} diff --git a/src/hotspot/share/gc/z/zAdaptiveHeap.hpp b/src/hotspot/share/gc/z/zAdaptiveHeap.hpp new file mode 100644 index 000000000000..b57bfc7bc3b8 --- /dev/null +++ b/src/hotspot/share/gc/z/zAdaptiveHeap.hpp @@ -0,0 +1,225 @@ +/* + * Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#ifndef SHARE_GC_Z_ZADAPTIVEHEAP_HPP +#define SHARE_GC_Z_ZADAPTIVEHEAP_HPP + +#include "gc/z/zGenerationId.hpp" +#include "gc/z/zStat.hpp" +#include "memory/allocation.hpp" +#include "utilities/numberSeq.hpp" + +struct ZHeapResizeMetrics { + const size_t _soft_max_capacity; + const size_t _current_max_capacity; + const size_t _heuristic_max_capacity; + const size_t _static_min_capacity; + const size_t _capacity; + const size_t _used; + const double _alloc_rate; +}; + +struct ZSystemMemoryPressureMetrics { + const physical_memory_size_type _used_memory; + const physical_memory_size_type _max_memory; + + const double _concerning_threshold; + const double _high_threshold; + const double _critical_threshold; + + double fraction_of_max(physical_memory_size_type memory) const; + physical_memory_size_type available_memory() const; + double used_fraction() const; + double available_fraction() const; +}; + +struct ZMemoryPressureMetrics { + const double _unscaled_gc_intensity; + const bool _is_containerized; + const ZSystemMemoryPressureMetrics _machine; + const ZSystemMemoryPressureMetrics _container; +}; + +struct ZSystemCpuPressureMetrics { + const double _processor_count; + const double _avg_process_load; + const double _avg_system_load; +}; + +struct ZCpuPressureMetrics { + const bool _has_container_cpu_metrics; + const bool _has_container_cpu_capacity_limit; + const double _generation_gc_cpu_overhead; + const double _avg_generation_gc_cpu_overhead; + const double _avg_total_gc_cpu_overhead; + const double _avg_gc_interval; + const double _avg_process_time; + const double _gc_time; + const double _mem_worker_time; + const double _avg_mem_worker_cpu_overhead; + const ZSystemCpuPressureMetrics _machine; + const ZSystemCpuPressureMetrics _container; +}; + +struct ZResourcePressure { + const double _scaled_gc_intensity; + const double _cpu_pressure; + const double _mem_pressure; + const double _cpu_vs_memory_pressure; + const double _cpu_vs_latency_pressure; +}; + +struct ZMachineMemoryInfo { + physical_memory_size_type _physical_memory; + physical_memory_size_type _available_memory; + bool _is_valid; +}; + +struct ZSystemCpuTime { + double _elapsed_time; + double _processor_count; +}; + +class ZAdaptiveHeap : public AllStatic { + friend class ZAdaptiveHeapTest; +private: + // We clamp ZGCIntensity between thes values to avoid precision errors + static constexpr double MinZGCIntensity = 1e-12; + static constexpr double MaxZGCIntensity = 1e12; + + static bool _explicit_max_capacity; + static bool _initialized; + static bool _initialized_generation_data; + + struct ZIntensitySmoother { + private: + ZLock* _lock; + TruncatedSeq _gc_intensities; + + public: + ZIntensitySmoother() + : _lock(nullptr), + _gc_intensities() {} + + void initialize(); + double record_and_smooth_gc_intensity(double scaled_gc_intensity); + }; + + struct ZGenerationOverhead { + double _last_machine_system_time; + double _last_container_system_time; + double _machine_processor_count; + double _container_processor_count; + double _last_process_time; + bool _has_last_machine_system_time; + bool _has_last_container_system_time; + bool _has_last_process_time; + double _last_time; + TruncatedSeq _process_times; + TruncatedSeq _machine_system_times; + TruncatedSeq _container_system_times; + TruncatedSeq _gc_times; + TruncatedSeq _gc_times_since_last; + TruncatedSeq _mem_worker_times; + + ZGenerationOverhead() + : _last_machine_system_time(), + _last_container_system_time(), + _machine_processor_count(), + _container_processor_count(), + _last_process_time(), + _has_last_machine_system_time(), + _has_last_container_system_time(), + _has_last_process_time(), + _process_times(), + _machine_system_times(), + _container_system_times(), + _gc_times(), + _gc_times_since_last(), + _mem_worker_times() {} + }; + + static Atomic _young_to_old_gc_time; + static Atomic _accumulated_young_gc_time; + static Atomic _accumulated_mem_worker_time; + static Atomic _avg_mem_worker_cpu_overhead; + static ZGenerationOverhead _young_data; + static ZGenerationOverhead _old_data; + static Atomic _initial_young_worker_cap; + static ZIntensitySmoother _gc_intensities; + + static void sample_generation_data(ZGenerationOverhead& generation_data); + static ZCpuPressureMetrics cpu_pressure_metrics(ZGenerationId generation); + + static ZResourcePressure compute_pressures(const ZMemoryPressureMetrics& mem_metrics, + const ZCpuPressureMetrics& cpu_metrics, + size_t projected_process_used_memory); + static double compute_memory_pressure(const ZMemoryPressureMetrics& metrics); + static double smoothed_gc_intensity(double scaled_gc_intensity); + + [[nodiscard]] static bool pd_machine_elapsed_system_cpu_time(ZSystemCpuTime& value); + [[nodiscard]] static bool pd_container_elapsed_system_cpu_time(ZSystemCpuTime& value); + [[nodiscard]] static bool machine_elapsed_system_cpu_time(ZSystemCpuTime& value); + [[nodiscard]] static bool container_elapsed_system_cpu_time(ZSystemCpuTime& value); + + static void pd_machine_memory_info(ZMachineMemoryInfo& info); + static bool pd_machine_compressed_memory(physical_memory_size_type& value); + static double machine_memory_compression_ratio(physical_memory_size_type machine_used_memory, + physical_memory_size_type machine_max_memory); + +public: + static void initialize(bool explicit_max_heap_size); + static void initialize_generation_data(); + + static ZMachineMemoryInfo machine_memory_info(); + static physical_memory_size_type machine_physical_memory(); + + static size_t compute_heap_size(const ZHeapResizeMetrics& metrics, ZGenerationId generation); + static double young_to_old_gc_time(); + static uint initial_young_worker_cap(); + + // Uncommit support + static uint64_t no_uncommit_delay(); + static uint64_t critical_uncommit_delay(); + static uint64_t urgent_uncommit_rate(); + static uint64_t uncommit_delay(size_t uncommit_granule); + + static uint64_t soft_ref_delay(); + + // Memory pressure + static ZMemoryPressureMetrics memory_pressure_metrics(); + static bool is_memory_pressure_concerning(const ZMemoryPressureMetrics& metrics); + static bool is_memory_pressure_high(const ZMemoryPressureMetrics& metrics); + static bool is_memory_pressure_critical(const ZMemoryPressureMetrics& metrics); + + // How adaptive are we? + static bool explicit_max_capacity(); + + static constexpr double DefaultGCIntensity = 5.0; + static constexpr double DefaultMaxRAMPercentage = 100.; + static constexpr size_t DefaultMinHeapSize = 2 * M; + + static void print(); +}; + +#endif // SHARE_GC_Z_ZADAPTIVEHEAP_HPP diff --git a/src/hotspot/share/gc/z/zAdaptiveHeap.inline.hpp b/src/hotspot/share/gc/z/zAdaptiveHeap.inline.hpp new file mode 100644 index 000000000000..a3548e8cacac --- /dev/null +++ b/src/hotspot/share/gc/z/zAdaptiveHeap.inline.hpp @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#ifndef SHARE_GC_Z_ZADAPTIVEHEAP_INLINE_HPP +#define SHARE_GC_Z_ZADAPTIVEHEAP_INLINE_HPP + +#include "gc/z/zAdaptiveHeap.hpp" + +#include "utilities/globalDefinitions.hpp" + +inline physical_memory_size_type ZSystemMemoryPressureMetrics::available_memory() const { + return _max_memory - MIN2(_used_memory, _max_memory); +} + +inline double ZSystemMemoryPressureMetrics::fraction_of_max(physical_memory_size_type memory) const { + return percent_of(memory, _max_memory) / 100.0; +} + +inline double ZSystemMemoryPressureMetrics::used_fraction() const { + return fraction_of_max(_used_memory); +} + +inline double ZSystemMemoryPressureMetrics::available_fraction() const { + return fraction_of_max(available_memory()); +} + +inline bool ZAdaptiveHeap::explicit_max_capacity() { + precond(_initialized); + return _explicit_max_capacity; +} + +#endif // SHARE_GC_Z_ZADAPTIVEHEAP_INLINE_HPP diff --git a/src/hotspot/share/gc/z/zArguments.cpp b/src/hotspot/share/gc/z/zArguments.cpp index f2ff35a6a438..989d5af728d7 100644 --- a/src/hotspot/share/gc/z/zArguments.cpp +++ b/src/hotspot/share/gc/z/zArguments.cpp @@ -21,16 +21,22 @@ * questions. */ +#include "gc/shared/gc_globals.hpp" #include "gc/shared/gcArguments.hpp" +#include "gc/z/zAdaptiveHeap.inline.hpp" #include "gc/z/zAddressSpaceLimit.hpp" #include "gc/z/zArguments.hpp" #include "gc/z/zCollectedHeap.hpp" #include "gc/z/zGlobals.hpp" +#include "gc/z/zHeap.hpp" #include "gc/z/zHeuristics.hpp" #include "gc/z/zUtils.inline.hpp" +#include "logging/log.hpp" #include "runtime/globals.hpp" #include "runtime/globals_extension.hpp" #include "runtime/java.hpp" +#include "runtime/os.hpp" +#include "utilities/globalDefinitions.hpp" void ZArguments::initialize_alignments() { SpaceAlignment = ZGranuleSize; @@ -40,14 +46,17 @@ void ZArguments::initialize_alignments() { void ZArguments::initialize_heap_flags_and_sizes() { GCArguments::initialize_heap_flags_and_sizes(); - if (!FLAG_IS_CMDLINE(MaxHeapSize) && - !FLAG_IS_CMDLINE(MaxRAMPercentage) && - !FLAG_IS_CMDLINE(SoftMaxHeapSize)) { + precond(!FLAG_IS_ERGO(SoftMaxHeapSize)); + + if (!ZAdaptiveHeap::explicit_max_capacity() && !ZAdaptiveHeapSizing) { // We are really just guessing how much memory the program needs. // When that is the case, we don't want the soft and hard limits to be the same // as it can cause flakiness in the number of GC threads used, in order to keep // to a random number we just pulled out of thin air. - FLAG_SET_ERGO(SoftMaxHeapSize, MaxHeapSize * 90 / 100); + FLAG_SET_ERGO_IF_DEFAULT(SoftMaxHeapSize, MaxHeapSize * 90 / 100); + } else { + // This denotes there is no soft max heap size set. + FLAG_SET_ERGO_IF_DEFAULT(SoftMaxHeapSize, 0); } } @@ -118,6 +127,98 @@ void ZArguments::select_max_gc_threads() { } } +void ZArguments::set_heap_size() { + // We purposely do not care about MinRAMPercentage as adaptive heap sizing + // will use up to the whole machine, so even if MinRAMPercentage is set to + // 100%, we will still satisfy it. + + const bool gc_intensity_was_zero = ZGCIntensity == 0.0; + const bool adaptive_is_explicit = !FLAG_IS_DEFAULT(ZAdaptiveHeapSizing); + const bool intensity_is_explicit = !FLAG_IS_DEFAULT(ZGCIntensity); + const bool adaptive_matches_intensity = ZAdaptiveHeapSizing != gc_intensity_was_zero; + + if ((adaptive_is_explicit || intensity_is_explicit) && !adaptive_matches_intensity) { + // The user has set ZAdaptiveHeapSizing and/or ZGCIntensity AND their state + // does not match. For example: + // +ZAdaptiveHeapSizing and ZGCIntensity = 0 OR + // -ZAdaptiveHeapSizing and ZGCIntensity != 0 + + if (!adaptive_is_explicit && intensity_is_explicit) { + FLAG_SET_ERGO(ZAdaptiveHeapSizing, !ZAdaptiveHeapSizing); + } else if (adaptive_is_explicit && !intensity_is_explicit) { + FLAG_SET_ERGO(ZGCIntensity, ZAdaptiveHeapSizing ? ZAdaptiveHeap::DefaultGCIntensity : 0.0); + } else { + const double old_intensity = ZGCIntensity; + FLAG_SET_ERGO(ZGCIntensity, ZAdaptiveHeapSizing ? ZAdaptiveHeap::DefaultGCIntensity : 0.0); + + // Both were set, log a warning for mismatching configuration + log_warning(gc)("-XX:%cZAdaptiveHeapSizing does not match value selected for -XX:ZGCIntensity=%f, setting ZGCIntensity to %f", + ZAdaptiveHeapSizing ? '+' : '-', old_intensity, ZGCIntensity); + } + } + + assert(!FLAG_IS_ERGO(MaxHeapSize), "Who set my heap size ergo?"); + assert(!FLAG_IS_ERGO(MaxRAMPercentage), "Who set my heap size ergo?"); + + const bool explicit_max_heap_size = !FLAG_IS_DEFAULT(MaxHeapSize) || + !FLAG_IS_DEFAULT(MaxRAMPercentage); + + ZAdaptiveHeap::initialize(explicit_max_heap_size); + + if (!ZAdaptiveHeapSizing) { + // Let the shared code setup the set the heap size when not adapting + GCArguments::set_heap_size(); + return; + } + + // When adapting, we set the heap size ourselves + assert(!FLAG_IS_ERGO(InitialHeapSize), "Who set my heap size ergo?"); + assert(!FLAG_IS_ERGO(MinHeapSize), "Who set my heap size ergo?"); + assert(!FLAG_IS_ERGO(MinRAMPercentage), "Who set my heap size ergo?"); + + if (!FLAG_IS_DEFAULT(ErgoHeapSizeLimit)) { + log_warning(gc, heap, init)("ZGC does not use ErgoHeapSizeLimit, the value is ignored"); + } + if (!FLAG_IS_DEFAULT(HeapBaseMinAddress)) { + log_warning(gc, heap, init)("ZGC does not use HeapBaseMinAddress, the value is ignored"); + } + + // When the user has specified MaxRAMPercentage explicitly, we dispatch to either + // the container or machine's limit. If not explicitly specified, we use the machine's + // limit as we might need to scale up to most of the underlying memory. + const double max_physical_memory = !FLAG_IS_DEFAULT(MaxRAMPercentage) + ? checked_cast(os::physical_memory()) + : checked_cast(os::Machine::physical_memory()); + const double initial_physical_memory = checked_cast(os::physical_memory()); + + FLAG_SET_ERGO_IF_DEFAULT(MaxRAMPercentage, ZAdaptiveHeap::DefaultMaxRAMPercentage); + FLAG_SET_ERGO_IF_DEFAULT_OR_ZERO(MinHeapSize, ZAdaptiveHeap::DefaultMinHeapSize); + + const size_t max_size = align_down((size_t)(max_physical_memory * (MaxRAMPercentage / 100.)), ZGranuleSize); + // GCArguments::initialize_heap_flags_and_sizes does not reject incompatible + // MaxHeapSize and InitialHeapSize if they originate from a user + // MaxRAMPercentage, so we set the MaxHeapSize to be at least InitialHeapSize + // as well. + assert(!FLAG_IS_DEFAULT(InitialHeapSize) || InitialHeapSize == 0, "Assumed in MaxHeapSize selection"); + FLAG_SET_ERGO_IF_DEFAULT(MaxHeapSize, MAX3(max_size, MinHeapSize, InitialHeapSize)); + + const size_t initial_size = (size_t)(initial_physical_memory * (InitialRAMPercentage / 100.)); + FLAG_SET_ERGO_IF_DEFAULT_OR_ZERO(InitialHeapSize, clamp(initial_size, MIN2(MinHeapSize, MaxHeapSize), MaxHeapSize)); + + // GCArguments::initialize_heap_flags_and_sizes will align up the heap sizes. + if (align_up(MaxHeapSize, ZGranuleSize) == align_up(MinHeapSize, ZGranuleSize)) { + if (!FLAG_IS_DEFAULT(ZAdaptiveHeapSizing)) { + log_warning(gc)("Adaptive heap sizing was enabled, but heap size is fixed. Disabling adaptive heap sizing."); + } + + FLAG_SET_ERGO(ZAdaptiveHeapSizing, false); + FLAG_SET_ERGO(ZGCIntensity, 0.0); + } + + // Enable concurrent heating if ZAdaptiveHeapSizing is turned on + FLAG_SET_ERGO_IF_DEFAULT(ZMemoryHeating, true); +} + void ZArguments::initialize() { GCArguments::initialize(); diff --git a/src/hotspot/share/gc/z/zArguments.hpp b/src/hotspot/share/gc/z/zArguments.hpp index eab2d22ac63a..011915a2abfc 100644 --- a/src/hotspot/share/gc/z/zArguments.hpp +++ b/src/hotspot/share/gc/z/zArguments.hpp @@ -37,6 +37,7 @@ class ZArguments : public GCArguments { static bool is_os_supported(); public: + virtual void set_heap_size(); virtual void initialize_alignments(); virtual void initialize_heap_flags_and_sizes(); virtual void initialize(); diff --git a/src/hotspot/share/gc/z/zCollectedHeap.cpp b/src/hotspot/share/gc/z/zCollectedHeap.cpp index db55fff673a0..09659591078c 100644 --- a/src/hotspot/share/gc/z/zCollectedHeap.cpp +++ b/src/hotspot/share/gc/z/zCollectedHeap.cpp @@ -111,7 +111,7 @@ void ZCollectedHeap::stop() { } size_t ZCollectedHeap::max_capacity() const { - return _heap.max_capacity(); + return _heap.dynamic_max_capacity(); } size_t ZCollectedHeap::capacity() const { @@ -243,7 +243,7 @@ MemoryUsage ZCollectedHeap::memory_usage() { const size_t initial_size = InitialHeapSize; const size_t committed = ZHeap::heap()->capacity(); const size_t used = MIN2(ZHeap::heap()->used(), committed); - const size_t max_size = ZHeap::heap()->max_capacity(); + const size_t max_size = MAX2(ZHeap::heap()->dynamic_max_capacity(), committed); return MemoryUsage(initial_size, used, committed, max_size); } @@ -326,7 +326,7 @@ VirtualSpaceSummary ZCollectedHeap::create_heap_space_summary() { // Fake values. ZGC does not commit memory contiguously in the reserved // address space, and the reserved space is larger than MaxHeapSize. const uintptr_t committed_end = ZAddressHeapBase + capacity(); - const uintptr_t reserved_end = ZAddressHeapBase + max_capacity(); + const uintptr_t reserved_end = MAX2(ZAddressHeapBase + max_capacity(), committed_end); return VirtualSpaceSummary((HeapWord*)start, (HeapWord*)committed_end, (HeapWord*)reserved_end); } diff --git a/src/hotspot/share/gc/z/zDirector.cpp b/src/hotspot/share/gc/z/zDirector.cpp index e5a060415884..621187c9958b 100644 --- a/src/hotspot/share/gc/z/zDirector.cpp +++ b/src/hotspot/share/gc/z/zDirector.cpp @@ -23,17 +23,22 @@ #include "cppstdlib/limits.hpp" #include "gc/shared/gc_globals.hpp" +#include "gc/z/zAdaptiveHeap.inline.hpp" #include "gc/z/zCollectedHeap.hpp" #include "gc/z/zDirector.hpp" #include "gc/z/zDriver.hpp" #include "gc/z/zGeneration.inline.hpp" +#include "gc/z/zGlobals.hpp" #include "gc/z/zHeap.inline.hpp" #include "gc/z/zHeuristics.hpp" #include "gc/z/zLock.inline.hpp" #include "gc/z/zStat.hpp" #include "logging/log.hpp" +#include "runtime/globals_extension.hpp" #include "runtime/init.hpp" +#include + ZDirector* ZDirector::_director; constexpr double one_in_1000 = 3.290527; @@ -46,7 +51,9 @@ struct ZWorkerResizeStats { }; struct ZDirectorHeapStats { - size_t _soft_max_heap_size; + size_t _current_max_capacity; + size_t _capacity; + size_t _heuristic_max_capacity; size_t _used; uint _total_collections; }; @@ -109,7 +116,7 @@ static uint discrete_young_gc_workers(double gc_workers) { static double select_young_gc_workers(const ZDirectorStats& stats, double serial_gc_time, double parallelizable_gc_time, double alloc_rate_sd_percent, double time_until_oom) { // Use all workers until we're warm if (!stats._old_stats._cycle._is_warm) { - const double not_warm_gc_workers = ZYoungGCThreads; + const double not_warm_gc_workers = ZAdaptiveHeapSizing ? 1 : ZYoungGCThreads; log_debug(gc, director)("Select Minor GC Workers (Not Warm), GCWorkers: %.3f", not_warm_gc_workers); return not_warm_gc_workers; } @@ -222,7 +229,19 @@ static ZDriverRequest rule_soft_minor_allocation_rate_dynamic(const ZDirectorSta serial_gc_time_passed, parallel_gc_time_passed, false /* conservative_alloc_rate */, - stats._heap._soft_max_heap_size /* capacity */); + stats._heap._heuristic_max_capacity /* capacity */); +} + +static size_t heuristic_hard_capacity(const ZDirectorStats& stats) { + if (!ZAdaptiveHeapSizing) { + return stats._heap._current_max_capacity; + } + + const size_t used_memory = stats._heap._capacity; + const size_t max_capacity = stats._heap._current_max_capacity; + const size_t available_memory = max_capacity - used_memory; + const size_t scaled_available_memory = (size_t)(available_memory * 0.2); + return align_down(used_memory + scaled_available_memory, ZGranuleSize); } static ZDriverRequest rule_semi_hard_minor_allocation_rate_dynamic(const ZDirectorStats& stats, @@ -232,7 +251,7 @@ static ZDriverRequest rule_semi_hard_minor_allocation_rate_dynamic(const ZDirect serial_gc_time_passed, parallel_gc_time_passed, false /* conservative_alloc_rate */, - ZHeap::heap()->max_capacity() /* capacity */); + heuristic_hard_capacity(stats) /* capacity */); } static ZDriverRequest rule_hard_minor_allocation_rate_dynamic(const ZDirectorStats& stats, @@ -242,7 +261,7 @@ static ZDriverRequest rule_hard_minor_allocation_rate_dynamic(const ZDirectorSta serial_gc_time_passed, parallel_gc_time_passed, true /* conservative_alloc_rate */, - ZHeap::heap()->max_capacity() /* capacity */); + stats._heap._current_max_capacity /* capacity */); } static bool rule_minor_allocation_rate_static(const ZDirectorStats& stats) { @@ -259,9 +278,9 @@ static bool rule_minor_allocation_rate_static(const ZDirectorStats& stats) { // Calculate amount of free memory available. Note that we take the // relocation headroom into account to avoid in-place relocation. - const size_t soft_max_capacity = stats._heap._soft_max_heap_size; + const size_t heuristic_max_capacity = stats._heap._heuristic_max_capacity; const size_t used = stats._heap._used; - const size_t free_including_headroom = soft_max_capacity - MIN2(soft_max_capacity, used); + const size_t free_including_headroom = heuristic_max_capacity - MIN2(heuristic_max_capacity, used); const size_t free = free_including_headroom - MIN2(free_including_headroom, ZHeuristics::relocation_headroom()); // Calculate time until OOM given the max allocation rate and the amount @@ -295,10 +314,10 @@ static bool rule_minor_allocation_rate_static(const ZDirectorStats& stats) { static bool is_young_small(const ZDirectorStats& stats) { // Calculate amount of freeable memory available. - const size_t soft_max_capacity = stats._heap._soft_max_heap_size; + const size_t heuristic_max_capacity = stats._heap._heuristic_max_capacity; const size_t young_used = stats._young_stats._general._used; - const double young_used_percent = percent_of(young_used, soft_max_capacity); + const double young_used_percent = percent_of(young_used, heuristic_max_capacity); // If the freeable memory isn't even 5% of the heap, we can't expect to free up // all that much memory, so let's not even try - it will likely be a wasted effort @@ -309,11 +328,11 @@ static bool is_young_small(const ZDirectorStats& stats) { static bool is_high_usage(const ZDirectorStats& stats, bool log = false) { // Calculate amount of free memory available. Note that we take the // relocation headroom into account to avoid in-place relocation. - const size_t soft_max_capacity = stats._heap._soft_max_heap_size; + const size_t heuristic_max_capacity = stats._heap._heuristic_max_capacity; const size_t used = stats._heap._used; - const size_t free_including_headroom = soft_max_capacity - MIN2(soft_max_capacity, used); + const size_t free_including_headroom = heuristic_max_capacity - MIN2(heuristic_max_capacity, used); const size_t free = free_including_headroom - MIN2(free_including_headroom, ZHeuristics::relocation_headroom()); - const double free_percent = percent_of(free, soft_max_capacity); + const double free_percent = percent_of(free, heuristic_max_capacity); if (log) { log_debug(gc, director)("Rule Minor: High Usage, Free: %zuMB(%.1f%%)", @@ -412,10 +431,12 @@ static bool rule_major_warmup(const ZDirectorStats& stats) { // Perform GC if heap usage passes 10/20/30% and no other GC has been // performed yet. This allows us to get some early samples of the GC // duration, which is needed by the other rules. - const size_t soft_max_capacity = stats._heap._soft_max_heap_size; + const size_t heuristic_max_capacity = stats._heap._heuristic_max_capacity; const size_t used = stats._heap._used; - const double used_threshold_percent = (stats._old_stats._cycle._nwarmup_cycles + 1) * 0.1; - const size_t used_threshold = (size_t)(soft_max_capacity * used_threshold_percent); + const double used_threshold_percent = ZAdaptiveHeapSizing + ? 0.75 + : ((stats._old_stats._cycle._nwarmup_cycles + 1) * 0.1); + const size_t used_threshold = (size_t)(heuristic_max_capacity * used_threshold_percent); log_debug(gc, director)("Rule Major: Warmup %.0f%%, Used: %zuMB, UsedThreshold: %zuMB", used_threshold_percent * 100, used / M, used_threshold / M); @@ -569,11 +590,10 @@ static bool rule_major_proactive(const ZDirectorStats& stats) { // of free space on the heap. // Only consider doing a proactive GC if the heap usage has grown by at least - // 10% of the max capacity since the previous GC, or more than 5 minutes has - // passed since the previous GC. This helps avoid superfluous GCs when running - // applications with very low allocation rate. + // 10% of the max capacity since the previous GC, and more than 5 minutes has + // passed since the previous GC. This helps avoid superfluous GCs. const size_t used_after_last_gc = stats._old_stats._stat_heap._used_at_relocate_end; - const size_t used_increase_threshold = (size_t)(stats._heap._soft_max_heap_size * 0.10); // 10% + const size_t used_increase_threshold = (size_t)(stats._heap._heuristic_max_capacity * 0.10); // 10% const size_t used_threshold = used_after_last_gc + used_increase_threshold; const size_t used = stats._heap._used; const double time_since_last_gc = stats._old_stats._cycle._time_since_last; @@ -780,14 +800,26 @@ static void adjust_gc(const ZDirectorStats& stats) { } } +static uint soft_initial_young_nworkers(uint proposed_soft_young_workers) { + if (!ZAdaptiveHeapSizing) { + return proposed_soft_young_workers; + } + + // If the proposed number of workers is obviously going to be above the GC CPU target, + // then throttle the thread count instead, and have concurrent heap expansion run while + // the GC intentionally misses the heuristic max heap size. + return MIN2(ZAdaptiveHeap::initial_young_worker_cap(), proposed_soft_young_workers); +} + static ZWorkerCounts initial_workers(const ZDirectorStats& stats, ZWorkerSelectionType type) { if (!UseDynamicNumberOfGCThreads) { return {ZYoungGCThreads, ZOldGCThreads}; } const ZDriverRequest soft_request = rule_soft_minor_allocation_rate_dynamic(stats, 0.0 /* serial_gc_time_passed */, 0.0 /* parallel_gc_time_passed */); + const uint soft_young_nworkers = soft_initial_young_nworkers(soft_request.young_nworkers()); const ZDriverRequest hard_request = rule_hard_minor_allocation_rate_dynamic(stats, 0.0 /* serial_gc_time_passed */, 0.0 /* parallel_gc_time_passed */); - const uint young_workers = MAX3(1u, soft_request.young_nworkers(), hard_request.young_nworkers()); + const uint young_workers = MAX3(1u, soft_young_nworkers, hard_request.young_nworkers()); return select_worker_threads(stats, young_workers, type); } @@ -863,9 +895,16 @@ bool ZDirector::wait_for_tick() { static ZDirectorHeapStats sample_heap_stats() { const ZHeap* const heap = ZHeap::heap(); const ZCollectedHeap* const collected_heap = ZCollectedHeap::heap(); + const size_t curr_max_capacity_sample = heap->current_max_capacity(); + const size_t used = heap->used(); + const size_t capacity = MAX2(heap->capacity(), used); + const size_t current_max_capacity = MAX2(capacity, curr_max_capacity_sample); + const size_t heuristic_max_capacity = MIN2(heap->heuristic_max_capacity(), current_max_capacity); return { - heap->soft_max_capacity(), - heap->used(), + current_max_capacity, + capacity, + heuristic_max_capacity, + used, collected_heap->total_collections() }; } @@ -913,6 +952,27 @@ static ZDirectorStats sample_stats() { }; } +static void adjust_capacity(const ZDirectorStats& stats, double sampling_interval) { + if (!ZAdaptiveHeapSizing) { + // We do not asynchronously adjust the capacity without adaptive heap sizing. + return; + } + + const size_t used = stats._heap._used; + const size_t max_capacity = stats._heap._current_max_capacity; + + const size_t used_next_byte_sample = MIN2(used + 2 * stats._mutator_alloc_rate._sampling_granule, max_capacity); + + const double avg_alloc_rate = stats._mutator_alloc_rate._avg; + const size_t used_next_time_sample = MIN2(used + 2 * size_t(sampling_interval * avg_alloc_rate), max_capacity); + + const size_t used_after_yc = stats._heap._heuristic_max_capacity; + + const size_t used_soon = align_up(MAX3(used_next_time_sample, used_next_byte_sample, used_after_yc), ZGranuleSize); + + ZHeap::heap()->adjust_capacity(used_soon); +} + void ZDirector::run_thread() { // Main loop while (wait_for_tick()) { @@ -926,6 +986,7 @@ void ZDirector::run_thread() { if (!start_gc(stats)) { adjust_gc(stats); } + adjust_capacity(stats, 1.0 / DecisionHz); } } diff --git a/src/hotspot/share/gc/z/zDriver.cpp b/src/hotspot/share/gc/z/zDriver.cpp index e8f7af179cb4..06d68aabdfcf 100644 --- a/src/hotspot/share/gc/z/zDriver.cpp +++ b/src/hotspot/share/gc/z/zDriver.cpp @@ -120,7 +120,8 @@ ZDriverMinor::ZDriverMinor() _port(), _gc_timer(), _jfr_tracer(), - _used_at_start() { + _used_at_start(), + _max_at_start() { ZDriver::set_minor(this); set_name("ZDriverMinor"); create_and_start(); @@ -164,6 +165,14 @@ size_t ZDriverMinor::used_at_start() const { return _used_at_start; } +void ZDriverMinor::set_max_at_start(size_t max) { + _max_at_start = max; +} + +size_t ZDriverMinor::max_at_start() const { + return _max_at_start; +} + class ZDriverScopeMinor : public StackObj { private: GCIdMark _gc_id; @@ -321,7 +330,8 @@ ZDriverMajor::ZDriverMajor() _port(), _gc_timer(), _jfr_tracer(), - _used_at_start() { + _used_at_start(), + _max_at_start() { ZDriver::set_major(this); set_name("ZDriverMajor"); create_and_start(); @@ -380,6 +390,14 @@ size_t ZDriverMajor::used_at_start() const { return _used_at_start; } +void ZDriverMajor::set_max_at_start(size_t max) { + _max_at_start = max; +} + +size_t ZDriverMajor::max_at_start() const { + return _max_at_start; +} + class ZDriverScopeMajor : public StackObj { private: GCIdMark _gc_id; diff --git a/src/hotspot/share/gc/z/zDriver.hpp b/src/hotspot/share/gc/z/zDriver.hpp index 8f178b886885..fb35c9428211 100644 --- a/src/hotspot/share/gc/z/zDriver.hpp +++ b/src/hotspot/share/gc/z/zDriver.hpp @@ -80,6 +80,7 @@ class ZDriverMinor : public ZDriver { ConcurrentGCTimer _gc_timer; ZMinorTracer _jfr_tracer; size_t _used_at_start; + size_t _max_at_start; void gc(const ZDriverRequest& request); void handle_alloc_stalls() const; @@ -99,6 +100,8 @@ class ZDriverMinor : public ZDriver { void set_used_at_start(size_t used); size_t used_at_start() const; + void set_max_at_start(size_t max); + size_t max_at_start() const; }; class ZDriverMajor : public ZDriver { @@ -107,6 +110,7 @@ class ZDriverMajor : public ZDriver { ConcurrentGCTimer _gc_timer; ZMajorTracer _jfr_tracer; size_t _used_at_start; + size_t _max_at_start; void collect_young(const ZDriverRequest& request); @@ -129,6 +133,8 @@ class ZDriverMajor : public ZDriver { void set_used_at_start(size_t used); size_t used_at_start() const; + void set_max_at_start(size_t max); + size_t max_at_start() const; }; class ZDriverLocker : public StackObj { diff --git a/src/hotspot/share/gc/z/zGeneration.cpp b/src/hotspot/share/gc/z/zGeneration.cpp index 0f9f4e34a5ee..7c54ff8f9d40 100644 --- a/src/hotspot/share/gc/z/zGeneration.cpp +++ b/src/hotspot/share/gc/z/zGeneration.cpp @@ -28,6 +28,7 @@ #include "gc/shared/gcVMOperations.hpp" #include "gc/shared/isGCActiveMark.hpp" #include "gc/shared/suspendibleThreadSet.hpp" +#include "gc/z/zAdaptiveHeap.inline.hpp" #include "gc/z/zBarrierSet.hpp" #include "gc/z/zBarrierSetAssembler.hpp" #include "gc/z/zBarrierSetNMethod.hpp" @@ -195,10 +196,20 @@ void ZGeneration::flip_age_pages(const ZRelocationSetSelector* selector) { } static double fragmentation_limit(ZGenerationId generation) { + double min_fragmentation = 0.0; + if (ZAdaptiveHeapSizing && ZHeap::heap()->is_alloc_stalling()) { + // It can be dangerous to defragment too much when the critical + // reserve of machine memory is used. When + // stalling starts, there should be very limited amounts of + // external fragmentation in the system. If we can't easily + // recover memory after this point, rather consider throwing + // OOME and calling it a day. + min_fragmentation = 25; + } if (generation == ZGenerationId::old) { - return ZFragmentationLimit; + return MAX2(ZFragmentationLimit, min_fragmentation); } else { - return ZYoungCompactionLimit; + return MAX2(ZYoungCompactionLimit, min_fragmentation); } } @@ -386,6 +397,9 @@ void ZGeneration::at_collection_start(ConcurrentGCTimer* gc_timer) { void ZGeneration::at_collection_end() { workers()->set_inactive(); stat_cycle()->at_end(stat_workers(), should_record_stats()); + if (should_record_stats() && ZAdaptiveHeapSizing) { + ZHeap::heap()->adapt_heuristic_max_capacity(_id); + } // The heap at collection end data is gathered at relocate end clear_gc_timer(); } @@ -742,7 +756,7 @@ uint ZGenerationYoung::compute_tenuring_threshold(ZRelocationSetSelectorStats st const size_t young_garbage = ZGeneration::young()->stat_heap()->garbage_at_mark_end(); const size_t young_allocated = ZGeneration::young()->stat_heap()->allocated_at_mark_end(); - const size_t soft_max_capacity = ZHeap::heap()->soft_max_capacity(); + const size_t heuristic_max_capacity = ZHeap::heap()->heuristic_max_capacity(); // The life expectancy shows by what factor on average one age changes between // two ages in the age table. Values below 1 indicate generational behaviour where @@ -764,7 +778,7 @@ uint ZGenerationYoung::compute_tenuring_threshold(ZRelocationSetSelectorStats st // resident part of the young generation is compared to the entire heap. Values // below 1 indicate it is relatively big. Conversely, values above 1 indicate // it is relatively small. - const double young_residency_reciprocal = double(soft_max_capacity) / double(young_live_total); + const double young_residency_reciprocal = double(heuristic_max_capacity) / double(young_live_total); // The old residency factor clamps the old residency reciprocal to // at least 1. That implies this factor is 1 unless the resident memory of @@ -779,14 +793,24 @@ uint ZGenerationYoung::compute_tenuring_threshold(ZRelocationSetSelectorStats st // The allocated to garbage ratio, compares the ratio of newly allocated // memory since GC started to how much garbage we are freeing up. The higher // the value, the harder it is for the YC to keep up with the allocation rate. - const double allocated_garbage_ratio = double(young_allocated) / double(young_garbage + 1); + const double allocated_garbage_ratio = MIN2(double(young_allocated) / double(young_garbage + 1), 1.0); + + // The amount of CPU spent being spent collecting the young generation vs + // the old generation. Since the CPU time of both generations contribute + // to the adaptive heap sizing, we want to reduce the amount of time spent + // in the most dominant generation. + const double young_to_old_gc_time_ratio = ZAdaptiveHeap::young_to_old_gc_time(); + + // If there is urgency for promotion we should be aggressive, and if it can + // reduce total GC time, we should also be more aggressive. + const double promotion_aggressiveness = MAX2(allocated_garbage_ratio, young_to_old_gc_time_ratio); // We slow down the young residency factor with a log. A larger log slows // it down faster. We select a log between 2 - 16 scaled by the allocated // to garbage factor. This selects a larger log when the GC has a harder // time keeping up, which causes more promotions to the old generation, // making the young collections faster so they can catch up. - const double young_log = MAX2(MIN2(allocated_garbage_ratio, 1.0) * 16, 2.0); + const double young_log = MAX2(promotion_aggressiveness * 16, 2.0); // The young log residency is essentially the young residency factor, but slowed // down by the log_{young_log}(X) function described above. @@ -802,6 +826,7 @@ uint ZGenerationYoung::compute_tenuring_threshold(ZRelocationSetSelectorStats st log_trace(gc, reloc)("Young Allocated: %zuM", young_allocated / M); log_trace(gc, reloc)("Young Garbage: %zuM", young_garbage / M); log_debug(gc, reloc)("Allocated To Garbage: %.1f", allocated_garbage_ratio); + log_debug(gc, reloc)("Young To Old GC Time: %.1f", young_to_old_gc_time_ratio); log_trace(gc, reloc)("Young Log: %.1f", young_log); log_trace(gc, reloc)("Young Residency Reciprocal: %.1f", young_residency_reciprocal); log_trace(gc, reloc)("Young Residency Factor: %.1f", young_residency_factor); diff --git a/src/hotspot/share/gc/z/zGlobals.hpp b/src/hotspot/share/gc/z/zGlobals.hpp index 278b6480d1dc..7b6488347d32 100644 --- a/src/hotspot/share/gc/z/zGlobals.hpp +++ b/src/hotspot/share/gc/z/zGlobals.hpp @@ -89,4 +89,16 @@ const size_t ZMarkProactiveFlushMax = 10; // Try complete mark timeout const uint64_t ZMarkCompleteTimeout = 200; // us +// How much CPU resources concurrent workers should use by default +const double ZConcurrentWorkersCPUShare = 0.25; + +// How much memory reserve we prefer to have on the machine +const double ZMemoryConcerningThreshold = 0.2; + +// How much memory reserve we fight to have on the machine +const double ZMemoryHighThreshold = 0.1; + +// How much memory reserve we enforce to have on the machine +const double ZMemoryCriticalThreshold = 0.05; + #endif // SHARE_GC_Z_ZGLOBALS_HPP diff --git a/src/hotspot/share/gc/z/zHeap.cpp b/src/hotspot/share/gc/z/zHeap.cpp index 44e5974993e5..bf568b07124b 100644 --- a/src/hotspot/share/gc/z/zHeap.cpp +++ b/src/hotspot/share/gc/z/zHeap.cpp @@ -26,6 +26,7 @@ #include "gc/shared/gcLogPrecious.hpp" #include "gc/shared/locationPrinter.hpp" #include "gc/shared/tlab_globals.hpp" +#include "gc/z/zAdaptiveHeap.inline.hpp" #include "gc/z/zAddress.inline.hpp" #include "gc/z/zArray.inline.hpp" #include "gc/z/zGeneration.inline.hpp" @@ -48,6 +49,8 @@ #include "memory/metaspaceUtils.hpp" #include "memory/resourceArea.hpp" #include "runtime/javaThread.hpp" +#include "runtime/os.hpp" +#include "utilities/align.hpp" #include "utilities/debug.hpp" static const ZStatCounter ZCounterUndoPageAllocation("Memory", "Undo Page Allocation", ZStatUnitOpsPerSecond); @@ -61,7 +64,7 @@ ZHeap::ZHeap() : _page_allocator(MinHeapSize, InitialHeapSize, SoftMaxHeapSize, MaxHeapSize), _page_table(), _object_allocator(), - _serviceability(InitialHeapSize, min_capacity(), max_capacity()), + _serviceability(InitialHeapSize, static_min_capacity(), static_max_capacity()), _old(&_page_table, &_page_allocator), _young(&_page_table, _old.forwarding_table(), &_page_allocator), _tlab_usage(), @@ -86,8 +89,13 @@ ZHeap::ZHeap() } // Update statistics - _young.stat_heap()->at_initialize(_page_allocator.min_capacity(), _page_allocator.max_capacity()); - _old.stat_heap()->at_initialize(_page_allocator.min_capacity(), _page_allocator.max_capacity()); + _young.stat_heap()->at_initialize(_page_allocator.static_min_capacity(), MaxHeapSize); + _old.stat_heap()->at_initialize(_page_allocator.static_min_capacity(), MaxHeapSize); + + // Initialize Adaptive heap generation data + if (ZAdaptiveHeapSizing) { + ZAdaptiveHeap::initialize_generation_data(); + } // Successfully initialized _initialized = true; @@ -97,16 +105,24 @@ bool ZHeap::is_initialized() const { return _initialized; } -size_t ZHeap::min_capacity() const { - return _page_allocator.min_capacity(); +size_t ZHeap::static_min_capacity() const { + return _page_allocator.static_min_capacity(); +} + +size_t ZHeap::static_max_capacity() const { + return _page_allocator.static_max_capacity(); +} + +size_t ZHeap::dynamic_max_capacity() const { + return _page_allocator.dynamic_max_capacity(); } -size_t ZHeap::max_capacity() const { - return _page_allocator.max_capacity(); +size_t ZHeap::current_max_capacity() const { + return _page_allocator.current_max_capacity(); } -size_t ZHeap::soft_max_capacity() const { - return _page_allocator.soft_max_capacity(); +size_t ZHeap::heuristic_max_capacity() const { + return _page_allocator.heuristic_max_capacity(); } size_t ZHeap::capacity() const { @@ -117,6 +133,16 @@ size_t ZHeap::used() const { return _page_allocator.used(); } +void ZHeap::adapt_heuristic_max_capacity(ZGenerationId generation) { + precond(ZAdaptiveHeapSizing); + _page_allocator.adapt_heuristic_max_capacity(generation); +} + +void ZHeap::adjust_capacity(size_t used_soon) { + precond(ZAdaptiveHeapSizing); + _page_allocator.adjust_capacity(used_soon); +} + size_t ZHeap::used_generation(ZGenerationId id) const { return _page_allocator.used_generation(id); } diff --git a/src/hotspot/share/gc/z/zHeap.hpp b/src/hotspot/share/gc/z/zHeap.hpp index 64ebc253b260..68076a11d5a4 100644 --- a/src/hotspot/share/gc/z/zHeap.hpp +++ b/src/hotspot/share/gc/z/zHeap.hpp @@ -73,15 +73,19 @@ class ZHeap { void out_of_memory() const; // Heap metrics - size_t min_capacity() const; - size_t max_capacity() const; - size_t soft_max_capacity() const; + size_t static_min_capacity() const; + size_t static_max_capacity() const; + size_t dynamic_max_capacity() const; + size_t current_max_capacity() const; + size_t heuristic_max_capacity() const; size_t capacity() const; size_t used() const; size_t used_generation(ZGenerationId id) const; size_t used_young() const; size_t used_old() const; size_t unused() const; + void adapt_heuristic_max_capacity(ZGenerationId generation); + void adjust_capacity(size_t used_soon); size_t tlab_capacity() const; size_t tlab_used() const; diff --git a/src/hotspot/share/gc/z/zHeuristics.cpp b/src/hotspot/share/gc/z/zHeuristics.cpp index ccd31ee1749a..7a504927a359 100644 --- a/src/hotspot/share/gc/z/zHeuristics.cpp +++ b/src/hotspot/share/gc/z/zHeuristics.cpp @@ -104,7 +104,7 @@ uint ZHeuristics::nconcurrent_workers() { // a negative impact on the application throughput, while using too few // threads will prolong the GC cycle and we then risk being out-run by the // application. - return MAX2(nworkers(25.0), 1u); + return MAX2(nworkers(ZConcurrentWorkersCPUShare * 100), 1u); } size_t ZHeuristics::significant_heap_overhead() { diff --git a/src/hotspot/share/gc/z/zLargePages.cpp b/src/hotspot/share/gc/z/zLargePages.cpp index c259448563bd..73ca7c2d505f 100644 --- a/src/hotspot/share/gc/z/zLargePages.cpp +++ b/src/hotspot/share/gc/z/zLargePages.cpp @@ -22,6 +22,7 @@ */ #include "gc/shared/gcLogPrecious.hpp" +#include "gc/z/zAdaptiveHeap.hpp" #include "gc/z/zLargePages.hpp" #include "runtime/os.hpp" @@ -31,7 +32,10 @@ bool ZLargePages::_os_enforced_transparent_mode; void ZLargePages::initialize() { pd_initialize(); - const size_t memory = static_cast(os::physical_memory()); + const size_t memory = os::is_containerized() + ? os::physical_memory() + : ZAdaptiveHeap::machine_physical_memory(); + log_info_p(gc, init)("Memory: " PROPERFMT, PROPERFMTARGS(memory)); log_info_p(gc, init)("Large Page Support: %s", to_string()); } @@ -48,6 +52,9 @@ const char* ZLargePages::to_string() { return "Enabled (Transparent)"; } + case Collapse: + return "Enabled (Transparent, JVM enforced)"; + default: if (_os_enforced_transparent_mode) { return "Disabled (OS enforced)"; diff --git a/src/hotspot/share/gc/z/zLargePages.hpp b/src/hotspot/share/gc/z/zLargePages.hpp index 72b9e85095c7..b94ae2b6cc8f 100644 --- a/src/hotspot/share/gc/z/zLargePages.hpp +++ b/src/hotspot/share/gc/z/zLargePages.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,13 +25,15 @@ #define SHARE_GC_Z_ZLARGEPAGES_HPP #include "memory/allStatic.hpp" +#include "utilities/globalDefinitions.hpp" class ZLargePages : public AllStatic { private: enum State { Disabled, Explicit, - Transparent + Transparent, + Collapse }; static State _state; @@ -45,6 +47,11 @@ class ZLargePages : public AllStatic { static bool is_enabled(); static bool is_explicit(); static bool is_transparent(); + static bool is_collapse(); + + static bool should_try_fallocate(); + + static bool pd_collapse(void* addr, size_t bytes); static const char* to_string(); }; diff --git a/src/hotspot/share/gc/z/zLargePages.inline.hpp b/src/hotspot/share/gc/z/zLargePages.inline.hpp index d057553249ef..acf2fd8c261f 100644 --- a/src/hotspot/share/gc/z/zLargePages.inline.hpp +++ b/src/hotspot/share/gc/z/zLargePages.inline.hpp @@ -38,4 +38,14 @@ inline bool ZLargePages::is_transparent() { return _state == Transparent; } +inline bool ZLargePages::is_collapse() { + return _state == Collapse; +} + +inline bool ZLargePages::should_try_fallocate() { + // To use madvise(MADV_HUGEPAGE) we cannot use fallocate as the pages are created before we have + // a memory range. When using explict large pages other allocation modes are more efficient. + return LINUX_ONLY(!is_transparent() && !is_explicit()) NOT_LINUX(false); +} + #endif // SHARE_GC_Z_ZLARGEPAGES_INLINE_HPP diff --git a/src/hotspot/share/gc/z/zLock.hpp b/src/hotspot/share/gc/z/zLock.hpp index a843f2389d74..7a25b1aca69e 100644 --- a/src/hotspot/share/gc/z/zLock.hpp +++ b/src/hotspot/share/gc/z/zLock.hpp @@ -77,4 +77,14 @@ class ZLocker : public StackObj { ~ZLocker(); }; +template +class ZUnlocker : public StackObj { +private: + T* const _lock; + +public: + ZUnlocker(T* lock); + ~ZUnlocker(); +}; + #endif // SHARE_GC_Z_ZLOCK_HPP diff --git a/src/hotspot/share/gc/z/zLock.inline.hpp b/src/hotspot/share/gc/z/zLock.inline.hpp index ba5d66925217..93aa5c459590 100644 --- a/src/hotspot/share/gc/z/zLock.inline.hpp +++ b/src/hotspot/share/gc/z/zLock.inline.hpp @@ -116,4 +116,19 @@ inline ZLocker::~ZLocker() { } } +template +inline ZUnlocker::ZUnlocker(T* lock) + : _lock(lock) { + if (_lock != nullptr) { + _lock->unlock(); + } +} + +template +inline ZUnlocker::~ZUnlocker() { + if (_lock != nullptr) { + _lock->lock(); + } +} + #endif // SHARE_GC_Z_ZLOCK_INLINE_HPP diff --git a/src/hotspot/share/gc/z/zMappedCache.hpp b/src/hotspot/share/gc/z/zMappedCache.hpp index 8688e047ae1e..0175ceaa869c 100644 --- a/src/hotspot/share/gc/z/zMappedCache.hpp +++ b/src/hotspot/share/gc/z/zMappedCache.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -126,13 +126,15 @@ class ZMappedCache { public: ZMappedCache(); + size_t size() const; + void insert(const ZVirtualMemory& vmem); ZVirtualMemory remove_contiguous(size_t size); ZVirtualMemory remove_contiguous_power_of_2(size_t min_size, size_t max_size); size_t remove_discontiguous(size_t size, ZArray* out); - // ZUncommitter support + // Uncommit support void reset_min_size_watermark(); size_t min_size_watermark(); size_t remove_for_uncommit(size_t size, ZArray* out); diff --git a/src/hotspot/share/gc/z/zMappedCache.inline.hpp b/src/hotspot/share/gc/z/zMappedCache.inline.hpp new file mode 100644 index 000000000000..dc27a8e8e8b0 --- /dev/null +++ b/src/hotspot/share/gc/z/zMappedCache.inline.hpp @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#ifndef SHARE_GC_Z_ZMAPPEDCACHE_INLINE_HPP +#define SHARE_GC_Z_ZMAPPEDCACHE_INLINE_HPP + +#include "gc/z/zMappedCache.hpp" + +inline size_t ZMappedCache::size() const { + return _size; +} + +#endif // SHARE_GC_Z_ZMAPPEDCACHE_INLINE_HPP diff --git a/src/hotspot/share/gc/z/zMemoryWorker.cpp b/src/hotspot/share/gc/z/zMemoryWorker.cpp new file mode 100644 index 000000000000..2653f44c9011 --- /dev/null +++ b/src/hotspot/share/gc/z/zMemoryWorker.cpp @@ -0,0 +1,1122 @@ +/* + * Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include "gc/shared/gc_globals.hpp" +#include "gc/z/zAdaptiveHeap.inline.hpp" +#include "gc/z/zGlobals.hpp" +#include "gc/z/zLock.inline.hpp" +#include "gc/z/zMemoryWorker.hpp" +#include "gc/z/zNUMA.inline.hpp" +#include "gc/z/zPage.inline.hpp" +#include "gc/z/zPageAllocator.hpp" +#include "gc/z/zUtils.inline.hpp" +#include "gc/z/zVirtualMemory.inline.hpp" +#include "logging/log.hpp" +#include "logging/logHandle.hpp" +#include "runtime/init.hpp" +#include "utilities/debug.hpp" +#include "utilities/globalDefinitions.hpp" +#include "utilities/rbTree.inline.hpp" + +RBTreeOrdering ZMemoryWorker::ZHeatingRequestTreeComparator::cmp(zoffset first, zoffset second) { + if (first < second) { + // Start before second + return RBTreeOrdering::LT; + } + + if (first > second) { + // Start after second + return RBTreeOrdering::GT; + } + + // Same position + return RBTreeOrdering::EQ; +} + +enum class ZMemoryWorkerMode { + Uninitialized, + Commit, + Uncommit, + UncommitTargetless, + Heat, + Wait, +}; + +static const char* memory_worker_completed_mode_name(ZMemoryWorkerMode mode) { + switch (mode) { + case ZMemoryWorkerMode::Commit: + return "Committed"; + case ZMemoryWorkerMode::Uncommit: + case ZMemoryWorkerMode::UncommitTargetless: + return "Uncommitted"; + case ZMemoryWorkerMode::Heat: + return "Heated"; + case ZMemoryWorkerMode::Wait: + case ZMemoryWorkerMode::Uninitialized: + break; + } + + DEBUG_ONLY(ShouldNotReachHere();) + return "Unknown"; +} + +static const char* memory_worker_start_mode_name(ZMemoryWorkerMode mode) { + switch (mode) { + case ZMemoryWorkerMode::Commit: + return "Commit"; + case ZMemoryWorkerMode::Uncommit: + case ZMemoryWorkerMode::UncommitTargetless: + return "Uncommit"; + case ZMemoryWorkerMode::Heat: + return "Heat"; + case ZMemoryWorkerMode::Wait: + case ZMemoryWorkerMode::Uninitialized: + break; + } + + DEBUG_ONLY(ShouldNotReachHere();) + return "Unknown"; +} + +bool ZMemoryWorker::is_enabled() { + return ZAdaptiveHeapSizing || ZMemoryHeating; +} + +ZMemoryWorker::ZMemoryWorker(uint32_t id, ZPartition* partition) + : _id(id), + _partition(partition), + _lock(), + _heating_requests(), + _heating_request_bytes(0), + _target_commit_capacity(0), + _target_uncommit_capacity(0), + _targetless_uncommit(false), + _uncommit_request_time(), + _stop(false), + _currently_heating() { + if (!is_enabled()) { + // Disabled, do not start + _stop = true; + return; + } + set_name("ZMemoryWorker#%u", _id); + create_and_start(); + + if (ZNUMA::is_enabled()) { + // If NUMA is enabled we set the affinity of the thread to CPUs associated + // with the partition that the ZMemoryWorker will work on. + os::numa_set_thread_affinity(this, ZNUMA::numa_id_to_node(_id)); + } +} + +ZMemoryWorker::~ZMemoryWorker() {} + +bool ZMemoryWorker::is_stop_requested() { + return _stop; +} + +size_t ZMemoryWorker::commit_granule(size_t target_capacity) { + const size_t smallest_granule = ZGranuleSize; + const size_t largest_granule = MAX2(ZPageSizeMediumMax, smallest_granule); + + return clamp(align_up(target_capacity / 128, ZGranuleSize), smallest_granule, largest_granule); +} + +size_t ZMemoryWorker::uncommit_granule(size_t target_capacity) { + const size_t smallest_granule = ZGranuleSize; + const size_t largest_granule = MAX2(ZPageSizeMediumMax, smallest_granule); + + return clamp(align_up(target_capacity / 128, ZGranuleSize), smallest_granule, largest_granule); +} + +bool ZMemoryWorker::has_heating_request() { + assert((_heating_request_bytes == 0) == (_heating_requests.size() == 0), + "_heating_request_bytes: %zu, _heating_requests.size(): %zu", + _heating_request_bytes, _heating_requests.size()); + return _heating_request_bytes != 0; +} + +void ZMemoryWorker::verify_heating_requests() { +#ifdef ASSERT + // Don't verify the tree if it's "too" large + if (_heating_requests.size() > 10) { + return; + } + + const ZHeatingRequestNode* prev = nullptr; + size_t total_size = 0; + + _heating_requests.visit_range_in_order(zoffset(0), zoffset(ZAddressOffsetMax), [&](const ZHeatingRequestNode* node) { + // If there is a previous node, make sure that it is not adjacent to this + // node. Adjacent nodes should be merged when inserted to the tree. + if (prev != nullptr) { + assert(prev->key() + prev->val() != node->key(), "Should be merged. " + "[" PTR_FORMAT ", " PTR_FORMAT "] [" PTR_FORMAT ", " PTR_FORMAT "]", + untype(prev->key()), prev->val(), untype(node->key()), node->val()); + } + total_size += node->val(); + prev = node; + return true; + }); + + assert(total_size == _heating_request_bytes, "%zu == %zu", total_size, _heating_request_bytes); +#endif // ASSERT +} + +void ZMemoryWorker::set_targetless_uncommit(Ticks now) { + precond(ZUncommit); + + _targetless_uncommit = true; + if (_uncommit_request_time == Ticks()) { + _uncommit_request_time = now; + } +} + +void ZMemoryWorker::set_targeted_uncommit(size_t requested_capacity, Ticks now) { + precond(ZUncommit); + + _target_uncommit_capacity = requested_capacity; + if (_uncommit_request_time == Ticks()) { + _uncommit_request_time = now; + } +} + +void ZMemoryWorker::clear_targetless_uncommit() { + _targetless_uncommit = false; + + if (_target_uncommit_capacity == 0u) { + // No more uncommit request + _uncommit_request_time = Ticks(); + } +} + +void ZMemoryWorker::clear_targeted_uncommit() { + _target_uncommit_capacity = 0u; + + if (!_targetless_uncommit) { + // No more uncommit request + _uncommit_request_time = Ticks(); + } +} + +void ZMemoryWorker::stop_heap_resizing() { + precond(ZAdaptiveHeapSizing); + + // Remove requests to resize the capacity + ZLocker locker(&_lock); + _target_commit_capacity = 0u; + clear_targeted_uncommit(); +} + +void ZMemoryWorker::stop_shrink_capacity_granule() { + precond(ZAdaptiveHeapSizing); + + if (!ZUncommit) { + // Uncommit turned off. + return; + } + + // Remove requests to resize the capacity + ZLocker locker(&_lock); + clear_targetless_uncommit(); +} + +void ZMemoryWorker::request_grow_capacity(size_t requested_capacity) { + precond(ZAdaptiveHeapSizing); + + ZLocker locker(&_lock); + _target_commit_capacity = requested_capacity; + clear_targeted_uncommit(); + + _lock.notify_all(); +} + +void ZMemoryWorker::request_shrink_capacity(size_t requested_capacity) { + precond(ZAdaptiveHeapSizing); + + if (!ZUncommit) { + // Uncommit turned off. + return; + } + + const Ticks now = Ticks::now(); + + ZLocker locker(&_lock); + set_targeted_uncommit(requested_capacity, now); + _target_commit_capacity = 0u; + + _lock.notify_all(); +} + +void ZMemoryWorker::request_shrink_capacity_granule() { + precond(ZAdaptiveHeapSizing); + + if (!ZUncommit) { + // Uncommit turned off. + return; + } + + const Ticks now = Ticks::now(); + + ZLocker locker(&_lock); + set_targetless_uncommit(now); + + _lock.notify_all(); +} + +static constexpr uint64_t TargetUncommitMinDelay = 10; + +static bool has_uncommit_matured(Ticks now, Ticks since, uint64_t uncommit_delay) { + precond(now != Ticks()); + precond(since != Ticks()); + + return (now - since).milliseconds() >= uncommit_delay; +} + +static uint64_t remaining_uncommit_wait_duration(Ticks since, uint64_t uncommit_delay) { + precond(since != Ticks()); + + const uint64_t elapsed = (Ticks::now() - since).milliseconds(); + return elapsed >= uncommit_delay ? 0 : uncommit_delay - elapsed; +} + +static uint64_t targeted_uncommit_delay(uint64_t uncommit_delay) { + return MIN2(TargetUncommitMinDelay, uncommit_delay); +} + +static bool has_targetless_uncommit_matured(Ticks now, Ticks since, uint64_t uncommit_delay) { + // Short term shrinking as requested by the director should shrink according + // to the memory pressure based uncommit delay schedule. + return has_uncommit_matured(now, since, uncommit_delay); +} + +static bool has_targeted_uncommit_matured(Ticks now, Ticks since, uint64_t uncommit_delay) { + precond(ZUncommit); + + // Long term shrinking as requested by a GC should shrink fairly quickly + // regardless of memory pressure. + const uint64_t targeted_delay = targeted_uncommit_delay(uncommit_delay); + return has_uncommit_matured(now, since, targeted_delay); +} + +void ZMemoryWorker::remove_heating_request_range(const ZVirtualMemory& vmem) { + ZArray to_remove; + + const zoffset start_inclusive = vmem.start(); + const zoffset end_inclusive = vmem.start() + (vmem.size() - ZGranuleSize); + _heating_requests.visit_range_in_order(start_inclusive, end_inclusive, [&](const ZHeatingRequestNode* node) { + // Const cast the node, we only use it to modify the tree after + // visit_range_in_order is completed. + to_remove.push(const_cast(node)); + return true; + }); + + for (ZHeatingRequestNode* node: to_remove) { + const ZVirtualMemory to_remove(node->key(), node->val()); + assert(vmem.contains(to_remove), + "[" PTR_FORMAT ", " PTR_FORMAT "] [" PTR_FORMAT ", " PTR_FORMAT "]", + untype(vmem.start()), untype(vmem.end()), + untype(to_remove.start()), untype(to_remove.end())); + _heating_requests.remove(node); + _heating_request_bytes -= to_remove.size(); + } +} + +void ZMemoryWorker::register_heating_request(const ZVirtualMemory& vmem) { + precond(ZMemoryHeating); + + ZLocker locker(&_lock); + if (_stop) { + // Don't add more requests during termination + return; + } + + if (!has_heating_request() && _currently_heating.is_null()) { + // There is currently no heating going on, notify the memory worker. + _lock.notify_all(); + } + + // Verification + verify_heating_requests(); + ZOnScopeExit on_scope_exit([&]() { verify_heating_requests(); }); + + assert(_currently_heating.is_null() || !vmem.overlaps(_currently_heating), + "[" PTR_FORMAT ", " PTR_FORMAT "] [" PTR_FORMAT ", " PTR_FORMAT "]", + untype(vmem.start()), untype(vmem.end()), + untype(_currently_heating.start()), untype(_currently_heating.end())); + + const ZHeatingRequestTree::Cursor insert_cursor = _heating_requests.cursor(vmem.start()); + assert(!insert_cursor.found(), "must not be in tree"); + + // Account for the new size upfront + _heating_request_bytes += vmem.size(); + + const ZHeatingRequestTree::Cursor prev_cursor = _heating_requests.prev(insert_cursor); + const ZHeatingRequestTree::Cursor next_cursor = _heating_requests.next(insert_cursor); + + const bool extends_left = prev_cursor.valid() && prev_cursor.found() && + zoffset_end(prev_cursor.node()->key() + prev_cursor.node()->val()) == vmem.start(); + + const bool extends_right = next_cursor.valid() && next_cursor.found() && + zoffset_end(next_cursor.node()->key()) == vmem.end(); + + if (extends_left && extends_right) { + const ZVirtualMemory left_vmem(prev_cursor.node()->key(), prev_cursor.node()->val()); + const ZVirtualMemory right_vmem(next_cursor.node()->key(), next_cursor.node()->val()); + assert(left_vmem.adjacent_to(vmem), "must be"); + assert(vmem.adjacent_to(right_vmem), "must be"); + + const size_t new_size = left_vmem.size() + vmem.size() + right_vmem.size(); + // Update the left node's size + prev_cursor.node()->set_val(new_size); + + // And remove the right node + _heating_requests.remove(next_cursor.node()); + + return; + } + + if (extends_left) { + const ZVirtualMemory left_vmem(prev_cursor.node()->key(), prev_cursor.node()->val()); + assert(left_vmem.adjacent_to(vmem), "must be"); + + const size_t new_size = left_vmem.size() + vmem.size(); + prev_cursor.node()->set_val(new_size); + + return; + } + + if (extends_right) { + const ZVirtualMemory right_vmem(next_cursor.node()->key(), next_cursor.node()->val()); + assert(vmem.adjacent_to(right_vmem), "must be"); + + ZVirtualMemory new_vmem = vmem; + new_vmem.grow_from_back(right_vmem.size()); + + // Right now we don't have a way to change the key of an inserted CHeap-allocated + // node, so we have to remove the right node and insert a new node with the updated + // key and size. + _heating_requests.remove(next_cursor.node()); + + ZHeatingRequestNode* const new_node = _heating_requests.allocate_node(new_vmem.start(), new_vmem.size()); + _heating_requests.insert(new_node->key(), new_node); + + return; + } + + // The request does not extend another vmem to the left or right + ZHeatingRequestNode* const new_node = _heating_requests.allocate_node(vmem.start(), vmem.size()); + _heating_requests.insert(new_node->key(), new_node); +} + +ZVirtualMemory ZMemoryWorker::pop_heating_request() { + assert(has_heating_request(), "precondition"); + + // Verification + verify_heating_requests(); + ZOnScopeExit on_scope_exit([&]() { verify_heating_requests(); }); + + const size_t max_heating = 16 * ZGranuleSize; + + ZHeatingRequestNode* const node = _heating_requests.leftmost(); + const size_t size = node->val(); + + // If the node is small enough, we remove it entirely from the tree. Otherwise, + // we update the node's size in-place without removing and inserting it. + + if (size <= max_heating) { + const ZVirtualMemory vmem(node->key(), size); + _heating_requests.remove(node); + _heating_request_bytes -= size; + + return vmem; + } + + const size_t size_remainder = size - max_heating; + const ZVirtualMemory vmem(node->key() + size_remainder, max_heating); + + // Update the value of the node to the new size + node->set_val(size_remainder); + _heating_request_bytes -= max_heating; + + return vmem; +} + +void ZMemoryWorker::remove_heating_request(const ZVirtualMemory& vmem) { + precond(ZMemoryHeating); + + ZLocker locker(&_lock); + + // Verification + verify_heating_requests(); + ZOnScopeExit on_scope_exit([&]() { verify_heating_requests(); }); + + while (!_currently_heating.is_null() && vmem.overlaps(_currently_heating)) { + // Wait while currently heating overlaps with this vmem + _lock.wait(); + } + + const uintptr_t vmem_start = (uintptr_t)vmem.start(); + const uintptr_t vmem_end = vmem_start + vmem.size(); + + // Cut off overlap on the left + if (vmem.start() > zoffset(0)) { + ZHeatingRequestNode* const left_node = _heating_requests.closest_leq(vmem.start() - ZGranuleSize); + if (left_node != nullptr) { + const uintptr_t left_start = (uintptr_t)left_node->key(); + const size_t left_size = left_node->val(); + const uintptr_t left_end = left_start + left_size; + + if (left_end > vmem_start) { + // There is an intersection on the left side + const size_t left_leading = vmem_start - left_start; + left_node->set_val(left_leading); + + if (left_end > vmem_end) { + // Intersection continues to the right side + const size_t left_trailing = left_end - vmem_end; + ZHeatingRequestNode* const new_node = _heating_requests.allocate_node(zoffset(vmem_end), left_trailing); + _heating_requests.insert(zoffset(vmem_end), new_node); + _heating_request_bytes -= vmem.size(); + } else { + _heating_request_bytes -= left_size - left_leading; + } + } + } + } + + // Cut off overlap on the right + ZHeatingRequestNode* const right_node = _heating_requests.closest_leq(vmem.start() + vmem.size()); + if (right_node != nullptr) { + const uintptr_t right_start = (uintptr_t)right_node->key(); + const size_t right_size = right_node->val(); + const uintptr_t right_end = right_start + right_size; + + if (right_start < vmem_end && right_end > vmem_end) { + // There is an intersection on the right side + _heating_requests.remove(right_node); + const size_t right_trailing = right_end - vmem_end; + ZHeatingRequestNode* const new_node = _heating_requests.allocate_node(zoffset(vmem_end), right_trailing); + _heating_requests.insert(zoffset(vmem_end), new_node); + _heating_request_bytes -= right_size - right_trailing; + } + } + + remove_heating_request_range(vmem); +} + +size_t ZMemoryWorker::process_heating_request() { + ZVirtualMemory vmem; + { + ZLocker locker(&_lock); + if (!has_heating_request()) { + // Unmapping removed the request; bail + return 0; + } + + assert(has_heating_request(), "who else processed it?"); + + vmem = pop_heating_request(); + + assert(_currently_heating.is_null(), "must be"); + _currently_heating = vmem; + } + + _partition->heat_memory(vmem); + + { + ZLocker locker(&_lock); + _currently_heating = {}; + _lock.notify_all(); + } + + return vmem.size(); +} + +bool ZMemoryWorker::await_start() { + for (;;) { + if (_stop) { + return false; + } + + if (is_init_completed()) { + return true; + } + + // Don't start working until JVM is bootstrapped + _lock.wait(); + } +} + +class ZMemoryWorker::ZMemoryWorkerState { +private: + using Mode = ZMemoryWorkerMode; + ZMemoryWorker* const _worker; + Ticks _init_time; + Ticks _update_time; + size_t _processed; + size_t _current_target_capacity; + size_t _init_target_capacity; + size_t _target_capacity; + uint64_t _uncommit_delay; + Mode _mode; + + bool try_commit() { + precond(ZAdaptiveHeapSizing); + precond(_mode == Mode::Commit); + + const bool targetless_uncommit = _worker->_targetless_uncommit; + + if (targetless_uncommit && has_targetless_uncommit_matured(_update_time, _worker->_uncommit_request_time, _uncommit_delay)) { + const size_t min_capacity = _worker->_partition->_static_min_capacity; + if (_current_target_capacity > min_capacity) { + // This might cause _current_target_capacity to undershoot the current capacity. + // So we might not uncommit the whole uncommit granule in this target capacity truncation. + // We allow this discrepancy. + const size_t uncommit_size = _worker->uncommit_granule(_current_target_capacity); + const size_t shrink_amount = MIN2(_current_target_capacity - min_capacity, uncommit_size); + + // Shrink heuristic max + _worker->_partition->_page_allocator->shrink_heuristic_max(shrink_amount); + log_debug(gc, heap)("Memory Worker (%d) Prevented Commit: %zuM(%.0f%%)", + _worker->_id, shrink_amount / M, + percent_of(shrink_amount, _current_target_capacity)); + + assert(_worker->_target_commit_capacity == _current_target_capacity, + "Should not have released the lock since update targets"); + _current_target_capacity -= shrink_amount; + _worker->_target_commit_capacity -= shrink_amount; + _worker->_uncommit_request_time = _update_time; + } + } + + const size_t commit_size = _worker->commit_granule(_current_target_capacity); + const size_t processed = [&]() { + ZUnlocker unlocker(&_worker->_lock); + return _worker->_partition->increase_and_commit_capacity(commit_size, _current_target_capacity); + }(); + + _processed += processed; + + return processed > 0; + } + + bool try_uncommit() { + precond(ZAdaptiveHeapSizing); + precond(_mode == Mode::Uncommit); + precond(ZUncommit); + + const size_t uncommit_size = _worker->uncommit_granule(_current_target_capacity); + const size_t processed = [&]() { + ZUnlocker unlocker(&_worker->_lock); + return _worker->uncommit(uncommit_size); + }(); + + _processed += processed; + + return processed > 0; + } + + bool try_targetless_uncommit() { + precond(ZAdaptiveHeapSizing); + precond(_mode == Mode::UncommitTargetless); + precond(ZUncommit); + precond(_target_capacity == 0); + precond(_init_target_capacity == 0); + + const size_t uncommit_size = _worker->uncommit_granule(_current_target_capacity); + const size_t processed = [&]() { + ZUnlocker unlocker(&_worker->_lock); + return _worker->uncommit(uncommit_size); + }(); + + _processed += processed; + + return processed > 0; + } + + bool try_heat() { + if (!_worker->has_heating_request()) { + return false; + } + precond(ZMemoryHeating); + + const size_t processed = [&]() { + ZUnlocker unlocker(&_worker->_lock); + return _worker->process_heating_request(); + }(); + + _processed += processed; + + return processed > 0; + } + +public: + explicit ZMemoryWorkerState(ZMemoryWorker* worker) + : _worker(worker), + _init_time(), + _update_time(), + _processed(0), + _current_target_capacity(0), + _init_target_capacity(0), + _target_capacity(0), + _uncommit_delay(0), + _mode(Mode::Uninitialized) {} + + ~ZMemoryWorkerState() { + postcond(_mode != Mode::Uninitialized); + + if (_processed > 0) { + ZUnlocker unlocker(&_worker->_lock); + const char* const mode_string = memory_worker_completed_mode_name(_mode); + const auto elapsed = Ticks::now() - _init_time; + const size_t processed_mb = _processed / M; + const double process_rate_mb_per_sec = double(processed_mb) / elapsed.seconds(); + + if (_mode == Mode::UncommitTargetless) { + log_info(gc, heap)("Memory Worker (%d) %s: %zuM in %.3fms (%.2f MiB/s)", + _worker->_id, mode_string, processed_mb, + elapsed.seconds() * MILLIUNITS, process_rate_mb_per_sec); + } else { + LogTargetHandle lt = [&]() -> LogTargetHandle { + if (_mode == ZMemoryWorkerMode::Heat) { + return LogTarget(Debug, gc, heap)(); + } else { + return LogTarget(Info, gc, heap)(); + } + }(); + + const double percent_of_init_target = percent_of(_processed, _init_target_capacity); + lt.print("Memory Worker (%d) %s: %zuM(%.0f%%) in %.3fms (%.2f MiB/s)", + _worker->_id, mode_string, processed_mb, percent_of_init_target, + elapsed.seconds() * MILLIUNITS, process_rate_mb_per_sec); + } + } + } + + void select_mode() { + precond(_mode == Mode::Uninitialized); + + { + ZUnlocker unlocker(&_worker->_lock); + _init_time = Ticks::now(); + if (ZAdaptiveHeapSizing) { + const size_t uncommit_size = _worker->uncommit_granule(_current_target_capacity); + _uncommit_delay = ZAdaptiveHeap::uncommit_delay(uncommit_size); + } + } + + const size_t target_commit_capacity = _worker->_target_commit_capacity; + const size_t target_uncommit_capacity = _worker->_target_uncommit_capacity; + const size_t capacity = _worker->_partition->capacity(); + const size_t min_capacity = _worker->_partition->_static_min_capacity; + + if (target_commit_capacity > 0 && capacity < target_commit_capacity) { + // First priority is committing memory + + postcond(ZAdaptiveHeapSizing); + postcond(target_uncommit_capacity == 0); + + _mode = Mode::Commit; + _init_target_capacity = target_commit_capacity; + } else if (target_uncommit_capacity > 0 && capacity > target_uncommit_capacity) { + // Second priority is uncommitting memory + + postcond(ZAdaptiveHeapSizing); + postcond(ZUncommit); + postcond(target_commit_capacity == 0); + postcond(target_uncommit_capacity >= min_capacity); + + _mode = Mode::Uncommit; + _init_target_capacity = target_uncommit_capacity; + } else { + // Third priority is uncommitting memory if a targetless uncommit matured + // or heating memory if available. + const bool targetless_uncommit_matured = _worker->_targetless_uncommit && ZUncommit && + has_targetless_uncommit_matured(_init_time, _worker->_uncommit_request_time, _uncommit_delay); + + if (targetless_uncommit_matured && capacity != min_capacity) { + postcond(ZAdaptiveHeapSizing); + _mode = Mode::UncommitTargetless; + } else if (_worker->has_heating_request()) { + _mode = Mode::Heat; + _init_target_capacity = _worker->_heating_request_bytes; + } else { + _mode = Mode::Wait; + } + + // Ensure that we have no other targets + _worker->_target_commit_capacity = 0u; + _worker->clear_targeted_uncommit(); + } + + LogTarget(Debug, gc, heap) lt; + + if (_init_target_capacity > 0 && lt.is_enabled()) { + ZUnlocker unlocker(&_worker->_lock); + + const size_t capacity = _worker->_partition->capacity(); + const char* const mode_string = memory_worker_start_mode_name(_mode); + + lt.print("Memory Worker (%d) Start %s, Target: %zuM(%.0f%%)", + _worker->_id, mode_string, _init_target_capacity / M, + percent_of(_init_target_capacity, capacity)); + } + + postcond(_mode != Mode::Uninitialized); + } + + bool update_targets() { + precond(_mode != Mode::Uninitialized); + + if (_mode == Mode::Wait) { + // Go directly to await + return true; + } + + { + ZUnlocker unlocker(&_worker->_lock); + _update_time = Ticks::now(); + if (ZAdaptiveHeapSizing) { + const size_t uncommit_size = _worker->uncommit_granule(_current_target_capacity); + _uncommit_delay = ZAdaptiveHeap::uncommit_delay(uncommit_size); + } + } + + const bool targetless_uncommit = _worker->_targetless_uncommit; + const size_t capacity = _worker->_partition->capacity(); + const size_t target_commit_capacity = _worker->_target_commit_capacity; + const size_t target_uncommit_capacity = _worker->_target_uncommit_capacity; + + switch (_mode) { + case Mode::Commit: { + _current_target_capacity = target_commit_capacity; + return capacity < target_commit_capacity; + } + case Mode::Uncommit: { + _current_target_capacity = target_uncommit_capacity; + return target_uncommit_capacity != 0 && capacity > target_uncommit_capacity; + } + case Mode::UncommitTargetless: { + postcond(_init_target_capacity == 0); + postcond(_current_target_capacity == 0); + return target_commit_capacity == 0 && target_uncommit_capacity == 0 && targetless_uncommit; + } + case Mode::Heat: { + if (target_commit_capacity != 0 || target_uncommit_capacity != 0) { + // We have more important work to do + return false; + } + + return _processed < _init_target_capacity; + } + default: + return false; + } + } + + bool try_work() { + switch (_mode) { + case Mode::Commit: { + return try_commit(); + } + case Mode::Uncommit: { + if (!has_targeted_uncommit_matured(_update_time, _worker->_uncommit_request_time, _uncommit_delay)) { + // Wait for the uncommit request to mature + return true; + } + + if (try_uncommit()) { + // Set new uncommit request time + _worker->_uncommit_request_time = _update_time; + return true; + } + + // Failed to uncommit + // Reset the uncommit target + _worker->clear_targeted_uncommit(); + + return false; + } + case Mode::UncommitTargetless: { + if (!has_targetless_uncommit_matured(_update_time, _worker->_uncommit_request_time, _uncommit_delay)) { + // Wait for the uncommit request to mature + return true; + } + + if (try_targetless_uncommit()) { + // Set new uncommit request time + _worker->_uncommit_request_time = _update_time; + return true; + } + + // Failed to uncommit + // Stop targetless uncommit + _worker->clear_targetless_uncommit(); + + return false; + } + case Mode::Heat: { + return try_heat(); + } + case Mode::Wait: { + // Go directly to await + return true; + } + default: + ShouldNotReachHere(); + } + } + + bool await_work() { + switch (_mode) { + case Mode::Commit: { + // Commit immediately unless we have reached capacity + return _worker->_partition->capacity() < _current_target_capacity; + } + case Mode::Uncommit: { + precond(_current_target_capacity != 0); + + const auto get_wait_duration = [&]() -> uint64_t { + // Read the _uncommit_request_time before releasing the lock + const Ticks request_time = _worker->_uncommit_request_time; + + ZUnlocker unlocker(&_worker->_lock); + const size_t uncommit_size = _worker->uncommit_granule(_current_target_capacity); + const uint64_t targeted_delay = targeted_uncommit_delay(ZAdaptiveHeap::uncommit_delay(uncommit_size)); + return remaining_uncommit_wait_duration(request_time, targeted_delay); + }; + + const auto is_uncommit_done = [&]() { + const size_t capacity = _worker->_partition->capacity(); + const size_t target_uncommit_capacity = _worker->_target_uncommit_capacity; + return target_uncommit_capacity == 0 || capacity <= target_uncommit_capacity; + }; + + for (;;) { + // Is there more work? + if (_worker->is_stop_requested() || is_uncommit_done()) { + return false; + } + + const uint64_t wait_duration = get_wait_duration(); + + // Is there more work? + if (_worker->is_stop_requested() || is_uncommit_done()) { + return false; + } + + if (wait_duration == 0) { + // We are done waiting + return true; + } + + // Wait for uncommit + _worker->_lock.wait(wait_duration); + } + + return true; + } + case Mode::UncommitTargetless: { + // Never await targetless uncommit + return false; + } + case Mode::Heat: { + // Heat immediately if there are more requests + return _worker->has_heating_request(); + } + case Mode::Wait: { + const auto get_targetless_uncommit_wait_duration = [&]() -> uint64_t { + // Read the _uncommit_request_time before releasing the lock + const Ticks request_time = _worker->_uncommit_request_time; + + ZUnlocker unlocker(&_worker->_lock); + const size_t uncommit_size = _worker->uncommit_granule(_current_target_capacity); + const uint64_t uncommit_delay = ZAdaptiveHeap::uncommit_delay(uncommit_size); + return remaining_uncommit_wait_duration(request_time, uncommit_delay); + }; + + const auto check_for_work_non_targetless_uncommit = [&]() { + if (_worker->_target_commit_capacity > 0) { + // There is commit work to be done + return true; + } + + if (_worker->_target_uncommit_capacity > 0) { + // There is uncommit work to be done + return true; + } + + if (_worker->_heating_request_bytes > 0) { + // There is heating work to be done + return true; + } + + return false; + }; + + for (;;) { + if (check_for_work_non_targetless_uncommit() || _worker->is_stop_requested()) { + return false; + } + + // If the capacity is already minimal, wait until something changes externally. + const size_t capacity = _worker->_partition->capacity(); + const size_t min_capacity = _worker->_partition->_static_min_capacity; + + if (_worker->_targetless_uncommit && capacity != min_capacity) { + // There might be targetless uncommit work to be done + const uint64_t wait_duration = get_targetless_uncommit_wait_duration(); + + if (check_for_work_non_targetless_uncommit() || wait_duration == 0 || _worker->is_stop_requested()) { + // There is targetless work to be done or the targetless uncommit has matured + return false; + } + + // Wait for notification or targetless maturity + _worker->_lock.wait(wait_duration); + } else { + // Wait for more work + _worker->_lock.wait(); + } + } + } + default: + ShouldNotReachHere(); + } + } +}; + +class ZMemoryWorkerTimer { +private: + jlong _start; + +public: + ZMemoryWorkerTimer() + : _start(os::current_thread_cpu_time(true /* user + sys */)) {} + + ~ZMemoryWorkerTimer() { + jlong elapsed = os::current_thread_cpu_time(true /* user + sys */) - _start; + ZStatMemoryWorkers::add_accumulated_vtime(double(elapsed) / NANOSECS_PER_SEC); + } +}; + +void ZMemoryWorker::run_thread() { + // We always hold the lock, except when interacting with the OS, or awaiting + // more work. + ZLocker locker(&_lock); + + // Wait until started + if (!await_start()) { + // GC initialization failed + return; + } + + while (!_stop) { + ZMemoryWorkerState worker_state(this); + + worker_state.select_mode(); + + while (!_stop) { + ZMemoryWorkerTimer timer; + + if (!worker_state.update_targets()) { + break; + } + + if (!worker_state.try_work()) { + break; + } + + if (!worker_state.await_work()) { + break; + } + } + } +} + +size_t ZMemoryWorker::uncommit(size_t to_uncommit) { + ZArray flushed_vmems; + size_t flushed = 0; + + { + // We need to join the suspendible thread set while manipulating capacity + // and used, to make sure GC safepoints will have a consistent view. + SuspendibleThreadSetJoiner sts_joiner; + ZLocker locker(&_partition->_page_allocator->_lock); + + ZMappedCache& cache = _partition->_cache; + + // Never uncommit below min capacity + const size_t retain = MAX2(_partition->_used, _partition->_static_min_capacity); + const size_t release = _partition->_capacity - retain; + const size_t flush = MIN2(release, to_uncommit); + + // Flush memory from the mapped cache for uncommit + flushed = cache.remove_for_uncommit(flush, &flushed_vmems); + if (flushed == 0) { + // Nothing flushed + return 0; + } + + // Record flushed memory as claimed and how much we've flushed for this partition + _partition->increase_claimed(flushed); + } + + // Unmap and uncommit flushed memory + for (const ZVirtualMemory vmem : flushed_vmems) { + _partition->unmap_virtual(vmem); + _partition->uncommit_physical(vmem); + _partition->free_physical(vmem); + _partition->free_virtual(vmem); + } + + { + SuspendibleThreadSetJoiner sts_joiner; + ZLocker locker(&_partition->_page_allocator->_lock); + + // Adjust claimed and capacity to reflect the uncommit + _partition->decrease_claimed(flushed); + _partition->decrease_capacity(flushed); + } + + if (flushed > 0) { + _partition->_page_allocator->truncate_heuristic_max_after_capacity_decrease(ZPageAllocator::TruncationReason::Uncommit); + } + + return flushed; +} + +void ZMemoryWorker::terminate() { + ZLocker locker(&_lock); + _stop = true; + _lock.notify_all(); + + _heating_requests.remove_all(); + _heating_request_bytes = 0; + + while (!_currently_heating.is_null()) { + // Trying to unmap what's currently being heated; calm down! + _lock.wait(); + } +} diff --git a/src/hotspot/share/gc/z/zMemoryWorker.hpp b/src/hotspot/share/gc/z/zMemoryWorker.hpp new file mode 100644 index 000000000000..173b242434b2 --- /dev/null +++ b/src/hotspot/share/gc/z/zMemoryWorker.hpp @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#ifndef SHARE_GC_Z_ZMEMORYWORKER_HPP +#define SHARE_GC_Z_ZMEMORYWORKER_HPP + +#include "gc/z/zAdaptiveHeap.hpp" +#include "gc/z/zAddress.hpp" +#include "gc/z/zArray.hpp" +#include "gc/z/zList.hpp" +#include "gc/z/zLock.hpp" +#include "gc/z/zThread.hpp" +#include "gc/z/zVirtualMemory.hpp" +#include "memory/allocation.hpp" +#include "utilities/rbTree.hpp" +#include "utilities/ticks.hpp" + +class ZPartition; + +// This worker is enabled with adaptive heap sizing. +// Its responsibilities are: +// * Change heap capacity +// - Commit memory concurrently when growing the heap +// - Uncommit memory concurrently due to long-term shrinking +// - Uncommit memory concurrently due to urgent memory pressure +// * Heat up memory before mutators start using it +// - Pre-touching memory concurrently +// - Collapse small pages to large pages +// There is one memory worker per heap partition + +class ZMemoryWorker : public ZThread { +private: + class ZMemoryWorkerState; + + class ZHeatingRequestTreeComparator : public AllStatic { + public: + static RBTreeOrdering cmp(zoffset first, zoffset second); + }; + + using ZHeatingRequestTree = RBTreeCHeap; + using ZHeatingRequestNode = RBNode; + + const uint32_t _id; + ZPartition* const _partition; + ZConditionLock _lock; + ZHeatingRequestTree _heating_requests; + size_t _heating_request_bytes; + size_t _target_commit_capacity; + size_t _target_uncommit_capacity; + bool _targetless_uncommit; + Ticks _uncommit_request_time; + bool _stop; + ZVirtualMemory _currently_heating; + + bool await_start(); + bool is_stop_requested(); + size_t commit_granule(size_t target_capacity); + size_t uncommit_granule(size_t target_capacity); + + size_t uncommit(size_t to_uncommit); + + bool has_heating_request(); + void remove_heating_request_range(const ZVirtualMemory& vmem); + ZVirtualMemory pop_heating_request(); + size_t process_heating_request(); + + void verify_heating_requests(); + + void set_targetless_uncommit(Ticks now); + void set_targeted_uncommit(size_t requested_capacity, Ticks now); + void clear_targetless_uncommit(); + void clear_targeted_uncommit(); + +protected: + virtual void run_thread(); + virtual void terminate(); + +public: + static bool is_enabled(); + + ZMemoryWorker(uint32_t id, ZPartition* partition); + ~ZMemoryWorker(); + + // Heap resizing requests + void stop_heap_resizing(); + void stop_shrink_capacity_granule(); + void request_grow_capacity(size_t requested_capacity); + void request_shrink_capacity(size_t requested_capacity); + void request_shrink_capacity_granule(); + + // Heating requests + void register_heating_request(const ZVirtualMemory& vmem); + void remove_heating_request(const ZVirtualMemory& vmem); +}; + +#endif // SHARE_GC_Z_ZMEMORYWORKER_HPP diff --git a/src/hotspot/share/gc/z/zNUMA.cpp b/src/hotspot/share/gc/z/zNUMA.cpp index cf2d88a90e5a..2d2225649669 100644 --- a/src/hotspot/share/gc/z/zNUMA.cpp +++ b/src/hotspot/share/gc/z/zNUMA.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,6 +27,8 @@ #include "utilities/macros.hpp" bool ZNUMA::_enabled; +uint32_t ZNUMA::_node_count; +uint32_t ZNUMA::_bound_node_count; uint32_t ZNUMA::_count; void ZNUMA::initialize() { diff --git a/src/hotspot/share/gc/z/zNUMA.hpp b/src/hotspot/share/gc/z/zNUMA.hpp index 838a114c210e..a3ff049bf492 100644 --- a/src/hotspot/share/gc/z/zNUMA.hpp +++ b/src/hotspot/share/gc/z/zNUMA.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -35,6 +35,8 @@ class ZNUMA : public AllStatic { private: static bool _enabled; + static uint32_t _node_count; + static uint32_t _bound_node_count; static uint32_t _count; static void pd_initialize(); @@ -45,6 +47,9 @@ class ZNUMA : public AllStatic { static bool is_enabled(); static bool is_faked(); + static bool is_bound(); + + static uint32_t bound_count(); static uint32_t count(); static uint32_t id(); diff --git a/src/hotspot/share/gc/z/zNUMA.inline.hpp b/src/hotspot/share/gc/z/zNUMA.inline.hpp index d90b44e905a0..9c325f98b04d 100644 --- a/src/hotspot/share/gc/z/zNUMA.inline.hpp +++ b/src/hotspot/share/gc/z/zNUMA.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -38,6 +38,14 @@ inline bool ZNUMA::is_faked() { return ZFakeNUMA > 1; } +inline uint32_t ZNUMA::bound_count() { + return _bound_node_count; +} + +inline bool ZNUMA::is_bound() { + return _bound_node_count != _node_count; +} + inline uint32_t ZNUMA::count() { return _count; } diff --git a/src/hotspot/share/gc/z/zPageAllocator.cpp b/src/hotspot/share/gc/z/zPageAllocator.cpp index dbda9e9e9a21..95cab98dde5d 100644 --- a/src/hotspot/share/gc/z/zPageAllocator.cpp +++ b/src/hotspot/share/gc/z/zPageAllocator.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -21,8 +21,10 @@ * questions. */ +#include "gc/shared/gc_globals.hpp" #include "gc/shared/gcLogPrecious.hpp" #include "gc/shared/suspendibleThreadSet.hpp" +#include "gc/z/zAdaptiveHeap.inline.hpp" #include "gc/z/zAddress.hpp" #include "gc/z/zAllocationFlags.hpp" #include "gc/z/zArray.inline.hpp" @@ -33,7 +35,8 @@ #include "gc/z/zGlobals.hpp" #include "gc/z/zLargePages.inline.hpp" #include "gc/z/zLock.inline.hpp" -#include "gc/z/zMappedCache.hpp" +#include "gc/z/zMappedCache.inline.hpp" +#include "gc/z/zMemoryWorker.hpp" #include "gc/z/zNUMA.inline.hpp" #include "gc/z/zPage.inline.hpp" #include "gc/z/zPageAge.hpp" @@ -82,6 +85,12 @@ static void check_numa_mismatch(const ZVirtualMemory& vmem, uint32_t desired_id) } } +enum class ZPageAllocationAttempt { + initial, + retry, + stall, +}; + class ZMemoryAllocation : public CHeapObj { private: const size_t _size; @@ -92,7 +101,7 @@ class ZMemoryAllocation : public CHeapObj { size_t _harvested; size_t _increased_capacity; size_t _committed_capacity; - bool _commit_failed; + bool _has_set_committed_capacity; explicit ZMemoryAllocation(const ZMemoryAllocation& other) : ZMemoryAllocation(other._size) { @@ -124,7 +133,7 @@ class ZMemoryAllocation : public CHeapObj { void transfer_claimed_capacity(const ZMemoryAllocation& from) { assert(from._committed_capacity == 0, "Unexpected value %zu", from._committed_capacity); - assert(!from._commit_failed, "Unexpected value"); + assert(!_has_set_committed_capacity, "Should not have set committed capacity yet"); // Transfer increased capacity _increased_capacity += from._increased_capacity; @@ -153,7 +162,7 @@ class ZMemoryAllocation : public CHeapObj { _harvested(0), _increased_capacity(0), _committed_capacity(0), - _commit_failed(false) {} + _has_set_committed_capacity(false) {} void reset_for_retry() { assert(_satisfied_from_cache_vmem.is_null(), "Incompatible with reset"); @@ -164,7 +173,7 @@ class ZMemoryAllocation : public CHeapObj { _harvested = 0; _increased_capacity = 0; _committed_capacity = 0; - _commit_failed = false; + _has_set_committed_capacity = false; } size_t size() const { @@ -238,13 +247,14 @@ class ZMemoryAllocation : public CHeapObj { } void set_committed_capacity(size_t committed_capacity) { - assert(_committed_capacity == 0, "Should only commit once"); + precond(!_has_set_committed_capacity); _committed_capacity = committed_capacity; - _commit_failed = committed_capacity != _increased_capacity; + _has_set_committed_capacity = true; } bool commit_failed() const { - return _commit_failed; + precond(_has_set_committed_capacity); + return _committed_capacity != _increased_capacity; } static void destroy(ZMemoryAllocation* allocation) { @@ -586,6 +596,10 @@ ZPhysicalMemoryManager& ZPartition::physical_memory_manager() { return _page_allocator->_physical; } +ZLock* ZPartition::lock() const { + return &_page_allocator->_lock; +} + #ifdef ASSERT void ZPartition::verify_virtual_memory_multi_partition_association(const ZVirtualMemory& vmem) const { @@ -625,28 +639,125 @@ void ZPartition::verify_memory_allocation_association(const ZMemoryAllocation* a #endif // ASSERT -ZPartition::ZPartition(uint32_t numa_id, ZPageAllocator* page_allocator) +ZPartition::ZPartition(uint32_t numa_id, + ZPageAllocator* page_allocator, + size_t static_min_capacity, + size_t static_max_capacity) : _page_allocator(page_allocator), _cache(), _uncommitter(numa_id, this), - _min_capacity(ZNUMA::calculate_share(numa_id, page_allocator->min_capacity())), - _max_capacity(ZNUMA::calculate_share(numa_id, page_allocator->max_capacity())), - _current_max_capacity(_max_capacity), + _mem_worker(numa_id, this), + _static_min_capacity(ZNUMA::calculate_share(numa_id, static_min_capacity)), + _static_max_capacity(ZNUMA::calculate_share(numa_id, static_max_capacity)), _capacity(0), _claimed(0), _used(0), _numa_id(numa_id) {} +size_t ZPartition::dynamic_max_capacity() const { + return ZNUMA::calculate_share(_numa_id, _page_allocator->dynamic_max_capacity()); +} + +size_t ZPartition::current_max_capacity() const { + return ZNUMA::calculate_share(_numa_id, _page_allocator->current_max_capacity()); +} + +size_t ZPartition::static_max_capacity() const { + return _static_max_capacity; +} + +size_t ZPartition::capacity() const { + return AtomicAccess::load(&_capacity); +} + +const ZUncommitter& ZPartition::uncommitter() const { + return _uncommitter; +} + +ZUncommitter& ZPartition::uncommitter() { + return _uncommitter; +} + +const ZMemoryWorker& ZPartition::memory_worker() const { + return _mem_worker; +} + +ZMemoryWorker& ZPartition::memory_worker() { + return _mem_worker; +} + uint32_t ZPartition::numa_id() const { return _numa_id; } -size_t ZPartition::available() const { - return _current_max_capacity - _used - _claimed; +size_t ZPartition::available(ZPageAllocationAttempt attempt, size_t capacity_limit) const { + assert(_capacity == _used + _claimed + _cache.size(), "Should be consistent" + " _capacity: %zx _used: %zx _claimed: %zx _cache.size(): %zx", + _capacity, _used, _claimed, _cache.size()); + + if (attempt == ZPageAllocationAttempt::initial) { + return available_from_any_capacity(capacity_limit); + } + + if (attempt == ZPageAllocationAttempt::retry || attempt == ZPageAllocationAttempt::stall) { + return available_from_cache(capacity_limit); + } + + ShouldNotReachHere(); +} + +size_t ZPartition::available_from_any_capacity(size_t capacity_limit) const { + assert(_capacity == _used + _claimed + _cache.size(), "Should be consistent" + " _capacity: %zx _used: %zx _claimed: %zx _cache.size(): %zx", + _capacity, _used, _claimed, _cache.size()); + assert(capacity_limit <= _static_max_capacity, "Invalid capacity limit for partition: %zx > %zx", capacity_limit, _static_max_capacity); + const size_t unavailable = _used + _claimed; + + if (capacity_limit < unavailable) { + // The current max capacity may be below what is handed out + return 0; + } + + return capacity_limit - unavailable; +} + +size_t ZPartition::available_from_increase_capacity(size_t capacity_limit) const { + precond(capacity_limit >= _capacity); + const size_t available = ZPartition::available_from_any_capacity(capacity_limit); + const size_t cached = _cache.size(); + + return available - cached; +} + +size_t ZPartition::available_from_cache(size_t capacity_limit) const { + const size_t available = ZPartition::available_from_any_capacity(capacity_limit); + const size_t cached = _cache.size(); + + // The current allowed available may be below what is in the cache + return MIN2(available, cached); } -size_t ZPartition::increase_capacity(size_t size) { - const size_t increased = MIN2(size, _current_max_capacity - _capacity); +size_t ZPartition::try_increase_capacity(size_t size, ZPageAllocationAttempt attempt, size_t capacity_limit) { + if (attempt == ZPageAllocationAttempt::initial) { + // We should only increase the capacity if the capacity limit is greater + // than the already committed memory, i.e., the capacity. + return (capacity_limit > _capacity) + ? increase_capacity(size, capacity_limit) + : 0; + } + + if (attempt == ZPageAllocationAttempt::retry || attempt == ZPageAllocationAttempt::stall) { + return 0; + } + + ShouldNotReachHere(); +} + +size_t ZPartition::increase_capacity(size_t size, size_t capacity_limit) { + assert(capacity_limit > _capacity, "Cannot increase capacity. capacity limit: %zu, capacity: %zu", capacity_limit, _capacity); + + const size_t available = available_from_increase_capacity(capacity_limit); + const size_t increased = MIN2(size, available); if (increased > 0) { // Update atomically since we have concurrent readers @@ -658,21 +769,9 @@ size_t ZPartition::increase_capacity(size_t size) { return increased; } -void ZPartition::decrease_capacity(size_t size, bool set_max_capacity) { +void ZPartition::decrease_capacity(size_t size) { // Update capacity atomically since we have concurrent readers AtomicAccess::sub(&_capacity, size); - - // Adjust current max capacity to avoid further attempts to increase capacity - if (set_max_capacity) { - const size_t current_max_capacity_before = _current_max_capacity; - AtomicAccess::store(&_current_max_capacity, _capacity); - - log_debug_p(gc)("Forced to lower max partition (%u) capacity from " - "%zuM(%.0f%%) to %zuM(%.0f%%)", - _numa_id, - current_max_capacity_before / M, percent_of(current_max_capacity_before, _max_capacity), - _current_max_capacity / M, percent_of(_current_max_capacity, _max_capacity)); - } } void ZPartition::increase_used(size_t size) { @@ -689,7 +788,36 @@ void ZPartition::decrease_used(size_t size) { _used -= size; } -void ZPartition::free_memory(const ZVirtualMemory& vmem) { +void ZPartition::increase_claimed(size_t size) { + // Update claimed atomically since we have concurrent readers + AtomicAccess::add(&_claimed, size); +} + +void ZPartition::decrease_claimed(size_t size) { + // Update claimed atomically since we have concurrent readers + AtomicAccess::sub(&_claimed, size); +} + +static void pretouch_memory(zoffset start, size_t size) { + // At this point we know that we have a valid zoffset / zaddress. + const zaddress zaddr = ZOffset::address(start); + const uintptr_t addr = untype(zaddr); + const size_t page_size = ZLargePages::is_explicit() ? ZGranuleSize : os::vm_page_size(); + os::pretouch_memory((void*)addr, (void*)(addr + size), page_size); +} + +void ZPartition::heat_memory(const ZVirtualMemory& vmem) const { + verify_virtual_memory_association(vmem, true /* check_multi_partition */); + + const ZPhysicalMemoryManager& manager = physical_memory_manager(); + + pretouch_memory(vmem.start(), vmem.size()); + if (ZLargePages::is_collapse()) { + manager.collapse(vmem); + } +} + +void ZPartition::free_used_memory(const ZVirtualMemory& vmem) { const size_t size = vmem.size(); // Cache the vmem @@ -699,12 +827,22 @@ void ZPartition::free_memory(const ZVirtualMemory& vmem) { decrease_used(size); } -void ZPartition::claim_from_cache_or_increase_capacity(ZMemoryAllocation* allocation) { +void ZPartition::free_claimed_memory(const ZVirtualMemory& vmem) { + const size_t size = vmem.size(); + + // Cache the vmem + _cache.insert(vmem); + + // Update accounting + decrease_claimed(size); +} + +void ZPartition::claim_from_cache_or_increase_capacity(ZMemoryAllocation* allocation, ZPageAllocationAttempt attempt, size_t capacity_limit) { const size_t size = allocation->size(); ZArray* const out = allocation->partial_vmems(); // We are guaranteed to succeed the claiming of capacity here - assert(available() >= size, "Must be"); + assert(available(attempt, capacity_limit) >= size, "Must be"); // Associate the allocation with this partition. allocation->set_partition(this); @@ -720,7 +858,7 @@ void ZPartition::claim_from_cache_or_increase_capacity(ZMemoryAllocation* alloca } // Try increase capacity - const size_t increased_capacity = increase_capacity(size); + const size_t increased_capacity = try_increase_capacity(size, attempt, capacity_limit); allocation->set_increased_capacity(increased_capacity); @@ -739,20 +877,20 @@ void ZPartition::claim_from_cache_or_increase_capacity(ZMemoryAllocation* alloca assert(harvested + increased_capacity == size, "Mismatch harvested: %zu increased_capacity: %zu size: %zu", - harvested, increased_capacity, size); + harvested / M, increased_capacity / M, size / M); return; } -bool ZPartition::claim_capacity(ZMemoryAllocation* allocation) { +bool ZPartition::claim_capacity(ZMemoryAllocation* allocation, ZPageAllocationAttempt attempt, size_t capacity_limit) { const size_t size = allocation->size(); - if (available() < size) { + if (available(attempt, capacity_limit) < size) { // Out of memory return false; } - claim_from_cache_or_increase_capacity(allocation); + claim_from_cache_or_increase_capacity(allocation, attempt, capacity_limit); // Updated used statistics increase_used(size); @@ -761,12 +899,22 @@ bool ZPartition::claim_capacity(ZMemoryAllocation* allocation) { return true; } -bool ZPartition::claim_capacity_fast_medium(ZMemoryAllocation* allocation) { +bool ZPartition::claim_capacity_fast_medium(ZMemoryAllocation* allocation, size_t capacity_limit) { precond(ZPageSizeMediumEnabled); // Try to allocate a medium page sized contiguous vmem + const size_t available_from_cache_limit = available_from_cache(capacity_limit); + const size_t power_of_2_limit = available_from_cache_limit == 0 + ? 0 + : round_down_power_of_2(available_from_cache_limit); const size_t min_size = ZPageSizeMediumMin; - const size_t max_size = ZStressFastMediumPageAllocation ? min_size : ZPageSizeMediumMax; + + if (power_of_2_limit < min_size) { + // No medium allocation size available within the capacity limit + return false; + } + + const size_t max_size = MIN2(ZStressFastMediumPageAllocation ? min_size : ZPageSizeMediumMax, power_of_2_limit); ZVirtualMemory vmem = _cache.remove_contiguous_power_of_2(min_size, max_size); if (vmem.is_null()) { @@ -777,7 +925,7 @@ bool ZPartition::claim_capacity_fast_medium(ZMemoryAllocation* allocation) { // Found a satisfying vmem in the cache allocation->set_satisfied_from_cache_vmem_fast_medium(vmem); - // Associate the allocation with this partition. + // Associate the allocation with this partition allocation->set_partition(this); // Updated used statistics @@ -787,6 +935,87 @@ bool ZPartition::claim_capacity_fast_medium(ZMemoryAllocation* allocation) { return true; } +size_t ZPartition::increase_and_commit_capacity(size_t size, size_t capacity_limit) { + assert(Thread::current()->is_ConcurrentGC_thread(), "Should only be called by concurrent GC threads"); + + size_t commit_size; + { + ZLocker locker(lock()); + + // We are not increasing capacity, so exit early + if (capacity_limit <= _capacity) { + return 0; + } + + commit_size = increase_capacity(size, capacity_limit); + + if (commit_size == 0) { + return 0; + } + + // We account for memory that is about to be committed as claimed + increase_claimed(commit_size); + } + + ZArray vmems; + + const size_t claimed_virtual = claim_virtual(commit_size, &vmems); + + const bool depleted_virtual = claimed_virtual != commit_size; + + size_t total_committed = 0; + bool commit_failed = depleted_virtual; + ZArray to_free(vmems.length()); + for (ZVirtualMemory vmem : vmems) { + if (commit_failed) { + // If a commit has failed free any remaining vmems + free_virtual(vmem); + continue; + } + + // Claim physical + claim_physical(vmem); + + // Commit memory + const size_t committed = commit_physical(vmem); + + // Keep track of total committed + total_committed += committed; + + if (committed != vmem.size()) { + commit_failed = true; + const ZVirtualMemory not_committed_vmem = vmem.shrink_from_back(vmem.size() - committed); + free_physical(not_committed_vmem); + free_virtual(not_committed_vmem); + } + + if (vmem.size() != 0) { + // Map memory. Since this function is called from the ZMemoryWorker, + // we heat memory synchronously instead of sending a heating request. + map_virtual(vmem, false /* heat */); + + heat_memory(vmem); + + to_free.push(vmem); + } + } + + if (commit_failed) { + // A commit has failed + + ZLocker locker(lock()); + const size_t not_committed = commit_size - total_committed; + decrease_capacity(not_committed); + decrease_claimed(not_committed); + _page_allocator->truncate_heuristic_max_after_capacity_decrease(ZPageAllocator::TruncationReason::CommitFailure); + } + + // Free the memory, which puts it into the cache + _page_allocator->free_claimed_memory(&to_free); + + return total_committed; +} + void ZPartition::sort_segments_physical(const ZVirtualMemory& vmem) { verify_virtual_memory_association(vmem, true /* check_multi_partition */); @@ -820,7 +1049,9 @@ size_t ZPartition::commit_physical(const ZVirtualMemory& vmem) { ZPhysicalMemoryManager& manager = physical_memory_manager(); // Commit physical memory - return manager.commit(vmem, _numa_id); + const size_t committed = manager.commit(vmem, _numa_id); + + return committed; } size_t ZPartition::uncommit_physical(const ZVirtualMemory& vmem) { @@ -830,45 +1061,65 @@ size_t ZPartition::uncommit_physical(const ZVirtualMemory& vmem) { ZPhysicalMemoryManager& manager = physical_memory_manager(); // Uncommit physical memory - return manager.uncommit(vmem); -} + const size_t uncommitted = manager.uncommit(vmem); -void ZPartition::map_virtual(const ZVirtualMemory& vmem) { - verify_virtual_memory_association(vmem); - - ZPhysicalMemoryManager& manager = physical_memory_manager(); + return uncommitted; +} - // Map virtual memory to physical memory - manager.map(vmem, _numa_id); +void ZPartition::map_virtual(const ZVirtualMemory& vmem, bool heat_memory) { + map_virtual_inner(vmem, false /* multi_partition */, false /* sort_segments */, heat_memory); } void ZPartition::unmap_virtual(const ZVirtualMemory& vmem) { - verify_virtual_memory_association(vmem); + unmap_virtual_inner(vmem, false /* multi_partition */); +} - ZPhysicalMemoryManager& manager = physical_memory_manager(); +void ZPartition::map_virtual_from_multi_partition(const ZVirtualMemory& vmem) { + map_virtual_inner(vmem, true /* multi_partition */, true /* sort_segments */, true /* register_heating */); +} - // Unmap virtual memory from physical memory - manager.unmap(vmem); +void ZPartition::unmap_virtual_from_multi_partition(const ZVirtualMemory& vmem) { + unmap_virtual_inner(vmem, true /* multi_partition */); } -void ZPartition::map_virtual_from_multi_partition(const ZVirtualMemory& vmem) { - verify_virtual_memory_multi_partition_association(vmem); +void ZPartition::map_virtual_inner(const ZVirtualMemory& vmem, bool multi_partition, bool sort_segments, bool register_heating) { + if (multi_partition) { + verify_virtual_memory_multi_partition_association(vmem); + } else { + verify_virtual_memory_association(vmem); + } ZPhysicalMemoryManager& manager = physical_memory_manager(); - // Sort physical segments - manager.sort_segments_physical(vmem); + if (sort_segments) { + // Sort physical segments before mapping multi-partition memory. + manager.sort_segments_physical(vmem); + } - // Map virtual memory to physical memory + // Map virtual memory to physical memory. manager.map(vmem, _numa_id); + + if (register_heating && ZMemoryHeating) { + // Register a heating request for this mapping. + _mem_worker.register_heating_request(vmem); + } } -void ZPartition::unmap_virtual_from_multi_partition(const ZVirtualMemory& vmem) { - verify_virtual_memory_multi_partition_association(vmem); +void ZPartition::unmap_virtual_inner(const ZVirtualMemory& vmem, bool multi_partition) { + if (multi_partition) { + verify_virtual_memory_multi_partition_association(vmem); + } else { + verify_virtual_memory_association(vmem); + } + + if (ZMemoryHeating) { + // Remove any heating request before unmapping. + _mem_worker.remove_heating_request(vmem); + } ZPhysicalMemoryManager& manager = physical_memory_manager(); - // Unmap virtual memory from physical memory + // Unmap virtual memory from physical memory. manager.unmap(vmem); } @@ -911,22 +1162,16 @@ ZVirtualMemory ZPartition::free_and_claim_virtual_from_low_exact_or_many(size_t return manager.insert_and_remove_from_low_exact_or_many(size, _numa_id, vmems_in_out); } -static void pretouch_memory(zoffset start, size_t size) { - // At this point we know that we have a valid zoffset / zaddress. - const zaddress zaddr = ZOffset::address(start); - const uintptr_t addr = untype(zaddr); - const size_t page_size = ZLargePages::is_explicit() ? ZGranuleSize : os::vm_page_size(); - os::pretouch_memory((void*)addr, (void*)(addr + size), page_size); -} - -class ZPreTouchTask : public ZTask { +class ZPreHeatTask : public ZTask { private: + ZPartition* const _partition; volatile uintptr_t _current; const uintptr_t _end; public: - ZPreTouchTask(zoffset start, zoffset_end end) - : ZTask("ZPreTouchTask"), + ZPreHeatTask(ZPartition* partition, zoffset start, zoffset_end end) + : ZTask("ZPreHeatTask", false /* account_vtime */), + _partition(partition), _current(untype(start)), _end(untype(end)) {} @@ -944,13 +1189,13 @@ class ZPreTouchTask : public ZTask { // At this point we know that we have a valid zoffset / zaddress. const zoffset offset = to_zoffset(claimed); - // Pre-touch the granule - pretouch_memory(offset, size); + // Pre-heat the granule + _partition->heat_memory(ZVirtualMemory(offset, size)); } } }; -bool ZPartition::prime(ZWorkers* workers, size_t size) { +bool ZPartition::prime(ZWorkers* workers, size_t size, size_t capacity_limit) { if (size == 0) { return true; } @@ -964,7 +1209,7 @@ bool ZPartition::prime(ZWorkers* workers, size_t size) { assert(claimed_size == size, "must succeed %zx == %zx", claimed_size, size); // Increase capacity - increase_capacity(claimed_size); + increase_capacity(claimed_size, capacity_limit); for (ZVirtualMemory vmem : vmems) { // Claim the backing physical memory @@ -978,13 +1223,17 @@ bool ZPartition::prime(ZWorkers* workers, size_t size) { return false; } - map_virtual(vmem); + // The primed memory should be "heated" asynchronously if it shouldn't be + // pre-touched up front during startup. + const bool async_heat = !AlwaysPreTouch; + + map_virtual(vmem, async_heat); check_numa_mismatch(vmem, _numa_id); - if (AlwaysPreTouch) { - // Pre-touch memory - ZPreTouchTask task(vmem.start(), vmem.end()); + if (!async_heat) { + // Pre-heat memory synchronously + ZPreHeatTask task(this, vmem.start(), vmem.end()); workers->run_all(&task); } @@ -1055,15 +1304,21 @@ void ZPartition::copy_physical_segments_from_partition(const ZVirtualMemory& at, manager.copy_physical_segments(to, at); } -void ZPartition::commit_increased_capacity(ZMemoryAllocation* allocation, const ZVirtualMemory& vmem) { +void ZPartition::commit_increased_capacity(ZMemoryAllocation* allocation, const ZVirtualMemory& vmem, size_t allowed_to_commit) { assert(allocation->increased_capacity() > 0, "Nothing to commit"); + assert(allowed_to_commit > 0, "Not allowed to commit anything"); const size_t already_committed = allocation->harvested(); - const ZVirtualMemory to_be_committed_vmem = vmem.last_part(already_committed); + assert(to_be_committed_vmem.size() == allocation->increased_capacity(), "must be the same size"); + + const size_t should_commit = MIN2(to_be_committed_vmem.size(), allowed_to_commit); + assert(should_commit > 0, "There must be something to commit"); - // Try to commit the uncommitted physical memory - const size_t committed = commit_physical(to_be_committed_vmem); + const ZVirtualMemory should_commit_vmem = to_be_committed_vmem.first_part(should_commit); + + // Try to commit the not-committed physical memory + const size_t committed = commit_physical(should_commit_vmem); // Keep track of the committed amount allocation->set_committed_capacity(committed); @@ -1096,20 +1351,25 @@ void ZPartition::free_memory_alloc_failed(ZMemoryAllocation* allocation) { // Adjust capacity to reflect the failed capacity increase const size_t remaining = allocation->size() - freed; if (remaining > 0) { - const bool set_max_capacity = allocation->commit_failed(); - decrease_capacity(remaining, set_max_capacity); + decrease_capacity(remaining); } } void ZPartition::threads_do(ThreadClosure* tc) const { - tc->do_thread(const_cast(&_uncommitter)); + if (ZUncommitter::is_enabled()) { + tc->do_thread(const_cast(&_uncommitter)); + } + + if (ZMemoryWorker::is_enabled()) { + tc->do_thread(const_cast(&_mem_worker)); + } } void ZPartition::print_on(outputStream* st) const { st->print("Partition %u ", _numa_id); st->fill_to(17); st->print_cr("used %zuM, capacity %zuM, max capacity %zuM", - _used / M, _capacity / M, _max_capacity / M); + _used / M, _capacity / M, dynamic_max_capacity() / M); StreamIndentor si(st, 1); print_cache_on(st); @@ -1204,19 +1464,20 @@ class ZMultiPartitionTracker : CHeapObj { } }; -ZPageAllocator::ZPageAllocator(size_t min_capacity, +ZPageAllocator::ZPageAllocator(size_t static_min_capacity, size_t initial_capacity, size_t soft_max_capacity, - size_t max_capacity) + size_t static_max_capacity) : _lock(), - _virtual(max_capacity), - _physical(max_capacity), - _min_capacity(min_capacity), - _max_capacity(max_capacity), + _virtual(static_max_capacity), + _physical(static_min_capacity, static_max_capacity), + _static_min_capacity(static_min_capacity), + _static_max_capacity(static_max_capacity), + _heuristic_max_capacity(ZAdaptiveHeapSizing ? initial_capacity : static_max_capacity), _used(0), _used_generations{0,0}, _collection_stats{{0, 0},{0, 0}}, - _partitions(ZValueIdTagType{}, this), + _partitions(ZValueIdTagType{}, this, static_min_capacity, static_max_capacity), _stalled(), _safe_destroy(), _initialized(false) { @@ -1225,10 +1486,12 @@ ZPageAllocator::ZPageAllocator(size_t min_capacity, return; } - log_info_p(gc, init)("Min Capacity: %zuM", min_capacity / M); + log_info_p(gc, init)("Min Capacity: %zuM", static_min_capacity / M); log_info_p(gc, init)("Initial Capacity: %zuM", initial_capacity / M); - log_info_p(gc, init)("Max Capacity: %zuM", max_capacity / M); - log_info_p(gc, init)("Soft Max Capacity: %zuM", soft_max_capacity / M); + log_info_p(gc, init)("Max Capacity: %zuM", static_max_capacity / M); + if (soft_max_capacity != 0) { + log_info_p(gc, init)("Soft Max Capacity: %zuM", soft_max_capacity / M); + } if (ZPageSizeMediumEnabled) { if (ZPageSizeMediumMin == ZPageSizeMediumMax) { log_info_p(gc, init)("Page Size Medium: %zuM", ZPageSizeMediumMax / M); @@ -1239,12 +1502,15 @@ ZPageAllocator::ZPageAllocator(size_t min_capacity, log_info_p(gc, init)("Medium Page Size: N/A"); } log_info_p(gc, init)("Pre-touch: %s", AlwaysPreTouch ? "Enabled" : "Disabled"); + ZAdaptiveHeap::print(); + log_info_p(gc, init)("Memory Heating: %s", ZMemoryHeating ? "Enabled" : "Disabled"); // Warn if system limits could stop us from reaching max capacity - _physical.warn_commit_limits(max_capacity); + const size_t expected_capacity = ZAdaptiveHeap::explicit_max_capacity() + ? static_max_capacity + : initial_capacity; - // Check if uncommit should and can be enabled - _physical.try_enable_uncommit(min_capacity, max_capacity); + _physical.warn_commit_limits(expected_capacity, static_max_capacity); // Successfully initialized _initialized = true; @@ -1259,8 +1525,9 @@ bool ZPageAllocator::prime_cache(ZWorkers* workers, size_t size) { for (ZPartition* partition; iter.next(&partition);) { const uint32_t numa_id = partition->numa_id(); const size_t to_prime = ZNUMA::calculate_share(numa_id, size); + const size_t partition_limit = ZNUMA::calculate_share(numa_id, _static_max_capacity); - if (!partition->prime(workers, to_prime)) { + if (!partition->prime(workers, to_prime, partition_limit)) { return false; } } @@ -1268,29 +1535,147 @@ bool ZPageAllocator::prime_cache(ZWorkers* workers, size_t size) { return true; } -size_t ZPageAllocator::min_capacity() const { - return _min_capacity; +size_t ZPageAllocator::static_min_capacity() const { + return _static_min_capacity; } -size_t ZPageAllocator::max_capacity() const { - return _max_capacity; +size_t ZPageAllocator::static_max_capacity() const { + return _static_max_capacity; } -size_t ZPageAllocator::soft_max_capacity() const { - const size_t current_max_capacity = ZPageAllocator::current_max_capacity(); - const size_t soft_max_heapsize = AtomicAccess::load(&SoftMaxHeapSize); - return MIN2(soft_max_heapsize, current_max_capacity); +size_t ZPageAllocator::dynamic_max_capacity() const { + if (!ZAdaptiveHeapSizing) { + return _static_max_capacity; + } + + physical_memory_size_type result = ZAdaptiveHeap::machine_physical_memory(); + + if (!os::is_containerized()) { + return clamp(align_down(size_t(result), ZGranuleSize), _static_min_capacity, _static_max_capacity); + } + + physical_memory_size_type hard_container_limit; + if (os::Container::memory_limit(hard_container_limit)) { + result = MIN2(result, hard_container_limit); + } + + physical_memory_size_type throttle_container_limit; + if (os::Container::memory_throttle_limit(throttle_container_limit)) { + result = MIN2(result, throttle_container_limit); + } + + return clamp(align_down(size_t(result), ZGranuleSize), _static_min_capacity, _static_max_capacity); +} + +static size_t calculate_system_max_capacity(size_t system_used, + size_t system_max, + size_t capacity, + size_t min_capacity, + size_t max_capacity) { + // It is a bit naive to assume all available memory can be directly turned + // into our own heap memory. We need auxiliary GC data structures, and other + // processes can also take the memory as we might not be alone. By scaling + // the available memory we stay on the pessimistic size, and let the estimated + // current max capacity grow gradually as we approach the limits instead. + const double near_avoid = (1.0 - ZMemoryCriticalThreshold); + const size_t scaled_system_max = size_t(double(system_max) * near_avoid); + size_t result; + + // Calculate capacity limits based on availability + if (system_used > scaled_system_max) { + const size_t overcommitted = system_used - scaled_system_max; + + if (overcommitted > capacity) { + return min_capacity; + } + + result = capacity - overcommitted; + } else { + const double system_available = double(scaled_system_max - system_used); + const size_t scaled_system_available = size_t(system_available * near_avoid); + + result = capacity + scaled_system_available; + } + + // Clamp and align the max capacity by its legal memory bounds + return clamp(align_down(result, ZGranuleSize), min_capacity, max_capacity); } size_t ZPageAllocator::current_max_capacity() const { - size_t current_max_capacity = 0; + if (!ZAdaptiveHeapSizing) { + // When not adapting use supplied max capacity + return _static_max_capacity; + } - ZPartitionConstIterator iter = partition_iterator(); - for (const ZPartition* partition; iter.next(&partition);) { - current_max_capacity += AtomicAccess::load(&partition->_current_max_capacity); + ZMachineMemoryInfo machine_memory_info = ZAdaptiveHeap::machine_memory_info(); + if (!machine_memory_info._is_valid) { + return dynamic_max_capacity(); + } + + const size_t machine_used_memory = size_t(machine_memory_info._physical_memory - machine_memory_info._available_memory); + const size_t machine_max_memory = size_t(machine_memory_info._physical_memory); + + const size_t cap = capacity(); + + const size_t machine_max_capacity = calculate_system_max_capacity(machine_used_memory, + machine_max_memory, + cap, + _static_min_capacity, + _static_max_capacity); + + if (!os::is_containerized()) { + return machine_max_capacity; + } + + physical_memory_size_type container_max_memory; + + // Keep below the hard memory limit or the OOM killer will get us + if (!os::Container::memory_limit(container_max_memory)) { + container_max_memory = machine_max_memory; + } + + // Avoid allocating past the throttle limit; the app will become useless here + physical_memory_size_type container_high_memory; + if (os::Container::memory_throttle_limit(container_high_memory)) { + container_max_memory = MIN2(container_max_memory, container_high_memory); + } + + physical_memory_size_type container_used_memory; + + if (!os::Container::used_memory(container_used_memory)) { + // If we can't measure memory usage in the container, we also can not compare + // it to any container limits. Effectively, the JVM is not appropriately + // containerized, so use machine max capacity. + return machine_max_capacity; + } + + const size_t container_max_capacity = calculate_system_max_capacity(size_t(container_used_memory), + container_max_memory, + cap, + _static_min_capacity, + _static_max_capacity); + + return MIN2(machine_max_capacity, container_max_capacity); +} + +static size_t clamp_to_soft_max_capacity(size_t value) { + assert(is_aligned(value, ZGranuleSize), "Must be granule aligned: 0x%08zx", value); + // Note that SoftMaxHeapSize is a manageable flag + const size_t soft_max_capacity = AtomicAccess::load(&SoftMaxHeapSize); + + if (soft_max_capacity == 0) { + // SoftMaxHeapSize is disabled / unspecified + return value; } - return current_max_capacity; + // Clamp to SoftMaxHeapSize, but should not be less than MinHeapSize + return clamp(value, MinHeapSize, MAX2(MinHeapSize, align_down(soft_max_capacity, ZGranuleSize))); +} + +size_t ZPageAllocator::heuristic_max_capacity() const { + const size_t heuristic_max_capacity = _heuristic_max_capacity.load_relaxed(); + + return clamp_to_soft_max_capacity(heuristic_max_capacity); } size_t ZPageAllocator::capacity() const { @@ -1327,6 +1712,166 @@ size_t ZPageAllocator::unused() const { return unused > 0 ? (size_t)unused : 0; } +void ZPageAllocator::shrink_heuristic_max(size_t shrink_amount) { + for (;;) { + const size_t heuristic_max = _heuristic_max_capacity.load_relaxed(); + const size_t capacity = MAX2(heuristic_max - MIN2(shrink_amount, heuristic_max), ZGranuleSize); + + if (heuristic_max <= capacity) { + return; + } + + if (_heuristic_max_capacity.compare_exchange(heuristic_max, capacity) == heuristic_max) { + break; + } + } +} + +void ZPageAllocator::truncate_heuristic_max_after_capacity_decrease(TruncationReason reason) { + // Adjust heuristic max capacity to ensure GC tries to keep below current capacity + const size_t capacity = ZPageAllocator::capacity(); + + for (;;) { + const size_t heuristic_max = _heuristic_max_capacity.load_relaxed(); + if (heuristic_max <= capacity) { + return; + } + if (_heuristic_max_capacity.compare_exchange(heuristic_max, capacity) != heuristic_max) { + continue; + } + const size_t current_max = current_max_capacity(); + if (reason == TruncationReason::CommitFailure) { + log_debug(gc)("Forced to lower heap size from " + "%zuM(%.0f%%) to %zuM(%.0f%%)", + heuristic_max / M, percent_of(heuristic_max, current_max), + capacity / M, percent_of(capacity, current_max)); + } + + if (ZAdaptiveHeapSizing) { + heap_truncated(capacity, reason); + } + return; + } +} + +void ZPageAllocator::adapt_heuristic_max_capacity(ZGenerationId generation) { + const size_t heuristic_max = heuristic_max_capacity(); + const size_t static_min_capacity = _static_min_capacity; + const size_t used = ZPageAllocator::used(); + const size_t capacity = MAX2(ZPageAllocator::capacity(), used); + const size_t curr_max_capacity = MAX2(capacity, current_max_capacity()); + const size_t highest_soft_capacity = clamp_to_soft_max_capacity(curr_max_capacity); + + const double alloc_rate = ZStatMutatorAllocRate::stats()._avg; + + const ZHeapResizeMetrics metrics = { + highest_soft_capacity, + curr_max_capacity, + heuristic_max, + static_min_capacity, + capacity, + used, + alloc_rate + }; + + const size_t selected_capacity = ZAdaptiveHeap::compute_heap_size(metrics, generation); + + // Update heuristic max capacity + _heuristic_max_capacity.store_relaxed(selected_capacity); + + heap_resized(selected_capacity); +} + +void ZPageAllocator::heap_resized(size_t selected_capacity) { + precond(ZAdaptiveHeapSizing); + + // Update per partition heuristic max capacity + ZPerNUMAIterator iter = partition_iterator(); + for (ZPartition* partition; iter.next(&partition);) { + ZMemoryWorker& memory_worker = partition->memory_worker(); + const uint32_t numa_id = partition->numa_id(); + + const size_t heuristic_max_capacity = ZNUMA::calculate_share(numa_id, selected_capacity); + const size_t capacity = partition->capacity(); + + if (capacity < heuristic_max_capacity) { + // Grow the heap + memory_worker.request_grow_capacity(heuristic_max_capacity); + } else { + // Consider shrinking the heap + + // Set up direct uncommit to shrink the heap + const size_t surplus_capacity = capacity - heuristic_max_capacity; + + // Uncommit 5% of the surplus at a time for a smooth capacity decline + const size_t uncommit_fraction = 20; + const size_t uncommit_request = align_up(surplus_capacity / uncommit_fraction, ZGranuleSize); + + const size_t requested_capacity = capacity - uncommit_request; + + // If the surplus capacity isn't over 5% of the capacity, the point of + // uncommitting heuristically seems questionable and might just cause + // pointless fluctuation. + if (surplus_capacity > capacity / uncommit_fraction) { + // Update memory worker target capacity + memory_worker.request_shrink_capacity(requested_capacity); + } else { + memory_worker.stop_heap_resizing(); + } + } + } + + // Complain about misconfigurations + _physical.warn_commit_limits(selected_capacity, dynamic_max_capacity()); +} + +void ZPageAllocator::heap_truncated(size_t selected_capacity, ZPageAllocator::TruncationReason reason) { + precond(ZAdaptiveHeapSizing); + + if (reason == TruncationReason::Uncommit) { + // Uncommit heap truncation is part of heap resizing. + return; + } + assert(reason == TruncationReason::CommitFailure, "Unexpected: %d", (int)reason); + + ZPerNUMAIterator iter = partition_iterator(); + for (ZPartition* partition; iter.next(&partition);) { + // Update memory worker target capacity + ZMemoryWorker& mem_worker = partition->memory_worker(); + mem_worker.stop_heap_resizing(); + } + + // Complain about misconfigurations + _physical.warn_commit_limits(selected_capacity, dynamic_max_capacity()); +} + +void ZPageAllocator::adjust_capacity(size_t used_soon) { + const ZMemoryPressureMetrics mem_metrics = ZAdaptiveHeap::memory_pressure_metrics(); + ZStatSystemMemoryUsage::record(mem_metrics); + + ZPerNUMAIterator iter = partition_iterator(); + + for (ZPartition* partition; iter.next(&partition);) { + ZMemoryWorker& mem_worker = partition->memory_worker(); + const size_t partition_capacity = partition->capacity(); + + if (ZAdaptiveHeap::is_memory_pressure_high(mem_metrics)) { + // When memory usage is high, request uncommitting if possible + mem_worker.request_shrink_capacity_granule(); + } else { + // When memory pressure is not high, stop targetless uncommit. + mem_worker.stop_shrink_capacity_granule(); + + // When memory pressure is not high, try to commit memory ahead of mutators. + const uint32_t numa_id = partition->numa_id(); + const size_t used_soon_share = ZNUMA::calculate_share(numa_id, used_soon); + if (used_soon_share > partition_capacity) { + mem_worker.request_grow_capacity(used_soon_share); + } + } + } +} + void ZPageAllocator::update_collection_stats(ZGenerationId id) { assert(SafepointSynchronize::is_at_safepoint(), "Should be at safepoint"); @@ -1346,9 +1891,7 @@ void ZPageAllocator::update_collection_stats(ZGenerationId id) { } ZPageAllocatorStats ZPageAllocator::stats_inner(ZGeneration* generation) const { - return ZPageAllocatorStats(_min_capacity, - _max_capacity, - soft_max_capacity(), + return ZPageAllocatorStats(heuristic_max_capacity(), capacity(), _used, _collection_stats[(int)generation->id()]._used_high, @@ -1404,8 +1947,9 @@ ZPage* ZPageAllocator::alloc_page(ZPageType type, size_t size, ZAllocationFlags ZPageAllocation allocation(type, size, flags, age, preferred_partition); // Allocate the page - ZPage* const page = alloc_page_inner(&allocation); + ZPage* const page = alloc_page_inner(&allocation, ZPageAllocationAttempt::initial); if (page == nullptr) { + // Out of memory return nullptr; } @@ -1464,9 +2008,7 @@ bool ZPageAllocator::alloc_page_stall(ZPageAllocation* allocation) { return result; } -ZPage* ZPageAllocator::alloc_page_inner(ZPageAllocation* allocation) { -retry: - +ZPage* ZPageAllocator::alloc_page_inner(ZPageAllocation* allocation, ZPageAllocationAttempt attempt) { // Claim the capacity needed for this allocation. // // The claimed capacity comes from memory already mapped in the cache, or @@ -1475,7 +2017,7 @@ ZPage* ZPageAllocator::alloc_page_inner(ZPageAllocation* allocation) { // // Note that this call might block in a safepoint if the non-blocking flag is // not set. - if (!claim_capacity_or_stall(allocation)) { + if (!claim_capacity_or_stall(allocation, &attempt)) { // Out of memory return nullptr; } @@ -1498,7 +2040,6 @@ ZPage* ZPageAllocator::alloc_page_inner(ZPageAllocation* allocation) { // Crash in debug builds for more information DEBUG_ONLY(fatal("Out of address space");) - return nullptr; } @@ -1509,18 +2050,19 @@ ZPage* ZPageAllocator::alloc_page_inner(ZPageAllocation* allocation) { // Commit memory for the increased capacity and map the entire vmem. if (!commit_and_map(allocation, vmem)) { free_after_alloc_page_failed(allocation); - goto retry; + assert(attempt != ZPageAllocationAttempt::retry, "Should be initial or stall"); + return alloc_page_inner(allocation, ZPageAllocationAttempt::retry); } return create_page(allocation, vmem); } -bool ZPageAllocator::claim_capacity_or_stall(ZPageAllocation* allocation) { +bool ZPageAllocator::claim_capacity_or_stall(ZPageAllocation* allocation, ZPageAllocationAttempt* attempt) { { ZLocker locker(&_lock); // Try to claim memory - if (claim_capacity(allocation)) { + if (claim_capacity(allocation, *attempt)) { // Keep track of usage increase_used(allocation->size()); @@ -1537,31 +2079,57 @@ bool ZPageAllocator::claim_capacity_or_stall(ZPageAllocation* allocation) { _stalled.insert_last(allocation); } + // We are stalling on this allocation + *attempt = ZPageAllocationAttempt::stall; + // Stall return alloc_page_stall(allocation); } -bool ZPageAllocator::claim_capacity(ZPageAllocation* allocation) { - // Fast medium allocation - if (allocation->flags().fast_medium()) { - return claim_capacity_fast_medium(allocation); - } - - // Round robin single-partition claiming - const uint32_t start_numa_id = allocation->preferred_partition(); - const uint32_t start_partition = start_numa_id; +bool ZPageAllocator::claim_capacity(ZPageAllocation* allocation, ZPageAllocationAttempt attempt) { + ZSinglePartitionAllocation* const single_partition_allocation = allocation->single_partition_allocation(); + const bool is_fast_medium = allocation->flags().fast_medium(); + const uint32_t start_partition = allocation->preferred_partition(); const uint32_t num_partitions = _partitions.count(); + // Round robin soft single-partition claiming + const size_t soft_limit = heuristic_max_capacity(); + + uint32_t highest_available_id = start_partition; + size_t highest_availiable = 0; + for (uint32_t i = 0; i < num_partitions; ++i) { const uint32_t partition_id = (start_partition + i) % num_partitions; + const size_t soft_partition_limit = ZNUMA::calculate_share(partition_id, soft_limit); - if (claim_capacity_single_partition(allocation->single_partition_allocation(), partition_id)) { + const bool claim_result = is_fast_medium + ? claim_capacity_single_partition_fast_medium(single_partition_allocation, partition_id, soft_partition_limit) + : claim_capacity_single_partition(single_partition_allocation, partition_id, attempt, soft_partition_limit); + + if (claim_result) { return true; } + + const ZPartition& partition = _partitions.get(partition_id); + const size_t partition_availiable = partition.available(attempt, partition.static_max_capacity()); + if (partition_availiable > highest_availiable) { + highest_available_id = partition_id; + highest_availiable = partition_availiable; + } + } + + // Hard single-partition claiming - only try from the highest availible partition + const size_t hard_partition_limit = _partitions.get(highest_available_id).static_max_capacity(); + const bool claim_result = is_fast_medium + ? claim_capacity_single_partition_fast_medium(single_partition_allocation, highest_available_id, hard_partition_limit) + : claim_capacity_single_partition(single_partition_allocation, highest_available_id, attempt, hard_partition_limit); + + if (claim_result) { + return true; } - if (!is_multi_partition_allowed(allocation)) { - // Multi-partition claiming is not possible + if (is_fast_medium || !is_multi_partition_allowed(allocation, attempt, _static_max_capacity)) { + // Multi-partition claiming is not allowed or not possible return false; } @@ -1572,35 +2140,24 @@ bool ZPageAllocator::claim_capacity(ZPageAllocation* allocation) { ZMultiPartitionAllocation* const multi_partition_allocation = allocation->multi_partition_allocation(); - claim_capacity_multi_partition(multi_partition_allocation, start_partition); + claim_capacity_multi_partition(multi_partition_allocation, highest_available_id, attempt); return true; } -bool ZPageAllocator::claim_capacity_fast_medium(ZPageAllocation* allocation) { - const uint32_t start_node = allocation->preferred_partition(); - const uint32_t numa_nodes = ZNUMA::count(); - - for (uint32_t i = 0; i < numa_nodes; ++i) { - const uint32_t numa_id = (start_node + i) % numa_nodes; - ZPartition& partition = _partitions.get(numa_id); - ZSinglePartitionAllocation* single_partition_allocation = allocation->single_partition_allocation(); - - if (partition.claim_capacity_fast_medium(single_partition_allocation->allocation())) { - return true; - } - } +bool ZPageAllocator::claim_capacity_single_partition_fast_medium(ZSinglePartitionAllocation* single_partition_allocation, uint32_t partition_id, size_t capacity_limit) { + ZPartition& partition = _partitions.get(partition_id); - return false; + return partition.claim_capacity_fast_medium(single_partition_allocation->allocation(), capacity_limit); } -bool ZPageAllocator::claim_capacity_single_partition(ZSinglePartitionAllocation* single_partition_allocation, uint32_t partition_id) { +bool ZPageAllocator::claim_capacity_single_partition(ZSinglePartitionAllocation* single_partition_allocation, uint32_t partition_id, ZPageAllocationAttempt attempt, size_t capacity_limit) { ZPartition& partition = _partitions.get(partition_id); - return partition.claim_capacity(single_partition_allocation->allocation()); + return partition.claim_capacity(single_partition_allocation->allocation(), attempt, capacity_limit); } -void ZPageAllocator::claim_capacity_multi_partition(ZMultiPartitionAllocation* multi_partition_allocation, uint32_t start_partition) { +void ZPageAllocator::claim_capacity_multi_partition(ZMultiPartitionAllocation* multi_partition_allocation, uint32_t start_partition, ZPageAllocationAttempt attempt) { const size_t size = multi_partition_allocation->size(); const uint32_t num_partitions = _partitions.count(); const size_t split_size = align_up(size / num_partitions, ZGranuleSize); @@ -1613,10 +2170,13 @@ void ZPageAllocator::claim_capacity_multi_partition(ZMultiPartitionAllocation* m return false; } + // Multi partition allocation, try to split evenly but at most static_max_capacity + const size_t capacity_limit = partition.static_max_capacity(); + const size_t max_alloc_size = claim_evenly ? MIN2(split_size, remaining) : remaining; // This guarantees that claim_physical below will succeed - const size_t alloc_size = MIN2(max_alloc_size, partition.available()); + const size_t alloc_size = MIN2(max_alloc_size, partition.available(attempt, capacity_limit)); // Skip over empty allocations if (alloc_size == 0) { @@ -1627,7 +2187,7 @@ void ZPageAllocator::claim_capacity_multi_partition(ZMultiPartitionAllocation* m ZMemoryAllocation partial_allocation(alloc_size); // Claim capacity for this allocation - this should succeed - const bool result = partition.claim_capacity(&partial_allocation); + const bool result = partition.claim_capacity(&partial_allocation, attempt, capacity_limit); assert(result, "Should have succeeded"); // Register allocation @@ -1824,12 +2384,37 @@ bool ZPageAllocator::commit_and_map_multi_partition(ZMultiPartitionAllocation* m return false; } +size_t ZPageAllocator::allowed_to_commit(size_t about_to_commit) { + // We only allow committing up to the current max capacity. Even if capacity + // may have been allowed to be increased on individual partitions, we may + // disallow that capacity to be committed if it turns out that it will put + // too much strain on the system. + const size_t current_max = current_max_capacity(); + const size_t capacity = ZPageAllocator::capacity(); + + // Capacity already accounts for the increase as part of this allocation, + // subtract it to get the already committed capacity. + const size_t committed_capacity = capacity - about_to_commit; + + if (committed_capacity >= current_max) { + return 0; + } + + return current_max - committed_capacity; +} + void ZPageAllocator::commit(ZMemoryAllocation* allocation, const ZVirtualMemory& vmem) { ZPartition& partition = allocation->partition(); + const size_t allowed_to_commit = ZPageAllocator::allowed_to_commit(allocation->increased_capacity()); - if (allocation->increased_capacity() > 0) { + // If we both have something to commit and we're allowed to commit something, + // then try to commit. + if (allocation->increased_capacity() > 0 && allowed_to_commit > 0) { // Commit memory - partition.commit_increased_capacity(allocation, vmem); + partition.commit_increased_capacity(allocation, vmem, allowed_to_commit); + } else { + // Record nothing to commit + allocation->set_committed_capacity(0); } } @@ -1878,10 +2463,10 @@ void ZPageAllocator::map_committed_single_partition(ZSinglePartitionAllocation* ZPartition& partition = allocation->partition(); const size_t total_committed = allocation->harvested() + allocation->committed_capacity(); - const ZVirtualMemory total_committed_vmem = vmem.first_part(total_committed); - if (total_committed_vmem.size() > 0) { + if (total_committed > 0) { // Map all the committed memory + const ZVirtualMemory total_committed_vmem = vmem.first_part(total_committed); partition.map_memory(allocation, total_committed_vmem); } } @@ -2005,6 +2590,9 @@ void ZPageAllocator::free_after_alloc_page_failed(ZPageAllocation* allocation) { // Free memory free_memory_alloc_failed(allocation); + // Try not to commit too much again + truncate_heuristic_max_after_capacity_decrease(TruncationReason::CommitFailure); + // Keep track of usage decrease_used(allocation->size()); @@ -2016,23 +2604,11 @@ void ZPageAllocator::free_after_alloc_page_failed(ZPageAllocation* allocation) { } void ZPageAllocator::free_memory_alloc_failed(ZPageAllocation* allocation) { - // The current max capacity may be decreased, store the value before freeing memory - const size_t current_max_capacity_before = current_max_capacity(); - if (allocation->is_multi_partition()) { free_memory_alloc_failed_multi_partition(allocation->multi_partition_allocation()); } else { free_memory_alloc_failed_single_partition(allocation->single_partition_allocation()); } - - const size_t current_max_capacity_after = current_max_capacity(); - - if (current_max_capacity_before != current_max_capacity_after) { - log_error_p(gc)("Forced to lower max Java heap size from " - "%zuM(%.0f%%) to %zuM(%.0f%%)", - current_max_capacity_before / M, percent_of(current_max_capacity_before, _max_capacity), - current_max_capacity_after / M, percent_of(current_max_capacity_after, _max_capacity)); - } } void ZPageAllocator::free_memory_alloc_failed_single_partition(ZSinglePartitionAllocation* single_partition_allocation) { @@ -2146,15 +2722,15 @@ void ZPageAllocator::remap_and_defragment(const ZVirtualMemory& vmem, ZArray* vmems) { +void ZPageAllocator::free_used_memory(ZArray* vmems) { ZLocker locker(&_lock); // Free the vmems for (const ZVirtualMemory vmem : *vmems) { ZPartition& partition = partition_from_vmem(vmem); - // Free the vmem - partition.free_memory(vmem); + // Free the used vmem + partition.free_used_memory(vmem); // Keep track of usage decrease_used(vmem.size()); @@ -2164,6 +2740,21 @@ void ZPageAllocator::free_memory(ZArray* vmems) { satisfy_stalled(); } +void ZPageAllocator::free_claimed_memory(ZArray* vmems) { + ZLocker locker(&_lock); + + // Free the vmems + for (const ZVirtualMemory vmem : *vmems) { + ZPartition& partition = partition_from_vmem(vmem); + + // Free the claimed vmem + partition.free_claimed_memory(vmem); + } + + // Try satisfy stalled allocations + satisfy_stalled(); +} + void ZPageAllocator::satisfy_stalled() { for (;;) { ZPageAllocation* const allocation = _stalled.first(); @@ -2172,7 +2763,7 @@ void ZPageAllocator::satisfy_stalled() { return; } - if (!claim_capacity(allocation)) { + if (!claim_capacity(allocation, ZPageAllocationAttempt::stall)) { // Allocation could not be satisfied, give up return; } @@ -2192,10 +2783,10 @@ bool ZPageAllocator::is_multi_partition_enabled() const { return _virtual.is_multi_partition_enabled(); } -bool ZPageAllocator::is_multi_partition_allowed(const ZPageAllocation* allocation) const { +bool ZPageAllocator::is_multi_partition_allowed(const ZPageAllocation* allocation, ZPageAllocationAttempt attempt, size_t total_capacity_limit) const { return is_multi_partition_enabled() && allocation->type() == ZPageType::large && - allocation->size() <= sum_available(); + allocation->size() <= sum_available(attempt, total_capacity_limit); } const ZPartition& ZPageAllocator::partition_from_partition_id(uint32_t numa_id) const { @@ -2210,12 +2801,18 @@ ZPartition& ZPageAllocator::partition_from_vmem(const ZVirtualMemory& vmem) { return partition_from_partition_id(_virtual.lookup_partition_id(vmem)); } -size_t ZPageAllocator::sum_available() const { +size_t ZPageAllocator::sum_available(ZPageAllocationAttempt attempt, size_t total_capacity_limit) const { size_t total = 0; ZPartitionConstIterator iter = partition_iterator(); for (const ZPartition* partition; iter.next(&partition);) { - total += partition->available(); + if (total_capacity_limit <= total) { + // The limit smaller than the total, we will have + return total_capacity_limit; + } + + const size_t partition_capacity_limit = MIN2(partition->static_max_capacity(), total_capacity_limit - total); + total += partition->available(attempt, partition_capacity_limit); } return total; @@ -2263,7 +2860,7 @@ void ZPageAllocator::free_page(ZPage* page) { decrease_used_generation(id, size); // Free the extracted vmems - free_memory(&vmems); + free_used_memory(&vmems); } void ZPageAllocator::free_pages(ZGenerationId id, const ZArray* pages) { @@ -2281,7 +2878,7 @@ void ZPageAllocator::free_pages(ZGenerationId id, const ZArray* pages) { } // Free the extracted vmems - free_memory(&vmems); + free_used_memory(&vmems); } void ZPageAllocator::enable_safe_destroy() const { @@ -2411,7 +3008,7 @@ void ZPageAllocator::print_total_usage_on(outputStream* st) const { st->print("ZHeap "); st->fill_to(17); st->print_cr("used %zuM, capacity %zuM, max capacity %zuM", - used() / M, capacity() / M, max_capacity() / M); + used() / M, capacity() / M, dynamic_max_capacity() / M); } void ZPageAllocator::print_partition_usage_on(outputStream* st) const { diff --git a/src/hotspot/share/gc/z/zPageAllocator.hpp b/src/hotspot/share/gc/z/zPageAllocator.hpp index a2f4c94e8d40..9aba68fe0092 100644 --- a/src/hotspot/share/gc/z/zPageAllocator.hpp +++ b/src/hotspot/share/gc/z/zPageAllocator.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -32,6 +32,7 @@ #include "gc/z/zList.hpp" #include "gc/z/zLock.hpp" #include "gc/z/zMappedCache.hpp" +#include "gc/z/zMemoryWorker.hpp" #include "gc/z/zPage.hpp" #include "gc/z/zPageAge.hpp" #include "gc/z/zPageType.hpp" @@ -42,6 +43,48 @@ #include "gc/z/zVirtualMemoryManager.hpp" #include "utilities/ostream.hpp" +// The ZPageAllocator maintains the memory for the Java heap, represented by the +// notion of capacity. The capacity is divided among one or more partitions, which +// handles a subset of the heap (or the whole heap in case of a single partition). +// +// The most important specific terms for capacity that we have are: +// - static min Absolute lower bound configured at startup (never changes). +// The heap must not shrink below this. +// - capacity Currently committed capacity +// - soft max Target limit; heuristics try to stay below this +// - heuristic max Heuristic-derived upper bound used to guide resizing +// - current max The effective maximum currently enforced; based on system +// memory usage +// - dynamic max Same as static max for non-container. Container limit +// if in container. +// - static max Absolute upper bound configured at startup (never changes). +// This is the same value of -Xmx if set on the command-line. +// +// Notes: +// - Layouts below are illustrative. Values may move relative to each other. +// - static min and static max never change during the program's execution. +// - The heap must stay within [static min, static max]. +// +// Not container: +// LOW <-> HIGH +// +// soft max current max +// | | +// [----|------------|-----------|-|-------------|---------] +// | | | | +// static min capacity heuristic max static max +// dynamic max +// +// Container: +// LOW <-> HIGH +// +// soft max +// capacity | current max +// | | | +// [----|-|--------|-|-------|----|------------------------] +// | | | | +// static min heuristic_max dynamic max static max + class ThreadClosure; class ZGeneration; class ZMemoryAllocation; @@ -53,19 +96,21 @@ class ZSegmentStash; class ZSinglePartitionAllocation; class ZVirtualMemory; class ZWorkers; +enum class ZPageAllocationAttempt; class ZPartition { friend class VMStructs; friend class ZPageAllocator; + friend class ZMemoryWorker; friend class ZUncommitter; private: ZPageAllocator* const _page_allocator; ZMappedCache _cache; ZUncommitter _uncommitter; - const size_t _min_capacity; - const size_t _max_capacity; - volatile size_t _current_max_capacity; + ZMemoryWorker _mem_worker; + const size_t _static_min_capacity; + const size_t _static_max_capacity; volatile size_t _capacity; volatile size_t _claimed; size_t _used; @@ -77,29 +122,60 @@ class ZPartition { const ZPhysicalMemoryManager& physical_memory_manager() const; ZPhysicalMemoryManager& physical_memory_manager(); + ZLock* lock() const; + void verify_virtual_memory_multi_partition_association(const ZVirtualMemory& vmem) const NOT_DEBUG_RETURN; void verify_virtual_memory_association(const ZVirtualMemory& vmem, bool check_multi_partition = false) const NOT_DEBUG_RETURN; void verify_virtual_memory_association(const ZArray* vmems) const NOT_DEBUG_RETURN; void verify_memory_allocation_association(const ZMemoryAllocation* allocation) const NOT_DEBUG_RETURN; + size_t increase_capacity(size_t size, size_t capacity_limit); + void map_virtual_inner(const ZVirtualMemory& vmem, bool multi_partition, bool sort_segments, bool register_heating); + void unmap_virtual_inner(const ZVirtualMemory& vmem, bool multi_partition); + public: - ZPartition(uint32_t numa_id, ZPageAllocator* page_allocator); + ZPartition(uint32_t numa_id, + ZPageAllocator* page_allocator, + size_t min_capacity, + size_t static_max_capacity); + + size_t dynamic_max_capacity() const; + size_t current_max_capacity() const; + size_t static_max_capacity() const; + size_t capacity() const; + + const ZUncommitter& uncommitter() const; + ZUncommitter& uncommitter(); + + const ZMemoryWorker& memory_worker() const; + ZMemoryWorker& memory_worker(); uint32_t numa_id() const; - size_t available() const; + size_t available(ZPageAllocationAttempt attempt, size_t capacity_limit) const; + size_t available_from_any_capacity(size_t capacity_limit) const; + size_t available_from_increase_capacity(size_t capacity_limit) const; + size_t available_from_cache(size_t capacity_limit) const; - size_t increase_capacity(size_t size); - void decrease_capacity(size_t size, bool set_max_capacity); + size_t try_increase_capacity(size_t size, ZPageAllocationAttempt attempt, size_t capacity_limit); + void decrease_capacity(size_t size); void increase_used(size_t size); void decrease_used(size_t size); - void free_memory(const ZVirtualMemory& vmem); + void increase_claimed(size_t size); + void decrease_claimed(size_t size); + + void heat_memory(const ZVirtualMemory& vmem) const; + + void free_used_memory(const ZVirtualMemory& vmem); + void free_claimed_memory(const ZVirtualMemory& vmem); - void claim_from_cache_or_increase_capacity(ZMemoryAllocation* allocation); - bool claim_capacity(ZMemoryAllocation* allocation); - bool claim_capacity_fast_medium(ZMemoryAllocation* allocation); + void claim_from_cache_or_increase_capacity(ZMemoryAllocation* allocation, ZPageAllocationAttempt attempt, size_t capacity_limit); + bool claim_capacity(ZMemoryAllocation* allocation, ZPageAllocationAttempt attempt, size_t capacity_limit); + bool claim_capacity_fast_medium(ZMemoryAllocation* allocation, size_t capacity_limit); + + size_t increase_and_commit_capacity(size_t size, size_t capacity_limit); void sort_segments_physical(const ZVirtualMemory& vmem); @@ -108,7 +184,7 @@ class ZPartition { size_t commit_physical(const ZVirtualMemory& vmem); size_t uncommit_physical(const ZVirtualMemory& vmem); - void map_virtual(const ZVirtualMemory& vmem); + void map_virtual(const ZVirtualMemory& vmem, bool heat_memory = true); void unmap_virtual(const ZVirtualMemory& vmem); void map_virtual_from_multi_partition(const ZVirtualMemory& vmem); @@ -121,14 +197,14 @@ class ZPartition { void free_and_claim_virtual_from_low_many(const ZVirtualMemory& vmem, ZArray* vmems_out); ZVirtualMemory free_and_claim_virtual_from_low_exact_or_many(size_t size, ZArray* vmems_in_out); - bool prime(ZWorkers* workers, size_t size); + bool prime(ZWorkers* workers, size_t size, size_t capacity_limit); ZVirtualMemory prepare_harvested_and_claim_virtual(ZMemoryAllocation* allocation); void copy_physical_segments_to_partition(const ZVirtualMemory& at, const ZVirtualMemory& from); void copy_physical_segments_from_partition(const ZVirtualMemory& at, const ZVirtualMemory& to); - void commit_increased_capacity(ZMemoryAllocation* allocation, const ZVirtualMemory& vmem); + void commit_increased_capacity(ZMemoryAllocation* allocation, const ZVirtualMemory& vmem, size_t allowed_to_commit); void map_memory(ZMemoryAllocation* allocation, const ZVirtualMemory& vmem); void free_memory_alloc_failed(ZMemoryAllocation* allocation); @@ -145,35 +221,37 @@ using ZPartitionConstIterator = ZPerNUMAConstIterator; class ZPageAllocator { friend class VMStructs; + friend class ZMemoryWorker; friend class ZMultiPartitionTracker; friend class ZPartition; friend class ZUncommitter; private: - mutable ZLock _lock; - ZVirtualMemoryManager _virtual; - ZPhysicalMemoryManager _physical; - const size_t _min_capacity; - const size_t _max_capacity; - volatile size_t _used; - volatile size_t _used_generations[2]; + mutable ZLock _lock; + ZVirtualMemoryManager _virtual; + ZPhysicalMemoryManager _physical; + const size_t _static_min_capacity; + const size_t _static_max_capacity; + Atomic _heuristic_max_capacity; + volatile size_t _used; + volatile size_t _used_generations[2]; struct { size_t _used_high; size_t _used_low; - } _collection_stats[2]; - ZPerNUMA _partitions; - ZList _stalled; - mutable ZSafeDelete _safe_destroy; - bool _initialized; + } _collection_stats[2]; + ZPerNUMA _partitions; + ZList _stalled; + mutable ZSafeDelete _safe_destroy; + bool _initialized; bool alloc_page_stall(ZPageAllocation* allocation); - ZPage* alloc_page_inner(ZPageAllocation* allocation); + ZPage* alloc_page_inner(ZPageAllocation* allocation, ZPageAllocationAttempt attempt); - bool claim_capacity_or_stall(ZPageAllocation* allocation); - bool claim_capacity(ZPageAllocation* allocation); - bool claim_capacity_fast_medium(ZPageAllocation* allocation); - bool claim_capacity_single_partition(ZSinglePartitionAllocation* single_partition_allocation, uint32_t partition_id); - void claim_capacity_multi_partition(ZMultiPartitionAllocation* multi_partition_allocation, uint32_t start_partition); + bool claim_capacity_or_stall(ZPageAllocation* allocation, ZPageAllocationAttempt* attempt); + bool claim_capacity(ZPageAllocation* allocation, ZPageAllocationAttempt attempt); + bool claim_capacity_single_partition_fast_medium(ZSinglePartitionAllocation* single_partition_allocation, uint32_t partition_id, size_t capacity_limit); + bool claim_capacity_single_partition(ZSinglePartitionAllocation* single_partition_allocation, uint32_t partition_id, ZPageAllocationAttempt attempt, size_t capacity_limit); + void claim_capacity_multi_partition(ZMultiPartitionAllocation* multi_partition_allocation, uint32_t start_partition, ZPageAllocationAttempt attempt); ZVirtualMemory satisfied_from_cache_vmem(const ZPageAllocation* allocation) const; @@ -192,6 +270,8 @@ class ZPageAllocator { bool commit_and_map_single_partition(ZSinglePartitionAllocation* single_partition_allocation, const ZVirtualMemory& vmem); bool commit_and_map_multi_partition(ZMultiPartitionAllocation* multi_partition_allocation, const ZVirtualMemory& vmem); + size_t allowed_to_commit(size_t about_to_commit); + void commit(ZMemoryAllocation* allocation, const ZVirtualMemory& vmem); bool commit_single_partition(ZSinglePartitionAllocation* single_partition_allocation, const ZVirtualMemory& vmem); bool commit_multi_partition(ZMultiPartitionAllocation* multi_partition_allocation, const ZVirtualMemory& vmem); @@ -215,18 +295,19 @@ class ZPageAllocator { void prepare_memory_for_free(ZPage* page, ZArray* vmems); void remap_and_defragment(const ZVirtualMemory& vmem, ZArray* vmems_out); - void free_memory(ZArray* vmems); + void free_used_memory(ZArray* vmems); + void free_claimed_memory(ZArray* vmems); void satisfy_stalled(); bool is_multi_partition_enabled() const; - bool is_multi_partition_allowed(const ZPageAllocation* allocation) const; + bool is_multi_partition_allowed(const ZPageAllocation* allocation, ZPageAllocationAttempt attempt, size_t total_capacity_limit) const; const ZPartition& partition_from_partition_id(uint32_t partition_id) const; ZPartition& partition_from_partition_id(uint32_t partition_id); ZPartition& partition_from_vmem(const ZVirtualMemory& vmem); - size_t sum_available() const; + size_t sum_available(ZPageAllocationAttempt attempt, size_t total_capacity_limit) const; void increase_used(size_t size); void decrease_used(size_t size); @@ -238,32 +319,46 @@ class ZPageAllocator { ZPageAllocatorStats stats_inner(ZGeneration* generation) const; public: - ZPageAllocator(size_t min_capacity, + ZPageAllocator(size_t static_min_capacity, size_t initial_capacity, size_t soft_max_capacity, - size_t max_capacity); + size_t static_max_capacity); bool is_initialized() const; bool prime_cache(ZWorkers* workers, size_t size); - size_t min_capacity() const; - size_t max_capacity() const; - size_t soft_max_capacity() const; + size_t static_min_capacity() const; + size_t static_max_capacity() const; + size_t dynamic_max_capacity() const; size_t current_max_capacity() const; + size_t heuristic_max_capacity() const; size_t capacity() const; size_t used() const; size_t used_generation(ZGenerationId id) const; size_t unused() const; - void increase_used_generation(ZGenerationId id, size_t size); - void decrease_used_generation(ZGenerationId id, size_t size); + // Adaptive heap sizing + enum class TruncationReason { + Uncommit, + CommitFailure, + }; - void promote_used(const ZPage* from, const ZPage* to); + void shrink_heuristic_max(size_t shrink_amount); + void truncate_heuristic_max_after_capacity_decrease(TruncationReason reason); + void adapt_heuristic_max_capacity(ZGenerationId generation); + void heap_resized(size_t selected_capacity); + void heap_truncated(size_t selected_capacity, TruncationReason reason); + void adjust_capacity(size_t used_soon); ZPageAllocatorStats stats(ZGeneration* generation) const; ZPageAllocatorStats update_and_stats(ZGeneration* generation); + void increase_used_generation(ZGenerationId id, size_t size); + void decrease_used_generation(ZGenerationId id, size_t size); + + void promote_used(const ZPage* from, const ZPage* to); + ZPage* alloc_page(ZPageType type, size_t size, ZAllocationFlags flags, ZPageAge age, uint32_t preferred_partition); void safe_destroy_page(ZPage* page); void free_page(ZPage* page); @@ -290,9 +385,7 @@ class ZPageAllocator { class ZPageAllocatorStats { private: - const size_t _min_capacity; - const size_t _max_capacity; - const size_t _soft_max_capacity; + const size_t _heuristic_max_capacity; const size_t _capacity; const size_t _used; const size_t _used_high; @@ -304,9 +397,7 @@ class ZPageAllocatorStats { const size_t _allocation_stalls; public: - ZPageAllocatorStats(size_t min_capacity, - size_t max_capacity, - size_t soft_max_capacity, + ZPageAllocatorStats(size_t heuristic_max_capacity, size_t capacity, size_t used, size_t used_high, @@ -317,9 +408,7 @@ class ZPageAllocatorStats { size_t compacted, size_t allocation_stalls); - size_t min_capacity() const; - size_t max_capacity() const; - size_t soft_max_capacity() const; + size_t heuristic_max_capacity() const; size_t capacity() const; size_t used() const; size_t used_high() const; diff --git a/src/hotspot/share/gc/z/zPageAllocator.inline.hpp b/src/hotspot/share/gc/z/zPageAllocator.inline.hpp index 5f357587fd79..0726e77badd5 100644 --- a/src/hotspot/share/gc/z/zPageAllocator.inline.hpp +++ b/src/hotspot/share/gc/z/zPageAllocator.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,9 +26,7 @@ #include "gc/z/zPageAllocator.hpp" -inline ZPageAllocatorStats::ZPageAllocatorStats(size_t min_capacity, - size_t max_capacity, - size_t soft_max_capacity, +inline ZPageAllocatorStats::ZPageAllocatorStats(size_t heuristic_max_capacity, size_t capacity, size_t used, size_t used_high, @@ -38,9 +36,7 @@ inline ZPageAllocatorStats::ZPageAllocatorStats(size_t min_capacity, size_t promoted, size_t compacted, size_t allocation_stalls) - : _min_capacity(min_capacity), - _max_capacity(max_capacity), - _soft_max_capacity(soft_max_capacity), + : _heuristic_max_capacity(heuristic_max_capacity), _capacity(capacity), _used(used), _used_high(used_high), @@ -51,16 +47,8 @@ inline ZPageAllocatorStats::ZPageAllocatorStats(size_t min_capacity, _compacted(compacted), _allocation_stalls(allocation_stalls) {} -inline size_t ZPageAllocatorStats::min_capacity() const { - return _min_capacity; -} - -inline size_t ZPageAllocatorStats::max_capacity() const { - return _max_capacity; -} - -inline size_t ZPageAllocatorStats::soft_max_capacity() const { - return _soft_max_capacity; +inline size_t ZPageAllocatorStats::heuristic_max_capacity() const { + return _heuristic_max_capacity; } inline size_t ZPageAllocatorStats::capacity() const { diff --git a/src/hotspot/share/gc/z/zPhysicalMemoryManager.cpp b/src/hotspot/share/gc/z/zPhysicalMemoryManager.cpp index 2e7a97028ff3..1587e1fdec33 100644 --- a/src/hotspot/share/gc/z/zPhysicalMemoryManager.cpp +++ b/src/hotspot/share/gc/z/zPhysicalMemoryManager.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -22,10 +22,12 @@ */ #include "gc/shared/gcLogPrecious.hpp" +#include "gc/z/zAdaptiveHeap.inline.hpp" #include "gc/z/zAddress.inline.hpp" #include "gc/z/zArray.inline.hpp" #include "gc/z/zGlobals.hpp" #include "gc/z/zGranuleMap.inline.hpp" +#include "gc/z/zInitialize.hpp" #include "gc/z/zLargePages.inline.hpp" #include "gc/z/zList.inline.hpp" #include "gc/z/zNMT.hpp" @@ -39,17 +41,22 @@ #include "runtime/globals.hpp" #include "runtime/globals_extension.hpp" #include "runtime/init.hpp" +#include "runtime/java.hpp" #include "runtime/os.hpp" #include "utilities/align.hpp" #include "utilities/debug.hpp" #include "utilities/globalDefinitions.hpp" #include "utilities/powerOfTwo.hpp" -ZPhysicalMemoryManager::ZPhysicalMemoryManager(size_t max_capacity) +ZPhysicalMemoryManager::ZPhysicalMemoryManager(size_t min_capacity, size_t max_capacity) : _backing(max_capacity), _physical_mappings(ZAddressOffsetMax) { assert(is_aligned(max_capacity, ZGranuleSize), "must be granule aligned"); + if (!_backing.is_initialized()) { + return; + } + // Setup backing storage limits ZBackingOffsetMax = max_capacity; ZBackingIndexMax = checked_cast(max_capacity >> ZGranuleSizeShift); @@ -77,6 +84,9 @@ ZPhysicalMemoryManager::ZPhysicalMemoryManager(size_t max_capacity) next_index += num_segments; } + // Check if uncommit should and can be enabled + try_enable_uncommit(min_capacity, max_capacity); + assert(untype(next_index) == ZBackingIndexMax, "must insert all capacity"); } @@ -84,16 +94,13 @@ bool ZPhysicalMemoryManager::is_initialized() const { return _backing.is_initialized(); } -void ZPhysicalMemoryManager::warn_commit_limits(size_t max_capacity) const { - _backing.warn_commit_limits(max_capacity); +void ZPhysicalMemoryManager::warn_commit_limits(size_t expected_capacity, size_t max_capacity) const { + _backing.warn_commit_limits(expected_capacity, max_capacity); } void ZPhysicalMemoryManager::try_enable_uncommit(size_t min_capacity, size_t max_capacity) { assert(!is_init_completed(), "Invalid state"); - // If uncommit is not explicitly disabled, max capacity is greater than - // min capacity, and uncommit is supported by the platform, then uncommit - // will be enabled. if (!ZUncommit) { log_info_p(gc, init)("Uncommit: Disabled"); return; @@ -105,13 +112,12 @@ void ZPhysicalMemoryManager::try_enable_uncommit(size_t min_capacity, size_t max return; } - // Test if uncommit is supported by the operating system by committing - // and then uncommitting a granule. + // Test if uncommit is supported by the operating system or file system by + // committing and then uncommitting a granule. const ZVirtualMemory vmem(zoffset(0), ZGranuleSize); if (!commit(vmem, 0) || !uncommit(vmem)) { - log_info_p(gc, init)("Uncommit: Implicitly Disabled (Not supported by operating system)"); - FLAG_SET_ERGO(ZUncommit, false); - return; + vm_exit_during_initialization("Uncommit not supported with the current configuration. " + "Use -XX:-ZUncommit to run without uncommit."); } const size_t max_delay_without_overflow = std::numeric_limits::max() / MILLIUNITS; @@ -391,3 +397,9 @@ void ZPhysicalMemoryManager::restore_segments(const ZArraySlice stash, const ZVirtualMemory& vmem) const; void copy_from_stash(const ZArraySlice stash, const ZVirtualMemory& vmem); + void try_enable_uncommit(size_t min_capacity, size_t max_capacity); + public: - ZPhysicalMemoryManager(size_t max_capacity); + ZPhysicalMemoryManager(size_t min_capacity, size_t max_capacity); bool is_initialized() const; - void warn_commit_limits(size_t max_capacity) const; - void try_enable_uncommit(size_t min_capacity, size_t max_capacity); + void warn_commit_limits(size_t expected_capacity, size_t max_capacity) const; void alloc(const ZVirtualMemory& vmem, uint32_t numa_id); void free(const ZVirtualMemory& vmem, uint32_t numa_id); @@ -74,6 +75,8 @@ class ZPhysicalMemoryManager { void stash_segments(const ZArraySlice& vmems, ZArray* stash_out) const; void restore_segments(const ZArraySlice& vmems, const ZArray& stash); + + void collapse(const ZVirtualMemory& vmem) const; }; #endif // SHARE_GC_Z_ZPHYSICALMEMORYMANAGER_HPP diff --git a/src/hotspot/share/gc/z/zRange.hpp b/src/hotspot/share/gc/z/zRange.hpp index 53c9980608e8..a002537e5998 100644 --- a/src/hotspot/share/gc/z/zRange.hpp +++ b/src/hotspot/share/gc/z/zRange.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -59,6 +59,7 @@ class ZRange { bool operator!=(const ZRange& other) const; bool contains(const ZRange& other) const; + bool overlaps(const ZRange& other) const; void grow_from_front(size_t size); void grow_from_back(size_t size); diff --git a/src/hotspot/share/gc/z/zRange.inline.hpp b/src/hotspot/share/gc/z/zRange.inline.hpp index d99cbefd32a8..6918ef84f9ef 100644 --- a/src/hotspot/share/gc/z/zRange.inline.hpp +++ b/src/hotspot/share/gc/z/zRange.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -87,6 +87,14 @@ inline bool ZRange::contains(const ZRange& other) const { return _start <= other._start && other.end() <= end(); } +template +inline bool ZRange::overlaps(const ZRange& other) const { + precond(!is_null()); + precond(!other.is_null()); + + return MAX2(_start, other._start) < MIN2(other.end(), end()); +} + template inline void ZRange::grow_from_front(size_t size) { precond(size_t(start()) >= size); diff --git a/src/hotspot/share/gc/z/zReferenceProcessor.cpp b/src/hotspot/share/gc/z/zReferenceProcessor.cpp index 229365a2e7e1..c398a355d610 100644 --- a/src/hotspot/share/gc/z/zReferenceProcessor.cpp +++ b/src/hotspot/share/gc/z/zReferenceProcessor.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,6 +25,7 @@ #include "gc/shared/referencePolicy.hpp" #include "gc/shared/referenceProcessorStats.hpp" #include "gc/shared/suspendibleThreadSet.hpp" +#include "gc/z/zAdaptiveHeap.inline.hpp" #include "gc/z/zCollectedHeap.hpp" #include "gc/z/zDriver.hpp" #include "gc/z/zHeap.inline.hpp" @@ -42,6 +43,14 @@ static const ZStatSubPhase ZSubPhaseConcurrentReferencesProcess("Concurrent References Process", ZGenerationId::old); static const ZStatSubPhase ZSubPhaseConcurrentReferencesEnqueue("Concurrent References Enqueue", ZGenerationId::old); +class ZLRUMaxHeapPolicy : public AbstractLRUReferencePolicy { +public: + // Capture state (of-the-VM) information needed to evaluate the policy + void setup() final { + set_max_interval((jlong)ZAdaptiveHeap::soft_ref_delay()); + } +}; + static ReferenceType reference_type(zaddress reference) { return InstanceKlass::cast(to_oop(reference)->klass())->reference_type(); } @@ -122,7 +131,7 @@ ZReferenceProcessor::ZReferenceProcessor(ZWorkers* workers) void ZReferenceProcessor::set_soft_reference_policy(bool clear_all_soft_references) { static AlwaysClearPolicy always_clear_policy; - static LRUMaxHeapPolicy lru_max_heap_policy; + static ZLRUMaxHeapPolicy lru_max_heap_policy; _uses_clear_all_soft_reference_policy = clear_all_soft_references; diff --git a/src/hotspot/share/gc/z/zServiceability.cpp b/src/hotspot/share/gc/z/zServiceability.cpp index e5ed99bfccc6..f2ef46bc9994 100644 --- a/src/hotspot/share/gc/z/zServiceability.cpp +++ b/src/hotspot/share/gc/z/zServiceability.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -35,18 +35,21 @@ struct ZMemoryUsageInfo { size_t _young_capacity; size_t _old_used; size_t _old_capacity; + size_t _max_capacity; }; static ZMemoryUsageInfo compute_memory_usage_info() { const size_t capacity = ZHeap::heap()->capacity(); const size_t old_used = ZHeap::heap()->used_old(); const size_t young_used = ZHeap::heap()->used_young(); + const size_t max_capacity = ZHeap::heap()->dynamic_max_capacity(); ZMemoryUsageInfo info; info._old_used = MIN2(old_used, capacity); info._old_capacity = info._old_used; info._young_capacity = capacity - info._old_capacity; info._young_used = MIN2(young_used, info._young_capacity); + info._max_capacity = MAX2(capacity, max_capacity); return info; } @@ -143,12 +146,16 @@ MemoryUsage ZServiceabilityMemoryPool::get_memory_usage() { const ZMemoryUsageInfo info = compute_memory_usage_info(); if (_generation_id == ZGenerationId::young) { - return MemoryUsage(initial_size(), info._young_used, info._young_capacity, max_size()); + return MemoryUsage(initial_size(), info._young_used, info._young_capacity, info._max_capacity); } else { - return MemoryUsage(initial_size(), info._old_used, info._old_capacity, max_size()); + return MemoryUsage(initial_size(), info._old_used, info._old_capacity, info._max_capacity); } } +size_t ZServiceabilityMemoryPool::max_size() const { + return ZHeap::heap()->dynamic_max_capacity(); +} + ZServiceabilityMemoryManager::ZServiceabilityMemoryManager(const char* name, MemoryPool* young_memory_pool, MemoryPool* old_memory_pool) diff --git a/src/hotspot/share/gc/z/zServiceability.hpp b/src/hotspot/share/gc/z/zServiceability.hpp index 2a3b56248df8..28a9c5202fb9 100644 --- a/src/hotspot/share/gc/z/zServiceability.hpp +++ b/src/hotspot/share/gc/z/zServiceability.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -41,6 +41,7 @@ class ZServiceabilityMemoryPool : public CollectedMemoryPool { public: ZServiceabilityMemoryPool(const char* name, ZGenerationId id, size_t min_capacity, size_t max_capacity); + virtual size_t max_size() const; virtual size_t used_in_bytes(); virtual MemoryUsage get_memory_usage(); }; diff --git a/src/hotspot/share/gc/z/zStat.cpp b/src/hotspot/share/gc/z/zStat.cpp index 5a429f02806c..09818e764975 100644 --- a/src/hotspot/share/gc/z/zStat.cpp +++ b/src/hotspot/share/gc/z/zStat.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -24,6 +24,7 @@ #include "cppstdlib/limits.hpp" #include "gc/shared/gc_globals.hpp" #include "gc/z/zAbort.inline.hpp" +#include "gc/z/zAdaptiveHeap.inline.hpp" #include "gc/z/zCollectedHeap.hpp" #include "gc/z/zCPU.inline.hpp" #include "gc/z/zDirector.hpp" @@ -48,12 +49,11 @@ #include "utilities/ticks.hpp" #define ZSIZE_FMT "%zuM(%.0f%%)" -#define ZSIZE_ARGS_WITH_MAX(size, max) ((size) / M), (percent_of(size, max)) -#define ZSIZE_ARGS(size) ZSIZE_ARGS_WITH_MAX(size, ZStatHeap::max_capacity()) +#define ZSIZE_ARGS(size, max) ((size) / M), (percent_of(size, max)) #define ZTABLE_ARGS_NA "%9s", "-" -#define ZTABLE_ARGS(size) "%8zuM (%.0f%%)", \ - ((size) / M), (percent_of(size, ZStatHeap::max_capacity())) +#define ZTABLE_ARGS(size, max) "%8zuM (%.0f%%)", \ + ((size) / M), (percent_of(size, max)) // // Stat sampler/counter data @@ -651,6 +651,20 @@ size_t ZStatPhaseCollection::used_at_start() const { : ZDriver::major()->used_at_start(); } +void ZStatPhaseCollection::set_max_at_start(size_t max) const { + if (_minor) { + ZDriver::minor()->set_max_at_start(max); + } else { + ZDriver::major()->set_max_at_start(max); + } +} + +size_t ZStatPhaseCollection::max_at_start() const { + return _minor + ? ZDriver::minor()->max_at_start() + : ZDriver::major()->max_at_start(); +} + void ZStatPhaseCollection::register_start(ConcurrentGCTimer* timer, const Ticks& start) const { const GCCause::Cause cause = _minor ? ZDriver::minor()->gc_cause() : ZDriver::major()->gc_cause(); @@ -660,6 +674,8 @@ void ZStatPhaseCollection::register_start(ConcurrentGCTimer* timer, const Ticks& ZCollectedHeap::heap()->trace_heap_before_gc(jfr_tracer()); set_used_at_start(ZHeap::heap()->used()); + const size_t max = ZHeap::heap()->heuristic_max_capacity(); + set_max_at_start(max); log_info(gc)("%s (%s)", name(), GCCause::to_string(cause)); } @@ -685,8 +701,8 @@ void ZStatPhaseCollection::register_end(ConcurrentGCTimer* timer, const Ticks& s log_info(gc)("%s (%s) " ZSIZE_FMT "->" ZSIZE_FMT " %.3fs", name(), GCCause::to_string(cause), - ZSIZE_ARGS(used_at_start()), - ZSIZE_ARGS(used_at_end), + ZSIZE_ARGS(used_at_start(), max_at_start()), + ZSIZE_ARGS(used_at_end, max_at_start()), duration.seconds()); } @@ -740,10 +756,12 @@ void ZStatPhaseGeneration::register_end(ConcurrentGCTimer* timer, const Ticks& s generation->stat_heap()->print(generation); + const size_t max_at_start = generation->stat_heap()->max_at_collection_start(); + log_info(gc, phases)("%s " ZSIZE_FMT "->" ZSIZE_FMT " %.3fs", name(), - ZSIZE_ARGS(generation->stat_heap()->used_at_collection_start()), - ZSIZE_ARGS(generation->stat_heap()->used_at_collection_end()), + ZSIZE_ARGS(generation->stat_heap()->used_at_collection_start(), max_at_start), + ZSIZE_ARGS(generation->stat_heap()->used_at_collection_end(), max_at_start), duration.seconds()); } @@ -950,8 +968,8 @@ void ZStatMutatorAllocRate::initialize() { void ZStatMutatorAllocRate::update_sampling_granule() { const size_t sampling_heap_granules = 128; - const size_t soft_max_capacity = ZHeap::heap()->soft_max_capacity(); - _sampling_granule = align_up(soft_max_capacity / sampling_heap_granules, ZGranuleSize); + const size_t heuristic_max_capacity = ZHeap::heap()->heuristic_max_capacity(); + _sampling_granule = align_up(heuristic_max_capacity / sampling_heap_granules, ZGranuleSize); } void ZStatMutatorAllocRate::sample_allocation(size_t allocation_bytes) { @@ -1013,7 +1031,70 @@ void ZStatMutatorAllocRate::sample_allocation(size_t allocation_bytes) { ZStatMutatorAllocRateStats ZStatMutatorAllocRate::stats() { ZLocker locker(_stat_lock); - return {_rate.avg(), _rate.predict_next(), _rate.sd()}; + return {_rate.avg(), _rate.predict_next(), _rate.sd(), _sampling_granule}; +} + +// +// Stat system memory usage stability +// +ZStatSystemMemoryUsage::ZSystemMemoryUsage::ZSystemMemoryUsage() + : _highest_usages(0.1 /* alpha */), + _highest_usage(0.0), + _memory_stability(0.0) {} + +ZStatSystemMemoryUsage::ZSystemMemoryUsage ZStatSystemMemoryUsage::_container_usage; +ZStatSystemMemoryUsage::ZSystemMemoryUsage ZStatSystemMemoryUsage::_machine_usage; + +void ZStatSystemMemoryUsage::ZSystemMemoryUsage::record_usage(double usage) { + double highest = _highest_usage.load_relaxed(); + if (highest < usage) { + // No need to do anything if the CAS fails; then someone else filled in an update value + _highest_usage.compare_exchange(highest, usage, memory_order_relaxed); + } +} + +double ZStatSystemMemoryUsage::ZSystemMemoryUsage::memory_stability() { + return _memory_stability.load_relaxed(); +} + +void ZStatSystemMemoryUsage::ZSystemMemoryUsage::sample_and_collect() { + double highest = _highest_usage.exchange(0.0, memory_order_relaxed); + if (highest != 0.0) { + _highest_usages.add(highest); + } + double sd = _highest_usages.dsd(); + _memory_stability.store_relaxed(sd); +} + +void ZStatSystemMemoryUsage::record(const ZMemoryPressureMetrics& metrics) { + const double machine_usage = double(metrics._machine._used_memory) / double(metrics._machine._max_memory); + record_machine_usage(machine_usage); + + if (metrics._is_containerized) { + const double container_usage = double(metrics._container._used_memory) / double(metrics._container._max_memory); + record_container_usage(container_usage); + } +} + +void ZStatSystemMemoryUsage::record_container_usage(double usage) { + _container_usage.record_usage(usage); +} + +void ZStatSystemMemoryUsage::record_machine_usage(double usage) { + _machine_usage.record_usage(usage); +} + +double ZStatSystemMemoryUsage::container_memory_stability() { + return _container_usage.memory_stability(); +} + +double ZStatSystemMemoryUsage::machine_memory_stability() { + return _machine_usage.memory_stability(); +} + +void ZStatSystemMemoryUsage::sample_and_collect() { + _container_usage.sample_and_collect(); + _machine_usage.sample_and_collect(); } // @@ -1037,6 +1118,9 @@ void ZStat::sample_and_collect(ZStatSamplerHistory* history) const { ZStatSamplerHistory& sampler_history = history[sampler->id()]; sampler_history.add(sampler->collect_and_reset()); } + + // Sample machine memory usage + ZStatSystemMemoryUsage::sample_and_collect(); } bool ZStat::should_print(LogTargetHandle log) const { @@ -1232,11 +1316,14 @@ ZStatCycle::ZStatCycle() _serial_time(0.7 /* alpha */), _parallelizable_time(0.7 /* alpha */), _parallelizable_duration(0.7 /* alpha */), - _last_active_workers(0.0) {} + _last_active_workers(0.0), + _start_vtime(), + _last_total_vtime() {} void ZStatCycle::at_start() { ZLocker locker(&_stat_lock); _start_of_last = Ticks::now(); + _start_vtime = os::current_thread_cpu_time(true /* user + sys */); } void ZStatCycle::at_end(ZStatWorkers* stat_workers, bool record_stats) { @@ -1252,6 +1339,7 @@ void ZStatCycle::at_end(ZStatWorkers* stat_workers, bool record_stats) { const double duration = (_end_of_last - _start_of_last).seconds(); const double workers_duration = stat_workers->get_and_reset_duration(); const double workers_time = stat_workers->get_and_reset_time(); + const double workers_vtime = stat_workers->get_and_reset_vtime(); const double serial_time = duration - workers_duration; _last_active_workers = workers_time / workers_duration; @@ -1260,6 +1348,9 @@ void ZStatCycle::at_end(ZStatWorkers* stat_workers, bool record_stats) { _serial_time.add(serial_time); _parallelizable_time.add(workers_time); _parallelizable_duration.add(workers_duration); + const jlong elapsed_vtime_nanos = os::current_thread_cpu_time(true /* user + sys */) - _start_vtime; + const double elapsed_vtime = double(elapsed_vtime_nanos) / NANOSECS_PER_SEC; + _last_total_vtime = elapsed_vtime + workers_vtime; if (end_of_last.value() != 0) { const double cycle_interval = (_end_of_last - end_of_last).seconds(); _cycle_intervals.add(cycle_interval); @@ -1320,7 +1411,8 @@ ZStatCycleStats ZStatCycle::stats() { _parallelizable_time.davg(), _parallelizable_time.dsd(), _parallelizable_duration.davg(), - _parallelizable_duration.dsd() + _parallelizable_duration.dsd(), + _last_total_vtime }; } @@ -1332,7 +1424,8 @@ ZStatWorkers::ZStatWorkers() _active_workers(0), _start_of_last(), _accumulated_duration(), - _accumulated_time() {} + _accumulated_time(), + _accumulated_vtime() {} void ZStatWorkers::at_start(uint active_workers) { ZLocker locker(&_stat_lock); @@ -1396,6 +1489,23 @@ double ZStatWorkers::get_and_reset_time() { return time; } +double ZStatWorkers::get_and_reset_vtime() { + ZLocker locker(&_stat_lock); + double result = _accumulated_vtime; + _accumulated_vtime = 0.0; + return result; +} + +void ZStatWorkers::add_accumulated_vtime(double vtime) { + for (;;) { + double prev = AtomicAccess::load(&_accumulated_vtime); + double new_val = prev + vtime; + if (AtomicAccess::cmpxchg(&_accumulated_vtime, prev, new_val) == prev) { + return; + } + } +} + ZStatWorkersStats ZStatWorkers::stats() { ZLocker locker(&_stat_lock); return { @@ -1404,6 +1514,31 @@ ZStatWorkersStats ZStatWorkers::stats() { }; } +// +// Stat memory workers +// +volatile double ZStatMemoryWorkers::_accumulated_vtime = 0.0; + +void ZStatMemoryWorkers::add_accumulated_vtime(double vtime) { + for (;;) { + double prev = AtomicAccess::load(&_accumulated_vtime); + double new_val = prev + vtime; + if (AtomicAccess::cmpxchg(&_accumulated_vtime, prev, new_val) == prev) { + return; + } + } +} + +double ZStatMemoryWorkers::get_and_reset_vtime() { + for (;;) { + double prev = AtomicAccess::load(&_accumulated_vtime); + double new_val = 0.0; + if (AtomicAccess::cmpxchg(&_accumulated_vtime, prev, new_val) == prev) { + return prev; + } + } +} + // // Stat load // @@ -1561,6 +1696,8 @@ void ZStatRelocation::print_age_table() { uint oldest_none_empty_age = 0; + const size_t max = ZGeneration::young()->stat_heap()->max_at_collection_start(); + for (ZPageAge age : ZPageAgeRangeAll) { uint i = untype(age); auto summarize_pages = [&](const ZRelocationSetSelectorGroupStats& stats) { @@ -1595,12 +1732,12 @@ void ZStatRelocation::print_age_table() { } else { return age_table() .left("%s", age_str.buffer()) - .left(ZTABLE_ARGS(live[i])); + .left(ZTABLE_ARGS(live[i], max)); } }; lt.print("%s", create_age_table() - .left(ZTABLE_ARGS(total[i] - live[i])) + .left(ZTABLE_ARGS(total[i] - live[i], max)) .left("%7zu / %zu", _selector_stats.small(age).npages_candidates(), _selector_stats.small(age).npages_selected()) @@ -1725,7 +1862,13 @@ size_t ZStatHeap::capacity_low() const { _at_relocate_end.capacity); } -size_t ZStatHeap::free(size_t used) const { +size_t ZStatHeap::free(size_t used, size_t capacity) const { + if (ZAdaptiveHeapSizing) { + // When adapting, the max capacity is likely the whole machine/container, + // so we compare against the currently committed capacity instead. + return capacity - used; + } + return _at_initialize.max_capacity - used; } @@ -1757,9 +1900,9 @@ void ZStatHeap::at_initialize(size_t min_capacity, size_t max_capacity) { void ZStatHeap::at_collection_start(const ZPageAllocatorStats& stats) { ZLocker locker(&_stat_lock); - _at_collection_start.soft_max_capacity = stats.soft_max_capacity(); + _at_collection_start.heuristic_max_capacity = stats.heuristic_max_capacity(); _at_collection_start.capacity = stats.capacity(); - _at_collection_start.free = free(stats.used()); + _at_collection_start.free = free(stats.used(), stats.capacity()); _at_collection_start.used = stats.used(); _at_collection_start.used_generation = stats.used_generation(); } @@ -1767,9 +1910,9 @@ void ZStatHeap::at_collection_start(const ZPageAllocatorStats& stats) { void ZStatHeap::at_mark_start(const ZPageAllocatorStats& stats) { ZLocker locker(&_stat_lock); - _at_mark_start.soft_max_capacity = stats.soft_max_capacity(); + _at_mark_start.heuristic_max_capacity = stats.heuristic_max_capacity(); _at_mark_start.capacity = stats.capacity(); - _at_mark_start.free = free(stats.used()); + _at_mark_start.free = free(stats.used(), stats.capacity()); _at_mark_start.used = stats.used(); _at_mark_start.used_generation = stats.used_generation(); _at_mark_start.allocation_stalls = stats.allocation_stalls(); @@ -1779,7 +1922,7 @@ void ZStatHeap::at_mark_end(const ZPageAllocatorStats& stats) { ZLocker locker(&_stat_lock); _at_mark_end.capacity = stats.capacity(); - _at_mark_end.free = free(stats.used()); + _at_mark_end.free = free(stats.used(), stats.capacity()); _at_mark_end.used = stats.used(); _at_mark_end.used_generation = stats.used_generation(); _at_mark_end.mutator_allocated = mutator_allocated(stats.used_generation(), 0 /* reclaimed */, 0 /* relocated */); @@ -1803,7 +1946,7 @@ void ZStatHeap::at_relocate_start(const ZPageAllocatorStats& stats) { assert(stats.compacted() == 0, "Nothing should have been compacted"); _at_relocate_start.capacity = stats.capacity(); - _at_relocate_start.free = free(stats.used()); + _at_relocate_start.free = free(stats.used(), stats.capacity()); _at_relocate_start.used = stats.used(); _at_relocate_start.used_generation = stats.used_generation(); _at_relocate_start.live = _at_mark_end.live - stats.promoted(); @@ -1821,14 +1964,14 @@ void ZStatHeap::at_relocate_end(const ZPageAllocatorStats& stats, bool record_st _at_relocate_end.capacity = stats.capacity(); _at_relocate_end.capacity_high = capacity_high(); _at_relocate_end.capacity_low = capacity_low(); - _at_relocate_end.free = free(stats.used()); - _at_relocate_end.free_high = free(stats.used_low()); - _at_relocate_end.free_low = free(stats.used_high()); + _at_relocate_end.free = free(stats.used(), stats.capacity()); + _at_relocate_end.free_high = free(stats.used_low(), stats.capacity()); + _at_relocate_end.free_low = free(stats.used_high(), MAX2(stats.used_high(), stats.capacity())); _at_relocate_end.used = stats.used(); _at_relocate_end.used_high = stats.used_high(); _at_relocate_end.used_low = stats.used_low(); _at_relocate_end.used_generation = stats.used_generation(); - _at_relocate_end.live = _at_mark_end.live - stats.promoted(); + _at_relocate_end.live = _at_mark_end.live - MIN2(stats.promoted(), _at_mark_end.live); _at_relocate_end.garbage = garbage(stats.freed(), stats.compacted(), stats.promoted()); _at_relocate_end.mutator_allocated = mutator_allocated(stats.used_generation(), stats.freed(), stats.compacted()); _at_relocate_end.reclaimed = reclaimed(stats.freed(), stats.compacted(), stats.promoted()); @@ -1846,14 +1989,14 @@ double ZStatHeap::reclaimed_avg() { return _reclaimed_bytes.davg() + std::numeric_limits::denorm_min(); } -size_t ZStatHeap::max_capacity() { - return _at_initialize.max_capacity; -} - size_t ZStatHeap::used_at_collection_start() const { return _at_collection_start.used; } +size_t ZStatHeap::max_at_collection_start() const { + return _at_collection_start.heuristic_max_capacity; +} + size_t ZStatHeap::used_at_mark_start() const { return _at_mark_start.used; } @@ -1862,6 +2005,10 @@ size_t ZStatHeap::used_generation_at_mark_start() const { return _at_mark_start.used_generation; } +size_t ZStatHeap::used_generation_at_relocate_end() const { + return _at_relocate_end.used_generation; +} + size_t ZStatHeap::live_at_mark_end() const { return _at_mark_end.live; } @@ -1909,12 +2056,13 @@ ZStatHeapStats ZStatHeap::stats() { } void ZStatHeap::print(const ZGeneration* generation) const { - log_info(gc, heap)("Min Capacity: " - ZSIZE_FMT, ZSIZE_ARGS(_at_initialize.min_capacity)); - log_info(gc, heap)("Max Capacity: " - ZSIZE_FMT, ZSIZE_ARGS(_at_initialize.max_capacity)); - log_info(gc, heap)("Soft Max Capacity: " - ZSIZE_FMT, ZSIZE_ARGS(_at_mark_start.soft_max_capacity)); + const size_t dynamic_max = ZHeap::heap()->dynamic_max_capacity(); + const size_t max = ZAdaptiveHeap::explicit_max_capacity() ? _at_initialize.max_capacity + : _at_collection_start.heuristic_max_capacity; + + log_info(gc, heap)("Min Capacity: " ZSIZE_FMT, ZSIZE_ARGS(_at_initialize.min_capacity, dynamic_max)); + log_info(gc, heap)("Max Capacity: " ZSIZE_FMT, ZSIZE_ARGS(dynamic_max, dynamic_max)); + log_info(gc, heap)("Target Capacity: " ZSIZE_FMT, ZSIZE_ARGS(_at_mark_start.heuristic_max_capacity, dynamic_max)); log_info(gc, heap)("Heap Statistics:"); ZStatTablePrinter heap_table(10, 18); @@ -1929,30 +2077,30 @@ void ZStatHeap::print(const ZGeneration* generation) const { .end()); log_info(gc, heap)("%s", heap_table() .right("Capacity:") - .left(ZTABLE_ARGS(_at_mark_start.capacity)) - .left(ZTABLE_ARGS(_at_mark_end.capacity)) - .left(ZTABLE_ARGS(_at_relocate_start.capacity)) - .left(ZTABLE_ARGS(_at_relocate_end.capacity)) - .left(ZTABLE_ARGS(_at_relocate_end.capacity_high)) - .left(ZTABLE_ARGS(_at_relocate_end.capacity_low)) + .left(ZTABLE_ARGS(_at_mark_start.capacity, max)) + .left(ZTABLE_ARGS(_at_mark_end.capacity, max)) + .left(ZTABLE_ARGS(_at_relocate_start.capacity, max)) + .left(ZTABLE_ARGS(_at_relocate_end.capacity, max)) + .left(ZTABLE_ARGS(_at_relocate_end.capacity_high, max)) + .left(ZTABLE_ARGS(_at_relocate_end.capacity_low, max)) .end()); log_info(gc, heap)("%s", heap_table() .right("Free:") - .left(ZTABLE_ARGS(_at_mark_start.free)) - .left(ZTABLE_ARGS(_at_mark_end.free)) - .left(ZTABLE_ARGS(_at_relocate_start.free)) - .left(ZTABLE_ARGS(_at_relocate_end.free)) - .left(ZTABLE_ARGS(_at_relocate_end.free_high)) - .left(ZTABLE_ARGS(_at_relocate_end.free_low)) + .left(ZTABLE_ARGS(_at_mark_start.free, max)) + .left(ZTABLE_ARGS(_at_mark_end.free, max)) + .left(ZTABLE_ARGS(_at_relocate_start.free, max)) + .left(ZTABLE_ARGS(_at_relocate_end.free, max)) + .left(ZTABLE_ARGS(_at_relocate_end.free_high, max)) + .left(ZTABLE_ARGS(_at_relocate_end.free_low, max)) .end()); log_info(gc, heap)("%s", heap_table() .right("Used:") - .left(ZTABLE_ARGS(_at_mark_start.used)) - .left(ZTABLE_ARGS(_at_mark_end.used)) - .left(ZTABLE_ARGS(_at_relocate_start.used)) - .left(ZTABLE_ARGS(_at_relocate_end.used)) - .left(ZTABLE_ARGS(_at_relocate_end.used_high)) - .left(ZTABLE_ARGS(_at_relocate_end.used_low)) + .left(ZTABLE_ARGS(_at_mark_start.used, max)) + .left(ZTABLE_ARGS(_at_mark_end.used, max)) + .left(ZTABLE_ARGS(_at_relocate_start.used, max)) + .left(ZTABLE_ARGS(_at_relocate_end.used, max)) + .left(ZTABLE_ARGS(_at_relocate_end.used_high, max)) + .left(ZTABLE_ARGS(_at_relocate_end.used_low, max)) .end()); log_info(gc, heap)("%s Generation Statistics:", generation->is_young() ? "Young" : "Old"); @@ -1966,46 +2114,46 @@ void ZStatHeap::print(const ZGeneration* generation) const { .end()); log_info(gc, heap)("%s", gen_table() .right("Used:") - .left(ZTABLE_ARGS(_at_mark_start.used_generation)) - .left(ZTABLE_ARGS(_at_mark_end.used_generation)) - .left(ZTABLE_ARGS(_at_relocate_start.used_generation)) - .left(ZTABLE_ARGS(_at_relocate_end.used_generation)) + .left(ZTABLE_ARGS(_at_mark_start.used_generation, max)) + .left(ZTABLE_ARGS(_at_mark_end.used_generation, max)) + .left(ZTABLE_ARGS(_at_relocate_start.used_generation, max)) + .left(ZTABLE_ARGS(_at_relocate_end.used_generation, max)) .end()); log_info(gc, heap)("%s", gen_table() .right("Live:") .left(ZTABLE_ARGS_NA) - .left(ZTABLE_ARGS(_at_mark_end.live)) - .left(ZTABLE_ARGS(_at_relocate_start.live)) - .left(ZTABLE_ARGS(_at_relocate_end.live)) + .left(ZTABLE_ARGS(_at_mark_end.live, max)) + .left(ZTABLE_ARGS(_at_relocate_start.live, max)) + .left(ZTABLE_ARGS(_at_relocate_end.live, max)) .end()); log_info(gc, heap)("%s", gen_table() .right("Garbage:") .left(ZTABLE_ARGS_NA) - .left(ZTABLE_ARGS(_at_mark_end.garbage)) - .left(ZTABLE_ARGS(_at_relocate_start.garbage)) - .left(ZTABLE_ARGS(_at_relocate_end.garbage)) + .left(ZTABLE_ARGS(_at_mark_end.garbage, max)) + .left(ZTABLE_ARGS(_at_relocate_start.garbage, max)) + .left(ZTABLE_ARGS(_at_relocate_end.garbage, max)) .end()); log_info(gc, heap)("%s", gen_table() .right("Allocated:") .left(ZTABLE_ARGS_NA) - .left(ZTABLE_ARGS(_at_mark_end.mutator_allocated)) - .left(ZTABLE_ARGS(_at_relocate_start.mutator_allocated)) - .left(ZTABLE_ARGS(_at_relocate_end.mutator_allocated)) + .left(ZTABLE_ARGS(_at_mark_end.mutator_allocated, max)) + .left(ZTABLE_ARGS(_at_relocate_start.mutator_allocated, max)) + .left(ZTABLE_ARGS(_at_relocate_end.mutator_allocated, max)) .end()); log_info(gc, heap)("%s", gen_table() .right("Reclaimed:") .left(ZTABLE_ARGS_NA) .left(ZTABLE_ARGS_NA) - .left(ZTABLE_ARGS(_at_relocate_start.reclaimed)) - .left(ZTABLE_ARGS(_at_relocate_end.reclaimed)) + .left(ZTABLE_ARGS(_at_relocate_start.reclaimed, max)) + .left(ZTABLE_ARGS(_at_relocate_end.reclaimed, max)) .end()); if (generation->is_young()) { log_info(gc, heap)("%s", gen_table() .right("Promoted:") .left(ZTABLE_ARGS_NA) .left(ZTABLE_ARGS_NA) - .left(ZTABLE_ARGS(_at_relocate_start.promoted)) - .left(ZTABLE_ARGS(_at_relocate_end.promoted)) + .left(ZTABLE_ARGS(_at_relocate_start.promoted, max)) + .left(ZTABLE_ARGS(_at_relocate_end.promoted, max)) .end()); } log_info(gc, heap)("%s", gen_table() @@ -2013,7 +2161,7 @@ void ZStatHeap::print(const ZGeneration* generation) const { .left(ZTABLE_ARGS_NA) .left(ZTABLE_ARGS_NA) .left(ZTABLE_ARGS_NA) - .left(ZTABLE_ARGS(_at_relocate_end.compacted)) + .left(ZTABLE_ARGS(_at_relocate_end.compacted, max)) .end()); } diff --git a/src/hotspot/share/gc/z/zStat.hpp b/src/hotspot/share/gc/z/zStat.hpp index 739c3cd5980c..42477424b64c 100644 --- a/src/hotspot/share/gc/z/zStat.hpp +++ b/src/hotspot/share/gc/z/zStat.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -46,6 +46,7 @@ class ZRelocationSetSelectorGroupStats; class ZStatSampler; class ZStatSamplerHistory; class ZStatWorkers; +struct ZMemoryPressureMetrics; struct ZStatCounterData; struct ZStatSamplerData; @@ -234,6 +235,9 @@ class ZStatPhaseCollection : public ZStatPhase { void set_used_at_start(size_t used) const; size_t used_at_start() const; + void set_max_at_start(size_t max) const; + size_t max_at_start() const; + public: ZStatPhaseCollection(const char* name, bool minor); @@ -353,6 +357,7 @@ struct ZStatMutatorAllocRateStats { double _avg; double _predict; double _sd; + size_t _sampling_granule; }; // @@ -379,6 +384,37 @@ class ZStatMutatorAllocRate : public AllStatic { static ZStatMutatorAllocRateStats stats(); }; +class ZStatSystemMemoryUsage : public AllStatic { +private: + class ZSystemMemoryUsage { + private: + NumberSeq _highest_usages; + Atomic _highest_usage; + Atomic _memory_stability; + + public: + ZSystemMemoryUsage(); + + void record_usage(double usage); + double memory_stability(); + void sample_and_collect(); + }; + + static ZSystemMemoryUsage _container_usage; + static ZSystemMemoryUsage _machine_usage; + +public: + static void record(const ZMemoryPressureMetrics& metrics); + static void record_container_usage(double usage); + static void record_machine_usage(double usage); + + // Returns 0.0 when memory usage is constant, and increases as memory spikes appear + static double container_memory_stability(); + static double machine_memory_stability(); + + static void sample_and_collect(); +}; + // // Stat thread // @@ -414,6 +450,7 @@ struct ZStatCycleStats { double _sd_parallelizable_time; double _avg_parallelizable_duration; double _sd_parallelizable_duration; + double _last_total_vtime; }; // @@ -430,6 +467,8 @@ class ZStatCycle { NumberSeq _parallelizable_time; NumberSeq _parallelizable_duration; double _last_active_workers; + jlong _start_vtime; + double _last_total_vtime; bool is_warm(); bool is_time_trustable(); @@ -462,6 +501,8 @@ class ZStatWorkers { Tickspan _accumulated_duration; Tickspan _accumulated_time; + volatile double _accumulated_vtime; + double accumulated_duration(); double accumulated_time(); uint active_workers(); @@ -475,9 +516,24 @@ class ZStatWorkers { double get_and_reset_duration(); double get_and_reset_time(); + void add_accumulated_vtime(double vtime); + double get_and_reset_vtime(); + ZStatWorkersStats stats(); }; +// +// Stat memory workers +// +class ZStatMemoryWorkers : public AllStatic { +private: + static volatile double _accumulated_vtime; + +public: + static void add_accumulated_vtime(double vtime); + static double get_and_reset_vtime(); +}; + // // Stat load // @@ -603,7 +659,7 @@ class ZStatHeap { } _at_initialize; struct ZAtGenerationCollectionStart { - size_t soft_max_capacity; + size_t heuristic_max_capacity; size_t capacity; size_t free; size_t used; @@ -611,7 +667,7 @@ class ZStatHeap { } _at_collection_start; struct ZAtMarkStart { - size_t soft_max_capacity; + size_t heuristic_max_capacity; size_t capacity; size_t free; size_t used; @@ -668,7 +724,7 @@ class ZStatHeap { size_t capacity_high() const; size_t capacity_low() const; - size_t free(size_t used) const; + size_t free(size_t used, size_t capacity) const; size_t mutator_allocated(size_t used, size_t freed, size_t relocated) const; size_t garbage(size_t freed, size_t relocated, size_t promoted) const; size_t reclaimed(size_t freed, size_t relocated, size_t promoted) const; @@ -684,10 +740,11 @@ class ZStatHeap { void at_relocate_start(const ZPageAllocatorStats& stats); void at_relocate_end(const ZPageAllocatorStats& stats, bool record_stats); - static size_t max_capacity(); + size_t max_at_collection_start() const; size_t used_at_collection_start() const; size_t used_at_mark_start() const; size_t used_generation_at_mark_start() const; + size_t used_generation_at_relocate_end() const; size_t live_at_mark_end() const; size_t allocated_at_mark_end() const; size_t garbage_at_mark_end() const; diff --git a/src/hotspot/share/gc/z/zTask.cpp b/src/hotspot/share/gc/z/zTask.cpp index 208a20c1914a..4a3e987439fe 100644 --- a/src/hotspot/share/gc/z/zTask.cpp +++ b/src/hotspot/share/gc/z/zTask.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -22,17 +22,29 @@ */ #include "gc/z/zTask.hpp" +#include "gc/z/zWorkers.hpp" +#include "utilities/globalDefinitions.hpp" ZTask::Task::Task(ZTask* task, const char* name) : WorkerTask(name), - _task(task) {} + _task(task), + _workers(nullptr) {} void ZTask::Task::work(uint worker_id) { + if (!_task->account_vtime()) { + _task->work(); + return; + } + + jlong start = os::current_thread_cpu_time(true /* user + sys */); _task->work(); + jlong elapsed = os::current_thread_cpu_time(true /* user + sys */) - start; + _workers->add_accumulated_vtime(double(elapsed) / NANOSECS_PER_SEC); } -ZTask::ZTask(const char* name) - : _worker_task(this, name) {} +ZTask::ZTask(const char* name, bool account_vtime) + : _worker_task(this, name), + _account_vtime(account_vtime) {} const char* ZTask::name() const { return _worker_task.name(); @@ -42,6 +54,14 @@ WorkerTask* ZTask::worker_task() { return &_worker_task; } +void ZTask::set_workers(ZWorkers* workers) { + _worker_task._workers = workers; +} + +bool ZTask::account_vtime() const { + return _account_vtime; +} + ZRestartableTask::ZRestartableTask(const char* name) : ZTask(name) {} diff --git a/src/hotspot/share/gc/z/zTask.hpp b/src/hotspot/share/gc/z/zTask.hpp index 37733f8873a4..7f4ecd8c074f 100644 --- a/src/hotspot/share/gc/z/zTask.hpp +++ b/src/hotspot/share/gc/z/zTask.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,11 +27,15 @@ #include "gc/shared/workerThread.hpp" #include "memory/allocation.hpp" +class ZWorkers; + class ZTask : public StackObj { private: class Task : public WorkerTask { + friend class ZTask; private: ZTask* const _task; + ZWorkers* _workers; public: Task(ZTask* task, const char* name); @@ -40,12 +44,16 @@ class ZTask : public StackObj { }; Task _worker_task; + const bool _account_vtime; public: - ZTask(const char* name); + ZTask(const char* name, bool account_vtime = true); const char* name() const; WorkerTask* worker_task(); + void set_workers(ZWorkers* workers); + + bool account_vtime() const; virtual void work() = 0; }; diff --git a/src/hotspot/share/gc/z/zUncommitter.cpp b/src/hotspot/share/gc/z/zUncommitter.cpp index 96af67a77b53..c419b29419bb 100644 --- a/src/hotspot/share/gc/z/zUncommitter.cpp +++ b/src/hotspot/share/gc/z/zUncommitter.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -22,6 +22,7 @@ */ #include "gc/shared/gc_globals.hpp" +#include "gc/z/zAdaptiveHeap.inline.hpp" #include "gc/z/zGlobals.hpp" #include "gc/z/zHeap.inline.hpp" #include "gc/z/zLock.inline.hpp" @@ -37,9 +38,14 @@ #include "utilities/ticks.hpp" #include +#include static const ZStatCounter ZCounterUncommit("Memory", "Uncommit", ZStatUnitBytesPerSecond); +bool ZUncommitter::is_enabled() { + return ZUncommit && !ZAdaptiveHeapSizing; +} + ZUncommitter::ZUncommitter(uint32_t id, ZPartition* partition) : _id(id), _partition(partition), @@ -51,16 +57,18 @@ ZUncommitter::ZUncommitter(uint32_t id, ZPartition* partition) _cycle_start(0.0), _to_uncommit(0), _uncommitted(0) { + if (!is_enabled()) { + // Disabled, do not start. + _stop = true; + return; + } + set_name("ZUncommitter#%u", id); create_and_start(); } bool ZUncommitter::wait(uint64_t timeout) const { ZLocker locker(&_lock); - while (!ZUncommit && !_stop) { - _lock.wait(); - } - if (!_stop && timeout > 0) { if (!uncommit_cycle_is_finished()) { log_trace(gc, heap)("Uncommitter (%u) Timeout: " UINT64_FORMAT "ms left to uncommit: " @@ -155,7 +163,7 @@ void ZUncommitter::run_thread() { } log_info(gc, heap)("Uncommitter (%u) Uncommitted: %zuM(%.0f%%) in %.3fms", - _id, _uncommitted / M, percent_of(_uncommitted, ZHeap::heap()->max_capacity()), + _id, _uncommitted / M, percent_of(_uncommitted, ZHeap::heap()->heuristic_max_capacity()), accumulated_time.seconds() * MILLIUNITS); } @@ -230,7 +238,7 @@ bool ZUncommitter::activate_uncommit_cycle() { const size_t to_uncommit = align_up(size_t(double(uncommit_watermark) * 0.9), ZGranuleSize); // Never uncommit below min capacity - const size_t uncommit_limit = _partition->_capacity - _partition->_min_capacity; + const size_t uncommit_limit = _partition->_capacity - _partition->_static_min_capacity; _to_uncommit = MIN2(uncommit_limit, to_uncommit); _uncommitted = 0; @@ -375,7 +383,7 @@ size_t ZUncommitter::uncommit() { // We flush out and uncommit chunks at a time (~0.8% of the max capacity, // but at least one granule and at most 256M), in case demand for memory // increases while we are uncommitting. - const size_t current_max_capacity = _partition->_current_max_capacity; + const size_t current_max_capacity = _partition->current_max_capacity(); const size_t limit_upper_bound = MAX2(ZGranuleSize, align_down(256 * M / ZNUMA::count(), ZGranuleSize)); const size_t limit = MIN2(align_up(current_max_capacity >> 7, ZGranuleSize), limit_upper_bound); @@ -387,7 +395,7 @@ size_t ZUncommitter::uncommit() { const size_t to_uncommit = MIN2(_to_uncommit, allowed_to_uncommit); // Never uncommit below min capacity. - const size_t retain = MAX2(_partition->_used, _partition->_min_capacity); + const size_t retain = MAX2(_partition->_used, _partition->_static_min_capacity); const size_t release = _partition->_capacity - retain; const size_t flush = MIN3(release, limit, to_uncommit); @@ -400,7 +408,7 @@ size_t ZUncommitter::uncommit() { } // Record flushed memory as claimed and how much we've flushed for this partition - AtomicAccess::add(&_partition->_claimed, flushed); + _partition->increase_claimed(flushed); } // Unmap and uncommit flushed memory @@ -416,8 +424,8 @@ size_t ZUncommitter::uncommit() { ZLocker locker(&_partition->_page_allocator->_lock); // Adjust claimed and capacity to reflect the uncommit - AtomicAccess::sub(&_partition->_claimed, flushed); - _partition->decrease_capacity(flushed, false /* set_max_capacity */); + _partition->decrease_claimed(flushed); + _partition->decrease_capacity(flushed); register_uncommit(flushed); } diff --git a/src/hotspot/share/gc/z/zUncommitter.hpp b/src/hotspot/share/gc/z/zUncommitter.hpp index a3cd2b17e236..d9e9d6661a6d 100644 --- a/src/hotspot/share/gc/z/zUncommitter.hpp +++ b/src/hotspot/share/gc/z/zUncommitter.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -70,6 +70,8 @@ class ZUncommitter : public ZThread { virtual void terminate(); public: + static bool is_enabled(); + ZUncommitter(uint32_t id, ZPartition* partition); void cancel_uncommit_cycle(); diff --git a/src/hotspot/share/gc/z/zUtils.hpp b/src/hotspot/share/gc/z/zUtils.hpp index cadb11594337..565827bbaf59 100644 --- a/src/hotspot/share/gc/z/zUtils.hpp +++ b/src/hotspot/share/gc/z/zUtils.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,6 +25,7 @@ #define SHARE_GC_Z_ZUTILS_HPP #include "gc/z/zAddress.hpp" +#include "memory/allocation.hpp" #include "memory/allStatic.hpp" #include "utilities/globalDefinitions.hpp" @@ -60,4 +61,17 @@ class ZUtils : public AllStatic { static void sort(T* array, int count, Comparator comparator); }; +template +class ZOnScopeExit : StackObj { + CallableType _call_on_scope_exit; + +public: + NONCOPYABLE(ZOnScopeExit); + template + ZOnScopeExit(Callable&& call_on_scope_exit); + ~ZOnScopeExit(); +}; + +template ZOnScopeExit(CallableType) -> ZOnScopeExit; + #endif // SHARE_GC_Z_ZUTILS_HPP diff --git a/src/hotspot/share/gc/z/zUtils.inline.hpp b/src/hotspot/share/gc/z/zUtils.inline.hpp index 658e278a8dd9..3f6d2ab2b10c 100644 --- a/src/hotspot/share/gc/z/zUtils.inline.hpp +++ b/src/hotspot/share/gc/z/zUtils.inline.hpp @@ -109,4 +109,12 @@ inline void ZUtils::sort(T* array, int count, Comparator comparator) { sort(array, static_cast(count), comparator); } +template +template +ZOnScopeExit::ZOnScopeExit(Callable&& call_on_scope_exit) + : _call_on_scope_exit(static_cast(call_on_scope_exit)) {} + +template +ZOnScopeExit::~ZOnScopeExit() { _call_on_scope_exit(); } + #endif // SHARE_GC_Z_ZUTILS_INLINE_HPP diff --git a/src/hotspot/share/gc/z/zVirtualMemoryManager.cpp b/src/hotspot/share/gc/z/zVirtualMemoryManager.cpp index aec54818a681..e618b7a17fb7 100644 --- a/src/hotspot/share/gc/z/zVirtualMemoryManager.cpp +++ b/src/hotspot/share/gc/z/zVirtualMemoryManager.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -253,7 +253,16 @@ ZVirtualMemoryManager::ZVirtualMemoryManager(size_t max_capacity) EXACTFMTARGS(limit), EXACTFMTARGS(desired), EXACTFMTARGS(requested)); if (reserved < max_capacity) { - ZInitialize::error_d("Failed to reserve " EXACTFMT " address space for Java heap", EXACTFMTARGS(max_capacity)); + reserver.unreserve_all(); + + const bool has_reserve_limit = os::reserve_memory_limit() != SIZE_MAX; + const char* extra_message = has_reserve_limit + ? " Use -Xmx to explicitly set the heap size to limit how much memory is reserved." + : ""; + + ZInitialize::error_d("Failed to reserve " EXACTFMT " address space for Java heap.%s", + EXACTFMTARGS(max_capacity), extra_message); + return; } diff --git a/src/hotspot/share/gc/z/zWorkers.cpp b/src/hotspot/share/gc/z/zWorkers.cpp index 1e7144a0c261..114d9ce2ea67 100644 --- a/src/hotspot/share/gc/z/zWorkers.cpp +++ b/src/hotspot/share/gc/z/zWorkers.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -97,6 +97,7 @@ void ZWorkers::run(ZTask* task) { _stats->at_start(active_workers()); } + task->set_workers(this); _workers.run_task(task->worker_task()); { @@ -130,6 +131,7 @@ void ZWorkers::run_all(ZTask* task) { // Execute task using all workers log_debug(gc, task)("Executing %s using %s with %u workers", task->name(), _workers.name(), active_workers()); + task->set_workers(this); _workers.run_task(task->worker_task()); // Restore number of active workers @@ -164,3 +166,7 @@ void ZWorkers::request_resize_workers(uint nworkers) { _requested_nworkers = nworkers; } + +void ZWorkers::add_accumulated_vtime(double vtime) { + _stats->add_accumulated_vtime(vtime); +} diff --git a/src/hotspot/share/gc/z/zWorkers.hpp b/src/hotspot/share/gc/z/zWorkers.hpp index 8d1f41525626..d65fa1728d29 100644 --- a/src/hotspot/share/gc/z/zWorkers.hpp +++ b/src/hotspot/share/gc/z/zWorkers.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -64,6 +64,8 @@ class ZWorkers { void request_resize_workers(uint nworkers); bool should_worker_resize(); + + void add_accumulated_vtime(double vtime); }; #endif // SHARE_GC_Z_ZWORKERS_HPP diff --git a/src/hotspot/share/gc/z/z_globals.hpp b/src/hotspot/share/gc/z/z_globals.hpp index 06adba80be03..d224b6ee2d06 100644 --- a/src/hotspot/share/gc/z/z_globals.hpp +++ b/src/hotspot/share/gc/z/z_globals.hpp @@ -34,10 +34,19 @@ range, \ constraint) \ \ + product(bool, ZAdaptiveHeapSizing, false, \ + "Enable Adaptive Heap Sizing") \ + \ product(double, ZAllocationSpikeTolerance, 2.0, \ "Allocation spike tolerance factor") \ range(0, INT_MAX) \ \ + product(double, ZGCIntensity, 0, MANAGEABLE, \ + "Level of GC intensity, used for adaptive heap sizing " \ + "policies to use either more CPU or memory") \ + range(0, DBL_MAX) \ + constraint(ZGCIntensityConstraintFunc, AfterErgo) \ + \ product(double, ZFragmentationLimit, 5.0, \ "Maximum allowed heap fragmentation") \ range(0, 100) \ @@ -117,6 +126,9 @@ "Young generation tenuring threshold, -1 for dynamic computation")\ range(-1, static_cast(ZPageAgeCount) - 1) \ \ + product(bool, ZMemoryHeating, true, DIAGNOSTIC, \ + "Use concurrent memory worker(s) to optimize committed memory") \ + \ develop(bool, ZVerifyOops, false, \ "Verify accessed oops") \ \ diff --git a/src/hotspot/share/runtime/arguments.hpp b/src/hotspot/share/runtime/arguments.hpp index 80f993eba70b..84cf7818aae7 100644 --- a/src/hotspot/share/runtime/arguments.hpp +++ b/src/hotspot/share/runtime/arguments.hpp @@ -25,6 +25,7 @@ #ifndef SHARE_RUNTIME_ARGUMENTS_HPP #define SHARE_RUNTIME_ARGUMENTS_HPP +#include "gc/shared/gcArguments.hpp" #include "jni.h" #include "memory/allocation.hpp" #include "memory/allStatic.hpp" diff --git a/src/hotspot/share/runtime/globals_extension.hpp b/src/hotspot/share/runtime/globals_extension.hpp index e01e33eb306f..07ba06ce6c6b 100644 --- a/src/hotspot/share/runtime/globals_extension.hpp +++ b/src/hotspot/share/runtime/globals_extension.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -96,4 +96,16 @@ ALL_FLAGS(DEFINE_FLAG_MEMBER_SETTER, } \ } while (0) +#define FLAG_SET_ERGO_IF_DEFAULT_OR_VALUE(name, set_value, check_value) \ + do { \ + if (FLAG_IS_DEFAULT(name)) { \ + FLAG_SET_ERGO(name, set_value); \ + } else if (name == check_value) { \ + FLAG_SET_ERGO(name, set_value); \ + } \ + } while (0) + +#define FLAG_SET_ERGO_IF_DEFAULT_OR_ZERO(name, value) \ + FLAG_SET_ERGO_IF_DEFAULT_OR_VALUE(name, value, 0) + #endif // SHARE_RUNTIME_GLOBALS_EXTENSION_HPP diff --git a/src/java.base/share/man/java.md b/src/java.base/share/man/java.md index 24f8f4436c4e..af25f5bfcaf5 100644 --- a/src/java.base/share/man/java.md +++ b/src/java.base/share/man/java.md @@ -2929,6 +2929,11 @@ Java HotSpot VM. some throughput cost. Pause times are independent of what heap size is used. Supports heap sizes from 8MB to 16TB. +[`-XX:+ZAdaptiveHeapSizing`]{#-XX__ZAdaptiveHeapSizing} +: Enables Adaptive Heap Sizing for ZGC, which dynamically adjusts the heap + size based on memory availability and system resources. By default, this + option is disabled. + [`-XX:ZAllocationSpikeTolerance=`]{#-XX_ZAllocationSpikeTolerance}*factor* : Sets the allocation spike tolerance for ZGC. By default, this option is set to 2.0. This factor describes the level of allocation spikes to expect. @@ -2945,6 +2950,13 @@ Java HotSpot VM. heap to be compacted more aggressively, to reclaim more memory at the cost of using more CPU time. +[`-XX:ZGCIntensity=`]{#-XX_ZGCIntensity}*intensity* +: Configures the trade-off between memory and CPU usage on the system when + Adaptive Heap Sizing is enabled with `-XX:+ZAdaptiveHeapSizing`. When + enabled, the default value is 5, striking a balance between memory and CPU + that is reasonable for most systems. When Adaptive Heap Sizing is disabled, + the effective value is 0. + [`-XX:+ZProactive`]{#-XX__ZProactive} : Enables proactive GC cycles when using ZGC. By default, this option is enabled. ZGC will start a proactive GC cycle if doing so is expected to diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/gc/z/ZPageAllocator.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/gc/z/ZPageAllocator.java index 46c830cc07c3..00317b6a6095 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/gc/z/ZPageAllocator.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/gc/z/ZPageAllocator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -47,7 +47,10 @@ public class ZPageAllocator extends VMObject { private static synchronized void initialize(TypeDataBase db) { Type type = db.lookupType("ZPageAllocator"); - maxCapacityField = type.getCIntegerField("_max_capacity"); + // This only represents the maximum possible capacity. Which may be different + // from what other servicability APIs (MXBeans, Runtime, etc.) report. + // See ZPageAllocator::static_max_capacity() vs ZPageAllocator::dynamic_max_capacity() + maxCapacityField = type.getCIntegerField("_static_max_capacity"); partitionsOffset = type.getAddressField("_partitions").getOffset(); numaCount = ZNUMA.count(); } diff --git a/test/hotspot/gtest/gc/z/test_zAdaptiveHeap.cpp b/test/hotspot/gtest/gc/z/test_zAdaptiveHeap.cpp new file mode 100644 index 000000000000..5c51b9658545 --- /dev/null +++ b/test/hotspot/gtest/gc/z/test_zAdaptiveHeap.cpp @@ -0,0 +1,182 @@ +/* + * Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include "gc/z/zAdaptiveHeap.hpp" +#include "gc/z/zGlobals.hpp" +#include "unittest.hpp" + +class ZAdaptiveHeapTest : public ::testing::Test { +protected: + static constexpr size_t K = 1024; + static constexpr size_t M = 1024 * K; + static constexpr size_t G = 1024 * M; + + static constexpr size_t container_capacity = 16 * G; + static constexpr size_t machine_capacity = 32 * G; + + static ZHeapResizeMetrics heap_resize_metrics() { + return { + container_capacity - 1 * G, + machine_capacity - 1 * G, + 4 * G, + 2 * M, + 5 * G, + 2 * G, + double(128 * M) + }; + } + + static ZSystemMemoryPressureMetrics sys_mem_pressure_metrics(double usage, physical_memory_size_type max) { + return { + physical_memory_size_type(usage * max), + max, + ZMemoryConcerningThreshold, + ZMemoryHighThreshold, + ZMemoryCriticalThreshold + }; + } + + static ZMemoryPressureMetrics mem_pressure_metrics(double container_usage, physical_memory_size_type container_max, + double machine_usage, physical_memory_size_type machine_max) { + return { + 5.0, + true, + sys_mem_pressure_metrics(machine_usage, machine_max), + sys_mem_pressure_metrics(container_usage, container_max) + }; + } + + static int concern_level(double availability) { + if (availability < ZMemoryCriticalThreshold) { + return 3; + } + + if (availability < ZMemoryHighThreshold) { + return 2; + } + + if (availability < ZMemoryConcerningThreshold) { + return 1; + } + + return 0; + } + + static double memory_pressure(const ZMemoryPressureMetrics& metrics) { + return ZAdaptiveHeap::compute_memory_pressure(metrics); + } +}; + +TEST_F(ZAdaptiveHeapTest, test_mem_pressure) { + double mem_pressures[101 * 101]; + + for (int i = 0; i <= 100; ++i) { + for (int j = 0; j <= 100; ++j) { + double container_availability = double(100 - j) / 100.0; + double machine_availability = double(100 - i) / 100.0; + + int container_concern_level = concern_level(container_availability); + int machine_concern_level = concern_level(machine_availability); + int highest_concern_level = MAX2(container_concern_level, machine_concern_level); + + ZMemoryPressureMetrics metrics = mem_pressure_metrics(1.0 - container_availability, 16 * G, + 1.0 - machine_availability, 32 * G); + + double mem_pressure = memory_pressure(metrics); + + switch (highest_concern_level) { + // Normal + case 0: { + EXPECT_EQ(mem_pressure, 1.0); + EXPECT_FALSE(ZAdaptiveHeap::is_memory_pressure_concerning(metrics)); + EXPECT_FALSE(ZAdaptiveHeap::is_memory_pressure_high(metrics)); + EXPECT_FALSE(ZAdaptiveHeap::is_memory_pressure_critical(metrics)); + break; + } + // Concerning + case 1: { + EXPECT_TRUE(ZAdaptiveHeap::is_memory_pressure_concerning(metrics)); + EXPECT_FALSE(ZAdaptiveHeap::is_memory_pressure_high(metrics)); + EXPECT_FALSE(ZAdaptiveHeap::is_memory_pressure_critical(metrics)); + EXPECT_GT(mem_pressure, 1.0); + EXPECT_LE(mem_pressure, 5.0); + break; + } + + // High + case 2: { + EXPECT_TRUE(ZAdaptiveHeap::is_memory_pressure_concerning(metrics)); + EXPECT_TRUE(ZAdaptiveHeap::is_memory_pressure_high(metrics)); + EXPECT_FALSE(ZAdaptiveHeap::is_memory_pressure_critical(metrics)); + EXPECT_GT(mem_pressure, 5.0); + EXPECT_LE(mem_pressure, 25.0); + break; + } + + // Critical + case 3: { + EXPECT_TRUE(ZAdaptiveHeap::is_memory_pressure_concerning(metrics)); + EXPECT_TRUE(ZAdaptiveHeap::is_memory_pressure_high(metrics)); + EXPECT_TRUE(ZAdaptiveHeap::is_memory_pressure_critical(metrics)); + EXPECT_GT(mem_pressure, 25.0); + } + } + + mem_pressures[i * 101 + j] = mem_pressure; + + if (j > 0) { + // Pressure should not go down when only increasing container usage + EXPECT_GE(mem_pressure, mem_pressures[i * 101 + j - 1]); + } + + if (i > 0) { + // Pressure should not go down when only increasing machine usage + EXPECT_GE(mem_pressure, mem_pressures[(i - 1) * 101 + j]); + } + + double epsilon = 0.001; + + if (j > 1) { + double prev = mem_pressures[i * 101 + j - 1]; + double prev_prev = mem_pressures[i * 101 + j - 2]; + + double diff = mem_pressure - prev; + double prev_diff = prev - prev_prev; + + // Rate of increase should never go down when increasing container usage + EXPECT_GE(diff - prev_diff, -epsilon); + } + + if (i > 1) { + double prev = mem_pressures[(i - 1) * 101 + j]; + double prev_prev = mem_pressures[(i - 2) * 101 + j]; + + double diff = mem_pressure - prev; + double prev_diff = prev - prev_prev; + + // Rate of increase should never go down when increasing machine usage + EXPECT_GE(diff - prev_diff, -epsilon); + } + } + } +} diff --git a/test/hotspot/jtreg/ProblemList-Virtual.txt b/test/hotspot/jtreg/ProblemList-Virtual.txt index 775479953383..7edf6ce88f71 100644 --- a/test/hotspot/jtreg/ProblemList-Virtual.txt +++ b/test/hotspot/jtreg/ProblemList-Virtual.txt @@ -27,6 +27,7 @@ runtime/jni/critical/SuspendInCritical.java 8384369 generic-all serviceability/AsyncGetCallTrace/MyPackage/ASGCTBaseTest.java 8308026 generic-all serviceability/jvmti/Heap/IterateHeapWithEscapeAnalysisEnabled.java 8264699 generic-all +serviceability/jvmti/FollowReferences/KindSystemClass/KindSystemClass.java 8383422 generic-all vmTestbase/vm/mlvm/indy/func/jvmti/mergeCP_indy2manyDiff_a/TestDescription.java 8308367 generic-all vmTestbase/nsk/jvmti/unit/functions/Dispose/JvmtiTest/TestDescription.java 8387429 generic-all diff --git a/test/hotspot/jtreg/gc/arguments/TestSoftMaxHeapSizeFlag.java b/test/hotspot/jtreg/gc/arguments/TestSoftMaxHeapSizeFlag.java index 8f0f17c23a73..e6eac6aad9b6 100644 --- a/test/hotspot/jtreg/gc/arguments/TestSoftMaxHeapSizeFlag.java +++ b/test/hotspot/jtreg/gc/arguments/TestSoftMaxHeapSizeFlag.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -41,10 +41,10 @@ public class TestSoftMaxHeapSizeFlag { private static final long betweenXmsAndXmx = (Xms + Xmx) / 2; public static void main(String args[]) throws Exception { - // Test default value + // Test default value; expect 0 or Xmx ProcessTools.executeTestJava("-Xms" + Xms, "-Xmx" + Xmx, "-XX:+PrintFlagsFinal", "-version") - .shouldMatch("SoftMaxHeapSize[ ]+=[ ]+" + Xmx) + .shouldMatch("(SoftMaxHeapSize[ ]+=[ ]+" + 0 + ")|(SoftMaxHeapSize[ ]+=[ ]+" + Xmx + ")") .shouldHaveExitValue(0); // Test setting small value diff --git a/test/hotspot/jtreg/gc/z/TestAdaptiveHeapSizingMode.java b/test/hotspot/jtreg/gc/z/TestAdaptiveHeapSizingMode.java new file mode 100644 index 000000000000..01c1570abca6 --- /dev/null +++ b/test/hotspot/jtreg/gc/z/TestAdaptiveHeapSizingMode.java @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package gc.z; + +/** + * @test TestAdaptiveHeapSizingMode + * @requires vm.gc.Z & vm.flagless + * @summary Test that heap size flags affect ZGC Adaptive Heap Sizing mode. + * @library / /test/lib + * @run driver gc.z.TestAdaptiveHeapSizingMode + */ + +import jdk.test.lib.JDKToolFinder; + +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class TestAdaptiveHeapSizingMode { + private static List createTestSpecificArgs(String... heapSizeArgs) { + List args = new ArrayList<>(); + args.add("-XX:+UseZGC"); + args.add("-XX:+ZAdaptiveHeapSizing"); + args.add("-Xlog:gc+init"); + args.addAll(Arrays.asList(heapSizeArgs)); + args.add("--version"); + return args; + } + + private static void assertExpectedMode(OutputAnalyzer out, List args, String expectedMode) { + String output = out.getOutput(); + String needle = "Heap Sizing: " + expectedMode; + if (!output.contains(needle)) { + throw new AssertionError("Expected to find: \"" + needle + "\"\n" + + "Arguments: " + String.join(" ", args) + "\n" + + "Output:\n" + output); + } + } + + private static void assertHeapSizingMode(String expectedMode, String... heapSizeArgs) throws Exception { + List args = createTestSpecificArgs(heapSizeArgs); + + // Launch child JVM + ProcessBuilder pb = ProcessTools.createLimitedTestJavaProcessBuilder(args); + OutputAnalyzer out = new OutputAnalyzer(pb.start()); + out.shouldHaveExitValue(0); + + assertExpectedMode(out, args, expectedMode); + } + + private static void assertHeapSizingModeClean(String expectedMode, String... heapSizeArgs) throws Exception { + String java = JDKToolFinder.getJDKTool("java"); + List args = createTestSpecificArgs(heapSizeArgs); + + List cmd = new ArrayList<>(args.size() + 1); + cmd.add(java); // executable + cmd.addAll(args); // arguments + + // Launch child JVM + ProcessBuilder pb = new ProcessBuilder(cmd); + OutputAnalyzer out = new OutputAnalyzer(pb.start()); + out.shouldHaveExitValue(0); + + assertExpectedMode(out, args, expectedMode); + } + + private static final String FIXED_MODE = "Fixed"; + private static final String IMPLICIT_MODE = "Adaptive (Implicit Boundaries)"; + private static final String EXPLICIT_MODE = "Adaptive (Explicit Boundaries)"; + + public static void main(String[] args) throws Exception { + // No flags + assertHeapSizingModeClean(IMPLICIT_MODE); + + // Only lower-bound + assertHeapSizingModeClean(EXPLICIT_MODE, + "-XX:MinHeapSize=100m"); + assertHeapSizingModeClean(EXPLICIT_MODE, + "-Xms100m"); + + // Only upper-bound + assertHeapSizingModeClean(EXPLICIT_MODE, + "-XX:MaxRAMPercentage=10"); + assertHeapSizingModeClean(EXPLICIT_MODE, + "-Xmx100m"); + + // Xms == Xmx + assertHeapSizingMode(FIXED_MODE, + "-Xms100m", + "-Xmx100m"); + + // Xms != Xmx + assertHeapSizingMode(EXPLICIT_MODE, + "-Xms50m", + "-Xmx100m"); + + // MinHeapSize == Xmx + assertHeapSizingMode(FIXED_MODE, + "-XX:MinHeapSize=100m", + "-Xmx100m"); + + // MinHeapSize < Xmx + assertHeapSizingMode(EXPLICIT_MODE, + "-XX:MinHeapSize=50m", + "-Xmx100m"); + } +} diff --git a/test/hotspot/jtreg/gc/z/TestUncommit.java b/test/hotspot/jtreg/gc/z/TestUncommit.java index 0e674358be54..1a8f7f0ee829 100644 --- a/test/hotspot/jtreg/gc/z/TestUncommit.java +++ b/test/hotspot/jtreg/gc/z/TestUncommit.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,8 +26,9 @@ /* * @test TestUncommit * @requires vm.gc.Z + * @requires vm.opt.ZAdaptiveHeapSizing == null | !vm.opt.ZAdaptiveHeapSizing * @summary Test ZGC uncommit unused memory - * @run main/othervm/timeout=480 -XX:+UseZGC -Xlog:gc*,gc+heap=debug,gc+stats=off -Xms128M -Xmx512M -XX:ZUncommitDelay=5 gc.z.TestUncommit + * @run main/othervm/timeout=480 -XX:+UseZGC -Xlog:gc*,gc+heap=debug,gc+stats=off -XX:-ZAdaptiveHeapSizing -Xms128M -Xmx512M -XX:ZUncommitDelay=5 gc.z.TestUncommit */ import java.util.ArrayList; diff --git a/test/hotspot/jtreg/runtime/CommandLine/OptionsValidation/TestOptionsWithRangesDynamic.java b/test/hotspot/jtreg/runtime/CommandLine/OptionsValidation/TestOptionsWithRangesDynamic.java index 480e8929ff8f..709a5d656259 100644 --- a/test/hotspot/jtreg/runtime/CommandLine/OptionsValidation/TestOptionsWithRangesDynamic.java +++ b/test/hotspot/jtreg/runtime/CommandLine/OptionsValidation/TestOptionsWithRangesDynamic.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -61,6 +61,10 @@ public static void main(String[] args) throws Exception { * Exclude SoftMaxHeapSize as its valid range is only known at runtime. */ excludeTestRange("SoftMaxHeapSize"); + /* + * Exclude ZGCIntensity as its valid range depends on its initial value. + */ + excludeTestRange("ZGCIntensity"); Asserts.assertGT(allWriteableOptions.size(), 0, "Options with ranges not found!"); diff --git a/test/jdk/java/lang/management/MemoryMXBean/CollectionUsageThreshold.java b/test/jdk/java/lang/management/MemoryMXBean/CollectionUsageThreshold.java index 33f18a4e533c..7b1d927b7405 100644 --- a/test/jdk/java/lang/management/MemoryMXBean/CollectionUsageThreshold.java +++ b/test/jdk/java/lang/management/MemoryMXBean/CollectionUsageThreshold.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -72,7 +72,11 @@ public class CollectionUsageThreshold { */ public static void main(String a[]) throws Throwable { final String main = "CollectionUsageThreshold$TestMain"; - RunUtil.runTestKeepGcOpts(main); + // CollectionUsageThreshold$TestMain expects only System.gc() triggered + // GCs. ZGC's Adaptive Heap Sizing thus requires some buffer to not + // trigger heuristic GCs. Similarly this test is incompatible with any + // period GC heuristics / options like ZCollectionInterval. + RunUtil.runTestKeepGcOpts(main, "-Xms128m", "-Xmx512m"); RunUtil.runTestClearGcOpts(main, "-XX:+UseSerialGC"); RunUtil.runTestClearGcOpts(main, "-XX:+UseParallelGC"); RunUtil.runTestClearGcOpts(main, "-XX:+UseG1GC"); @@ -259,9 +263,11 @@ private void checkResult() throws InterruptedException, BrokenBarrierException { } private void fail(String msg) { - // reset the barrier to cause BrokenBarrierException to avoid hanging - barrier.reset(); - throw new RuntimeException(msg); + System.out.println("Checker failed: " + msg); + // We cannot simply do a barrier.reset() here as the main thread may + // not yet have reached the barrier yet. For example in the case we + // got an unexpected GC. + System.exit(-1); } } } diff --git a/test/jdk/jdk/jfr/event/gc/detailed/TestZUncommitEvent.java b/test/jdk/jdk/jfr/event/gc/detailed/TestZUncommitEvent.java index f77ada9bf1bf..320ffb66889c 100644 --- a/test/jdk/jdk/jfr/event/gc/detailed/TestZUncommitEvent.java +++ b/test/jdk/jdk/jfr/event/gc/detailed/TestZUncommitEvent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -38,7 +38,7 @@ * @requires vm.hasJFR & vm.gc.Z * @requires vm.flagless * @library /test/lib /test/jdk /test/hotspot/jtreg - * @run main/othervm -XX:+UseZGC -Xms32M -Xmx128M -Xlog:gc,gc+heap -XX:+ZUncommit -XX:ZUncommitDelay=1 jdk.jfr.event.gc.detailed.TestZUncommitEvent + * @run main/othervm -XX:+UseZGC -Xms32M -Xmx128M -Xlog:gc,gc+heap -XX:+ZUncommit -XX:ZUncommitDelay=1 -XX:-ZAdaptiveHeapSizing jdk.jfr.event.gc.detailed.TestZUncommitEvent */ public class TestZUncommitEvent { From 69c9e4add9b384924cb8fd39febd4427eacbae8e Mon Sep 17 00:00:00 2001 From: Yunbo Zhang Date: Wed, 21 Oct 2026 14:12:22 +0000 Subject: [PATCH 160/169] 8386253: G1: Old gen collectionUsage memory pool should be updated on eager reclaim Reviewed-by: tschatzl --- .../share/gc/g1/g1MonitoringSupport.cpp | 12 +++ .../share/gc/g1/g1MonitoringSupport.hpp | 2 + src/hotspot/share/services/memoryService.hpp | 4 +- ...ReclaimHumongousMXBeanCollectionUsage.java | 100 ++++++++++++++++++ 4 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 test/hotspot/jtreg/gc/g1/TestEagerReclaimHumongousMXBeanCollectionUsage.java diff --git a/src/hotspot/share/gc/g1/g1MonitoringSupport.cpp b/src/hotspot/share/gc/g1/g1MonitoringSupport.cpp index 72a17f563a09..01f8f67c3d66 100644 --- a/src/hotspot/share/gc/g1/g1MonitoringSupport.cpp +++ b/src/hotspot/share/gc/g1/g1MonitoringSupport.cpp @@ -367,6 +367,10 @@ G1MonitoringScope::~G1MonitoringScope() { MemoryService::track_memory_usage(); } +void G1MonitoringScope::set_all_memory_pools_affected() { + _tms.set_all_memory_pools_affected(); +} + G1YoungGCMonitoringScope::G1YoungGCMonitoringScope(G1MonitoringSupport* monitoring_support, bool all_memory_pools_affected) : G1MonitoringScope(monitoring_support, @@ -376,6 +380,14 @@ G1YoungGCMonitoringScope::G1YoungGCMonitoringScope(G1MonitoringSupport* monitori all_memory_pools_affected) { } +G1YoungGCMonitoringScope::~G1YoungGCMonitoringScope() { + G1GCPhaseTimes* phase_times = G1CollectedHeap::heap()->phase_times(); + if (phase_times->sum_thread_work_items(G1GCPhaseTimes::EagerlyReclaimHumongousObjects, + G1GCPhaseTimes::EagerlyReclaimNumReclaimed) != 0) { + set_all_memory_pools_affected(); + } +} + G1FullGCMonitoringScope::G1FullGCMonitoringScope(G1MonitoringSupport* monitoring_support) : G1MonitoringScope(monitoring_support, monitoring_support->_full_collection_counters, diff --git a/src/hotspot/share/gc/g1/g1MonitoringSupport.hpp b/src/hotspot/share/gc/g1/g1MonitoringSupport.hpp index 8a7040e626d8..7cdbad9054fd 100644 --- a/src/hotspot/share/gc/g1/g1MonitoringSupport.hpp +++ b/src/hotspot/share/gc/g1/g1MonitoringSupport.hpp @@ -232,11 +232,13 @@ class G1MonitoringScope : public StackObj { const char* end_message, bool all_memory_pools_affected = true); ~G1MonitoringScope(); + void set_all_memory_pools_affected(); }; class G1YoungGCMonitoringScope : public G1MonitoringScope { public: G1YoungGCMonitoringScope(G1MonitoringSupport* monitoring_support, bool all_memory_pools_affected); + ~G1YoungGCMonitoringScope(); }; class G1FullGCMonitoringScope : public G1MonitoringScope { diff --git a/src/hotspot/share/services/memoryService.hpp b/src/hotspot/share/services/memoryService.hpp index 1a947ec1d0c9..6541c6ea3870 100644 --- a/src/hotspot/share/services/memoryService.hpp +++ b/src/hotspot/share/services/memoryService.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -152,6 +152,8 @@ class TraceMemoryManagerStats : public StackObj { bool recordGCEndTime, bool countCollection); + void set_all_memory_pools_affected() { _allMemoryPoolsAffected = true; } + ~TraceMemoryManagerStats(); }; diff --git a/test/hotspot/jtreg/gc/g1/TestEagerReclaimHumongousMXBeanCollectionUsage.java b/test/hotspot/jtreg/gc/g1/TestEagerReclaimHumongousMXBeanCollectionUsage.java new file mode 100644 index 000000000000..09d7a77d9507 --- /dev/null +++ b/test/hotspot/jtreg/gc/g1/TestEagerReclaimHumongousMXBeanCollectionUsage.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package gc.g1; + +/* + * @test TestEagerReclaimHumongousMXBeanCollectionUsage + * @bug 8386253 + * @summary Test that eager reclaim updates the old generation collection usage. + * @requires vm.gc.G1 + * @library /test/lib / + * @build jdk.test.whitebox.WhiteBox + * @modules java.base/jdk.internal.misc + * java.management + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm -XX:+UseG1GC -Xms128m -Xmx128m -XX:G1HeapRegionSize=1m + * -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * gc.g1.TestEagerReclaimHumongousMXBeanCollectionUsage + */ + +import java.lang.management.ManagementFactory; +import java.lang.management.MemoryPoolMXBean; + +import jdk.test.lib.Asserts; +import jdk.test.whitebox.WhiteBox; + +public class TestEagerReclaimHumongousMXBeanCollectionUsage { + private static final WhiteBox WB = WhiteBox.getWhiteBox(); + private static final int M = 1024 * 1024; + private static Object humongous; + + private static MemoryPoolMXBean findOldGenPool() { + for (MemoryPoolMXBean pool : ManagementFactory.getMemoryPoolMXBeans()) { + if (pool.getName().equals("G1 Old Gen")) { + return pool; + } + } + throw new RuntimeException("G1 Old Gen pool not found"); + } + + public static void main(String[] args) throws Exception { + MemoryPoolMXBean oldGen = findOldGenPool(); + + WB.fullGC(); + long baseline = oldGen.getCollectionUsage().getUsed(); + + humongous = new byte[4 * M]; + WB.concurrentGCAcquireControl(); + long withHumongous; + try { + WB.concurrentGCRunTo(WB.G1_AFTER_REBUILD_STARTED); + withHumongous = oldGen.getCollectionUsage().getUsed(); + WB.concurrentGCRunToIdle(); + } finally { + WB.concurrentGCReleaseControl(); + } + + // Keep the humongous object live across several young collections. + for (int i = 0; i < 5; i++) { + WB.youngGC(); + } + + humongous = null; + WB.youngGC(); + + long current = oldGen.getUsage().getUsed(); + long collection = oldGen.getCollectionUsage().getUsed(); + System.out.println("baseline=" + baseline + + " withHumongous=" + withHumongous + + " current=" + current + + " collection=" + collection); + + Asserts.assertGT(withHumongous, baseline, + "The humongous object should be reflected in collection usage"); + Asserts.assertLT(current, withHumongous, + "The eagerly reclaimed object should no longer be in current usage"); + Asserts.assertLT(collection, withHumongous, + "Collection usage should be updated after eager reclaim"); + } +} From 5c1c7c1b8bdc7b607111a62dbc96ef34e1506bee Mon Sep 17 00:00:00 2001 From: Daniel Fuchs Date: Wed, 21 Oct 2026 14:30:08 +0000 Subject: [PATCH 161/169] 8394607: HttpClient HTTP/2: fully received response body discarded when peer closes connection before InputStream is read (regression from JDK-8317808) Reviewed-by: djelinski --- .../internal/net/http/Http3Connection.java | 27 +- .../internal/net/http/Http3ExchangeImpl.java | 36 ++- .../classes/jdk/internal/net/http/Stream.java | 30 ++- .../http/qpack/readers/HeaderFrameReader.java | 6 +- .../http2/ConnectionFlowControlTest.java | 16 ++ .../http2/H2GoAwayPromptConnectionClose.java | 170 +++++++++++-- .../http3/H3RespondGoAwayAndClose.java | 235 ++++++++++++++++++ .../test/lib/http3/Http3ServerExchange.java | 21 ++ 8 files changed, 514 insertions(+), 27 deletions(-) create mode 100644 test/jdk/java/net/httpclient/http3/H3RespondGoAwayAndClose.java diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/Http3Connection.java b/src/java.net.http/share/classes/jdk/internal/net/http/Http3Connection.java index 024de6bd49b3..cb7b4889a672 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/Http3Connection.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/Http3Connection.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -668,13 +668,27 @@ void close(final Http3Error error, final String logMsg, closeCause == null ? "" : closeCause.toString()); debug.log("State is: " + describeClosedState(closedState)); } + // record the error in each unfinished exchange exchanges.values().forEach(e -> e.recordError(closeCause)); // close the underlying QUIC connection connection.close(error.code(), logMsg, closeCause); final TerminationCause tc = connection.quicConnection.terminationCause(); assert tc != null : "termination cause is null"; - // close all HTTP streams - exchanges.values().forEach(exchange -> exchange.cancelImpl(tc.getCloseCause(), error)); + + // Figure out whether this is an orderly close. + boolean orderlyClose = tc.isAppLayer() && tc.getCloseCode() == H3_NO_ERROR.code(); + if (orderlyClose) { + // For an orderly close, we will let the caller pull the bytes + // from exchanges which have received all promised bytes + // (but haven't processed them yet), but cancel all exchanges + // for which not all bytes have been received. + exchanges.values().stream() + .filter(Http3ExchangeImpl::shouldCancelOnOrderlyClose) + .forEach(exchange -> exchange.cancelImpl(tc.getCloseCause(), error)); + } else { + // close all HTTP streams + exchanges.values().forEach(exchange -> exchange.cancelImpl(tc.getCloseCause(), error)); + } pushManager.cancelAllPromises(tc.getCloseCause(), error); discardConnectionState(); // No longer wait for reading HTTP/3 stream types: @@ -717,6 +731,13 @@ void connectionError(Http3Stream exchange, Throwable throwable, long errorCod final Optional error = Http3Error.fromCode(errorCode); assert error.isPresent() : "not a HTTP3 error code: " + errorCode; close(error.get(), logMsg, throwable); + if (exchange instanceof Http3ExchangeImpl impl) { + // if the error originates from the exchange itself, + // makes sure it gets cancelled even if it has buffered + // all data, as processing buffered data can still trigger + // malformed frame errors + impl.cancelImpl(throwable, error.get()); + } } public String toString() { diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/Http3ExchangeImpl.java b/src/java.net.http/share/classes/jdk/internal/net/http/Http3ExchangeImpl.java index 4625968682f1..b8970293b362 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/Http3ExchangeImpl.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/Http3ExchangeImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -84,6 +84,8 @@ import static jdk.internal.net.http.common.Utils.readContentLength; import static jdk.internal.net.http.http3.ConnectionSettings.UNLIMITED_MAX_FIELD_SECTION_SIZE; +import static jdk.internal.net.http.quic.streams.QuicReceiverStream.ReceivingStreamState.DATA_READ; +import static jdk.internal.net.http.quic.streams.QuicReceiverStream.ReceivingStreamState.DATA_RECVD; /** * This class represents an HTTP/3 Request/Response stream. @@ -196,7 +198,12 @@ Http3Connection http3Connection() { } void recordError(Throwable closeCause) { - errorRef.compareAndSet(null, closeCause); + if (!allDataReceivedButStillBuffered()) { + // only record the error if we have not received + // all bytes; Otherwise we can let the caller + // pull them. + errorRef.compareAndSet(null, closeCause); + } } private sealed class HeadersConsumer extends StreamHeadersConsumer permits PushHeadersConsumer { @@ -1724,6 +1731,31 @@ void onSubscriptionError(Throwable t) { readScheduler.runOrSchedule(); } + // We do not want to cancel the exchange on orderly + // close if all data has been received but some of + // it is still buffered. In that case we will + // let the client pull the buffered data even + // after the underlying connection has closed. + boolean shouldCancelOnOrderlyClose() { + return !allDataReceivedButStillBuffered(); + } + + private boolean allDataReceivedButStillBuffered() { + // if responseReceived is true, no data is still + // buffered + if (responseReceived) return false; + + // if the headerFrameReader is blocked, the data + // the QPack decoder is waiting for may not be present + // in the decoder stream, or may never arrive: + // in this case, we will consider that not all data has + // arrived, regardless of our receiving stream state. + if (headerFrameReader.isBlocked()) return false; + + var state = stream.receivingState(); + return state == DATA_RECVD || state == DATA_READ; + } + // This loop is triggered to push response body data into // the body subscriber. It is called from the processQuicData // loop. However, we cannot call onNext() if we have no demands. diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/Stream.java b/src/java.net.http/share/classes/jdk/internal/net/http/Stream.java index 9826b280438d..779040bb89ac 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/Stream.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/Stream.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -130,7 +130,7 @@ class Stream extends ExchangeImpl { final HeadersConsumer rspHeadersConsumer; final HttpHeadersBuilder responseHeadersBuilder; final HttpHeaders requestPseudoHeaders; - volatile HttpResponse.BodySubscriber responseSubscriber; + volatile BodySubscriber responseSubscriber; final HttpRequest.BodyPublisher requestPublisher; volatile RequestSubscriber requestSubscriber; volatile int responseCode; @@ -139,7 +139,7 @@ class Stream extends ExchangeImpl { private final AtomicReference errorRef = new AtomicReference<>(); final CompletableFuture requestBodyCF = new MinimalFuture<>(); volatile CompletableFuture responseBodyCF; - volatile HttpResponse.BodySubscriber pendingResponseSubscriber; + volatile BodySubscriber pendingResponseSubscriber; volatile boolean stopRequested; /** True if END_STREAM has been seen in a frame received on this stream. */ @@ -182,13 +182,18 @@ HttpConnection connection() { return connection.connection; } + private CompletableFuture getBody(BodySubscriber subscriber) { + var bodyCf = ResponseSubscribers.getBodyAsync(exchange.parentExecutor, subscriber); + return bodyCf == null ? null : bodyCf.toCompletableFuture(); + } + /** * Invoked either from incoming() -> {receiveDataFrame() or receiveResetFrame() } * of after user subscription window has re-opened, from SubscriptionBase.request() */ private void schedule() { boolean onCompleteCalled = false; - HttpResponse.BodySubscriber subscriber = responseSubscriber; + BodySubscriber subscriber = responseSubscriber; // prevents drainInputQueue() from running concurrently inputQLock.lock(); try { @@ -203,7 +208,22 @@ private void schedule() { if (debug.on()) debug.log("subscribing user subscriber"); subscriber.onSubscribe(userSubscription); } - while (!inputQ.isEmpty() && errorRef.get() == null) { + + CompletableFuture bodyCf = null; + while (!inputQ.isEmpty()) { + if (errorRef.get() != null) { + if (bodyCf == null) bodyCf = getBody(subscriber); + + // if the bodyCf is completed exceptionally, or cancelled, + // there is no point in pulling the data. + // if bodyCf is null just stop here; this is not expected. + if (bodyCf == null || bodyCf.isCompletedExceptionally()) break; + + // otherwise, we just ignore the error for now to give a chance + // to the caller to pull the buffered data. The error will + // eventually be reported when the buffered data has been pulled, + // unless the entire response was successfully decoded and completed. + } Http2Frame frame = inputQ.peek(); if (frame instanceof ResetFrame rf) { inputQ.remove(); diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/qpack/readers/HeaderFrameReader.java b/src/java.net.http/share/classes/jdk/internal/net/http/qpack/readers/HeaderFrameReader.java index a4ea55661fe6..78949bfb481e 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/qpack/readers/HeaderFrameReader.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/qpack/readers/HeaderFrameReader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -321,6 +321,10 @@ private void resetPrefixVars() { fieldSectionSizeTracker.set(0); } + public boolean isBlocked() { + return state == State.AWAITING_DT_INSERT_COUNT; + } + public FieldSectionPrefix decodedSectionPrefix() { if (deltaBase == -1L) { throw new IllegalStateException("Field Section Prefix not parsed yet"); diff --git a/test/jdk/java/net/httpclient/http2/ConnectionFlowControlTest.java b/test/jdk/java/net/httpclient/http2/ConnectionFlowControlTest.java index 36c46ed3aba9..459d3c7a223f 100644 --- a/test/jdk/java/net/httpclient/http2/ConnectionFlowControlTest.java +++ b/test/jdk/java/net/httpclient/http2/ConnectionFlowControlTest.java @@ -162,6 +162,7 @@ void test(String uri) throws Exception { is.readAllBytes(); } System.out.printf("%s did not fail: %s%n", query, response.statusCode()); + responses.remove(query, response); } catch (AssertionError t1) { // since we won't pull all responses, the client // will not exit unless we ask it to shutdown now. @@ -189,6 +190,21 @@ void test(String uri) throws Exception { t0 = t0.getCause(); } t = t0; + // make sure to pull on the already received responses + // if any - otherwise attempting to close the client will + // block forever; This may happen if this line above throws: + // var response = cc.send(request, BodyHandlers.ofInputStream()); + if (!responses.isEmpty() && !client.isTerminated()) { + for (var e : responses.entrySet()) { + System.out.println("Pulling response: " + e.getKey()); + try (var is = e.getValue().body()) { + is.readAllBytes(); + System.out.println("Got response for: " + e.getKey()); + } catch (Exception x) { + System.out.println("Got exception for: " + e.getKey() + ": " + x); + } + } + } } if (t == null) { // we could fail here if we haven't waited long enough diff --git a/test/jdk/java/net/httpclient/http2/H2GoAwayPromptConnectionClose.java b/test/jdk/java/net/httpclient/http2/H2GoAwayPromptConnectionClose.java index 83237bf76c00..a94d42c41434 100644 --- a/test/jdk/java/net/httpclient/http2/H2GoAwayPromptConnectionClose.java +++ b/test/jdk/java/net/httpclient/http2/H2GoAwayPromptConnectionClose.java @@ -34,6 +34,7 @@ import java.net.http.HttpResponse; import java.net.http.HttpResponse.BodyHandlers; import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; import java.time.Duration; import java.time.Instant; import java.util.ArrayList; @@ -41,14 +42,16 @@ import java.util.List; import java.util.Map; import java.util.Random; -import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.Semaphore; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLParameters; import javax.net.ssl.SSLServerSocket; +import jdk.internal.net.http.frame.DataFrame; +import jdk.internal.net.http.frame.FramesEncoder; import jdk.internal.net.http.frame.HeadersFrame; import jdk.internal.net.http.frame.Http2Frame; import jdk.internal.net.http.frame.SettingsFrame; @@ -62,14 +65,17 @@ import org.junit.jupiter.api.Test; import static java.net.http.HttpClient.Builder.NO_PROXY; import static java.net.http.HttpClient.Version.HTTP_2; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; /* * @test - * @bug 8385131 + * @bug 8385131 8394607 * @summary Verifies that if the HttpClient receives a GOAWAY frame on a HTTP/2 connection, * then the HttpClient terminates the connection as promptly as appropriate instead - * of relying on the idle connection management to do so. + * of relying on the idle connection management to do so. Also verifies that if + * the server closes the connection first, the client can still receive the response + * bytes after the connection is closed. * @library /test/lib * @build jdk.test.lib.net.SimpleSSLContext jdk.test.lib.RandomFactory jdk.test.lib.net.URIBuilder * @comment An arbitrary high value for idle connection timeout to prevent idle @@ -84,14 +90,22 @@ class H2GoAwayPromptConnectionClose { private static final String REQUEST_PATH = "/" + H2GoAwayPromptConnectionClose.class.getSimpleName(); + private static final String XKEY = "x-send-response-and-close"; + private static final String RESPONSE = """ + Lorem ipsum dolor sit amet, + caesarem legato alacrem. + """; + private static final Random random = RandomFactory.getRandom(); + private static final SSLContext sslCtx = SimpleSSLContext.findSSLContext(); private static final ExecutorService executor = Executors.newCachedThreadPool(); private static HttpClient client; private static MinimalH2OnlyServer server; - // a latch that is counted down when the server notices a EOF on the connection's input stream - // due to the client closing the connection. - private static final CountDownLatch connTerminationLatch = new CountDownLatch(1); + // a semaphore that is released when the server notices an EOF on the connection's + // input stream due to the client closing the connection, or when the server + // closes the connection without waiting for the client + private static final Semaphore connTerminationLatch = new Semaphore(0); @BeforeAll static void beforeAll() throws Exception { @@ -142,6 +156,7 @@ void testConnectionTermination() throws Exception { .port(serverAddr.getPort()) .path(REQUEST_PATH) .build(); + assertEquals(0, connTerminationLatch.availablePermits()); final HttpRequest req = HttpRequest.newBuilder().version(HTTP_2).uri(reqURI).build(); System.err.println("issuing request: " + req); final HttpResponse resp = client.send(req, BodyHandlers.ofString()); @@ -149,11 +164,71 @@ void testConnectionTermination() throws Exception { assertEquals(HTTP_2, resp.version(), "unexpected HTTP version in response"); System.err.println("awaiting connection termination"); - connTerminationLatch.await(); + connTerminationLatch.acquire(); + } + + /* + * Issues a "https" HTTP/2 request and expects the request to complete normally. + * On the server side, in addition to sending the HTTP/2 response, the server also sends + * a GOAWAY frame to the client. In this scenario, the server sends a response body + * composed of several DATA frames, then immediately closes the connection after + * having sent the response body bytes and GOAWAY. + * The client waits for the server to close the connection, then waits for a short + * delay before starting to pull the response body bytes from the HttpResponseInputStream. + * The test completes successfully only if the client is able to pull all the response bytes + * from the input stream, despite the server having already closed the connection. + * If it fails to pull all the bytes, then it's a sign that the exchange was + * cancelled immediately on reception of the connection FIN, despite having buffered all + * the response bytes. + * This scenario will usually fail without the fix for JDK-8394607. + */ + @Test + void testBufferedResponseReceived() throws Exception { + System.out.println("testBufferedResponseReceived"); + final InetSocketAddress serverAddr = server.getServerAddress(); + final URI reqURI = URIBuilder.newBuilder() + .scheme("https") + .host(serverAddr.getAddress()) + .port(serverAddr.getPort()) + .path(REQUEST_PATH) + .build(); + final HttpRequest req = HttpRequest.newBuilder().version(HTTP_2) + .uri(reqURI) + .header(XKEY, "yes") + .build(); + for (int i=0; i < 5; i++) { + assertEquals(0, connTerminationLatch.availablePermits()); + System.out.printf("issuing request[%s]: %s%n", i, req); + System.err.printf("issuing request[%s]: %s%n", i, req); + final HttpResponse resp = client.send(req, BodyHandlers.ofInputStream()); + assertEquals(200, resp.statusCode(), "unexpected status code"); + assertEquals(HTTP_2, resp.version(), "unexpected HTTP version in response"); + System.out.println("awaiting connection termination"); + System.err.println("awaiting connection termination"); + connTerminationLatch.acquire(); + + // At this point the server has closed the connection. + // Wait a little bit to give a chance to the client to + // observe connection closure, since having closed the + // connection from the server side doesn't guarantee that + // the TCP FIN has reached the client side and has already + // been processed. + Thread.sleep(random.nextLong(100, 1000)); + + // Start pulling the bytes from the input stream. The bytes + // should have been all received and buffered, so it should + // be possible to pull them even though the connection was closed + System.out.println("Pulling response bytes"); + System.err.println("Pulling response bytes"); + byte[] bytes = resp.body().readAllBytes(); + assertArrayEquals(RESPONSE.getBytes(StandardCharsets.UTF_8), bytes); + System.out.printf("Got expected response for request[%s]%n", i); + System.err.printf("Got expected response for request[%s]%n", i); + } } /* - * The test requires fine grained control over when the server sends the GOAWAY frame + * The test requires fine-grained control over when the server sends the GOAWAY frame * over the HTTP/2 connection and also to detect that the client has closed the underlying * connection. So we read/write HTTP/2 protocol messages directly over a * ServerSocket/Socket pair, instead of using the HTTP servers in the test library. @@ -210,7 +285,6 @@ public void close() throws IOException { // Processes the data on a single HTTP/2 socket connection private static final class Handler implements Runnable { - private static final Random random = RandomFactory.getRandom(); // number of bytes that are taken by the (common) Length and Type fields of // a HTTP/2 frame private static final int SIZE_OF_LENGTH_AND_TYPE_FIELDS = 9; @@ -224,6 +298,8 @@ private static final class Handler implements Runnable { private final Socket socket; private final String logId; + private final FramesEncoder framesEncoder = new FramesEncoder(); + private record ParsedRequest(int streamId, Map> headers) { /* @@ -236,6 +312,17 @@ private String getRequestPath() { } return vals.getFirst(); } + + // the XKEY header indicates whether the client + // wants the server to send response bytes and + // immediately close the connection after that + private String getXKey() { + final List vals = headers.get(XKEY); + if (vals == null || vals.isEmpty()) { + return null; + } + return vals.getFirst(); + } } private Handler(final Socket socket) { @@ -246,6 +333,7 @@ private Handler(final Socket socket) { private void log(final String msg) { System.err.println("[" + this.logId + "] " + msg); + System.out.println("[" + this.logId + "] " + msg); } @Override @@ -283,6 +371,14 @@ private void handleSocketConnection() throws IOException { throw new IOException("unexpected request path " + reqPath); } log("received HTTP/2 request " + req.headers + " on stream " + req.streamId); + + // if XKEY is "yes" we will send a response body and close + // the socket from the server side immediately after + log(XKEY +": " + req.getXKey()); + final String responseBody = "yes".equals(req.getXKey()) + ? RESPONSE + : null; + // send the HTTP/2 response for the stream as well as a GOAWAY frame on the connection. // we randomly decide which one gets sent first and it shouldn't matter // which order they are sent, the end result for the client should be the same @@ -292,16 +388,25 @@ private void handleSocketConnection() throws IOException { if (sendGoAwayFirst) { sendGoAway(req.streamId); log("sent GOAWAY"); - sendResponse(req.streamId); + sendResponse(req.streamId, responseBody); log("sent a HTTP/2 response"); } else { - sendResponse(req.streamId); + sendResponse(req.streamId, responseBody); log("sent a HTTP/2 response"); sendGoAway(req.streamId); log("sent GOAWAY"); } - // wait for the client to close the connection - waitForEOF(); + + if (responseBody == null) { + // testConnectionTermination: + // wait for the client to close the connection + waitForEOF(); + } else { + // testBufferedResponseReceived: + // close the connection and notify the client + socket.close(); + connTerminationLatch.release(); + } } private void readClientPreface() throws IOException { @@ -460,7 +565,7 @@ private void sendGoAway(final int lastStreamId) throws IOException { out.flush(); } - private void sendResponse(final int streamId) throws IOException { + private void sendResponse(final int streamId, String response) throws IOException { // HPACK encoder final Encoder encoder = new Encoder(MAX_HEADER_TABLE_CAPACITY); encoder.header(":status", "200"); @@ -477,7 +582,11 @@ private void sendResponse(final int streamId) throws IOException { frame[1] = (byte) ((fieldBlock.length >> 8) & 0xFF); frame[2] = (byte) (fieldBlock.length & 0xFF); frame[3] = 0x01; // HEADERS frame type - frame[4] = 0x05; // flags = END_STREAM (0x01) | END_HEADERS (0x04) + if (response == null) { + frame[4] = 0x05; // flags = END_STREAM (0x01) | END_HEADERS (0x04) + } else { + frame[4] = 0x04; // flags = END_HEADERS (0x04) + } frame[5] = (byte) ((streamId >> 24) & 0x7F); frame[6] = (byte) ((streamId >> 16) & 0xFF); frame[7] = (byte) ((streamId >> 8) & 0xFF); @@ -488,6 +597,35 @@ private void sendResponse(final int streamId) throws IOException { final OutputStream out = socket.getOutputStream(); out.write(frame); out.flush(); + if (response != null) { + writeResponseBody(out, streamId, response); + } + } + + private void writeResponseBody(OutputStream out, int streamId, String response) throws IOException { + log("Sending response body"); + byte[] bytes = response.getBytes(StandardCharsets.UTF_8); + ByteBuffer payload = ByteBuffer.wrap(bytes); + assert bytes.length > 10 : "response body needs more than 10 bytes"; + // send body in several data frames, by chunks of 10 bytes + for (int i = 0 ; i < bytes.length ; i += 10) { + payload.position(i); + payload.limit(Math.min(i + 10, bytes.length)); + int flags = payload.limit() < bytes.length + ? 0 + : DataFrame.END_STREAM; + DataFrame dataFrame = new DataFrame(streamId, flags, payload); + log("Sending DataFrame: " + dataFrame); + for (ByteBuffer buffer : framesEncoder.encodeFrame(dataFrame)) { + if (buffer.hasRemaining()) { + byte[] towrite = new byte[buffer.remaining()]; + log("Writing %s bytes".formatted(towrite.length)); + buffer.get(towrite); + out.write(towrite); + out.flush(); + } + } + } } private void waitForEOF() throws IOException { @@ -495,7 +633,7 @@ private void waitForEOF() throws IOException { final Instant start = Instant.now(); this.socket.getInputStream().readAllBytes(); // let the test know that the connection has been terminated - connTerminationLatch.countDown(); + connTerminationLatch.release(); final Instant end = Instant.now(); log("took " + Duration.between(start, end) + " to reach EOF"); } diff --git a/test/jdk/java/net/httpclient/http3/H3RespondGoAwayAndClose.java b/test/jdk/java/net/httpclient/http3/H3RespondGoAwayAndClose.java new file mode 100644 index 000000000000..722c3ba03481 --- /dev/null +++ b/test/jdk/java/net/httpclient/http3/H3RespondGoAwayAndClose.java @@ -0,0 +1,235 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8394607 + * @library /test/lib /test/jdk/java/net/httpclient/lib + * @build jdk.test.lib.net.SimpleSSLContext jdk.httpclient.test.lib.common.TestUtil + * jdk.httpclient.test.lib.http2.Http2TestServer + * @run junit/othervm ${test.main.class} + * @summary This test verifies that if a server sends a GOAWAY and/or + * closes the connection right after sending response body bytes, + * the client will still be able to pull the response bytes if + * they have been buffered. + */ + + +// Interesting additional settings for debugging and manual testing: +// ----------------------------------------------------------------- +// -Djdk.httpclient.HttpClient.log=requests,errors,quic:retransmit:control,http3 +// -Djdk.internal.httpclient.debug=err + +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpClient.Builder; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse.BodyHandlers; +import java.nio.charset.StandardCharsets; +import java.util.Random; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Semaphore; +import javax.net.ssl.SSLContext; + +import jdk.httpclient.test.lib.common.HttpServerAdapters; +import jdk.httpclient.test.lib.http3.Http3ServerExchange; +import jdk.internal.net.http.quic.streams.QuicSenderStream.SendingStreamState; +import jdk.test.lib.RandomFactory; +import jdk.test.lib.net.SimpleSSLContext; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import static java.net.http.HttpClient.Version.HTTP_3; +import static java.net.http.HttpOption.H3_DISCOVERY; +import static java.net.http.HttpOption.Http3DiscoveryMode.HTTP_3_URI_ONLY; +import static jdk.internal.net.http.quic.streams.QuicSenderStream.SendingStreamState.DATA_RECVD; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class H3RespondGoAwayAndClose implements HttpServerAdapters { + private static HttpTestServer httpsServer; + private static HttpClient client = null; + static String httpsURIString; + private static final SSLContext sslContext = SimpleSSLContext.findSSLContext(); + private static final ExecutorService serverExec = + Executors.newThreadPerTaskExecutor(Thread.ofVirtual() + .name("server-vt-worker-", 1).factory()); + private static final String RESPONSE = """ + Lorem ipsum dolor sit amet, + caesarem legato alacrem. + """; + private static final Semaphore gate = new Semaphore(0); + private static final Random random = RandomFactory.getRandom(); + + private static void initialize() throws Exception { + try { + client = getClient(); + + httpsServer = HttpTestServer.create(HTTP_3_URI_ONLY, sslContext, serverExec); + httpsServer.addHandler(new TestHandler(), "/"); + httpsURIString = "https://" + httpsServer.serverAuthority() + "/bar/"; + + httpsServer.start(); + } catch (Throwable e) { + System.err.println("Throwing now"); + e.printStackTrace(); + throw e; + } + } + + public static void main(String[] args) throws Exception { + new H3RespondGoAwayAndClose().test(); + } + + @Test + public void test() throws Exception { + try { + initialize(); + HttpRequest request = HttpRequest.newBuilder(URI.create(httpsURIString)) + .version(HTTP_3) + .setOption(H3_DISCOVERY, HTTP_3_URI_ONLY) + .GET().build(); + + for (int i=0 ; i < 5; i++) { + assertEquals(0, gate.availablePermits()); + System.out.printf("issuing request[%s]: %s%n", i, request); + System.err.printf("issuing request[%s]: %s%n", i, request); + var resp = client.send(request, BodyHandlers.ofInputStream()); + Assertions.assertEquals(200, resp.statusCode()); + assertEquals(HTTP_3, resp.version(), "unexpected HTTP version in response"); + + System.out.println("awaiting connection termination"); + System.err.println("awaiting connection termination"); + gate.acquire(); + + // At this point the server has closed the connection. + // Wait a little bit to give a chance to the client to + // observe connection closure, since having closed the + // connection from the server side doesn't guarantee that + // the ConnectionCloseFrame has reached the client side + // and has already been processed. + Thread.sleep(random.nextLong(100, 1000)); + + // Start pulling the bytes from the input stream. The bytes + // should have been all received and buffered, so it should + // be possible to pull them even though the connection was closed + System.out.println("Pulling response bytes"); + System.err.println("Pulling response bytes"); + byte[] bytes = resp.body().readAllBytes(); + assertArrayEquals(RESPONSE.getBytes(StandardCharsets.UTF_8), bytes); + System.out.printf("Got expected response for request[%s]%n", i); + System.err.printf("Got expected response for request[%s]%n", i); + } + } catch (Throwable tt) { + System.err.println("tt caught"); + tt.printStackTrace(); + throw tt; + } finally { + client.close(); + httpsServer.stop(); + serverExec.close(); + } + } + + static HttpClient createClient(SSLContext sslContext, ExecutorService clientExec) { + var builder = HttpServerAdapters.createClientBuilderForH3() + .sslContext(sslContext) + .version(HTTP_3) + .proxy(Builder.NO_PROXY); + if (clientExec != null) { + builder = builder.executor(clientExec); + } + return builder.build(); + } + + static HttpClient getClient() { + if (client == null) { + client = createClient(sslContext, null); + } + return client; + } + + private final static byte[] RESPONSE_BYTES = RESPONSE.getBytes(StandardCharsets.UTF_8); + + private static class TestHandler implements HttpTestHandler { + @Override + public void handle(HttpTestExchange t) throws IOException { + try (var in = t.getRequestBody()) { + byte[] input = in.readAllBytes(); + // passing -1 here ensures that each call to + // out.write() will create a DataFrame. + t.sendResponseHeaders(200, -1); + assert RESPONSE_BYTES.length > 10 + : "Insufficient body length: " + RESPONSE_BYTES.length; + try (var out = t.getResponseBody()) { + for (int i=0; i sendPing() { final QuicConnectionImpl quicConn = serverConn.quicConnection(); From 71326b76a6deb1c9d620fd7337cdb9af23f9570a Mon Sep 17 00:00:00 2001 From: Justin Lu Date: Wed, 21 Oct 2026 16:38:34 +0000 Subject: [PATCH 162/169] 8392357: Permit SyncProviderException.getSyncResolver() to throw IllegalStateException Reviewed-by: alanb, lancea --- .../classes/javax/sql/rowset/spi/SyncProviderException.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/java.sql.rowset/share/classes/javax/sql/rowset/spi/SyncProviderException.java b/src/java.sql.rowset/share/classes/javax/sql/rowset/spi/SyncProviderException.java index b1dd9f7e7611..be7cae9d796d 100644 --- a/src/java.sql.rowset/share/classes/javax/sql/rowset/spi/SyncProviderException.java +++ b/src/java.sql.rowset/share/classes/javax/sql/rowset/spi/SyncProviderException.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -126,6 +126,8 @@ public SyncProviderException(SyncResolver syncResolver) { * implementation is also returned if the SyncResolver() or * SyncResolver(String) constructors are used to instantiate * the SyncResolver instance. + * @throws IllegalStateException if a {@code SyncResolver} is not set and the default + * implementation cannot be loaded. */ public SyncResolver getSyncResolver() { if (syncResolver != null) { From 201a66451c16273ec7f780fbb76ce219bf08af68 Mon Sep 17 00:00:00 2001 From: Justin Lu Date: Wed, 21 Oct 2026 16:42:00 +0000 Subject: [PATCH 163/169] 8394152: CachedRowSet.acceptChanges throws ArrayIndexOutOfBoundsException when the table has more primary-key columns than selected columns Reviewed-by: lancea --- .../rowset/internal/CachedRowSetWriter.java | 31 ++-- .../CachedRowSetInsertionTest.java | 148 ++++++++++++++++++ 2 files changed, 165 insertions(+), 14 deletions(-) create mode 100644 test/jdk/javax/sql/test/rowset/cachedrowset/CachedRowSetInsertionTest.java diff --git a/src/java.sql.rowset/share/classes/com/sun/rowset/internal/CachedRowSetWriter.java b/src/java.sql.rowset/share/classes/com/sun/rowset/internal/CachedRowSetWriter.java index 56f0d30c6df6..9f3f99d99034 100644 --- a/src/java.sql.rowset/share/classes/com/sun/rowset/internal/CachedRowSetWriter.java +++ b/src/java.sql.rowset/share/classes/com/sun/rowset/internal/CachedRowSetWriter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -336,6 +336,7 @@ public boolean writeData(RowSetInternal caller) throws SQLException { crs.beforeFirst(); int rows =1; + String[] primaryKeys = null; while (crs.next()) { if (crs.rowDeleted()) { // The row has been deleted. @@ -350,9 +351,18 @@ public boolean writeData(RowSetInternal caller) throws SQLException { } else if (crs.rowInserted()) { // The row has been inserted. - pstmtIns = con.prepareStatement(insertCmd); - if (insertNewRow(crs, pstmtIns, this.crsResolve)) { + if (primaryKeys == null) { + try (ResultSet rs = con.getMetaData().getPrimaryKeys(null, null, + crs.getTableName())) { + var pks = new ArrayList(); + while (rs.next()) { + pks.add(rs.getString("COLUMN_NAME")); + } + primaryKeys = pks.toArray(new String[0]); + } + } + if (insertNewRow(crs, pstmtIns, primaryKeys)) { status.add(rows, SyncResolver.INSERT_ROW_CONFLICT); conflicts++; } else { @@ -816,31 +826,24 @@ else if(rsval != null && (!rsval.equals(orig))) * and to whose underlying data source the row will be inserted * @param pstmt the {@code PreparedStatement} object that will be used * to execute the insertion + * @param primaryKeys the {@code String[]} of primary key columns from the underlying + * data source * @return {@code false} to indicate that the insertion was successful; * {@code true} otherwise * @throws SQLException if a database access error occurs */ private boolean insertNewRow(CachedRowSet crs, - PreparedStatement pstmt, CachedRowSetImpl crsRes) throws SQLException { + PreparedStatement pstmt, String[] primaryKeys) throws SQLException { boolean returnVal = false; try (PreparedStatement pstmtSel = con.prepareStatement(selectCmd, ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY); - ResultSet rs = pstmtSel.executeQuery(); - ResultSet rs2 = con.getMetaData().getPrimaryKeys(null, null, - crs.getTableName()) - ) { + ResultSet rs = pstmtSel.executeQuery()) { ResultSetMetaData rsmd = crs.getMetaData(); int icolCount = rsmd.getColumnCount(); - String[] primaryKeys = new String[icolCount]; - int k = 0; - while (rs2.next()) { - primaryKeys[k] = rs2.getString("COLUMN_NAME"); - k++; - } if (rs.next()) { for (String pkName : primaryKeys) { diff --git a/test/jdk/javax/sql/test/rowset/cachedrowset/CachedRowSetInsertionTest.java b/test/jdk/javax/sql/test/rowset/cachedrowset/CachedRowSetInsertionTest.java new file mode 100644 index 000000000000..d7ccaf43c20d --- /dev/null +++ b/test/jdk/javax/sql/test/rowset/cachedrowset/CachedRowSetInsertionTest.java @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package test.rowset.cachedrowset; + +import java.sql.DatabaseMetaData; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Types; +import java.util.List; +import javax.sql.rowset.RowSetMetaDataImpl; +import javax.sql.rowset.RowSetProvider; + +import org.junit.jupiter.api.Test; +import util.StubConnection; +import util.StubDatabaseMetaData; +import util.StubPreparedStatement; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +// This test uses stub classes to simulate the bug without an actual +// connection to a database. +public class CachedRowSetInsertionTest { + + /* + * Address a bug where insertions would fail with AIOOBE if the amount of + * primary key columns in the datasource were greater than the amount of + * columns that were selected in the insertion. + */ + @Test + void primaryKeyInsertionFailureTest() throws SQLException { + try (var conn = new TestStubConnection(); + var rows = RowSetProvider.newFactory().createCachedRowSet()) { + RowSetMetaDataImpl metadata = new RowSetMetaDataImpl(); + metadata.setColumnCount(1); + metadata.setColumnType(1, Types.INTEGER); + metadata.setColumnName(1, "N"); + rows.setMetaData(metadata); + rows.setTableName("T"); + // See `TestStubDatabaseMetaData` which creates the two primary keys cols: A and B. + // We only select one column: N for our insertion, which allows the bug to be reproduced + // because N (1) < A & B (2). + rows.moveToInsertRow(); + rows.updateInt("N", -1); + rows.insertRow(); + rows.moveToCurrentRow(); + // Before the fix in 8394152, this line would fail + assertDoesNotThrow(() -> rows.acceptChanges(conn), + "insertion fails when there are more primary key columns than selected columns"); + } + } + + // All the stub classes implement the minimal methods required to exercise + // the proper writeData -> insertNewRow path. The one that really matters and sets + // critical data is `TestStubDatabaseMetaData.getPrimaryKeys()`. + private static class TestStubConnection extends StubConnection { + + @Override + public DatabaseMetaData getMetaData() { + return new TestStubDatabaseMetaData(); + } + + @Override + public PreparedStatement prepareStatement(String sql) { + return new TestStubPreparedStatement(this); + } + + @Override + public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) { + return new TestStubPreparedStatement(this); + } + + @Override + public void commit() {} + } + + /* + * This stub class is what is important and relevant to the bug! + * What we populate in here is what the `CachedRowSetWriter` will query to determine + * the primary keys related to the underlying data source. + */ + private static class TestStubDatabaseMetaData extends StubDatabaseMetaData { + + // Populate the metadata to have two primary keys: A and B. + // The returned set consist of 1 col, with 2 rows whose values are A and B. + @Override + public ResultSet getPrimaryKeys(String catalog, String schema, String table) + throws SQLException { + var crs = RowSetProvider.newFactory().createCachedRowSet(); + RowSetMetaDataImpl metadata = new RowSetMetaDataImpl(); + metadata.setColumnCount(1); + metadata.setColumnType(1, Types.VARCHAR); + metadata.setColumnName(1, "COLUMN_NAME"); + crs.setMetaData(metadata); + for (var pk : List.of("A", "B")) { + crs.moveToInsertRow(); + crs.updateString("COLUMN_NAME", pk); + crs.insertRow(); + } + crs.moveToCurrentRow(); + crs.beforeFirst(); + return crs; + } + } + + private static class TestStubPreparedStatement extends StubPreparedStatement { + + public TestStubPreparedStatement(StubConnection con) { + super(con); + } + + @Override + public ResultSet executeQuery() throws SQLException { + return RowSetProvider.newFactory().createCachedRowSet(); + } + + @Override + public void setObject(int parameterIndex, Object x) {} + + @Override + public int executeUpdate() { + return 1; + } + + @Override + public void close() {} + } +} From 22bc7357ce6bc5f1ed2f1e83e0544b2c25f781d4 Mon Sep 17 00:00:00 2001 From: Weijun Wang Date: Tue, 20 Oct 2026 21:35:46 +0000 Subject: [PATCH 164/169] 8395142: CA Enhancements Reviewed-by: ahgross, rhalade, abarashev, mullan --- .../BlockedCertsConverter.java | 29 +- .../blockedcertsconverter/blocked.certs.pem | 430 ++++++++++++++++++ .../sun/security/lib/CheckBlockedCerts.java | 16 +- 3 files changed, 456 insertions(+), 19 deletions(-) diff --git a/make/jdk/src/classes/build/tools/blockedcertsconverter/BlockedCertsConverter.java b/make/jdk/src/classes/build/tools/blockedcertsconverter/BlockedCertsConverter.java index 1b2a3ebebaf0..f99635a9dee1 100644 --- a/make/jdk/src/classes/build/tools/blockedcertsconverter/BlockedCertsConverter.java +++ b/make/jdk/src/classes/build/tools/blockedcertsconverter/BlockedCertsConverter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -28,11 +28,9 @@ import java.io.IOException; import java.math.BigInteger; import java.security.MessageDigest; -import java.security.PublicKey; import java.security.cert.Certificate; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; -import java.security.interfaces.ECPublicKey; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -40,6 +38,7 @@ import java.util.Set; import java.util.TreeSet; +import sun.security.util.CurveDB; import sun.security.util.DerInputStream; import sun.security.util.DerOutputStream; import sun.security.util.DerValue; @@ -140,11 +139,8 @@ private static List altEncodings(X509Certificate c) List algIds = Arrays.asList(seq[1], altAlgId(seq[1])); List sigs; - PublicKey p = c.getPublicKey(); - if (p instanceof ECPublicKey) { - ECPublicKey ep = (ECPublicKey) p; - BigInteger mod = ep.getParams().getOrder(); - sigs = Arrays.asList(seq[2], altSig(mod, seq[2])); + if (c.getSigAlgName().contains("ECDSA")) { + sigs = Arrays.asList(seq[2], altSig(seq[2])); } else { sigs = Arrays.asList(seq[2]); } @@ -163,14 +159,27 @@ private static List altEncodings(X509Certificate c) return result; } - private static DerValue altSig(BigInteger mod, DerValue sig) + private static DerValue altSig(DerValue sig) throws IOException { byte[] sigBits = sig.getBitString(); + int sigLength = sigBits.length; DerInputStream in = - new DerInputStream(sigBits, 0, sigBits.length, false); + new DerInputStream(sigBits, 0, sigLength, false); DerValue[] values = in.getSequence(2); BigInteger r = values[0].getBigInteger(); BigInteger s = values[1].getBigInteger(); + BigInteger mod; + // The following checks cover more than 99.99999% of signature lengths + // for the three NIST-P curves. Fail if the length lies outside. + if (sigLength >= 100 && sigLength <= 104) { + mod = CurveDB.P_384.getOrder(); + } else if (sigLength >= 68 && sigLength <= 72) { + mod = CurveDB.P_256.getOrder(); + } else if (sigLength >= 135 && sigLength <= 139) { + mod = CurveDB.P_521.getOrder(); + } else { + throw new RuntimeException("Unrecognized signature length: " + sigLength); + } BigInteger s2 = s.negate().mod(mod); DerOutputStream out = new DerOutputStream(); out.putInteger(r); diff --git a/src/java.base/share/data/blockedcertsconverter/blocked.certs.pem b/src/java.base/share/data/blockedcertsconverter/blocked.certs.pem index 4a3b33b03079..59b08d76b8c4 100644 --- a/src/java.base/share/data/blockedcertsconverter/blocked.certs.pem +++ b/src/java.base/share/data/blockedcertsconverter/blocked.certs.pem @@ -747,3 +747,433 @@ lZA8SFd5CTDy3EBUU5UwzXUc5HoIRUxXSPycu3aIBWawg3sCdKiAoikScPAWj0bM 0vmsP/8QSlTOBqO+QFQ6R82BtTvBNU3qbVICV4QObsxib++FAFL56NApPqskg7Vz LfNIAjKabHUcjbuZkmg6jr4BfYW7+oQDHCsYgADjjKGdKz/8U/fP -----END CERTIFICATE----- + +// Subject: CN=EM DV TLS CA - G3B, O=eMudhra BV, C=NL +// Issuer: CN=AAA Certificate Services, O=Comodo CA Limited, L=Salford, ST=Greater Manchester, C=GB +// Serial Number: +// a0:24:fd:f4:ba:66:5c:78:5c:e4:a0:20:48:38:58:b7 +-----BEGIN CERTIFICATE----- +MIIDzjCCAragAwIBAgIRAKAk/fS6Zlx4XOSgIEg4WLcwDQYJKoZIhvcNAQELBQAw +ezELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G +A1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNV +BAMMGEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczAeFw0yNTA1MTQwMDAwMDBaFw0y +ODEyMzEyMzU5NTlaMD8xCzAJBgNVBAYTAk5MMRMwEQYDVQQKDAplTXVkaHJhIEJW +MRswGQYDVQQDDBJFTSBEViBUTFMgQ0EgLSBHM0IwdjAQBgcqhkjOPQIBBgUrgQQA +IgNiAATnntnASpdzxHNeUREdqFrCvV5kqzdLgOEsvCA47B5PDjuBL8mFnh5nr7BO +L4vJf3SFumWqkR7opORbYtXMA9Vr5nizw4y03JN8okYOI4RTyJKTXSnUT5HhVga2 +PM7atGijggE1MIIBMTAfBgNVHSMEGDAWgBSgEQojPpbxB+zirynvgqV/0DCktDAd +BgNVHQ4EFgQU4h4v5xwZOQ5VS2LBICRXNSSKJRQwDgYDVR0PAQH/BAQDAgEGMBIG +A1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMC +MDEGA1UdIAQqMCgwCAYGZ4EMAQIBMA0GCysGAQQBg44hAQABMA0GCysGAQQBg44h +AQJkMEMGA1UdHwQ8MDowOKA2oDSGMmh0dHA6Ly9jcmwuY29tb2RvY2EuY29tL0FB +QUNlcnRpZmljYXRlU2VydmljZXMuY3JsMDQGCCsGAQUFBwEBBCgwJjAkBggrBgEF +BQcwAYYYaHR0cDovL29jc3AuY29tb2RvY2EuY29tMA0GCSqGSIb3DQEBCwUAA4IB +AQBzQ1IoqpdotM6gFrR4Q3YrKifOLeaj5XXunldpVE/vZn2TxC2y0s8oYiLR5ICE +wqVXSB7N6BM1Mp+Nif7Jbhb2IswAfSR4Iik8+B0iRmi7lYoNlycCgHjmsjOmZaxM +QkPmjXAJbG5f4V41OsvJZmS7lI3r0IuvEDVX3GAZ13on6IImSs9ybNpHQ7NCqkMQ +7wH4uAO1dtpFycgJSdHaCOhHjrox2BTYGLdpUrYwY3EOAjlZ8UGgsxs7mG+xquMl +bN1LFURwfnkVMP3T/DBIZ/ys2+lNFyxK3FlArrV4/000uTnVB1lO8oGORYdXoT5V +GUyRCEIDhF5TBUuKF4E3kf1h +-----END CERTIFICATE----- + +// Subject: CN=EM EV TLS CA - G3B, O=eMudhra BV, C=NL +// Issuer: CN=AAA Certificate Services, O=Comodo CA Limited, L=Salford, ST=Greater Manchester, C=GB +// Serial Number: +// d8:f8:68:5e:4f:2b:d9:d8:24:02:a8:5a:1c:1e:b7:31 +-----BEGIN CERTIFICATE----- +MIIDzTCCArWgAwIBAgIRANj4aF5PK9nYJAKoWhwetzEwDQYJKoZIhvcNAQELBQAw +ezELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G +A1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNV +BAMMGEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczAeFw0yNTA1MTQwMDAwMDBaFw0y +ODEyMzEyMzU5NTlaMD8xCzAJBgNVBAYTAk5MMRMwEQYDVQQKDAplTXVkaHJhIEJW +MRswGQYDVQQDDBJFTSBFViBUTFMgQ0EgLSBHM0IwdjAQBgcqhkjOPQIBBgUrgQQA +IgNiAAQl26haZNOX2gsQUunqVG5Ie+/qnJ2daKDF6tz+73Wmm7XW/AH0K/vKbrQL +zWTAdn42/9Bdl6gTxaoMbJUw55z18IZkaPE4BkIKpchRhAseJSVA55DktlTDhS7W +SvHoZX2jggE0MIIBMDAfBgNVHSMEGDAWgBSgEQojPpbxB+zirynvgqV/0DCktDAd +BgNVHQ4EFgQUPtwg2YlGmId7hz/YgUeyXBXYRwYwDgYDVR0PAQH/BAQDAgEGMBIG +A1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMC +MDAGA1UdIAQpMCcwBwYFZ4EMAQEwDQYLKwYBBAGDjiEBAAEwDQYLKwYBBAGDjiEB +AngwQwYDVR0fBDwwOjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20vQUFB +Q2VydGlmaWNhdGVTZXJ2aWNlcy5jcmwwNAYIKwYBBQUHAQEEKDAmMCQGCCsGAQUF +BzABhhhodHRwOi8vb2NzcC5jb21vZG9jYS5jb20wDQYJKoZIhvcNAQELBQADggEB +AJNFuAFW9Gnw/8+kCO4KIlZdnM1Sh2u+8cK0EYAHhXpb5FlGvYkcARigo5oS7VoM +7FOYlTueV6yICZKNAYawZPJPhNCGK+rp3cQBEz6d0n8OZEYZov9lb3ll8ORgSDuf +FVIW6Gagg/UnOamLBTOR8eD5V73SCme/B2cHUDw+m5E8cc+0Ujzz3Cn3ex5JrhXk +2T3s8PoVzEmhTgGboFfcWO1EYhPPrYwdxPYRQIXAXUXhLEOVNL9ASbZJLFI8E1IM +m7NwxVN2s38jWpaCUaaF0tg7hfiy7oe13rzJfHsdqldJVTTUTPWVx8TC4ju0VLet +bAlwhwFT6AP8uIVMGUHNw24= +-----END CERTIFICATE----- + +// Subject: CN=EM OV TLS CA - G3B, O=eMudhra BV, C=NL +// Issuer: CN=AAA Certificate Services, O=Comodo CA Limited, L=Salford, ST=Greater Manchester, C=GB +// Serial Number: +// e9:42:23:bd:e9:2f:91:4e:5a:b9:ac:ab:fd:ac:37:10 +-----BEGIN CERTIFICATE----- +MIIDzjCCAragAwIBAgIRAOlCI73pL5FOWrmsq/2sNxAwDQYJKoZIhvcNAQELBQAw +ezELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G +A1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNV +BAMMGEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczAeFw0yNTA1MTQwMDAwMDBaFw0y +ODEyMzEyMzU5NTlaMD8xCzAJBgNVBAYTAk5MMRMwEQYDVQQKDAplTXVkaHJhIEJW +MRswGQYDVQQDDBJFTSBPViBUTFMgQ0EgLSBHM0IwdjAQBgcqhkjOPQIBBgUrgQQA +IgNiAARziXqg6zz9yosSQC/68KvE4SErr43ud4rJ7i1tuD+sKAxeBWOK56Ks1xwy +Ko42HN659pxDArndjXPRzGyqsswK7k4hbOl53VGKSvS2JuVoTN1lgDftDIVOTMEv +TPhkbQ2jggE1MIIBMTAfBgNVHSMEGDAWgBSgEQojPpbxB+zirynvgqV/0DCktDAd +BgNVHQ4EFgQUm9hlUa/n5yw3rQjuaRnZvzeErgowDgYDVR0PAQH/BAQDAgEGMBIG +A1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMC +MDEGA1UdIAQqMCgwCAYGZ4EMAQICMA0GCysGAQQBg44hAQABMA0GCysGAQQBg44h +AQJuMEMGA1UdHwQ8MDowOKA2oDSGMmh0dHA6Ly9jcmwuY29tb2RvY2EuY29tL0FB +QUNlcnRpZmljYXRlU2VydmljZXMuY3JsMDQGCCsGAQUFBwEBBCgwJjAkBggrBgEF +BQcwAYYYaHR0cDovL29jc3AuY29tb2RvY2EuY29tMA0GCSqGSIb3DQEBCwUAA4IB +AQALRoOSvkzRa+tN5Rje9W5GiXd/qsicd9N6Gnh0SWC7VyWH+HycIqAnFCMYZznj +CeGusomFYHE42SHWr85JDIbgzy6y3dO4i9n9w0Bx7yrXWBbDzFS8VgA3+EY4Cg7y +mtoC3RPOJVtaXXPZ+68X804g1Mj+3vCSHj2lNPVgco0OzSIOJ/KMg1R9MLBkiKzb +bERAGZHejY0HBhYd76IINZN8z6bBNah2UdFYxsOisLynPQUkol6gRLum024/FzVl +CCDfnUusrkr+mAfPAvD/Vb8bhpA7jYtepuU+2mIdShj5lLlvAwZphn+ypWycuo2x +0k9mldz2dqI9nhCiY+TTIOOR +-----END CERTIFICATE----- + +// Subject: CN=EM DV TLS CA - G3C, O=eMudhra Inc, C=US +// Issuer: CN=AAA Certificate Services, O=Comodo CA Limited, L=Salford, ST=Greater Manchester, C=GB +// Serial Number: +// 91:f3:47:f1:a5:b7:d3:99:62:b9:5e:0e:e4:e2:99:d1 +-----BEGIN CERTIFICATE----- +MIIDzzCCAregAwIBAgIRAJHzR/Glt9OZYrleDuTimdEwDQYJKoZIhvcNAQELBQAw +ezELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G +A1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNV +BAMMGEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczAeFw0yNTA1MTQwMDAwMDBaFw0y +ODEyMzEyMzU5NTlaMEAxCzAJBgNVBAYTAlVTMRQwEgYDVQQKDAtlTXVkaHJhIElu +YzEbMBkGA1UEAwwSRU0gRFYgVExTIENBIC0gRzNDMHYwEAYHKoZIzj0CAQYFK4EE +ACIDYgAEOnp4YsjETTJN3WkDOmeCwO/u/kBV5ooSP6A3G6O9kZRRTuFypDPjYw8C +vRiAN9nlFJ+bd9uUooHfmHsv6oiLdL3+j9pMEJnubKGVZv4+8OX2sJpVbMyLpQVh +3xsqRB0Bo4IBNTCCATEwHwYDVR0jBBgwFoAUoBEKIz6W8Qfs4q8p74Klf9AwpLQw +HQYDVR0OBBYEFK5+EFw8KZbBqEcnBVnx4D3U5QrzMA4GA1UdDwEB/wQEAwIBBjAS +BgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcD +AjAxBgNVHSAEKjAoMAgGBmeBDAECATANBgsrBgEEAYOOIQEAATANBgsrBgEEAYOO +IQECZDBDBgNVHR8EPDA6MDigNqA0hjJodHRwOi8vY3JsLmNvbW9kb2NhLmNvbS9B +QUFDZXJ0aWZpY2F0ZVNlcnZpY2VzLmNybDA0BggrBgEFBQcBAQQoMCYwJAYIKwYB +BQUHMAGGGGh0dHA6Ly9vY3NwLmNvbW9kb2NhLmNvbTANBgkqhkiG9w0BAQsFAAOC +AQEAr7b6Mx11TIJgVzHt6EH5qGNWeiiqoMrp8E5/k8s+jvupx1n083pZ5XOzUzND +wEUe0W4C4FIGUHNQ4XDwqOw0yK42xqm2dvCKgsko2UvDlT0haZVtTCeWMQOe5J/J +ziaMfZwsZNfZ3aaI6XPtWDZtwY8AaNfyaeCpbtm5GcGnyNWk4jSNpbtpo6NjfXuP +CFOm3rTjJ4zytP7Tso6iMm0L01cYFOlTmoPv4515R2r3SPnKDtijoB/D/vgxzojv +DafgyG9afX8+laO9NbMMz4sqC1vZx3984i/sBNFiugm3Vvdq5OdJRjuGC1QILIs6 +dg/K3i8VQqRpbfaM9kIlMzZ3dQ== +-----END CERTIFICATE----- + +// Subject: CN=EM OV TLS CA - G3C, O=eMudhra Inc, C=US +// Issuer: CN=AAA Certificate Services, O=Comodo CA Limited, L=Salford, ST=Greater Manchester, C=GB +// Serial Number: +// 8d:5a:48:11:93:9e:f4:47:d7:71:11:cd:d9:84:9a:e9 +-----BEGIN CERTIFICATE----- +MIIDzzCCAregAwIBAgIRAI1aSBGTnvRH13ERzdmEmukwDQYJKoZIhvcNAQELBQAw +ezELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G +A1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNV +BAMMGEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczAeFw0yNTA1MTQwMDAwMDBaFw0y +ODEyMzEyMzU5NTlaMEAxCzAJBgNVBAYTAlVTMRQwEgYDVQQKDAtlTXVkaHJhIElu +YzEbMBkGA1UEAwwSRU0gT1YgVExTIENBIC0gRzNDMHYwEAYHKoZIzj0CAQYFK4EE +ACIDYgAEE4JRzVKskpjByKrZdzId65fnCb0LTKCspV3IrVHc/J0ViaFZfjpRIOHv +1jHGPhg1ysrimGmmLhYx6KKBgatWYW3EXZGKvAnzqw5jz3kqpNztc4e+N7ulaVSf +HAKycO1no4IBNTCCATEwHwYDVR0jBBgwFoAUoBEKIz6W8Qfs4q8p74Klf9AwpLQw +HQYDVR0OBBYEFDTq6gzryQra+dDF4u2beMmHnhtsMA4GA1UdDwEB/wQEAwIBBjAS +BgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcD +AjAxBgNVHSAEKjAoMAgGBmeBDAECAjANBgsrBgEEAYOOIQEAATANBgsrBgEEAYOO +IQECbjBDBgNVHR8EPDA6MDigNqA0hjJodHRwOi8vY3JsLmNvbW9kb2NhLmNvbS9B +QUFDZXJ0aWZpY2F0ZVNlcnZpY2VzLmNybDA0BggrBgEFBQcBAQQoMCYwJAYIKwYB +BQUHMAGGGGh0dHA6Ly9vY3NwLmNvbW9kb2NhLmNvbTANBgkqhkiG9w0BAQsFAAOC +AQEAHlbhZpjhkPEAQVhPHRtBDaHqiisVD20pQ1TUeSx3l62Qe7NQ0b7vWQy7DN0i +KOu7BGpP5EnIjvXbz9FhpfPJ4FhKEt3tQ51rqXz2oD8vRaryvExWPobDUz3j7HN6 +OzBsVMIe3PD7AAkpky0x8zMSi/ep5R9OMzybmNptbtppyuB5ytgq+h7lQdRIP+CQ +TaJX3qTgfMqzntYre5GljmEB5hU2/rSn8zoSU2APkJidlZFpghtgbD4I2VRHZvCw +FFr/SrFM9pwrgbJNZ1MtyTSVgI5NlNb0aMfLR+/r6jJt3ROeY2yh2QQ2dx+Yqgdb +uL5ChFWOQU+ErSDnUq7yr+pjBQ== +-----END CERTIFICATE----- + +// Subject: CN=EM OV CS CA - G3C, O=eMudhra Inc, C=US +// Issuer: CN=AAA Certificate Services, O=Comodo CA Limited, L=Salford, ST=Greater Manchester, C=GB +// Serial Number: +// d6:a5:ef:61:3b:39:0f:32:ef:9c:df:34:9f:77:28:41 +-----BEGIN CERTIFICATE----- +MIIDxTCCAq2gAwIBAgIRANal72E7OQ8y75zfNJ93KEEwDQYJKoZIhvcNAQELBQAw +ezELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G +A1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNV +BAMMGEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczAeFw0yNTA1MTQwMDAwMDBaFw0y +ODEyMzEyMzU5NTlaMD8xCzAJBgNVBAYTAlVTMRQwEgYDVQQKDAtlTXVkaHJhIElu +YzEaMBgGA1UEAwwRRU0gT1YgQ1MgQ0EgLSBHM0MwdjAQBgcqhkjOPQIBBgUrgQQA +IgNiAAS58OBjRwKmnKaq3UQ8/WLEG5uksgkkaX15dOdpGhJvuWr7PjTC/TvF9IMG +HyZdwqWENjRzI1EENrKPBPOGI2MGeYDiUD4CUy1lRiNacWWiWaHvSW90G1/e53hy +COS4+u+jggEsMIIBKDAfBgNVHSMEGDAWgBSgEQojPpbxB+zirynvgqV/0DCktDAd +BgNVHQ4EFgQU7m9zKY1vv6ezG8OVNPYVVkR4ZrcwDgYDVR0PAQH/BAQDAgEGMBIG +A1UdEwEB/wQIMAYBAf8CAQAwEwYDVR0lBAwwCgYIKwYBBQUHAwMwMgYDVR0gBCsw +KTAIBgZngQwBBAEwDQYLKwYBBAGDjiEBAAEwDgYMKwYBBAGDjiEBAoFIMEMGA1Ud +HwQ8MDowOKA2oDSGMmh0dHA6Ly9jcmwuY29tb2RvY2EuY29tL0FBQUNlcnRpZmlj +YXRlU2VydmljZXMuY3JsMDQGCCsGAQUFBwEBBCgwJjAkBggrBgEFBQcwAYYYaHR0 +cDovL29jc3AuY29tb2RvY2EuY29tMA0GCSqGSIb3DQEBCwUAA4IBAQBgGbZ+NDkn +2Fr5dCVs/51+6cT5iEPP1stglX4TI2UaNh9LmPdltxXw3crV8gK5L8i+d+ScGibF +y0obtBHNTldXDgLnAHIzY3n+pceyKj7O2ae9CYnyuu5RBifeI3XYKowWE+oXnVGF +Oy0CvCJ/JsdP6aYgoCa9hGnAYicYQQL6MOMQZ5PuhcSM8t4giIzrpgrEkUP93GYE +emDG5Z8BYwKs80lLeQNBnwg/VHJ3iEF/o84RXYwrlrKqJ+33C094o6LNikMJCqOK +/WYHZSR8Xyhf8GXFAj5pZuI6Bcpug2V/E7i42j8OBhc25aNYDqK+B0qmb90sjNUB +g3tJeSIb/599 +-----END CERTIFICATE----- + +// Subject: CN=EM OV CS CA - G3B, O=eMudhra BV, C=NL +// Issuer: CN=AAA Certificate Services, O=Comodo CA Limited, L=Salford, ST=Greater Manchester, C=GB +// Serial Number: +// 41:e3:2b:73:ac:18:5d:6c:e0:d7:48:0b:ac:f8:4e:69 +-----BEGIN CERTIFICATE----- +MIIDwzCCAqugAwIBAgIQQeMrc6wYXWzg10gLrPhOaTANBgkqhkiG9w0BAQsFADB7 +MQswCQYDVQQGEwJHQjEbMBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYD +VQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEhMB8GA1UE +AwwYQUFBIENlcnRpZmljYXRlIFNlcnZpY2VzMB4XDTI1MDUxNDAwMDAwMFoXDTI4 +MTIzMTIzNTk1OVowPjELMAkGA1UEBhMCTkwxEzARBgNVBAoMCmVNdWRocmEgQlYx +GjAYBgNVBAMMEUVNIE9WIENTIENBIC0gRzNCMHYwEAYHKoZIzj0CAQYFK4EEACID +YgAEqrsPsGZqz88B+tUr/P1MK/LLPDSX3nOVDlgZth/mlQmHQcjEkUObNKBDHK/+ +W/m0wcjorgAB+fTcitgsWfNjnpbNbD1NsM7+wmw9YlF7N3YTcnQ/jordd7EAZPKr +5eBQo4IBLDCCASgwHwYDVR0jBBgwFoAUoBEKIz6W8Qfs4q8p74Klf9AwpLQwHQYD +VR0OBBYEFHn66OzP2w11jaRY1p+27dx8Sd7mMA4GA1UdDwEB/wQEAwIBBjASBgNV +HRMBAf8ECDAGAQH/AgEAMBMGA1UdJQQMMAoGCCsGAQUFBwMDMDIGA1UdIAQrMCkw +CAYGZ4EMAQQBMA0GCysGAQQBg44hAQABMA4GDCsGAQQBg44hAQKBSDBDBgNVHR8E +PDA6MDigNqA0hjJodHRwOi8vY3JsLmNvbW9kb2NhLmNvbS9BQUFDZXJ0aWZpY2F0 +ZVNlcnZpY2VzLmNybDA0BggrBgEFBQcBAQQoMCYwJAYIKwYBBQUHMAGGGGh0dHA6 +Ly9vY3NwLmNvbW9kb2NhLmNvbTANBgkqhkiG9w0BAQsFAAOCAQEAIh0OzWsdBYhY +7+yxPGyosRL+uUSN6/Mrd30fBuougs5FVWQ3cRgLhImnUUgXADY9buugWhMmfT3c +yU5T1MFXZXClCILm5mfg+oqm/ikUWJl3Ayf2GvYf2O++6vzqvkn418nI5Lpdt0O7 +Li524YS/3CZHDA+zZQ7rrHpZXgQ9Hmp8AUQ4FneaGjNanFoYxiZxwCyW5z4u7AoK +0bmOVqKtcDFoVNz5++G9TYDJdhWOXmKKgzqRo1MK0PjazNT7MGHqknP93SqMVJ1g +mX/qNWxYteAjlvFanQxBNX/+TRxDjuFsEHO/7BvqSpIIck0UEEfizhN8zNpWLbcl +EqVgl0g39A== +-----END CERTIFICATE----- + +// Subject: CN=EM DV TLS CA - G3B, O=eMudhra BV, C=NL +// Issuer: CN=emSign Root TLS CA - G3, O=eMudhra Technologies Limited, C=IN +// Serial Number: +// a4:ae:e6:78:f2:33:62:b4:c0:e4:60:b7:72:f2:14 +-----BEGIN CERTIFICATE----- +MIIDaDCCAu6gAwIBAgIQAKSu5njyM2K0wORgt3LyFDAKBggqhkjOPQQDAzBWMQsw +CQYDVQQGEwJJTjElMCMGA1UECgwcZU11ZGhyYSBUZWNobm9sb2dpZXMgTGltaXRl +ZDEgMB4GA1UEAwwXZW1TaWduIFJvb3QgVExTIENBIC0gRzMwHhcNMjUwNDMwMTU1 +NDA4WhcNNDAwNDI4MTgzMDAwWjA/MQswCQYDVQQGEwJOTDETMBEGA1UECgwKZU11 +ZGhyYSBCVjEbMBkGA1UEAwwSRU0gRFYgVExTIENBIC0gRzNCMHYwEAYHKoZIzj0C +AQYFK4EEACIDYgAE557ZwEqXc8RzXlERHahawr1eZKs3S4DhLLwgOOweTw47gS/J +hZ4eZ6+wTi+LyX90hbplqpEe6KTkW2LVzAPVa+Z4s8OMtNyTfKJGDiOEU8iSk10p +1E+R4VYGtjzO2rRoo4IBljCCAZIwHwYDVR0jBBgwFoAU4TqrIXzx401UrtRyt/2L +WUIgkFQwHQYDVR0OBBYEFOIeL+ccGTkOVUtiwSAkVzUkiiUUMB0GA1UdJQQWMBQG +CCsGAQUFBwMBBggrBgEFBQcDAjBdBgNVHSAEVjBUMDkGCysGAQQBg44hAQABMCow +KAYIKwYBBQUHAgEWHGh0dHA6Ly9yZXBvc2l0b3J5LmVtc2lnbi5jb20wDQYLKwYB +BAGDjiEBAmQwCAYGZ4EMAQIBMHUGCCsGAQUFBwEBBGkwZzAkBggrBgEFBQcwAYYY +aHR0cDovL29jc3AtYi5lbXNpZ24uY29tMD8GCCsGAQUFBzAChjNodHRwOi8vcmVw +b3NpdG9yeS5lbXNpZ24uY29tL2NlcnRzL0VNRFZUTFNDQUczQi5wN2MwNwYDVR0f +BDAwLjAsoCqgKIYmaHR0cDovL2NybC5lbXNpZ24uY29tLz9yb290dGxzY2FnMy5j +cmwwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwCgYIKoZIzj0E +AwMDaAAwZQIwLmsGXi6oFMJ2klkcEMXMug6gvxQWZWkewiDWVzQyl2L6z6DuQfPO +Ibh1grvdULDCAjEAxZvLlxUavG69J6wnSjC8XBbqNIQsJ6CE2QdNOe84Ptxb6VG8 +gt2TlfJbHii+KNvK +-----END CERTIFICATE----- + +// Subject: CN=EM DV TLS CA - G3C, O=eMudhra Inc, C=US +// Issuer: CN=emSign Root TLS CA - G3, O=eMudhra Technologies Limited, C=IN +// Serial Number: +// 58:8b:2e:3c:2b:b8:cd:a1:3e:1a:12:09:25:43:21 +-----BEGIN CERTIFICATE----- +MIIDaDCCAu6gAwIBAgIPWIsuPCu4zaE+GhIJJUMhMAoGCCqGSM49BAMDMFYxCzAJ +BgNVBAYTAklOMSUwIwYDVQQKDBxlTXVkaHJhIFRlY2hub2xvZ2llcyBMaW1pdGVk +MSAwHgYDVQQDDBdlbVNpZ24gUm9vdCBUTFMgQ0EgLSBHMzAeFw0yNTA0MzAxNTM1 +MDlaFw00MDA0MjgxODMwMDBaMEAxCzAJBgNVBAYTAlVTMRQwEgYDVQQKDAtlTXVk +aHJhIEluYzEbMBkGA1UEAwwSRU0gRFYgVExTIENBIC0gRzNDMHYwEAYHKoZIzj0C +AQYFK4EEACIDYgAEOnp4YsjETTJN3WkDOmeCwO/u/kBV5ooSP6A3G6O9kZRRTuFy +pDPjYw8CvRiAN9nlFJ+bd9uUooHfmHsv6oiLdL3+j9pMEJnubKGVZv4+8OX2sJpV +bMyLpQVh3xsqRB0Bo4IBljCCAZIwHwYDVR0jBBgwFoAU4TqrIXzx401UrtRyt/2L +WUIgkFQwHQYDVR0OBBYEFK5+EFw8KZbBqEcnBVnx4D3U5QrzMB0GA1UdJQQWMBQG +CCsGAQUFBwMBBggrBgEFBQcDAjBdBgNVHSAEVjBUMDkGCysGAQQBg44hAQABMCow +KAYIKwYBBQUHAgEWHGh0dHA6Ly9yZXBvc2l0b3J5LmVtc2lnbi5jb20wDQYLKwYB +BAGDjiEBAmQwCAYGZ4EMAQIBMHUGCCsGAQUFBwEBBGkwZzAkBggrBgEFBQcwAYYY +aHR0cDovL29jc3AtYy5lbXNpZ24uY29tMD8GCCsGAQUFBzAChjNodHRwOi8vcmVw +b3NpdG9yeS5lbXNpZ24uY29tL2NlcnRzL0VNRFZUTFNDQUczQy5wN2MwNwYDVR0f +BDAwLjAsoCqgKIYmaHR0cDovL2NybC5lbXNpZ24uY29tLz9yb290dGxzY2FnMy5j +cmwwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwCgYIKoZIzj0E +AwMDaAAwZQIxAMEN2HWMocbuWdy1d33pH6JpMvP/V/DIS5PtZbv1Q7TXEbxrcyxg +yuddOJscYDJJlAIwB4sH1BpbScDzkdD4tJBSwrG/1Mx82vb8YaySehNuu1+1oXNd +AEKwaJ0mVzScy2+6 +-----END CERTIFICATE----- + +// Subject: CN=EM EV TLS CA - G3B, O=eMudhra BV, C=NL +// Issuer: CN=emSign Root TLS CA - G3, O=eMudhra Technologies Limited, C=IN +// Serial Number: +// c7:c4:1e:fe:92:d8:25:f0:5e:ae:84:1a:4a:82:c1 +-----BEGIN CERTIFICATE----- +MIIDaDCCAu2gAwIBAgIQAMfEHv6S2CXwXq6EGkqCwTAKBggqhkjOPQQDAzBWMQsw +CQYDVQQGEwJJTjElMCMGA1UECgwcZU11ZGhyYSBUZWNobm9sb2dpZXMgTGltaXRl +ZDEgMB4GA1UEAwwXZW1TaWduIFJvb3QgVExTIENBIC0gRzMwHhcNMjUwNDMwMTYw +MzMxWhcNNDAwNDI4MTgzMDAwWjA/MQswCQYDVQQGEwJOTDETMBEGA1UECgwKZU11 +ZGhyYSBCVjEbMBkGA1UEAwwSRU0gRVYgVExTIENBIC0gRzNCMHYwEAYHKoZIzj0C +AQYFK4EEACIDYgAEJduoWmTTl9oLEFLp6lRuSHvv6pydnWigxerc/u91ppu11vwB +9Cv7ym60C81kwHZ+Nv/QXZeoE8WqDGyVMOec9fCGZGjxOAZCCqXIUYQLHiUlQOeQ +5LZUw4Uu1krx6GV9o4IBlTCCAZEwHwYDVR0jBBgwFoAU4TqrIXzx401UrtRyt/2L +WUIgkFQwHQYDVR0OBBYEFD7cINmJRpiHe4c/2IFHslwV2EcGMB0GA1UdJQQWMBQG +CCsGAQUFBwMBBggrBgEFBQcDAjBcBgNVHSAEVTBTMDkGCysGAQQBg44hAQABMCow +KAYIKwYBBQUHAgEWHGh0dHA6Ly9yZXBvc2l0b3J5LmVtc2lnbi5jb20wDQYLKwYB +BAGDjiEBAngwBwYFZ4EMAQEwdQYIKwYBBQUHAQEEaTBnMCQGCCsGAQUFBzABhhho +dHRwOi8vb2NzcC1iLmVtc2lnbi5jb20wPwYIKwYBBQUHMAKGM2h0dHA6Ly9yZXBv +c2l0b3J5LmVtc2lnbi5jb20vY2VydHMvRU1FVlRMU0NBRzNCLnA3YzA3BgNVHR8E +MDAuMCygKqAohiZodHRwOi8vY3JsLmVtc2lnbi5jb20vP3Jvb3R0bHNjYWczLmNy +bDAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADAKBggqhkjOPQQD +AwNpADBmAjEA9Jq999oaOUKhFwopw33iCmt0OHIn3gjT43GfuOHR/YckXdwoNdDA +lIEhz0NgRO3/AjEAvY/a3OMvktIZLYjn+WLKmp0QhrIMV+e6EpS7j4GVeOKouNp6 +MN/P+ByGeiRMghzB +-----END CERTIFICATE----- + +// Subject: CN=EM EV TLS CA - G3C, O=eMudhra Inc, C=US +// Issuer: CN=emSign Root TLS CA - G3, O=eMudhra Technologies Limited, C=IN +// Serial Number: +// 80:09:92:8a:64:10:76:01:af:05:fa:2f:19:f1:09 +-----BEGIN CERTIFICATE----- +MIIDaDCCAu6gAwIBAgIQAIAJkopkEHYBrwX6LxnxCTAKBggqhkjOPQQDAzBWMQsw +CQYDVQQGEwJJTjElMCMGA1UECgwcZU11ZGhyYSBUZWNobm9sb2dpZXMgTGltaXRl +ZDEgMB4GA1UEAwwXZW1TaWduIFJvb3QgVExTIENBIC0gRzMwHhcNMjUwNDMwMTUz +OTMxWhcNNDAwNDI4MTgzMDAwWjBAMQswCQYDVQQGEwJVUzEUMBIGA1UECgwLZU11 +ZGhyYSBJbmMxGzAZBgNVBAMMEkVNIEVWIFRMUyBDQSAtIEczQzB2MBAGByqGSM49 +AgEGBSuBBAAiA2IABC1W6Cmm+4LGSkaX2WCvlR6QZ66R+SbTQwbaSKiq63hptqZ+ +bSdCbgRCqgOzbhmI3/Y2UU7hzwf1uks/8qBmRa59MdWGatMao9JG6wsxd+6Zy3Vm +xRbOdICKZo+R8MR/yqOCAZUwggGRMB8GA1UdIwQYMBaAFOE6qyF88eNNVK7Ucrf9 +i1lCIJBUMB0GA1UdDgQWBBSmJV6AqU2385Z8G8eR35K87kvrXzAdBgNVHSUEFjAU +BggrBgEFBQcDAQYIKwYBBQUHAwIwXAYDVR0gBFUwUzA5BgsrBgEEAYOOIQEAATAq +MCgGCCsGAQUFBwIBFhxodHRwOi8vcmVwb3NpdG9yeS5lbXNpZ24uY29tMA0GCysG +AQQBg44hAQJ4MAcGBWeBDAEBMHUGCCsGAQUFBwEBBGkwZzAkBggrBgEFBQcwAYYY +aHR0cDovL29jc3AtYy5lbXNpZ24uY29tMD8GCCsGAQUFBzAChjNodHRwOi8vcmVw +b3NpdG9yeS5lbXNpZ24uY29tL2NlcnRzL0VNRVZUTFNDQUczQy5wN2MwNwYDVR0f +BDAwLjAsoCqgKIYmaHR0cDovL2NybC5lbXNpZ24uY29tLz9yb290dGxzY2FnMy5j +cmwwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwCgYIKoZIzj0E +AwMDaAAwZQIwBy1wTnzH4lXDTy2PD91ysLjvnlVN3JbJmc1BIMEU8TOLkYVHibMc +9HeyonikFSfmAjEA6uE3QRxlrZTz/1/lXPTEMUhoauAtycagtlNFp79kjhvNa2/f +Aal1GNxsOutLnr0Y +-----END CERTIFICATE----- + +// Subject: CN=EM OV CS CA - G3B, O=eMudhra BV, C=NL +// Issuer: CN=emSign Root CS CA - G3, O=eMudhra Technologies Limited, C=IN +// Serial Number: +// 78:18:93:3f:8f:85:b4:61:9a:2b:35:fb:a8:3d:0a +-----BEGIN CERTIFICATE----- +MIIDWzCCAuCgAwIBAgIPeBiTP4+FtGGaKzX7qD0KMAoGCCqGSM49BAMDMFUxCzAJ +BgNVBAYTAklOMSUwIwYDVQQKDBxlTXVkaHJhIFRlY2hub2xvZ2llcyBMaW1pdGVk +MR8wHQYDVQQDDBZlbVNpZ24gUm9vdCBDUyBDQSAtIEczMB4XDTI1MDQzMDE2MDgy +N1oXDTQwMDQyODE4MzAwMFowPjELMAkGA1UEBhMCTkwxEzARBgNVBAoMCmVNdWRo +cmEgQlYxGjAYBgNVBAMMEUVNIE9WIENTIENBIC0gRzNCMHYwEAYHKoZIzj0CAQYF +K4EEACIDYgAEqrsPsGZqz88B+tUr/P1MK/LLPDSX3nOVDlgZth/mlQmHQcjEkUOb +NKBDHK/+W/m0wcjorgAB+fTcitgsWfNjnpbNbD1NsM7+wmw9YlF7N3YTcnQ/jord +d7EAZPKr5eBQo4IBizCCAYcwHwYDVR0jBBgwFoAUaHQD+IXKjVRQVx0W4MjeRQ2r +zS0wHQYDVR0OBBYEFHn66OzP2w11jaRY1p+27dx8Sd7mMBMGA1UdJQQMMAoGCCsG +AQUFBwMDMF4GA1UdIARXMFUwOQYLKwYBBAGDjiEBAAEwKjAoBggrBgEFBQcCARYc +aHR0cDovL3JlcG9zaXRvcnkuZW1zaWduLmNvbTAOBgwrBgEEAYOOIQECgUgwCAYG +Z4EMAQQBMHQGCCsGAQUFBwEBBGgwZjAkBggrBgEFBQcwAYYYaHR0cDovL29jc3At +Yi5lbXNpZ24uY29tMD4GCCsGAQUFBzAChjJodHRwOi8vcmVwb3NpdG9yeS5lbXNp +Z24uY29tL2NlcnRzL0VNT1ZDU0NBRzNCLnA3YzA2BgNVHR8ELzAtMCugKaAnhiVo +dHRwOi8vY3JsLmVtc2lnbi5jb20vP3Jvb3Rjc2NhZzMuY3JsMA4GA1UdDwEB/wQE +AwIBBjASBgNVHRMBAf8ECDAGAQH/AgEAMAoGCCqGSM49BAMDA2kAMGYCMQC/zr03 +YEELsNKqzUtH2dVFi4BaKFdyrr/6G1dIWp8KAVgsw2/u4lyyboUDaC9jDvICMQC9 +HTxku84tNX9dmHOtH7aC2Nu/JM0jTf6KjZAvovCJzzv1IFWSZpVLZibynMLXOrM= +-----END CERTIFICATE----- + +// Subject: CN=EM OV CS CA - G3C, O=eMudhra Inc, C=US +// Issuer: CN=emSign Root CS CA - G3, O=eMudhra Technologies Limited, C=IN +// Serial Number: +// 54:4b:7f:92:2c:31:ae:22:0a:4a:92:29:16:f6:e6 +-----BEGIN CERTIFICATE----- +MIIDWzCCAuGgAwIBAgIPVEt/kiwxriIKSpIpFvbmMAoGCCqGSM49BAMDMFUxCzAJ +BgNVBAYTAklOMSUwIwYDVQQKDBxlTXVkaHJhIFRlY2hub2xvZ2llcyBMaW1pdGVk +MR8wHQYDVQQDDBZlbVNpZ24gUm9vdCBDUyBDQSAtIEczMB4XDTI1MDQzMDE2Mzg0 +MFoXDTQwMDQyODE4MzAwMFowPzELMAkGA1UEBhMCVVMxFDASBgNVBAoMC2VNdWRo +cmEgSW5jMRowGAYDVQQDDBFFTSBPViBDUyBDQSAtIEczQzB2MBAGByqGSM49AgEG +BSuBBAAiA2IABLnw4GNHAqacpqrdRDz9YsQbm6SyCSRpfXl052kaEm+5avs+NML9 +O8X0gwYfJl3CpYQ2NHMjUQQ2so8E84YjYwZ5gOJQPgJTLWVGI1pxZaJZoe9Jb3Qb +X97neHII5Lj676OCAYswggGHMB8GA1UdIwQYMBaAFGh0A/iFyo1UUFcdFuDI3kUN +q80tMB0GA1UdDgQWBBTub3MpjW+/p7Mbw5U09hVWRHhmtzATBgNVHSUEDDAKBggr +BgEFBQcDAzBeBgNVHSAEVzBVMDkGCysGAQQBg44hAQABMCowKAYIKwYBBQUHAgEW +HGh0dHA6Ly9yZXBvc2l0b3J5LmVtc2lnbi5jb20wDgYMKwYBBAGDjiEBAoFIMAgG +BmeBDAEEATB0BggrBgEFBQcBAQRoMGYwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3Nw +LWMuZW1zaWduLmNvbTA+BggrBgEFBQcwAoYyaHR0cDovL3JlcG9zaXRvcnkuZW1z +aWduLmNvbS9jZXJ0cy9FTU9WQ1NDQUczQy5wN2MwNgYDVR0fBC8wLTAroCmgJ4Yl +aHR0cDovL2NybC5lbXNpZ24uY29tLz9yb290Y3NjYWczLmNybDAOBgNVHQ8BAf8E +BAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADAKBggqhkjOPQQDAwNoADBlAjEA8yPg +8sntOhLwV2QXJ5LEaKSpo/fnE3uOgTadK73fDhyjiqr0Io9GVuQTYqBPbQ2TAjBg +euApmBS486+iei03lRrp139dPw6k28jjGNvWsAU7A1N86J1zPJVDEyVH+nMAWC0= +-----END CERTIFICATE----- + +// Subject: CN=EM OV TLS CA - G3B, O=eMudhra BV, C=NL +// Issuer: CN=emSign Root TLS CA - G3, O=eMudhra Technologies Limited, C=IN +// Serial Number: +// 6c:ad:a3:67:fb:1c:10:00:88:12:0e:a2:02:d5:68 +-----BEGIN CERTIFICATE----- +MIIDZjCCAu2gAwIBAgIPbK2jZ/scEACIEg6iAtVoMAoGCCqGSM49BAMDMFYxCzAJ +BgNVBAYTAklOMSUwIwYDVQQKDBxlTXVkaHJhIFRlY2hub2xvZ2llcyBMaW1pdGVk +MSAwHgYDVQQDDBdlbVNpZ24gUm9vdCBUTFMgQ0EgLSBHMzAeFw0yNTA0MzAxNjAw +MTJaFw00MDA0MjgxODMwMDBaMD8xCzAJBgNVBAYTAk5MMRMwEQYDVQQKDAplTXVk +aHJhIEJWMRswGQYDVQQDDBJFTSBPViBUTFMgQ0EgLSBHM0IwdjAQBgcqhkjOPQIB +BgUrgQQAIgNiAARziXqg6zz9yosSQC/68KvE4SErr43ud4rJ7i1tuD+sKAxeBWOK +56Ks1xwyKo42HN659pxDArndjXPRzGyqsswK7k4hbOl53VGKSvS2JuVoTN1lgDft +DIVOTMEvTPhkbQ2jggGWMIIBkjAfBgNVHSMEGDAWgBThOqshfPHjTVSu1HK3/YtZ +QiCQVDAdBgNVHQ4EFgQUm9hlUa/n5yw3rQjuaRnZvzeErgowHQYDVR0lBBYwFAYI +KwYBBQUHAwEGCCsGAQUFBwMCMF0GA1UdIARWMFQwOQYLKwYBBAGDjiEBAAEwKjAo +BggrBgEFBQcCARYcaHR0cDovL3JlcG9zaXRvcnkuZW1zaWduLmNvbTANBgsrBgEE +AYOOIQECbjAIBgZngQwBAgIwdQYIKwYBBQUHAQEEaTBnMCQGCCsGAQUFBzABhhho +dHRwOi8vb2NzcC1iLmVtc2lnbi5jb20wPwYIKwYBBQUHMAKGM2h0dHA6Ly9yZXBv +c2l0b3J5LmVtc2lnbi5jb20vY2VydHMvRU1PVlRMU0NBRzNCLnA3YzA3BgNVHR8E +MDAuMCygKqAohiZodHRwOi8vY3JsLmVtc2lnbi5jb20vP3Jvb3R0bHNjYWczLmNy +bDAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADAKBggqhkjOPQQD +AwNnADBkAjBAk+5BizdJhrED3wb7F2Gm7bWI60dlNd075nghT2TrR4NWKZtJZcbc +28xxIY/NONICMF6mPdsjosaqmvEMWqKcBh4vsIvXSKnQj5o8zrFB4oKS+accjYTe +8Dz3bQjauvy9uw== +-----END CERTIFICATE----- + +// Subject: CN=EM OV TLS CA - G3C, O=eMudhra Inc, C=US +// Issuer: CN=emSign Root TLS CA - G3, O=eMudhra Technologies Limited, C=IN +// Serial Number: +// 29:69:26:c2:27:a3:a1:52:63:92:b1:94:82:a5:04 +-----BEGIN CERTIFICATE----- +MIIDaDCCAu6gAwIBAgIPKWkmwiejoVJjkrGUgqUEMAoGCCqGSM49BAMDMFYxCzAJ +BgNVBAYTAklOMSUwIwYDVQQKDBxlTXVkaHJhIFRlY2hub2xvZ2llcyBMaW1pdGVk +MSAwHgYDVQQDDBdlbVNpZ24gUm9vdCBUTFMgQ0EgLSBHMzAeFw0yNTA0MzAxNTM3 +NDFaFw00MDA0MjgxODMwMDBaMEAxCzAJBgNVBAYTAlVTMRQwEgYDVQQKDAtlTXVk +aHJhIEluYzEbMBkGA1UEAwwSRU0gT1YgVExTIENBIC0gRzNDMHYwEAYHKoZIzj0C +AQYFK4EEACIDYgAEE4JRzVKskpjByKrZdzId65fnCb0LTKCspV3IrVHc/J0ViaFZ +fjpRIOHv1jHGPhg1ysrimGmmLhYx6KKBgatWYW3EXZGKvAnzqw5jz3kqpNztc4e+ +N7ulaVSfHAKycO1no4IBljCCAZIwHwYDVR0jBBgwFoAU4TqrIXzx401UrtRyt/2L +WUIgkFQwHQYDVR0OBBYEFDTq6gzryQra+dDF4u2beMmHnhtsMB0GA1UdJQQWMBQG +CCsGAQUFBwMBBggrBgEFBQcDAjBdBgNVHSAEVjBUMDkGCysGAQQBg44hAQABMCow +KAYIKwYBBQUHAgEWHGh0dHA6Ly9yZXBvc2l0b3J5LmVtc2lnbi5jb20wDQYLKwYB +BAGDjiEBAm4wCAYGZ4EMAQICMHUGCCsGAQUFBwEBBGkwZzAkBggrBgEFBQcwAYYY +aHR0cDovL29jc3AtYy5lbXNpZ24uY29tMD8GCCsGAQUFBzAChjNodHRwOi8vcmVw +b3NpdG9yeS5lbXNpZ24uY29tL2NlcnRzL0VNT1ZUTFNDQUczQy5wN2MwNwYDVR0f +BDAwLjAsoCqgKIYmaHR0cDovL2NybC5lbXNpZ24uY29tLz9yb290dGxzY2FnMy5j +cmwwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwCgYIKoZIzj0E +AwMDaAAwZQIxAMSZt2xsPeumRdeSMSQ3CbzIZeQ/q7GDCWfeE1Pq+mhgA6gRM44c +NVF3xJ+wsxJk+wIwFJbaDMokD8Ei6eNOiA8yUMYvfzQyngmrhZQMdPCK1SewrR79 +4rSb9vGqxZzelQth +-----END CERTIFICATE----- + +// Subject: CN=EM EV TLS CA - G3C, O=eMudhra Inc, C=US +// Issuer: CN=AAA Certificate Services, O=Comodo CA Limited, L=Salford, ST=Greater Manchester, C=GB +// Serial Number: +// 05:f0:78:75:2d:ec:e7:a1:bb:85:95:d3:03:16:71:71 +-----BEGIN CERTIFICATE----- +MIIDzTCCArWgAwIBAgIQBfB4dS3s56G7hZXTAxZxcTANBgkqhkiG9w0BAQsFADB7 +MQswCQYDVQQGEwJHQjEbMBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYD +VQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEhMB8GA1UE +AwwYQUFBIENlcnRpZmljYXRlIFNlcnZpY2VzMB4XDTI1MDUxNDAwMDAwMFoXDTI4 +MTIzMTIzNTk1OVowQDELMAkGA1UEBhMCVVMxFDASBgNVBAoMC2VNdWRocmEgSW5j +MRswGQYDVQQDDBJFTSBFViBUTFMgQ0EgLSBHM0MwdjAQBgcqhkjOPQIBBgUrgQQA +IgNiAAQtVugppvuCxkpGl9lgr5UekGeukfkm00MG2kioqut4abamfm0nQm4EQqoD +s24ZiN/2NlFO4c8H9bpLP/KgZkWufTHVhmrTGqPSRusLMXfumct1ZsUWznSAimaP +kfDEf8qjggE0MIIBMDAfBgNVHSMEGDAWgBSgEQojPpbxB+zirynvgqV/0DCktDAd +BgNVHQ4EFgQUpiVegKlNt/OWfBvHkd+SvO5L618wDgYDVR0PAQH/BAQDAgEGMBIG +A1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMC +MDAGA1UdIAQpMCcwBwYFZ4EMAQEwDQYLKwYBBAGDjiEBAAEwDQYLKwYBBAGDjiEB +AngwQwYDVR0fBDwwOjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20vQUFB +Q2VydGlmaWNhdGVTZXJ2aWNlcy5jcmwwNAYIKwYBBQUHAQEEKDAmMCQGCCsGAQUF +BzABhhhodHRwOi8vb2NzcC5jb21vZG9jYS5jb20wDQYJKoZIhvcNAQELBQADggEB +AIFfHiaMSjnrdZygRYV4ekRBmG7ybcFk4hGQWMMyMFi0cAEk2+dGfz5bl8IaIbLq +z+uVJzhOgVEX7WqIfgbWfFsfPYHYjPHBmy7SlZ3BwA86IQ6qF/etpX2/qL9A5yRN +N3nVowUpaqV6H3aCpkAh3wR2ZNkvAbLDvPz3J0l+sEWjWv+ZISOV4R+hXDTdPXV0 +1lKxnbwqEsdo/sE2QkCDKwFrZh/0uKjWspydJj+jSaDLTrGNx50Dgmn7VEcD1ZI5 +FXjknVUiO25YlmS1dtVZSBbyUKSjlrP/yDwPcZunoAsmCAAtCKsFLviLaymzvsKI +eSSDGxpSySp2VCxompGDk8g= +-----END CERTIFICATE----- diff --git a/test/jdk/sun/security/lib/CheckBlockedCerts.java b/test/jdk/sun/security/lib/CheckBlockedCerts.java index 5afbf4b610b9..f739b022bb29 100644 --- a/test/jdk/sun/security/lib/CheckBlockedCerts.java +++ b/test/jdk/sun/security/lib/CheckBlockedCerts.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -56,21 +56,19 @@ public static void main(String[] args) throws Exception { } } - // All certs in the pem files - Set blocked = new HashSet<>(); - // Assumes the full src is available File blockedCertsFile = new File(System.getProperty("test.src"), "../../../../../src/java.base/share/data/blockedcertsconverter/blocked.certs.pem"); CertificateFactory cf = CertificateFactory.getInstance("X.509"); + int expectedCount = 0; try (FileInputStream fis = new FileInputStream(blockedCertsFile)) { Collection certs = cf.generateCertificates(fis); System.out.println(certs.size()); for (Certificate c: certs) { - blocked.add(c); X509Certificate cert = ((X509Certificate)c); + expectedCount += cert.getSigAlgName().contains("ECDSA") ? 4 : 2; if (!UntrustedCertificates.isUntrusted(cert)) { System.out.println(cert.getSubjectX500Principal() + " is trusted"); @@ -101,11 +99,11 @@ public static void main(String[] args) throws Exception { failed = true; } // There are two unique fingerprints for each RSA certificate - if (ccount != blocked.size() * 2 - && !blocked.isEmpty()) { + if (ccount != expectedCount + && expectedCount != 0) { System.out.println("Wrong blocked.certs size: " - + ccount + " fingerprints, " - + blocked.size() + " certs"); + + ccount + " fingerprints, expected" + + expectedCount); failed = true; } } From f921bd7c9e2388673f9f1e6ba59666e9c9fc9542 Mon Sep 17 00:00:00 2001 From: Patrick Fontanilla Date: Wed, 21 Oct 2026 17:07:00 +0000 Subject: [PATCH 165/169] 8392050: Purge CollectedHeap::safepoint_workers() support Reviewed-by: stefank, shade, wkemper, kdnilsen --- src/hotspot/share/gc/g1/g1CollectedHeap.hpp | 2 +- .../gc/parallel/parallelScavengeHeap.hpp | 2 +- src/hotspot/share/gc/shared/collectedHeap.hpp | 15 +- .../share/gc/shared/gcVMOperations.cpp | 13 +- .../gc/shenandoah/shenandoahEvacTracker.cpp | 5 +- .../share/gc/shenandoah/shenandoahHeap.cpp | 33 +-- .../share/gc/shenandoah/shenandoahHeap.hpp | 5 +- .../gc/shenandoah/shenandoahHeap.inline.hpp | 6 - src/hotspot/share/gc/z/zCollectedHeap.cpp | 10 +- src/hotspot/share/gc/z/zCollectedHeap.hpp | 6 +- src/hotspot/share/gc/z/zRuntimeWorkers.cpp | 48 ----- src/hotspot/share/gc/z/zRuntimeWorkers.hpp | 43 ---- src/hotspot/share/memory/heapInspection.cpp | 12 +- src/hotspot/share/memory/heapInspection.hpp | 2 +- src/hotspot/share/nmt/memMapPrinter.cpp | 20 +- src/hotspot/share/runtime/threads.cpp | 2 + .../share/services/diagnosticWorkers.cpp | 70 +++++++ .../share/services/diagnosticWorkers.hpp | 59 ++++++ src/hotspot/share/services/heapDumper.cpp | 29 ++- .../dcmd/gc/ClassHistogramParallelTest.java | 198 ++++++++++++++++++ .../dcmd/gc/HeapDumpParallelTest.java | 116 +++++++--- 21 files changed, 489 insertions(+), 207 deletions(-) delete mode 100644 src/hotspot/share/gc/z/zRuntimeWorkers.cpp delete mode 100644 src/hotspot/share/gc/z/zRuntimeWorkers.hpp create mode 100644 src/hotspot/share/services/diagnosticWorkers.cpp create mode 100644 src/hotspot/share/services/diagnosticWorkers.hpp create mode 100644 test/hotspot/jtreg/serviceability/dcmd/gc/ClassHistogramParallelTest.java diff --git a/src/hotspot/share/gc/g1/g1CollectedHeap.hpp b/src/hotspot/share/gc/g1/g1CollectedHeap.hpp index 448a40c1d2ec..20d8bb8ad9a2 100644 --- a/src/hotspot/share/gc/g1/g1CollectedHeap.hpp +++ b/src/hotspot/share/gc/g1/g1CollectedHeap.hpp @@ -1311,7 +1311,7 @@ class G1CollectedHeap : public CollectedHeap { // WhiteBox testing support. bool supports_concurrent_gc_breakpoints() const override; - WorkerThreads* safepoint_workers() override { return _workers; } + bool supports_parallel_object_iteration() const override { return true; } // The methods below are here for convenience and dispatch the // appropriate method depending on value of the given VerifyOption diff --git a/src/hotspot/share/gc/parallel/parallelScavengeHeap.hpp b/src/hotspot/share/gc/parallel/parallelScavengeHeap.hpp index 0cb3887833ae..a28e094572b5 100644 --- a/src/hotspot/share/gc/parallel/parallelScavengeHeap.hpp +++ b/src/hotspot/share/gc/parallel/parallelScavengeHeap.hpp @@ -272,7 +272,7 @@ class ParallelScavengeHeap : public CollectedHeap { void print_gc_on(outputStream* st) const override; void gc_threads_do(ThreadClosure* tc) const override; - WorkerThreads* safepoint_workers() override { return &_workers; } + bool supports_parallel_object_iteration() const override { return true; } PreGenGCValues get_pre_gc_values() const; void print_heap_change(const PreGenGCValues& pre_gc_values) const; diff --git a/src/hotspot/share/gc/shared/collectedHeap.hpp b/src/hotspot/share/gc/shared/collectedHeap.hpp index c1305f961fbb..e153565b59c0 100644 --- a/src/hotspot/share/gc/shared/collectedHeap.hpp +++ b/src/hotspot/share/gc/shared/collectedHeap.hpp @@ -402,6 +402,10 @@ class CollectedHeap : public CHeapObj { // Iterate over all objects, calling "cl.do_object" on each. virtual void object_iterate(ObjectClosure* cl) = 0; + // Returns true if the collector implements parallel_object_iterator(), so + // heap dump and heap inspection may iterate the heap with multiple worker threads. + virtual bool supports_parallel_object_iteration() const { return false; } + virtual ParallelObjectIteratorImpl* parallel_object_iterator(uint thread_num) { return nullptr; } @@ -475,17 +479,6 @@ class CollectedHeap : public CHeapObj { // this collector. The default implementation returns false. virtual bool supports_concurrent_gc_breakpoints() const; - // Workers used in non-GC safepoints for parallel safepoint cleanup. If this - // method returns null, cleanup tasks are done serially in the VMThread. See - // `SafepointSynchronize::do_cleanup_tasks` for details. - // GCs using a GC worker thread pool inside GC safepoints may opt to share - // that pool with non-GC safepoints, avoiding creating extraneous threads. - // Such sharing is safe, because GC safepoints and non-GC safepoints never - // overlap. For example, `G1CollectedHeap::workers()` (for GC safepoints) and - // `G1CollectedHeap::safepoint_workers()` (for non-GC safepoints) return the - // same thread-pool. - virtual WorkerThreads* safepoint_workers() { return nullptr; } - // Support for object pinning. This is used by JNI Get*Critical() // and Release*Critical() family of functions. The GC must guarantee // that pinned objects never move and don't get reclaimed as garbage. diff --git a/src/hotspot/share/gc/shared/gcVMOperations.cpp b/src/hotspot/share/gc/shared/gcVMOperations.cpp index 6dbfd56b4e97..59209fb8de44 100644 --- a/src/hotspot/share/gc/shared/gcVMOperations.cpp +++ b/src/hotspot/share/gc/shared/gcVMOperations.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -204,16 +204,7 @@ void VM_GC_HeapInspection::doit() { } } HeapInspection inspect; - WorkerThreads* workers = Universe::heap()->safepoint_workers(); - if (workers != nullptr) { - // The GC provided a WorkerThreads to be used during a safepoint. - // Can't run with more threads than provided by the WorkerThreads. - const uint capped_parallel_thread_num = MIN2(_parallel_thread_num, workers->max_workers()); - WithActiveWorkers with_active_workers(workers, capped_parallel_thread_num); - inspect.heap_inspection(_out, workers); - } else { - inspect.heap_inspection(_out, nullptr); - } + inspect.heap_inspection(_out, _parallel_thread_num); } VM_CollectForMetadataAllocation::VM_CollectForMetadataAllocation(ClassLoaderData* loader_data, diff --git a/src/hotspot/share/gc/shenandoah/shenandoahEvacTracker.cpp b/src/hotspot/share/gc/shenandoah/shenandoahEvacTracker.cpp index 72d0773eccdf..aad35f512c40 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahEvacTracker.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahEvacTracker.cpp @@ -1,6 +1,6 @@ /* * Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,6 +27,7 @@ #include "gc/shenandoah/shenandoahHeap.inline.hpp" #include "gc/shenandoah/shenandoahThreadLocalData.hpp" #include "runtime/thread.hpp" +#include "runtime/threads.hpp" #include "runtime/threadSMR.inline.hpp" ShenandoahEvacuationStats::ShenandoahEvacuations* ShenandoahEvacuationStats::get_category( @@ -120,7 +121,7 @@ ShenandoahCycleStats ShenandoahEvacuationTracker::flush_cycle_to_global() { java_threads_iterator.list()->threads_do(&aggregate_mutators); ShenandoahStatAggregator aggregate_workers(&workers); - ShenandoahHeap::heap()->gc_threads_do(&aggregate_workers); + Threads::non_java_threads_do(&aggregate_workers); _mutators_global.accumulate(&mutators); _workers_global.accumulate(&workers); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp index 0da82666e5a5..d8fef7e37acd 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp @@ -272,11 +272,6 @@ jint ShenandoahHeap::initialize() { _workers->initialize_workers(); } - if (ParallelGCThreads > 1) { - _safepoint_workers = new ShenandoahWorkerThreads("Safepoint Cleanup Thread", ParallelGCThreads); - _safepoint_workers->initialize_workers(); - } - // // Reserve and commit memory for bitmap(s) // @@ -567,7 +562,6 @@ ShenandoahHeap::ShenandoahHeap() : _alloc_rate_decay(&_alloc_rate), _max_workers(MAX3(ConcGCThreads, ParallelGCThreads, 1U)), _workers(nullptr), - _safepoint_workers(nullptr), _heap_region_special(false), _num_regions(0), _regions(nullptr), @@ -724,13 +718,6 @@ void ShenandoahHeap::post_initialize() { // Now, we will let WorkerThreads to initialize gclab when new worker is created. _workers->set_initialize_gclab(); - // Note that the safepoint workers may require gclabs if the threads are used to create a heap dump - // during a concurrent evacuation phase. - if (_safepoint_workers != nullptr) { - _safepoint_workers->threads_do(&init_gclabs); - _safepoint_workers->set_initialize_gclab(); - } - JFR_ONLY(ShenandoahJFRSupport::register_jfr_type_serializers();) } @@ -1597,10 +1584,6 @@ void ShenandoahHeap::labs_make_parsable() { } workers()->threads_do(&cl); - - if (safepoint_workers() != nullptr) { - safepoint_workers()->threads_do(&cl); - } } void ShenandoahHeap::tlabs_retire(bool resize) { @@ -1637,10 +1620,6 @@ void ShenandoahHeap::gclabs_retire(bool resize) { } workers()->threads_do(&cl); - - if (safepoint_workers() != nullptr) { - safepoint_workers()->threads_do(&cl); - } } // Returns size in bytes @@ -1715,9 +1694,6 @@ void ShenandoahHeap::gc_threads_do(ThreadClosure* tcl) const { } workers()->threads_do(tcl); - if (_safepoint_workers != nullptr) { - _safepoint_workers->threads_do(tcl); - } } void ShenandoahHeap::print_tracing_info() const { @@ -1864,7 +1840,7 @@ void ShenandoahHeap::object_iterate(ObjectClosure* cl) { ShenandoahScanObjectStack oop_stack; ObjectIterateScanRootClosure oops(&_aux_bit_map, &oop_stack); // Seed the stack with root scan - scan_roots_for_iteration(&oop_stack, &oops); + scan_roots_for_iteration(&oops, 1); // Work through the oop stack to traverse heap while (! oop_stack.is_empty()) { @@ -1893,13 +1869,12 @@ bool ShenandoahHeap::prepare_aux_bitmap_for_iteration() { return true; } -void ShenandoahHeap::scan_roots_for_iteration(ShenandoahScanObjectStack* oop_stack, ObjectIterateScanRootClosure* oops) { +void ShenandoahHeap::scan_roots_for_iteration(ObjectIterateScanRootClosure* oops, uint num_workers) { // Process GC roots according to current GC cycle // This populates the work stack with initial objects // It is important to relinquish the associated locks before diving // into heap dumper - uint n_workers = safepoint_workers() != nullptr ? safepoint_workers()->active_workers() : 1; - ShenandoahHeapIterationRootScanner rp(n_workers); + ShenandoahHeapIterationRootScanner rp(num_workers); rp.roots_do(oops); } @@ -1964,7 +1939,7 @@ class ShenandoahParallelObjectIterator : public ParallelObjectIteratorImpl { } ObjectIterateScanRootClosure oops(_aux_bit_map, &_roots_stack); - _heap->scan_roots_for_iteration(&_roots_stack, &oops); + _heap->scan_roots_for_iteration(&oops, _num_workers); _init_ready = prepare_worker_queues(); } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp b/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp index 945ffbcdc7ce..d6ed39070c25 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp @@ -279,7 +279,6 @@ class ShenandoahHeap : public CollectedHeap { private: uint _max_workers; ShenandoahWorkerThreads* _workers; - ShenandoahWorkerThreads* _safepoint_workers; virtual void initialize_controller(); @@ -288,7 +287,6 @@ class ShenandoahHeap : public CollectedHeap { void assert_gc_workers(uint nworker) NOT_DEBUG_RETURN; WorkerThreads* workers() const; - WorkerThreads* safepoint_workers() override; void gc_threads_do(ThreadClosure* tcl) const override; @@ -626,7 +624,7 @@ class ShenandoahHeap : public CollectedHeap { ShenandoahAffiliation new_affiliation); // Heap iteration support - void scan_roots_for_iteration(ShenandoahScanObjectStack* oop_stack, ObjectIterateScanRootClosure* oops); + void scan_roots_for_iteration(ObjectIterateScanRootClosure* oops, uint num_workers); bool prepare_aux_bitmap_for_iteration(); void reclaim_aux_bitmap_for_iteration(); @@ -686,6 +684,7 @@ class ShenandoahHeap : public CollectedHeap { void object_iterate(ObjectClosure* cl) override; // Parallel heap iteration support ParallelObjectIteratorImpl* parallel_object_iterator(uint workers) override; + bool supports_parallel_object_iteration() const override { return true; } // Keep alive an object that was loaded with AS_NO_KEEPALIVE. void keep_alive(oop obj) override; diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeap.inline.hpp b/src/hotspot/share/gc/shenandoah/shenandoahHeap.inline.hpp index 072275343306..8b4954457a35 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahHeap.inline.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeap.inline.hpp @@ -72,10 +72,6 @@ inline WorkerThreads* ShenandoahHeap::workers() const { return _workers; } -inline WorkerThreads* ShenandoahHeap::safepoint_workers() { - return _safepoint_workers; -} - inline void ShenandoahHeap::notify_gc_progress() { _gc_no_progress_count.store_relaxed((size_t) 0); @@ -237,8 +233,6 @@ inline HeapWord* ShenandoahHeap::allocate_from_gclab(Thread* thread, size_t size PLAB* gclab = ShenandoahThreadLocalData::gclab(thread); if (gclab == nullptr) { - assert(!thread->is_Java_thread() && !thread->is_Worker_thread(), - "Performance: thread should have GCLAB: %s", thread->name()); // No GCLABs in this thread, fallback to shared allocation return nullptr; } diff --git a/src/hotspot/share/gc/z/zCollectedHeap.cpp b/src/hotspot/share/gc/z/zCollectedHeap.cpp index 09659591078c..5afbd7238a1b 100644 --- a/src/hotspot/share/gc/z/zCollectedHeap.cpp +++ b/src/hotspot/share/gc/z/zCollectedHeap.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -66,8 +66,7 @@ ZCollectedHeap::ZCollectedHeap() _driver_minor(new ZDriverMinor()), _driver_major(new ZDriverMajor()), _director(new ZDirector()), - _stat(new ZStat()), - _runtime_workers() {} + _stat(new ZStat()) {} CollectedHeap::Name ZCollectedHeap::kind() const { return CollectedHeap::Z; @@ -307,8 +306,8 @@ void ZCollectedHeap::verify_nmethod(nmethod* nm) { // Does nothing } -WorkerThreads* ZCollectedHeap::safepoint_workers() { - return _runtime_workers.workers(); +bool ZCollectedHeap::supports_parallel_object_iteration() const { + return true; } void ZCollectedHeap::gc_threads_do(ThreadClosure* tc) const { @@ -317,7 +316,6 @@ void ZCollectedHeap::gc_threads_do(ThreadClosure* tc) const { tc->do_thread(_driver_minor); tc->do_thread(_stat); _heap.threads_do(tc); - _runtime_workers.threads_do(tc); } VirtualSpaceSummary ZCollectedHeap::create_heap_space_summary() { diff --git a/src/hotspot/share/gc/z/zCollectedHeap.hpp b/src/hotspot/share/gc/z/zCollectedHeap.hpp index 19c9ab1bbdf0..44ba3fdaf33c 100644 --- a/src/hotspot/share/gc/z/zCollectedHeap.hpp +++ b/src/hotspot/share/gc/z/zCollectedHeap.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -28,7 +28,6 @@ #include "gc/z/zBarrierSet.hpp" #include "gc/z/zHeap.hpp" #include "gc/z/zInitialize.hpp" -#include "gc/z/zRuntimeWorkers.hpp" #include "memory/metaspace.hpp" #include "services/memoryUsage.hpp" @@ -48,7 +47,6 @@ class ZCollectedHeap : public CollectedHeap { ZDriverMajor* _driver_major; ZDirector* _director; ZStat* _stat; - ZRuntimeWorkers _runtime_workers; HeapWord* allocate_new_tlab(size_t min_size, size_t requested_size, @@ -101,7 +99,7 @@ class ZCollectedHeap : public CollectedHeap { void unregister_nmethod(nmethod* nm) override; void verify_nmethod(nmethod* nmethod) override; - WorkerThreads* safepoint_workers() override; + bool supports_parallel_object_iteration() const override; void gc_threads_do(ThreadClosure* tc) const override; diff --git a/src/hotspot/share/gc/z/zRuntimeWorkers.cpp b/src/hotspot/share/gc/z/zRuntimeWorkers.cpp deleted file mode 100644 index c8c4cde9ba0f..000000000000 --- a/src/hotspot/share/gc/z/zRuntimeWorkers.cpp +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -#include "gc/shared/gc_globals.hpp" -#include "gc/shared/gcLogPrecious.hpp" -#include "gc/z/zRuntimeWorkers.hpp" -#include "runtime/java.hpp" - -ZRuntimeWorkers::ZRuntimeWorkers() - : _workers("RuntimeWorker", ParallelGCThreads) { - - log_info_p(gc, init)("Runtime Workers: %u", _workers.max_workers()); - - // Initialize worker threads - _workers.initialize_workers(); - _workers.set_active_workers(_workers.max_workers()); - if (_workers.active_workers() != _workers.max_workers()) { - vm_exit_during_initialization("Failed to create ZRuntimeWorkers"); - } -} - -WorkerThreads* ZRuntimeWorkers::workers() { - return &_workers; -} - -void ZRuntimeWorkers::threads_do(ThreadClosure* tc) const { - _workers.threads_do(tc); -} diff --git a/src/hotspot/share/gc/z/zRuntimeWorkers.hpp b/src/hotspot/share/gc/z/zRuntimeWorkers.hpp deleted file mode 100644 index dede5b0b2d9b..000000000000 --- a/src/hotspot/share/gc/z/zRuntimeWorkers.hpp +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) 2018, 2021, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -#ifndef SHARE_GC_Z_ZRUNTIMEWORKERS_HPP -#define SHARE_GC_Z_ZRUNTIMEWORKERS_HPP - -#include "gc/shared/workerThread.hpp" - -class ThreadClosure; - -class ZRuntimeWorkers { -private: - WorkerThreads _workers; - -public: - ZRuntimeWorkers(); - - WorkerThreads* workers(); - - void threads_do(ThreadClosure* tc) const; -}; - -#endif // SHARE_GC_Z_ZRUNTIMEWORKERS_HPP diff --git a/src/hotspot/share/memory/heapInspection.cpp b/src/hotspot/share/memory/heapInspection.cpp index 0a8510c5eb83..27a807a66b9c 100644 --- a/src/hotspot/share/memory/heapInspection.cpp +++ b/src/hotspot/share/memory/heapInspection.cpp @@ -40,6 +40,7 @@ #include "runtime/atomicAccess.hpp" #include "runtime/fieldDescriptor.inline.hpp" #include "runtime/os.hpp" +#include "services/diagnosticWorkers.hpp" #include "utilities/globalDefinitions.hpp" #include "utilities/macros.hpp" #include "utilities/stack.inline.hpp" @@ -734,9 +735,18 @@ uintx HeapInspection::populate_table(KlassInfoTable* cit, BoolObjectClosure *fil return ric.missed_count(); } -void HeapInspection::heap_inspection(outputStream* st, WorkerThreads* workers) { +void HeapInspection::heap_inspection(outputStream* st, uint num_workers) { ResourceMark rm; + // Use parallel workers if we have enough active workers and parallel heap iteration is supported. + WorkerThreads* workers = nullptr; + if (Universe::heap()->supports_parallel_object_iteration() && num_workers > 1) { + const uint active_workers = DiagnosticWorkers::try_and_set_active_workers(num_workers); + if (active_workers > 1) { + workers = DiagnosticWorkers::workers(); + } + } + KlassInfoTable cit(false); if (!cit.allocation_failed()) { // populate table with object allocation info diff --git a/src/hotspot/share/memory/heapInspection.hpp b/src/hotspot/share/memory/heapInspection.hpp index 08095d3845a3..51f595e18249 100644 --- a/src/hotspot/share/memory/heapInspection.hpp +++ b/src/hotspot/share/memory/heapInspection.hpp @@ -205,7 +205,7 @@ class KlassInfoClosure; class HeapInspection : public StackObj { public: - void heap_inspection(outputStream* st, WorkerThreads* workers) NOT_SERVICES_RETURN; + void heap_inspection(outputStream* st, uint num_workers) NOT_SERVICES_RETURN; uintx populate_table(KlassInfoTable* cit, BoolObjectClosure* filter, WorkerThreads* workers) NOT_SERVICES_RETURN_(0); static void find_instances_at_safepoint(Klass* k, GrowableArray* result) NOT_SERVICES_RETURN; }; diff --git a/src/hotspot/share/nmt/memMapPrinter.cpp b/src/hotspot/share/nmt/memMapPrinter.cpp index 639e06292fc7..aabc6673d083 100644 --- a/src/hotspot/share/nmt/memMapPrinter.cpp +++ b/src/hotspot/share/nmt/memMapPrinter.cpp @@ -39,6 +39,7 @@ #include "runtime/thread.hpp" #include "runtime/threadSMR.hpp" #include "runtime/vmThread.hpp" +#include "services/diagnosticWorkers.hpp" #include "utilities/bitMap.hpp" #include "utilities/bitMap.inline.hpp" #include "utilities/globalDefinitions.hpp" @@ -174,12 +175,12 @@ static bool vma_touches_thread_stack(const void* from, const void* to, const Thr return t->stack_base_or_null() != nullptr && range_intersects(from, to, (const void*)t->stack_end(), (const void*)t->stack_base()); } -struct GCThreadClosure : public ThreadClosure { +struct ThreadStackClosure : public ThreadClosure { bool _found; uintx _tid; const void* const _from; const void* const _to; - GCThreadClosure(const void* from, const void* to) : _found(false), _tid(0), _from(from), _to(to) {} + ThreadStackClosure(const void* from, const void* to) : _found(false), _tid(0), _from(from), _to(to) {} void do_thread(Thread* t) override { if (_tid == 0 && t != nullptr && vma_touches_thread_stack(_from, _to, t)) { _found = true; @@ -221,12 +222,19 @@ static void print_thread_details_for_supposed_stack_address(const void* from, co #undef HANDLE_THREAD if (Universe::heap() != nullptr) { - GCThreadClosure cl(from, to); - Universe::heap()->gc_threads_do(&cl); - if (cl._found) { - print_thread_details(cl._tid, "GC Thread", st); + ThreadStackClosure gc_cl(from, to); + Universe::heap()->gc_threads_do(&gc_cl); + if (gc_cl._found) { + print_thread_details(gc_cl._tid, "GC Thread", st); + return; } } + + ThreadStackClosure diag_cl(from, to); + DiagnosticWorkers::threads_do(&diag_cl); + if (diag_cl._found) { + print_thread_details(diag_cl._tid, "DiagWorker", st); + } } /////////////// diff --git a/src/hotspot/share/runtime/threads.cpp b/src/hotspot/share/runtime/threads.cpp index dd167a6acf75..7e2897270c9d 100644 --- a/src/hotspot/share/runtime/threads.cpp +++ b/src/hotspot/share/runtime/threads.cpp @@ -100,6 +100,7 @@ #include "runtime/vmOperations.hpp" #include "sanitizers/address.hpp" #include "services/attachListener.hpp" +#include "services/diagnosticWorkers.hpp" #include "services/management.hpp" #include "services/threadIdTable.hpp" #include "services/threadService.hpp" @@ -1466,6 +1467,7 @@ void Threads::print_on_error(outputStream* st, Thread* current, char* buf, if (Universe::heap() != nullptr) { PrintOnErrorClosure print_closure(st, current, buf, buflen, &found_current); Universe::heap()->gc_threads_do(&print_closure); + DiagnosticWorkers::threads_do(&print_closure); num_other += print_closure.num_printed(); } diff --git a/src/hotspot/share/services/diagnosticWorkers.cpp b/src/hotspot/share/services/diagnosticWorkers.cpp new file mode 100644 index 000000000000..bad6948be490 --- /dev/null +++ b/src/hotspot/share/services/diagnosticWorkers.cpp @@ -0,0 +1,70 @@ +/* + * Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "gc/shared/workerThread.hpp" +#include "logging/log.hpp" +#include "runtime/os.hpp" +#include "runtime/safepoint.hpp" +#include "runtime/thread.hpp" +#include "services/diagnosticWorkers.hpp" + +Atomic DiagnosticWorkers::_workers{}; + +WorkerThreads* DiagnosticWorkers::workers() { + assert(Thread::current()->is_VM_thread(), "Must be the VM thread"); + assert_at_safepoint(); + if (_workers.load_relaxed() == nullptr) { + const uint max_workers = (uint)os::active_processor_count(); + if (max_workers > 1) { + WorkerThreads* pool = new WorkerThreads("DiagWorker", max_workers); + // Use release_store() because the error reporter can read _workers while + // the pool is being created. + _workers.release_store(pool); + log_info(gc, task)("Created diagnostic worker pool (max %u workers)", pool->max_workers()); + } + } + return _workers.load_relaxed(); +} + +void DiagnosticWorkers::threads_do(ThreadClosure* tc) { + // The error reporter can call this method outside a safepoint, so use load_acquire(). + WorkerThreads* workers = _workers.load_acquire(); + if (workers != nullptr) { + workers->threads_do(tc); + } +} + +uint DiagnosticWorkers::try_and_set_active_workers(uint num_workers) { + assert(Thread::current()->is_VM_thread(), "Must be the VM thread"); + assert_at_safepoint(); + if (num_workers == 0) { + return 0; + } + + WorkerThreads* pool = workers(); + if (pool == nullptr) { + return 0; + } + return pool->set_active_workers(MIN2(num_workers, pool->max_workers())); +} diff --git a/src/hotspot/share/services/diagnosticWorkers.hpp b/src/hotspot/share/services/diagnosticWorkers.hpp new file mode 100644 index 000000000000..3127f47e85ed --- /dev/null +++ b/src/hotspot/share/services/diagnosticWorkers.hpp @@ -0,0 +1,59 @@ +/* + * Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_SERVICES_DIAGNOSTICWORKERS_HPP +#define SHARE_SERVICES_DIAGNOSTICWORKERS_HPP + +#include "memory/allStatic.hpp" +#include "runtime/atomic.hpp" +#include "utilities/globalDefinitions.hpp" + +class ThreadClosure; +class WorkerThreads; + +// Worker pool used by diagnostic tasks, for example heap dump and heap inspection. +// The pool is created lazily on the first parallel request and threads are added to +// the pool as each request needs them. Threads are reused and exist until the VM exits. +class DiagnosticWorkers : public AllStatic { +private: + // Written once by the VM thread at a safepoint. Also read by the error reporter, + // which can run outside a safepoint. + static Atomic _workers; + +public: + // Returns the diagnostic worker pool, creating it on first use and sizing it to the + // active processor count at that time. Returns nullptr if the pool hasn't been created + // and the active processor count is 1. Can only be called by the VM thread at a safepoint. + static WorkerThreads* workers(); + + // Applies the closure to each diagnostic worker if the worker pool has been created. + static void threads_do(ThreadClosure* tc); + + // Sets the number of active worker threads (capped at max_workers()) and returns the + // active thread count. Creates the worker pool on first use. Returns 0 if the pool + // wasn't created or num_workers is 0. Can only be called by the VM thread at a safepoint. + static uint try_and_set_active_workers(uint num_workers); +}; + +#endif // SHARE_SERVICES_DIAGNOSTICWORKERS_HPP diff --git a/src/hotspot/share/services/heapDumper.cpp b/src/hotspot/share/services/heapDumper.cpp index 906a24746c10..2bc8d09d2c4c 100644 --- a/src/hotspot/share/services/heapDumper.cpp +++ b/src/hotspot/share/services/heapDumper.cpp @@ -61,6 +61,7 @@ #include "runtime/vframe.hpp" #include "runtime/vmOperations.hpp" #include "runtime/vmThread.hpp" +#include "services/diagnosticWorkers.hpp" #include "services/heapDumper.hpp" #include "services/heapDumperCompression.hpp" #include "services/threadService.hpp" @@ -2253,7 +2254,7 @@ class DumperController : public CHeapObj { public: DumperController(uint number) : - // _lock and _global_writer_lock are used for synchronization between GC worker threads inside safepoint, + // _lock and _global_writer_lock are used for synchronization between diagnostic worker threads inside safepoint, // so we lock with _no_safepoint_check_flag. // signal_start() acquires _lock when global writer is locked, // its rank must be less than _global_writer_lock rank. @@ -2557,7 +2558,8 @@ class VM_HeapDumper : public VM_GC_Operation, public WorkerTask, public Unmounte } int dump_seq() { return _dump_seq; } bool is_parallel_dump() { return _num_dumper_threads > 1; } - void prepare_parallel_dump(WorkerThreads* workers); + // Returns the worker pool for a parallel dump or nullptr for a serial dump. + WorkerThreads* prepare_parallel_dump(); VMOp_Type type() const { return VMOp_HeapDumper; } virtual bool doit_prologue(); @@ -2600,14 +2602,21 @@ bool VM_HeapDumper::doit_prologue() { return VM_GC_Operation::doit_prologue(); } -void VM_HeapDumper::prepare_parallel_dump(WorkerThreads* workers) { - uint num_active_workers = workers != nullptr ? workers->active_workers() : 0; - uint num_requested_dump_threads = _num_dumper_threads; - // check if we can dump in parallel based on requested and active threads - if (num_active_workers <= 1 || num_requested_dump_threads <= 1) { +WorkerThreads* VM_HeapDumper::prepare_parallel_dump() { + const uint num_requested_dump_threads = _num_dumper_threads; + uint num_active_workers = 0; + WorkerThreads* workers = nullptr; + + if (Universe::heap()->supports_parallel_object_iteration() && _num_dumper_threads > 1) { + num_active_workers = DiagnosticWorkers::try_and_set_active_workers(_num_dumper_threads); + } + + // check if we can dump in parallel based on active threads + if (num_active_workers <= 1) { _num_dumper_threads = 1; } else { - _num_dumper_threads = clamp(num_requested_dump_threads, 2U, num_active_workers); + workers = DiagnosticWorkers::workers(); + _num_dumper_threads = num_active_workers; } _dumper_controller = new (std::nothrow) DumperController(_num_dumper_threads); bool can_parallel = _num_dumper_threads > 1; @@ -2615,6 +2624,7 @@ void VM_HeapDumper::prepare_parallel_dump(WorkerThreads* workers) { "actual dump threads %u, parallelism %s", num_requested_dump_threads, num_active_workers, _num_dumper_threads, can_parallel ? "true" : "false"); + return workers; } // The VM operation that dumps the heap. The dump consists of the following @@ -2656,8 +2666,7 @@ void VM_HeapDumper::doit() { } } - WorkerThreads* workers = ch->safepoint_workers(); - prepare_parallel_dump(workers); + WorkerThreads* workers = prepare_parallel_dump(); if (!is_parallel_dump()) { work(VMDumperId); diff --git a/test/hotspot/jtreg/serviceability/dcmd/gc/ClassHistogramParallelTest.java b/test/hotspot/jtreg/serviceability/dcmd/gc/ClassHistogramParallelTest.java new file mode 100644 index 000000000000..9d21e3cf5ee3 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/dcmd/gc/ClassHistogramParallelTest.java @@ -0,0 +1,198 @@ +/* + * Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import jdk.test.lib.Asserts; +import jdk.test.lib.apps.LingeredApp; +import jdk.test.lib.dcmd.PidJcmdExecutor; +import jdk.test.lib.process.OutputAnalyzer; + +/* + * @test id=Serial + * @bug 8392050 + * @requires vm.gc.Serial + * @summary Verify serial heap inspection doesn't create and use the diagnostic worker pool (Serial GC) + * @library /test/lib + * @run driver ClassHistogramParallelTest -XX:+UseSerialGC + */ + +/* + * @test id=Parallel + * @bug 8392050 + * @requires vm.gc.Parallel + * @summary Verify parallel heap inspection uses the diagnostic worker pool (Parallel GC) + * @library /test/lib + * @run driver ClassHistogramParallelTest -XX:+UseParallelGC + */ + +/* + * @test id=G1 + * @bug 8392050 + * @requires vm.gc.G1 + * @summary Verify parallel heap inspection uses the diagnostic worker pool (G1 GC) + * @library /test/lib + * @run driver ClassHistogramParallelTest -XX:+UseG1GC + */ + +/* + * @test id=Shenandoah + * @bug 8392050 + * @requires vm.gc.Shenandoah + * @summary Verify parallel heap inspection uses the diagnostic worker pool (Shenandoah GC) + * @library /test/lib + * @run driver ClassHistogramParallelTest -XX:+UseShenandoahGC + */ + +/* + * @test id=ShenandoahGenerational + * @bug 8392050 + * @requires vm.gc.Shenandoah + * @summary Verify parallel heap inspection uses the diagnostic worker pool (Generational Shenandoah GC) + * @library /test/lib + * @run driver ClassHistogramParallelTest -XX:+UseShenandoahGC -XX:ShenandoahGCMode=generational + */ + +/* + * @test id=Z + * @bug 8392050 + * @requires vm.gc.Z + * @summary Verify parallel heap inspection uses the diagnostic worker pool (Z GC) + * @library /test/lib + * @run driver ClassHistogramParallelTest -XX:+UseZGC + */ + +/* + * @test id=Epsilon + * @bug 8392050 + * @requires vm.gc.Epsilon + * @summary Verify serial heap inspection doesn't create and use the diagnostic worker pool (Epsilon GC) + * @library /test/lib + * @run driver ClassHistogramParallelTest -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC + */ + +public class ClassHistogramParallelTest { + private static final int ACTIVE_PROCESSOR_COUNT = 8; + private static final int DEFAULT_WORKERS = Math.max(1, ACTIVE_PROCESSOR_COUNT * 3 / 8); + private static final String POOL_CREATION_MSG = + "Created diagnostic worker pool (max " + ACTIVE_PROCESSOR_COUNT + " workers)"; + + private static void checkAndVerify(OutputAnalyzer threadPrintOut, LingeredApp app, + int expectedDiagWorkers, String[] gcArgs) throws Exception { + threadPrintOut.shouldHaveExitValue(0); + OutputAnalyzer appOut = new OutputAnalyzer(app.getProcessStdout()); + + List gcFlags = Arrays.asList(gcArgs); + if (gcFlags.contains("-XX:+UseSerialGC") || gcFlags.contains("-XX:+UseEpsilonGC")) { + System.out.println("Serial or Epsilon GC detected."); + expectedDiagWorkers = 0; + } + + if (expectedDiagWorkers > 1) { + appOut.shouldContain(POOL_CREATION_MSG); + + // Verify that the worker pool was only created once. + int count = (int)appOut.stdoutAsLines().stream() + .filter(line -> line.contains(POOL_CREATION_MSG)) + .count(); + Asserts.assertEquals(1, count); + + threadPrintOut.shouldContain("\"DiagWorker#" + (expectedDiagWorkers - 1) + "\""); + threadPrintOut.shouldNotContain("\"DiagWorker#" + expectedDiagWorkers + "\""); + } else { + appOut.shouldNotContain("Created diagnostic worker pool"); + threadPrintOut.shouldNotContain("DiagWorker"); + } + } + + private static LingeredApp launchApp(String[] gcArgs) throws IOException { + LingeredApp theApp = new LingeredApp(); + List jvmArgs = new ArrayList<>(List.of("-Xlog:gc+task", + "-Xmx512m", + "-XX:ActiveProcessorCount=" + ACTIVE_PROCESSOR_COUNT)); + jvmArgs.addAll(Arrays.asList(gcArgs)); + LingeredApp.startApp(theApp, jvmArgs.toArray(new String[0])); + return theApp; + } + + public static void main(String[] args) throws Exception { + LingeredApp theApp = launchApp(args); + try { + // Expect error message + OutputAnalyzer out = attachJcmd(theApp.getPid(), "GC.class_histogram -parallel=-1"); + out.shouldContain("Parallel thread number out of range (>=0): -1"); + + // Expect serial inspection. + test("-parallel=1", 0, args, 1); + + // If no parallel threads are passed in or -parallel=0, the default number of parallel + // threads to use is based on CPUs: max(1, ACTIVE_PROCESSOR_COUNT * 3 / 8). + test("", DEFAULT_WORKERS, args, 1); + test("-parallel=0", DEFAULT_WORKERS, args, 1); + + // Expect parallel heap inspection. + test("-parallel=2", 2, args, 1); + test("-parallel=" + ACTIVE_PROCESSOR_COUNT, ACTIVE_PROCESSOR_COUNT, args, 1); + test("-parallel=" + Integer.MAX_VALUE, ACTIVE_PROCESSOR_COUNT, args, 1); + + // Expect that the diagnostic worker pool is created once for multiple requests. + test("-parallel=2", 2, args, 5); + } finally { + theApp.stopApp(); + } + } + + private static void test(String parallelThreads, int expectedDiagWorkers, String[] gcArgs, int attempts) throws Exception { + LingeredApp theApp = launchApp(gcArgs); + try { + OutputAnalyzer classHistogramOut = attachJcmd(theApp.getPid(), "GC.class_histogram " + parallelThreads); + verifyHistogram(classHistogramOut); + + for (int i = 1; i < attempts; i++) { + classHistogramOut = attachJcmd(theApp.getPid(), "GC.class_histogram " + parallelThreads); + verifyHistogram(classHistogramOut); + } + + OutputAnalyzer threadPrintOut = attachJcmd(theApp.getPid(), "Thread.print"); + theApp.stopApp(); + checkAndVerify(threadPrintOut, theApp, expectedDiagWorkers, gcArgs); + } finally { + theApp.stopApp(); + } + } + + private static OutputAnalyzer attachJcmd(long lingeredAppPid, String cmd) throws Exception { + System.out.println("Testing pid " + lingeredAppPid); + PidJcmdExecutor executor = new PidJcmdExecutor("" + lingeredAppPid); + return executor.execute(cmd, true); + } + + private static void verifyHistogram(OutputAnalyzer out) throws Exception { + out.shouldHaveExitValue(0); + // Verify that the histogram was printed. + out.shouldMatch("^Total\\s+\\d+\\s+\\d+"); + } +} diff --git a/test/hotspot/jtreg/serviceability/dcmd/gc/HeapDumpParallelTest.java b/test/hotspot/jtreg/serviceability/dcmd/gc/HeapDumpParallelTest.java index 71507fa6e705..57361fdab05c 100644 --- a/test/hotspot/jtreg/serviceability/dcmd/gc/HeapDumpParallelTest.java +++ b/test/hotspot/jtreg/serviceability/dcmd/gc/HeapDumpParallelTest.java @@ -24,33 +24,89 @@ import java.io.File; import java.io.IOException; -import java.nio.file.Files; +import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; -import jdk.test.lib.Asserts; -import jdk.test.lib.JDKToolLauncher; import jdk.test.lib.apps.LingeredApp; import jdk.test.lib.dcmd.PidJcmdExecutor; import jdk.test.lib.process.OutputAnalyzer; -import jdk.test.lib.process.ProcessTools; import jdk.test.lib.hprof.HprofParser; -/** - * @test - * @bug 8306441 8319053 8384612 - * @summary Verify the integrity of generated heap dump and capability of parallel dump +/* + * @test id=Serial + * @bug 8306441 8319053 8384612 8392050 + * @requires vm.gc.Serial + * @summary Verify the integrity of generated heap dump and that parallel dump requests run serially (Serial GC) + * @library /test/lib + * @run driver HeapDumpParallelTest -XX:+UseSerialGC + */ + +/* + * @test id=Parallel + * @bug 8306441 8319053 8384612 8392050 + * @requires vm.gc.Parallel + * @summary Verify the integrity of generated heap dump and capability of parallel dump (Parallel GC) + * @library /test/lib + * @run driver HeapDumpParallelTest -XX:+UseParallelGC + */ + +/* + * @test id=G1 + * @bug 8306441 8319053 8384612 8392050 + * @requires vm.gc.G1 + * @summary Verify the integrity of generated heap dump and capability of parallel dump (G1 GC) + * @library /test/lib + * @run driver HeapDumpParallelTest -XX:+UseG1GC + */ + +/* + * @test id=Shenandoah + * @bug 8306441 8319053 8384612 8392050 + * @requires vm.gc.Shenandoah + * @summary Verify the integrity of generated heap dump and capability of parallel dump (Shenandoah GC) + * @library /test/lib + * @run driver HeapDumpParallelTest -XX:+UseShenandoahGC + */ + +/* + * @test id=ShenandoahGenerational + * @bug 8306441 8319053 8384612 8392050 + * @requires vm.gc.Shenandoah + * @summary Verify the integrity of generated heap dump and capability of parallel dump (Generational Shenandoah GC) + * @library /test/lib + * @run driver HeapDumpParallelTest -XX:+UseShenandoahGC -XX:ShenandoahGCMode=generational + */ + +/* + * @test id=Z + * @bug 8306441 8319053 8384612 8392050 + * @requires vm.gc.Z + * @summary Verify the integrity of generated heap dump and capability of parallel dump (Z GC) * @library /test/lib - * @run main HeapDumpParallelTest + * @run driver HeapDumpParallelTest -XX:+UseZGC + */ + +/* + * @test id=Epsilon + * @bug 8306441 8319053 8384612 8392050 + * @requires vm.gc.Epsilon + * @summary Verify the integrity of generated heap dump and that parallel dump requests run serially (Epsilon GC) + * @library /test/lib + * @run driver HeapDumpParallelTest -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC */ public class HeapDumpParallelTest { private static final String heapDumpFileName = "parallelHeapDump.bin"; + private static final int ACTIVE_PROCESSOR_COUNT = 8; + private static final int DEFAULT_WORKERS = Math.max(1, ACTIVE_PROCESSOR_COUNT * 3 / 8); - private static void checkAndVerify(OutputAnalyzer dcmdOut, LingeredApp app, File heapDumpFile, boolean expectSerial) throws Exception { + private static void checkAndVerify(OutputAnalyzer dcmdOut, LingeredApp app, File heapDumpFile, + int expectedDiagWorkers) throws Exception { dcmdOut.shouldHaveExitValue(0); dcmdOut.shouldContain("Heap dump file created"); OutputAnalyzer appOut = new OutputAnalyzer(app.getProcessStdout()); @@ -59,13 +115,17 @@ private static void checkAndVerify(OutputAnalyzer dcmdOut, LingeredApp app, File appOut.shouldMatch("\\[gc *\\] Using "); if (appOut.getStdout().contains("Using Serial") || appOut.getStdout().contains("Using Epsilon")) { System.out.println("Serial or Epsilon GC detected."); - expectSerial = true; + expectedDiagWorkers = 0; } - if (!expectSerial) { + if (expectedDiagWorkers > 1) { + appOut.shouldContain("Created diagnostic worker pool (max " + ACTIVE_PROCESSOR_COUNT + " workers)"); appOut.shouldContain("Dump heap objects in parallel"); appOut.shouldContain("Merge heap files complete"); + appOut.shouldContain("active dump threads " + expectedDiagWorkers + ","); } else { + appOut.shouldNotContain("Created diagnostic worker pool"); appOut.shouldNotContain("Dump heap objects in parallel"); + appOut.shouldContain("active dump threads 0"); } HprofParser.parseAndVerify(heapDumpFile); @@ -83,11 +143,12 @@ private static void checkAndVerify(OutputAnalyzer dcmdOut, LingeredApp app, File } } - private static LingeredApp launchApp() throws IOException { + private static LingeredApp launchApp(String[] gcArgs) throws IOException { LingeredApp theApp = new LingeredApp(); - LingeredApp.startApp(theApp, "-Xlog:gc,heapdump", "-Xmx512m", - "-XX:-UseDynamicNumberOfGCThreads", - "-XX:ParallelGCThreads=2"); + List jvmArgs = new ArrayList<>(List.of("-Xlog:gc,heapdump,gc+task", "-Xmx512m", + "-XX:ActiveProcessorCount=" + ACTIVE_PROCESSOR_COUNT)); + jvmArgs.addAll(Arrays.asList(gcArgs)); + LingeredApp.startApp(theApp, jvmArgs.toArray(new String[0])); return theApp; } @@ -97,7 +158,7 @@ public static void main(String[] args) throws Exception { heapDumpFile.delete(); } - LingeredApp theApp = launchApp(); + LingeredApp theApp = launchApp(args); try { // Expect error message OutputAnalyzer out = attachJcmdHeapDump(heapDumpFile, theApp.getPid(), "-parallel=" + -1); @@ -108,27 +169,34 @@ public static void main(String[] args) throws Exception { out.shouldContain("Invalid number of parallel dump threads."); // Expect serial dump because 0 implies to disable parallel dump - test(heapDumpFile, "-parallel=" + 0, true); + test(heapDumpFile, "-parallel=" + 0, 0, args); // Expect serial dump - test(heapDumpFile, "-parallel=" + 1, true); + test(heapDumpFile, "-parallel=" + 1, 0, args); // Expect parallel dump - test(heapDumpFile, "-parallel=" + Integer.MAX_VALUE, false); + test(heapDumpFile, "-parallel=" + 2, 2, args); // Expect parallel dump - test(heapDumpFile, "-gz=9 -overwrite -parallel=" + Math.max(Runtime.getRuntime().availableProcessors(), 2), false); + test(heapDumpFile, "-parallel=" + Integer.MAX_VALUE, ACTIVE_PROCESSOR_COUNT, args); + + // Expect parallel dump + test(heapDumpFile, "-gz=9 -overwrite -parallel=" + ACTIVE_PROCESSOR_COUNT, ACTIVE_PROCESSOR_COUNT, args); + + // If no parallel threads are passed in, the default number of parallel threads + // to use is based on CPUs: max(1, ACTIVE_PROCESSOR_COUNT * 3 / 8). + test(heapDumpFile, "", DEFAULT_WORKERS, args); } finally { theApp.stopApp(); } } - private static void test(File heapDumpFile, String arg, boolean expectSerial) throws Exception { - LingeredApp theApp = launchApp(); + private static void test(File heapDumpFile, String arg, int expectedDiagWorkers, String[] gcArgs) throws Exception { + LingeredApp theApp = launchApp(gcArgs); try { OutputAnalyzer dcmdOut = attachJcmdHeapDump(heapDumpFile, theApp.getPid(), arg); theApp.stopApp(); - checkAndVerify(dcmdOut, theApp, heapDumpFile, expectSerial); + checkAndVerify(dcmdOut, theApp, heapDumpFile, expectedDiagWorkers); } finally { theApp.stopApp(); } From 6ed82b3b15804a9e6b1d93513936db0d10e40a7e Mon Sep 17 00:00:00 2001 From: Bill Huang Date: Wed, 21 Oct 2026 18:13:38 +0000 Subject: [PATCH 166/169] 8384611: Improve value class test coverage in java/util Collections/Ser and HashMap/PutNullKey Reviewed-by: smarks --- test/jdk/TEST.groups | 3 +- test/jdk/java/util/Collections/Ser.java | 92 +++------------------- test/jdk/java/util/HashMap/PutNullKey.java | 60 +++++++++++--- 3 files changed, 63 insertions(+), 92 deletions(-) diff --git a/test/jdk/TEST.groups b/test/jdk/TEST.groups index 036f80baaade..32d26499dc64 100644 --- a/test/jdk/TEST.groups +++ b/test/jdk/TEST.groups @@ -175,7 +175,8 @@ jdk_util = \ valhalla_adopted = \ java/util/Collections/AddAll.java \ java/util/Arrays \ - java/util/Collections + java/util/Collections \ + java/util/HashMap # All util components not part of some other util category jdk_util_other = \ diff --git a/test/jdk/java/util/Collections/Ser.java b/test/jdk/java/util/Collections/Ser.java index de99cdea8bbc..f1a8b949e73f 100644 --- a/test/jdk/java/util/Collections/Ser.java +++ b/test/jdk/java/util/Collections/Ser.java @@ -23,7 +23,7 @@ /* * @test - * @bug 4190323 + * @bug 4190323 8384611 * @summary EMPTY_SET, EMPTY_LIST, and the collections returned by * nCopies and singleton were spec'd to be serializable, but weren't. * @library /test/lib @@ -36,8 +36,6 @@ import java.io.ObjectOutputStream; import java.io.Serializable; import java.util.Collections; -import java.util.List; -import java.util.Set; public class Ser { @@ -56,91 +54,27 @@ private static class SerProxy implements Serializable { } public static void main(String[] args) throws Exception { + checkSerialization(Collections.EMPTY_SET, "empty set"); + checkSerialization(Collections.EMPTY_LIST, "empty list"); + checkSerialization(Collections.singleton("gumby"), "singleton"); + checkSerialization(Collections.nCopies(50, "gumby"), "nCopies"); + checkSerialization(Collections.singleton(new SerV(1)), "value singleton"); + checkSerialization(Collections.nCopies(5, new SerV(2)), "value nCopies"); + } + private static void checkSerialization(Object obj, String label) throws Exception { try { ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(bos); - out.writeObject(Collections.EMPTY_SET); - out.flush(); - ObjectInputStream in = new ObjectInputStream( - new ByteArrayInputStream(bos.toByteArray())); - - if (!Collections.EMPTY_SET.equals(in.readObject())) - throw new RuntimeException("empty set Ser/Deser failure."); - } catch (Exception e) { - throw new RuntimeException("Failed to serialize empty set:" + e); - } - - try { - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - ObjectOutputStream out = new ObjectOutputStream(bos); - out.writeObject(Collections.EMPTY_LIST); - out.flush(); - ObjectInputStream in = new ObjectInputStream( - new ByteArrayInputStream(bos.toByteArray())); - - if (!Collections.EMPTY_LIST.equals(in.readObject())) - throw new RuntimeException("empty list Ser/Deser failure."); - } catch (Exception e) { - throw new RuntimeException("Failed to serialize empty list:" + e); - } - - try { - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - ObjectOutputStream out = new ObjectOutputStream(bos); - Set gumby = Collections.singleton("gumby"); - out.writeObject(gumby); - out.flush(); - ObjectInputStream in = new ObjectInputStream( - new ByteArrayInputStream(bos.toByteArray())); - - if (!gumby.equals(in.readObject())) - throw new RuntimeException("Singleton Ser/Deser failure."); - } catch (Exception e) { - throw new RuntimeException("Failed to serialize singleton:" + e); - } - - try { - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - ObjectOutputStream out = new ObjectOutputStream(bos); - List gumbies = Collections.nCopies(50, "gumby"); - out.writeObject(gumbies); - out.flush(); - ObjectInputStream in = new ObjectInputStream( - new ByteArrayInputStream(bos.toByteArray())); - - if (!gumbies.equals(in.readObject())) - throw new RuntimeException("nCopies Ser/Deser failure."); - } catch (Exception e) { - throw new RuntimeException("Failed to serialize nCopies:" + e); - } - - try { - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - ObjectOutputStream out = new ObjectOutputStream(bos); - Set one = Collections.singleton(new SerV(1)); - out.writeObject(one); + out.writeObject(obj); out.flush(); ObjectInputStream in = new ObjectInputStream( new ByteArrayInputStream(bos.toByteArray())); - if (!one.equals(in.readObject())) - throw new RuntimeException("value singleton Ser/Deser failure"); - } catch (Exception e) { - throw new RuntimeException("Failed to serialize value singleton: " + e); - } - try { - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - ObjectOutputStream out = new ObjectOutputStream(bos); - List many = Collections.nCopies(5, new SerV(2)); - out.writeObject(many); - out.flush(); - ObjectInputStream in = new ObjectInputStream( - new ByteArrayInputStream(bos.toByteArray())); - if (!many.equals(in.readObject())) - throw new RuntimeException("value nCopies Ser/Deser failure"); + if (!obj.equals(in.readObject())) + throw new RuntimeException(label + " Ser/Deser failure."); } catch (Exception e) { - throw new RuntimeException("Failed to serialize value nCopies: " + e); + throw new RuntimeException("Failed to serialize " + label + ":" + e); } } } diff --git a/test/jdk/java/util/HashMap/PutNullKey.java b/test/jdk/java/util/HashMap/PutNullKey.java index 72aade00186e..d7caa431242d 100644 --- a/test/jdk/java/util/HashMap/PutNullKey.java +++ b/test/jdk/java/util/HashMap/PutNullKey.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,12 +23,15 @@ /** * @test - * @bug 8046085 + * @bug 8046085 8384611 * @summary Ensure that when trees are being used for collisions that null key * insertion still works. + * @library /test/lib */ +import jdk.test.lib.valueclass.AsValueClass; import java.util.*; +import java.util.function.IntFunction; import java.util.stream.IntStream; public class PutNullKey { @@ -61,30 +64,63 @@ public int hashCode() { @Override public boolean equals(Object o) { - if (null == o) { - return false; - } + return o instanceof CollidingHash t && value == t.value; + } + + @Override + public int compareTo(CollidingHash o) { + return Integer.compare(value, o.value); + } + } - if (o.getClass() != CollidingHash.class) { - return false; - } + // Not Comparable, so colliding keys in a tree bin are ordered by + // HashMap.TreeNode.tieBreakOrder, which uses System.identityHashCode. + @AsValueClass + public static class CollidingHashValue { - return value == ((CollidingHash) o).value; + private final int value; + + public CollidingHashValue(int value) { + this.value = value; } @Override - public int compareTo(CollidingHash o) { - return value - o.value; + public int hashCode() { + // intentionally bad hashcode. Force into first bin. + return 0; + } + + @Override + public boolean equals(Object o) { + return o instanceof CollidingHashValue t && value == t.value; } } public static void main(String[] args) throws Exception { + testCollidingHash(CollidingHash::new); + testCollidingHash(CollidingHashValue::new); + } + + private static void testCollidingHash(IntFunction function) { Map m = new HashMap<>(INITIAL_CAPACITY, LOAD_FACTOR); IntStream.range(0, SIZE) - .mapToObj(CollidingHash::new) + .mapToObj(function) .forEach(e -> { m.put(e, e); }); + for (int i = 0; i < SIZE; i++) { + Object key = function.apply(i); + if (!key.equals(m.get(key))) + throw new RuntimeException("Missing key " + i); + } + // kaboom? m.put(null, null); + + for (int i = 0; i < SIZE; i++) { + if (m.remove(function.apply(i)) == null) + throw new RuntimeException("Failed to remove key " + i); + } + if (m.size() != 1 || !m.containsKey(null)) + throw new RuntimeException("Unexpected map contents: " + m); } } From 972a048dbb43dc3a20a2dde8c4135e3ffebd3cd5 Mon Sep 17 00:00:00 2001 From: Albert Mingkun Yang Date: Thu, 22 Oct 2026 01:15:00 +0000 Subject: [PATCH 167/169] 8394901: Parallel: Move some RegionData methods to psParallelCompact.cpp Reviewed-by: tschatzl --- .../share/gc/parallel/psParallelCompact.cpp | 86 ++++++++++++++ .../share/gc/parallel/psParallelCompact.hpp | 105 ++---------------- 2 files changed, 95 insertions(+), 96 deletions(-) diff --git a/src/hotspot/share/gc/parallel/psParallelCompact.cpp b/src/hotspot/share/gc/parallel/psParallelCompact.cpp index 33b7a62a53c3..bd8b1c3af95c 100644 --- a/src/hotspot/share/gc/parallel/psParallelCompact.cpp +++ b/src/hotspot/share/gc/parallel/psParallelCompact.cpp @@ -131,6 +131,92 @@ void ParallelCompactData::RegionData::verify_clear() { } #endif +void ParallelCompactData::RegionData::set_destination_count(uint count) +{ + assert(count <= max_destination_count, "count too large"); + const state_t old_state = state(); + assert(state_status(old_state) == status_init, "cannot reset processed region"); + _state.store_relaxed(with_dest_count(old_state, static_cast(count))); +} + +void ParallelCompactData::RegionData::decrement_destination_count() +{ + state_t old_state = _state.fetch_then_sub(static_cast(1)); + assert(state_dest_count(old_state) > 0, "count would go negative"); +} + +void ParallelCompactData::RegionData::set_completed() +{ + const state_t old_state = state(); + assert(state_status(old_state) == status_normal, "can only complete normal regions"); + _state.store_relaxed(with_status(old_state, status_completed)); +} + +bool ParallelCompactData::RegionData::available_to_normal() +{ + const state_t old_state = state(); + assert(state_status(old_state) == status_init, "initial regions must be unprocessed"); + if (state_dest_count(old_state) == 0) { + _state.store_relaxed(make_state(0, status_normal)); + return true; + } + return false; +} + +bool ParallelCompactData::RegionData::try_mark_normal() +{ + assert(available(), "region must have no remaining destination dependencies"); + return _state.compare_set(make_state(0, status_init), make_state(0, status_normal)); +} + +bool ParallelCompactData::RegionData::try_complete_filled_shadow() +{ + assert(available(), "region must have no remaining destination dependencies"); + return _state.compare_set(make_state(0, status_filled_shadow), make_state(0, status_completed)); +} + +bool ParallelCompactData::RegionData::try_mark_shadow() { + state_t old_state = state(); + while (true) { + if (state_dest_count(old_state) == 0 || + state_status(old_state) != status_init) { + return false; + } + const state_t new_state = with_status(old_state, status_shadow); + if (_state.compare_set(old_state, new_state)) { + return true; + } + old_state = state(); + } +} + +bool ParallelCompactData::RegionData::finish_shadow_fill() { + state_t old_state = state(); + while (true) { + state_t new_state; + bool copy_now = false; + assert(state_status(old_state) == status_shadow, "must be shadow"); + if (state_dest_count(old_state) == 0) { + new_state = with_status(old_state, status_completed); + copy_now = true; + } else { + new_state = with_status(old_state, status_filled_shadow); + } + if (_state.compare_set(old_state, new_state)) { + return copy_now; + } + old_state = state(); + } +} + +void ParallelCompactData::RegionData::shadow_to_normal() { + assert(available(), "region must have no remaining destination dependencies"); + const state_t old_state = make_state(0, status_shadow); + const state_t new_state = make_state(0, status_normal); + bool result = _state.compare_set(old_state, new_state); + assert(result, "Fail to mark the region as normal"); +} + SpaceInfo PSParallelCompact::_space_info[PSParallelCompact::last_space_id]; HeapWord* PSParallelCompact::_old_space_dense_prefix = nullptr; HeapWord* PSParallelCompact::_old_space_new_top = nullptr; diff --git a/src/hotspot/share/gc/parallel/psParallelCompact.hpp b/src/hotspot/share/gc/parallel/psParallelCompact.hpp index 807bafff6942..c568b26dc04f 100644 --- a/src/hotspot/share/gc/parallel/psParallelCompact.hpp +++ b/src/hotspot/share/gc/parallel/psParallelCompact.hpp @@ -150,26 +150,26 @@ class ParallelCompactData _partial_obj_size = (region_sz_t) words; } - inline void set_destination_count(uint count); + void set_destination_count(uint count); - inline void set_completed(); - inline bool available_to_normal(); + void set_completed(); + bool available_to_normal(); // These are atomic. void add_live_obj(size_t words) { assert(words <= RegionSize - live_obj_size(), "overflow"); _live_obj_size.add_then_fetch(static_cast(words)); } - inline void decrement_destination_count(); - inline bool try_mark_normal(); - inline bool try_complete_filled_shadow(); + void decrement_destination_count(); + bool try_mark_normal(); + bool try_complete_filled_shadow(); // Mark the current region as shadow to enter the shadow processing path. - inline bool try_mark_shadow(); + bool try_mark_shadow(); // Finish shadow filling. Returns true if the caller must copy back immediately. - inline bool finish_shadow_fill(); + bool finish_shadow_fill(); // Special case: see PSParallelCompact::fill_and_update_shadow_region. - inline void shadow_to_normal(); + void shadow_to_normal(); bool is_init() const { return state_status(state()) == status_init; } bool is_normal() const { return state_status(state()) == status_normal; } bool is_shadow() const { return state_status(state()) == status_shadow; } @@ -307,93 +307,6 @@ class ParallelCompactData size_t _region_count; }; -inline void -ParallelCompactData::RegionData::set_destination_count(uint count) -{ - assert(count <= max_destination_count, "count too large"); - const state_t old_state = state(); - assert(state_status(old_state) == status_init, "cannot reset processed region"); - _state.store_relaxed(with_dest_count(old_state, static_cast(count))); -} - -inline void ParallelCompactData::RegionData::decrement_destination_count() -{ - state_t old_state = _state.fetch_then_sub(static_cast(1)); - assert(state_dest_count(old_state) > 0, "count would go negative"); -} - -inline void ParallelCompactData::RegionData::set_completed() -{ - const state_t old_state = state(); - assert(state_status(old_state) == status_normal, "can only complete normal regions"); - _state.store_relaxed(with_status(old_state, status_completed)); -} - -inline bool ParallelCompactData::RegionData::available_to_normal() -{ - const state_t old_state = state(); - assert(state_status(old_state) == status_init, "initial regions must be unprocessed"); - if (state_dest_count(old_state) == 0) { - _state.store_relaxed(make_state(0, status_normal)); - return true; - } - return false; -} - -inline bool ParallelCompactData::RegionData::try_mark_normal() -{ - assert(available(), "region must have no remaining destination dependencies"); - return _state.compare_set(make_state(0, status_init), make_state(0, status_normal)); -} - -inline bool ParallelCompactData::RegionData::try_complete_filled_shadow() -{ - assert(available(), "region must have no remaining destination dependencies"); - return _state.compare_set(make_state(0, status_filled_shadow), make_state(0, status_completed)); -} - -inline bool ParallelCompactData::RegionData::try_mark_shadow() { - state_t old_state = state(); - while (true) { - if (state_dest_count(old_state) == 0 || - state_status(old_state) != status_init) { - return false; - } - const state_t new_state = with_status(old_state, status_shadow); - if (_state.compare_set(old_state, new_state)) { - return true; - } - old_state = state(); - } -} - -inline bool ParallelCompactData::RegionData::finish_shadow_fill() { - state_t old_state = state(); - while (true) { - state_t new_state; - bool copy_now = false; - assert(state_status(old_state) == status_shadow, "must be shadow"); - if (state_dest_count(old_state) == 0) { - new_state = with_status(old_state, status_completed); - copy_now = true; - } else { - new_state = with_status(old_state, status_filled_shadow); - } - if (_state.compare_set(old_state, new_state)) { - return copy_now; - } - old_state = state(); - } -} - -void ParallelCompactData::RegionData::shadow_to_normal() { - assert(available(), "region must have no remaining destination dependencies"); - const state_t old_state = make_state(0, status_shadow); - const state_t new_state = make_state(0, status_normal); - bool result = _state.compare_set(old_state, new_state); - assert(result, "Fail to mark the region as normal"); -} - inline ParallelCompactData::RegionData* ParallelCompactData::region(size_t region_idx) const { From 7c891bdbc324ed01c67b7baf9e4c16a0d4984aec Mon Sep 17 00:00:00 2001 From: Albert Mingkun Yang Date: Thu, 22 Oct 2026 01:23:22 +0000 Subject: [PATCH 168/169] 8395364: G1: Remove redundant arg of G1ParScanThreadState::start_partial_objarray Reviewed-by: tschatzl --- src/hotspot/share/gc/g1/g1ParScanThreadState.cpp | 10 +++++----- src/hotspot/share/gc/g1/g1ParScanThreadState.hpp | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/hotspot/share/gc/g1/g1ParScanThreadState.cpp b/src/hotspot/share/gc/g1/g1ParScanThreadState.cpp index 4c2abf2fb54a..f644625cbb42 100644 --- a/src/hotspot/share/gc/g1/g1ParScanThreadState.cpp +++ b/src/hotspot/share/gc/g1/g1ParScanThreadState.cpp @@ -247,10 +247,7 @@ void G1ParScanThreadState::do_partial_array(PartialArrayState* state, bool stole } MAYBE_INLINE_EVACUATION -void G1ParScanThreadState::start_partial_objarray(oop from_obj, - oop to_obj) { - assert(from_obj->is_forwarded(), "precondition"); - assert(from_obj->forwardee() == to_obj, "precondition"); +void G1ParScanThreadState::start_partial_objarray(oop to_obj) { assert(to_obj->is_objArray(), "precondition"); assert(!_scanner.do_metadata(), "precondition"); assert(_scanner.skip_card_mark_set(), "precondition"); @@ -428,13 +425,16 @@ void G1ParScanThreadState::do_iterate_object(oop const obj, G1HeapRegionAttr const region_attr, G1HeapRegionAttr const dest_attr, uint age) { + assert(old->is_forwarded(), "precondition"); + assert(old->forwardee() == obj, "precondition"); + // Most objects are not arrays, so do one array check rather than // checking for each array category for each object. if (klass->is_array_klass()) { assert(!klass->is_stack_chunk_instance_klass(), "must be"); if (klass->is_objArray_klass()) { - start_partial_objarray(old, obj); + start_partial_objarray(obj); } else { // Nothing needs to be done for typeArrays. Body doesn't contain // any oops to scan, and the type in the klass will already be handled diff --git a/src/hotspot/share/gc/g1/g1ParScanThreadState.hpp b/src/hotspot/share/gc/g1/g1ParScanThreadState.hpp index 65859c5ade7e..a9478c55b9d2 100644 --- a/src/hotspot/share/gc/g1/g1ParScanThreadState.hpp +++ b/src/hotspot/share/gc/g1/g1ParScanThreadState.hpp @@ -183,7 +183,7 @@ class G1ParScanThreadState : public CHeapObj { private: void do_partial_array(PartialArrayState* state, bool stolen); - void start_partial_objarray(oop from, oop to); + void start_partial_objarray(oop to); void process_array_chunk(objArrayOop obj, size_t start, size_t end); HeapWord* allocate_copy_slow(G1HeapRegionAttr* dest_attr, From b6654fc389e88699b83e39cc56126d7e1fdc88fc Mon Sep 17 00:00:00 2001 From: Gui Cao Date: Thu, 22 Oct 2026 01:27:14 +0000 Subject: [PATCH 169/169] 8395341: RISC-V: C2 crashes with "bad AD file" for CMove with VectorTest condition Reviewed-by: ayang, fyang, dzhang --- src/hotspot/cpu/riscv/riscv_v.ad | 139 +++++++++++++ .../c2/riscv64/TestVectorTestCMove.java | 191 ++++++++++++++++++ .../compiler/lib/ir_framework/IRNode.java | 12 ++ 3 files changed, 342 insertions(+) create mode 100644 test/hotspot/jtreg/compiler/c2/riscv64/TestVectorTestCMove.java diff --git a/src/hotspot/cpu/riscv/riscv_v.ad b/src/hotspot/cpu/riscv/riscv_v.ad index f3e3a7c47ab8..09c790f6dd10 100644 --- a/src/hotspot/cpu/riscv/riscv_v.ad +++ b/src/hotspot/cpu/riscv/riscv_v.ad @@ -47,6 +47,36 @@ source %{ } } + // test_predicate selects the mask reduction; cmpcode selects the outer eq/ne condition. + static void vectorTestCMove(C2_MacroAssembler* masm, int cmpcode, Register dst, Register src, + VectorRegister mask, BasicType bt, uint vector_length, BoolTest::mask test_predicate) { + assert(test_predicate == BoolTest::ne || test_predicate == BoolTest::overflow, "unsupported predicate"); + assert(cmpcode == BoolTest::eq || cmpcode == BoolTest::ne, "unsupported condition"); + __ vsetvli_helper(bt, vector_length); + __ vcpop_m(t0, mask); + if (test_predicate == BoolTest::overflow) { + // Subtract vector_length so we can compare against zr without + // materializing vector_length in another register. + __ sub(t0, t0, vector_length); + } + __ enc_cmove(cmpcode, t0, zr, dst, src); + } + + static void vectorTestCMove(C2_MacroAssembler* masm, int cmpcode, FloatRegister dst, FloatRegister src, + VectorRegister mask, BasicType bt, uint vector_length, + BoolTest::mask test_predicate, bool is_single) { + assert(test_predicate == BoolTest::ne || test_predicate == BoolTest::overflow, "unsupported predicate"); + assert(cmpcode == BoolTest::eq || cmpcode == BoolTest::ne, "unsupported condition"); + __ vsetvli_helper(bt, vector_length); + __ vcpop_m(t0, mask); + if (test_predicate == BoolTest::overflow) { + // Subtract vector_length so we can compare against zr without + // materializing vector_length in another register. + __ sub(t0, t0, vector_length); + } + __ enc_cmove_fp_cmp(cmpcode, t0, zr, dst, src, is_single); + } + bool Matcher::match_rule_supported_auto_vectorization(int opcode, int vlen, BasicType bt) { if (opcode == Op_MulReductionVF || opcode == Op_MulReductionVD) { return false; @@ -6146,6 +6176,115 @@ instruct cmovI_vtest_alltrue_negate(iRegINoSp dst, cmpOp cop, vRegMask op1, vReg ins_pipe(pipe_slow); %} +// CMove with register values and a condition produced by VectorTest. +// +// The two rules above directly materialize a boolean (0 or 1) from anyTrue()/allTrue(). +// The rules below select between arbitrary register values, using the population +// count to form the condition passed to the corresponding cmove encoder: +// +// anytrue: vcpop.m t0, op1 // t0 != 0 iff some element is set +// alltrue: vcpop.m t0, op1; sub t0, t0, vl // t0 == 0 iff every element is set +// +// The outer Bool condition ($cop) is restricted to eq/ne by cmpOpEqNe. +// Separate rules distinguish the VectorTest predicates: ne for anyTrue, +// overflow for allTrue. + +instruct cmovI_vtest_anytrue(iRegINoSp dst, iRegI src, cmpOpEqNe cop, vRegMask op1, vRegMask op2) %{ + predicate(static_cast(n->in(1)->in(2))->get_predicate() == BoolTest::ne); + match(Set dst (CMoveI (Binary cop (VectorTest op1 op2)) (Binary dst src))); + format %{ "CMoveI $dst, (vectortest $cop $op1 $op2), $dst, $src\t#@cmovI_vtest_anytrue" %} + ins_encode %{ + vectorTestCMove(masm, $cop$$cmpcode, $dst$$Register, $src$$Register, + as_VectorRegister($op1$$reg), Matcher::vector_element_basic_type(this, $op1), + Matcher::vector_length(this, $op1), BoolTest::ne); + %} + ins_pipe(pipe_slow); +%} + +instruct cmovI_vtest_alltrue(iRegINoSp dst, iRegI src, cmpOpEqNe cop, vRegMask op1, vRegMask op2) %{ + predicate(static_cast(n->in(1)->in(2))->get_predicate() == BoolTest::overflow); + match(Set dst (CMoveI (Binary cop (VectorTest op1 op2)) (Binary dst src))); + format %{ "CMoveI $dst, (vectortest $cop $op1 $op2), $dst, $src\t#@cmovI_vtest_alltrue" %} + ins_encode %{ + vectorTestCMove(masm, $cop$$cmpcode, $dst$$Register, $src$$Register, + as_VectorRegister($op1$$reg), Matcher::vector_element_basic_type(this, $op1), + Matcher::vector_length(this, $op1), BoolTest::overflow); + %} + ins_pipe(pipe_slow); +%} + +instruct cmovL_vtest_anytrue(iRegLNoSp dst, iRegL src, cmpOpEqNe cop, vRegMask op1, vRegMask op2) %{ + predicate(static_cast(n->in(1)->in(2))->get_predicate() == BoolTest::ne); + match(Set dst (CMoveL (Binary cop (VectorTest op1 op2)) (Binary dst src))); + format %{ "CMoveL $dst, (vectortest $cop $op1 $op2), $dst, $src\t#@cmovL_vtest_anytrue" %} + ins_encode %{ + vectorTestCMove(masm, $cop$$cmpcode, $dst$$Register, $src$$Register, + as_VectorRegister($op1$$reg), Matcher::vector_element_basic_type(this, $op1), + Matcher::vector_length(this, $op1), BoolTest::ne); + %} + ins_pipe(pipe_slow); +%} + +instruct cmovL_vtest_alltrue(iRegLNoSp dst, iRegL src, cmpOpEqNe cop, vRegMask op1, vRegMask op2) %{ + predicate(static_cast(n->in(1)->in(2))->get_predicate() == BoolTest::overflow); + match(Set dst (CMoveL (Binary cop (VectorTest op1 op2)) (Binary dst src))); + format %{ "CMoveL $dst, (vectortest $cop $op1 $op2), $dst, $src\t#@cmovL_vtest_alltrue" %} + ins_encode %{ + vectorTestCMove(masm, $cop$$cmpcode, $dst$$Register, $src$$Register, + as_VectorRegister($op1$$reg), Matcher::vector_element_basic_type(this, $op1), + Matcher::vector_length(this, $op1), BoolTest::overflow); + %} + ins_pipe(pipe_slow); +%} + +instruct cmovF_vtest_anytrue(fRegF dst, fRegF src, cmpOpEqNe cop, vRegMask op1, vRegMask op2) %{ + predicate(static_cast(n->in(1)->in(2))->get_predicate() == BoolTest::ne); + match(Set dst (CMoveF (Binary cop (VectorTest op1 op2)) (Binary dst src))); + format %{ "CMoveF $dst, (vectortest $cop $op1 $op2), $dst, $src\t#@cmovF_vtest_anytrue" %} + ins_encode %{ + vectorTestCMove(masm, $cop$$cmpcode, $dst$$FloatRegister, $src$$FloatRegister, + as_VectorRegister($op1$$reg), Matcher::vector_element_basic_type(this, $op1), + Matcher::vector_length(this, $op1), BoolTest::ne, /* is_single */ true); + %} + ins_pipe(pipe_slow); +%} + +instruct cmovF_vtest_alltrue(fRegF dst, fRegF src, cmpOpEqNe cop, vRegMask op1, vRegMask op2) %{ + predicate(static_cast(n->in(1)->in(2))->get_predicate() == BoolTest::overflow); + match(Set dst (CMoveF (Binary cop (VectorTest op1 op2)) (Binary dst src))); + format %{ "CMoveF $dst, (vectortest $cop $op1 $op2), $dst, $src\t#@cmovF_vtest_alltrue" %} + ins_encode %{ + vectorTestCMove(masm, $cop$$cmpcode, $dst$$FloatRegister, $src$$FloatRegister, + as_VectorRegister($op1$$reg), Matcher::vector_element_basic_type(this, $op1), + Matcher::vector_length(this, $op1), BoolTest::overflow, /* is_single */ true); + %} + ins_pipe(pipe_slow); +%} + +instruct cmovD_vtest_anytrue(fRegD dst, fRegD src, cmpOpEqNe cop, vRegMask op1, vRegMask op2) %{ + predicate(static_cast(n->in(1)->in(2))->get_predicate() == BoolTest::ne); + match(Set dst (CMoveD (Binary cop (VectorTest op1 op2)) (Binary dst src))); + format %{ "CMoveD $dst, (vectortest $cop $op1 $op2), $dst, $src\t#@cmovD_vtest_anytrue" %} + ins_encode %{ + vectorTestCMove(masm, $cop$$cmpcode, $dst$$FloatRegister, $src$$FloatRegister, + as_VectorRegister($op1$$reg), Matcher::vector_element_basic_type(this, $op1), + Matcher::vector_length(this, $op1), BoolTest::ne, /* is_single */ false); + %} + ins_pipe(pipe_slow); +%} + +instruct cmovD_vtest_alltrue(fRegD dst, fRegD src, cmpOpEqNe cop, vRegMask op1, vRegMask op2) %{ + predicate(static_cast(n->in(1)->in(2))->get_predicate() == BoolTest::overflow); + match(Set dst (CMoveD (Binary cop (VectorTest op1 op2)) (Binary dst src))); + format %{ "CMoveD $dst, (vectortest $cop $op1 $op2), $dst, $src\t#@cmovD_vtest_alltrue" %} + ins_encode %{ + vectorTestCMove(masm, $cop$$cmpcode, $dst$$FloatRegister, $src$$FloatRegister, + as_VectorRegister($op1$$reg), Matcher::vector_element_basic_type(this, $op1), + Matcher::vector_length(this, $op1), BoolTest::overflow, /* is_single */ false); + %} + ins_pipe(pipe_slow); +%} + // anytrue instruct vtest_anytrue_branch(cmpOpEqNe cop, vRegMask op1, vRegMask op2, label lbl) %{ diff --git a/test/hotspot/jtreg/compiler/c2/riscv64/TestVectorTestCMove.java b/test/hotspot/jtreg/compiler/c2/riscv64/TestVectorTestCMove.java new file mode 100644 index 000000000000..028f93e20185 --- /dev/null +++ b/test/hotspot/jtreg/compiler/c2/riscv64/TestVectorTestCMove.java @@ -0,0 +1,191 @@ +/* + * Copyright (c) 2026, Alibaba Group Holding Limited. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @bug 8395341 + * @summary Conditional moves whose condition is a VectorTest are matched. + * + * A diamond controlled by VectorMask.anyTrue()/allTrue() is collapsed into a + * CMove by PhaseIdealLoop::conditional_move(), which only consults the type of + * the merged value and never looks at the comparison feeding the If. RISC-V has + * no flags register, so every cmove rule has to spell the comparison out in its + * own match rule, and a missing one makes the matcher give up with + * "bad AD file" -- an assert in a debug build, a silent and endlessly retried + * bailout in a product build. Each test therefore requests CompLevel.C2 so that + * a bailout fails the test either way. + * + * @library /test/lib / + * @requires os.arch == "riscv64" & vm.compiler2.enabled + * @requires vm.cpu.features ~= ".*rvv.*" + * @modules jdk.incubator.vector + * @run driver ${test.main.class} + */ + +package compiler.c2.riscv64; + +import compiler.lib.ir_framework.CompLevel; +import compiler.lib.ir_framework.IR; +import compiler.lib.ir_framework.IRNode; +import compiler.lib.ir_framework.Run; +import compiler.lib.ir_framework.Test; +import compiler.lib.ir_framework.TestFramework; + +import jdk.incubator.vector.ByteVector; +import jdk.incubator.vector.VectorMask; +import jdk.incubator.vector.VectorSpecies; + +import jdk.test.lib.Asserts; + +public class TestVectorTestCMove { + private static final VectorSpecies SPECIES = ByteVector.SPECIES_128; + + // Mask bit patterns, interpreted over the 16 lanes of SPECIES. + private static final long ALL = -1L; // every lane set -> anyTrue, allTrue + private static final long SOME = 1L; // only lane 0 set -> anyTrue + private static final long NONE = 0L; // no lane set -> neither + + public static void main(String[] args) { + TestFramework framework = new TestFramework(); + framework.addFlags("--add-modules=jdk.incubator.vector"); + framework.start(); + } + + // ---------------- int: CMoveI ---------------- + + @Test(compLevel = CompLevel.C2) + @IR(counts = {IRNode.RISCV_CMOVE_VTEST_ANYTRUE, ">0"}) + static int anyTrueI(long bits, int a, int b) { + return VectorMask.fromLong(SPECIES, bits).anyTrue() ? a : b; + } + + @Test(compLevel = CompLevel.C2) + @IR(counts = {IRNode.RISCV_CMOVE_VTEST_ALLTRUE, ">0"}) + static int allTrueI(long bits, int a, int b) { + return VectorMask.fromLong(SPECIES, bits).allTrue() ? a : b; + } + + // ---------------- long: CMoveL ---------------- + + @Test(compLevel = CompLevel.C2) + @IR(counts = {IRNode.RISCV_CMOVE_VTEST_ANYTRUE, ">0"}) + static long anyTrueL(long bits, long a, long b) { + return VectorMask.fromLong(SPECIES, bits).anyTrue() ? a : b; + } + + @Test(compLevel = CompLevel.C2) + @IR(counts = {IRNode.RISCV_CMOVE_VTEST_ALLTRUE, ">0"}) + static long allTrueL(long bits, long a, long b) { + return VectorMask.fromLong(SPECIES, bits).allTrue() ? a : b; + } + + // ---------------- float: CMoveF ---------------- + + @Test(compLevel = CompLevel.C2) + @IR(counts = {IRNode.RISCV_CMOVE_VTEST_ANYTRUE, ">0"}) + static float anyTrueF(long bits, float a, float b) { + return VectorMask.fromLong(SPECIES, bits).anyTrue() ? a : b; + } + + @Test(compLevel = CompLevel.C2) + @IR(counts = {IRNode.RISCV_CMOVE_VTEST_ALLTRUE, ">0"}) + static float allTrueF(long bits, float a, float b) { + return VectorMask.fromLong(SPECIES, bits).allTrue() ? a : b; + } + + // ---------------- double: CMoveD ---------------- + + @Test(compLevel = CompLevel.C2) + @IR(counts = {IRNode.RISCV_CMOVE_VTEST_ANYTRUE, ">0"}) + static double anyTrueD(long bits, double a, double b) { + return VectorMask.fromLong(SPECIES, bits).anyTrue() ? a : b; + } + + @Test(compLevel = CompLevel.C2) + @IR(counts = {IRNode.RISCV_CMOVE_VTEST_ALLTRUE, ">0"}) + static double allTrueD(long bits, double a, double b) { + return VectorMask.fromLong(SPECIES, bits).allTrue() ? a : b; + } + + // ---------------- driver ---------------- + // + // Every test sees all three mask patterns on each invocation. Keeping the + // branch unpredictable matters: conditional_move() refuses to convert a + // diamond whose If is almost always, or almost never, taken. + + @Run(test = "anyTrueI") + public void runAnyTrueI() { + Asserts.assertEQ(anyTrueI(ALL, 7, 9), 7); + Asserts.assertEQ(anyTrueI(SOME, 7, 9), 7); + Asserts.assertEQ(anyTrueI(NONE, 7, 9), 9); + } + + @Run(test = "allTrueI") + public void runAllTrueI() { + Asserts.assertEQ(allTrueI(ALL, 7, 9), 7); + Asserts.assertEQ(allTrueI(SOME, 7, 9), 9); + Asserts.assertEQ(allTrueI(NONE, 7, 9), 9); + } + + @Run(test = "anyTrueL") + public void runAnyTrueL() { + Asserts.assertEQ(anyTrueL(ALL, 7L, 9L), 7L); + Asserts.assertEQ(anyTrueL(SOME, 7L, 9L), 7L); + Asserts.assertEQ(anyTrueL(NONE, 7L, 9L), 9L); + } + + @Run(test = "allTrueL") + public void runAllTrueL() { + Asserts.assertEQ(allTrueL(ALL, 7L, 9L), 7L); + Asserts.assertEQ(allTrueL(SOME, 7L, 9L), 9L); + Asserts.assertEQ(allTrueL(NONE, 7L, 9L), 9L); + } + + @Run(test = "anyTrueF") + public void runAnyTrueF() { + Asserts.assertEQ(anyTrueF(ALL, 7f, 9f), 7f); + Asserts.assertEQ(anyTrueF(SOME, 7f, 9f), 7f); + Asserts.assertEQ(anyTrueF(NONE, 7f, 9f), 9f); + } + + @Run(test = "allTrueF") + public void runAllTrueF() { + Asserts.assertEQ(allTrueF(ALL, 7f, 9f), 7f); + Asserts.assertEQ(allTrueF(SOME, 7f, 9f), 9f); + Asserts.assertEQ(allTrueF(NONE, 7f, 9f), 9f); + } + + @Run(test = "anyTrueD") + public void runAnyTrueD() { + Asserts.assertEQ(anyTrueD(ALL, 7d, 9d), 7d); + Asserts.assertEQ(anyTrueD(SOME, 7d, 9d), 7d); + Asserts.assertEQ(anyTrueD(NONE, 7d, 9d), 9d); + } + + @Run(test = "allTrueD") + public void runAllTrueD() { + Asserts.assertEQ(allTrueD(ALL, 7d, 9d), 7d); + Asserts.assertEQ(allTrueD(SOME, 7d, 9d), 9d); + Asserts.assertEQ(allTrueD(NONE, 7d, 9d), 9d); + } +} diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java b/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java index 6ba3720b0385..cd22824605e9 100644 --- a/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java @@ -2503,6 +2503,18 @@ public static void anyStoreOfNodes(String irNodePlaceholder, String fieldHolder) machOnlyNameRegex(RISCV_LOAD_N2P_UNSCALED, "loadN2P_unscaled"); } + // Conditional move whose condition is a VectorTest. One rule per value type. + // Exclude the separate _negate rules that materialize boolean results. + public static final String RISCV_CMOVE_VTEST_ANYTRUE = PREFIX + "RISCV_CMOVE_VTEST_ANYTRUE" + POSTFIX; + static { + machOnlyNameRegex(RISCV_CMOVE_VTEST_ANYTRUE, "cmov[ILFD]_vtest_anytrue\\b"); + } + + public static final String RISCV_CMOVE_VTEST_ALLTRUE = PREFIX + "RISCV_CMOVE_VTEST_ALLTRUE" + POSTFIX; + static { + machOnlyNameRegex(RISCV_CMOVE_VTEST_ALLTRUE, "cmov[ILFD]_vtest_alltrue\\b"); + } + public static final String RISCV_VAND_NOTI_VX_MASKED = PREFIX + "RISCV_VAND_NOTI_VX_MASKED" + POSTFIX; static { machOnlyNameRegex(RISCV_VAND_NOTI_VX_MASKED, "vand_notI_vx_masked");