1 /*
  2  * Copyright (c) 2026, Amazon.com, Inc. or its affiliates. All rights reserved.
  3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  * This code is free software; you can redistribute it and/or modify it
  6  * under the terms of the GNU General Public License version 2 only, as
  7  * published by the Free Software Foundation.
  8  *
  9  * This code is distributed in the hope that it will be useful, but WITHOUT
 10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 12  * version 2 for more details (a copy is included in the LICENSE file that
 13  * accompanied this code).
 14  *
 15  * You should have received a copy of the GNU General Public License version
 16  * 2 along with this work; if not, write to the Free Software Foundation,
 17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 18  *
 19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 20  * or visit www.oracle.com if you need additional information or have any
 21  * questions.
 22  *
 23  */
 24 
 25 package gc.stress.ihash;
 26 
 27 /*
 28  * @test id=g1
 29  * @bug 8387150
 30  * @summary Identity hash code stays stable across GC relocation when the
 31  *          C2-compiled hashCode intrinsic recomputes an address-based hash.
 32  * @requires vm.gc.G1
 33  * @requires vm.compiler2.enabled
 34  * @requires vm.opt.UseCompactObjectHeaders == null | vm.opt.UseCompactObjectHeaders == true
 35  * @run main/othervm -XX:+UseCompactObjectHeaders -XX:+UseG1GC -Xms64m -Xmx64m
 36  *      gc.stress.ihash.TestHashCodeC2Relocation
 37  */
 38 
 39 /*
 40  * @test id=parallel
 41  * @bug 8387150
 42  * @summary Identity hash code stays stable across GC relocation when the
 43  *          C2-compiled hashCode intrinsic recomputes an address-based hash.
 44  * @requires vm.gc.Parallel
 45  * @requires vm.compiler2.enabled
 46  * @requires vm.opt.UseCompactObjectHeaders == null | vm.opt.UseCompactObjectHeaders == true
 47  * @run main/othervm -XX:+UseCompactObjectHeaders -XX:+UseParallelGC -Xms64m -Xmx64m
 48  *      gc.stress.ihash.TestHashCodeC2Relocation
 49  */
 50 
 51 /*
 52  * @test id=serial
 53  * @bug 8387150
 54  * @summary Identity hash code stays stable across GC relocation when the
 55  *          C2-compiled hashCode intrinsic recomputes an address-based hash.
 56  * @requires vm.gc.Serial
 57  * @requires vm.compiler2.enabled
 58  * @requires vm.opt.UseCompactObjectHeaders == null | vm.opt.UseCompactObjectHeaders == true
 59  * @run main/othervm -XX:+UseCompactObjectHeaders -XX:+UseSerialGC -Xms64m -Xmx64m
 60  *      gc.stress.ihash.TestHashCodeC2Relocation
 61  */
 62 
 63 /*
 64  * @test id=shenandoah
 65  * @bug 8387150
 66  * @summary Identity hash code stays stable across GC relocation when the
 67  *          C2-compiled hashCode intrinsic recomputes an address-based hash.
 68  * @requires vm.gc.Shenandoah
 69  * @requires vm.compiler2.enabled
 70  * @requires vm.opt.UseCompactObjectHeaders == null | vm.opt.UseCompactObjectHeaders == true
 71  * @run main/othervm -XX:+UseCompactObjectHeaders -XX:+UseShenandoahGC -Xms64m -Xmx64m
 72  *      gc.stress.ihash.TestHashCodeC2Relocation
 73  */
 74 
 75 /*
 76  * @test id=zgc
 77  * @bug 8387150
 78  * @summary Identity hash code stays stable across GC relocation when the
 79  *          C2-compiled hashCode intrinsic recomputes an address-based hash.
 80  * @requires vm.gc.Z
 81  * @requires vm.compiler2.enabled
 82  * @requires vm.opt.UseCompactObjectHeaders == null | vm.opt.UseCompactObjectHeaders == true
 83  * @run main/othervm -XX:+UseCompactObjectHeaders -XX:+UseZGC -Xms64m -Xmx64m
 84  *      gc.stress.ihash.TestHashCodeC2Relocation
 85  */
 86 
 87 /**
 88  * Regression test for a C2 miscompilation of the {@code System.identityHashCode}
 89  * intrinsic under {@code -XX:+UseCompactObjectHeaders}.
 90  *
 91  * With compact headers there is no room in the 64-bit header to always store a
 92  * 31-bit identity hash. An object that has been hashed but not yet expanded by
 93  * the GC (hashctrl state {@code 0b01}) has its identity hash recomputed on every
 94  * read from the object's current heap address ({@code FastHash(address)}). This
 95  * is only correct while the object stays at the same address; when the GC
 96  * relocates the object it freezes the hash into a hidden field (state
 97  * {@code 0b11}).
 98  *
 99  * The C2 intrinsic inlined the {@code 0b01} recompute and materialized the raw
100  * object address with a free-floating {@code CastP2X} (null control input).
101  * Global Code Motion could hoist that materialization above a GC safepoint;
102  * after a relocation the oop reference is updated but the already-materialized
103  * raw address is not, so the recomputed hash was derived from the stale
104  * pre-relocation address. The result: {@code System.identityHashCode} returned
105  * inconsistent values for a single live object, violating the
106  * {@code Object.hashCode()} stability contract.
107  *
108  * The reproducer hashes a fresh object, forces young-gen relocation via
109  * allocation churn, and re-reads the hash. The "install after relocation"
110  * ordering, warmed up so probe() is C2-compiled, is the shape that triggered the
111  * miscompiled path. A correct VM reports zero mismatches.
112  */
113 public class TestHashCodeC2Relocation {
114 
115     // Keep GC honest; defeat allocation elimination of the churn garbage.
116     static volatile Object sink;
117 
118     static final long ITERS = 2_000_000L;
119 
120     /** Allocate short-lived garbage so the next young GC relocates our surviving object. */
121     static void churn() {
122         Object[] junk = new Object[64];
123         for (int k = 0; k < 64; k++) {
124             junk[k] = new byte[256];
125         }
126         sink = junk;
127     }
128 
129     /**
130      * @return a non-null description of the first observed violation, or null if stable.
131      */
132     static String probe(long iters, boolean installBefore) {
133         for (long i = 0; i < iters; i++) {
134             Object o = new Object();
135             int h0, h1;
136             if (installBefore) {
137                 h0 = System.identityHashCode(o);   // install hash, THEN relocate
138                 churn();
139                 h1 = System.identityHashCode(o);
140             } else {
141                 churn();                           // relocate, THEN first read installs/loads hash
142                 h0 = System.identityHashCode(o);
143                 h1 = System.identityHashCode(o);
144             }
145             if (h1 != h0) {
146                 int settled = System.identityHashCode(o); // re-read returns the TRUE (frozen) hash
147                 return "identityHashCode returned inconsistent values for a live object"
148                         + " at iter=" + i + ": h0=" + h0 + " h1=" + h1
149                         + " settledReread=" + settled;
150             }
151         }
152         return null;
153     }
154 
155     public static void main(String[] args) {
156         // Warm up with the safe ordering first so that probe() is compiled by C2
157         // with the installBefore branch profiled -- the shape that exposed the bug.
158         String warmup = probe(ITERS, true);
159         if (warmup != null) {
160             throw new RuntimeException("[warmup/installBefore] " + warmup);
161         }
162 
163         // Detection run: read the identity hash only after relocation.
164         String result = probe(ITERS, false);
165         if (result != null) {
166             throw new RuntimeException("[installAfter] " + result);
167         }
168 
169         System.out.println("PASSED: identity hash codes stable across relocation");
170     }
171 }