diff a/test/hotspot/jtreg/gc/stress/ihash/TestHashCodeC2Relocation.java b/test/hotspot/jtreg/gc/stress/ihash/TestHashCodeC2Relocation.java --- /dev/null +++ b/test/hotspot/jtreg/gc/stress/ihash/TestHashCodeC2Relocation.java @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2026, 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.ihash; + +/* + * @test id=g1 + * @bug 8387150 + * @summary Identity hash code stays stable across GC relocation when the + * C2-compiled hashCode intrinsic recomputes an address-based hash. + * @requires vm.gc.G1 + * @requires vm.compiler2.enabled + * @requires vm.opt.UseCompactObjectHeaders == null | vm.opt.UseCompactObjectHeaders == true + * @run main/othervm -XX:+UseCompactObjectHeaders -XX:+UseG1GC -Xms64m -Xmx64m + * gc.stress.ihash.TestHashCodeC2Relocation + */ + +/* + * @test id=parallel + * @bug 8387150 + * @summary Identity hash code stays stable across GC relocation when the + * C2-compiled hashCode intrinsic recomputes an address-based hash. + * @requires vm.gc.Parallel + * @requires vm.compiler2.enabled + * @requires vm.opt.UseCompactObjectHeaders == null | vm.opt.UseCompactObjectHeaders == true + * @run main/othervm -XX:+UseCompactObjectHeaders -XX:+UseParallelGC -Xms64m -Xmx64m + * gc.stress.ihash.TestHashCodeC2Relocation + */ + +/* + * @test id=serial + * @bug 8387150 + * @summary Identity hash code stays stable across GC relocation when the + * C2-compiled hashCode intrinsic recomputes an address-based hash. + * @requires vm.gc.Serial + * @requires vm.compiler2.enabled + * @requires vm.opt.UseCompactObjectHeaders == null | vm.opt.UseCompactObjectHeaders == true + * @run main/othervm -XX:+UseCompactObjectHeaders -XX:+UseSerialGC -Xms64m -Xmx64m + * gc.stress.ihash.TestHashCodeC2Relocation + */ + +/* + * @test id=shenandoah + * @bug 8387150 + * @summary Identity hash code stays stable across GC relocation when the + * C2-compiled hashCode intrinsic recomputes an address-based hash. + * @requires vm.gc.Shenandoah + * @requires vm.compiler2.enabled + * @requires vm.opt.UseCompactObjectHeaders == null | vm.opt.UseCompactObjectHeaders == true + * @run main/othervm -XX:+UseCompactObjectHeaders -XX:+UseShenandoahGC -Xms64m -Xmx64m + * gc.stress.ihash.TestHashCodeC2Relocation + */ + +/* + * @test id=zgc + * @bug 8387150 + * @summary Identity hash code stays stable across GC relocation when the + * C2-compiled hashCode intrinsic recomputes an address-based hash. + * @requires vm.gc.Z + * @requires vm.compiler2.enabled + * @requires vm.opt.UseCompactObjectHeaders == null | vm.opt.UseCompactObjectHeaders == true + * @run main/othervm -XX:+UseCompactObjectHeaders -XX:+UseZGC -Xms64m -Xmx64m + * gc.stress.ihash.TestHashCodeC2Relocation + */ + +/** + * Regression test for a C2 miscompilation of the {@code System.identityHashCode} + * intrinsic under {@code -XX:+UseCompactObjectHeaders}. + * + * With compact headers there is no room in the 64-bit header to always store a + * 31-bit identity hash. An object that has been hashed but not yet expanded by + * the GC (hashctrl state {@code 0b01}) has its identity hash recomputed on every + * read from the object's current heap address ({@code FastHash(address)}). This + * is only correct while the object stays at the same address; when the GC + * relocates the object it freezes the hash into a hidden field (state + * {@code 0b11}). + * + * The C2 intrinsic inlined the {@code 0b01} recompute and materialized the raw + * object address with a free-floating {@code CastP2X} (null control input). + * Global Code Motion could hoist that materialization above a GC safepoint; + * after a relocation the oop reference is updated but the already-materialized + * raw address is not, so the recomputed hash was derived from the stale + * pre-relocation address. The result: {@code System.identityHashCode} returned + * inconsistent values for a single live object, violating the + * {@code Object.hashCode()} stability contract. + * + * The reproducer hashes a fresh object, forces young-gen relocation via + * allocation churn, and re-reads the hash. The "install after relocation" + * ordering, warmed up so probe() is C2-compiled, is the shape that triggered the + * miscompiled path. A correct VM reports zero mismatches. + */ +public class TestHashCodeC2Relocation { + + // Keep GC honest; defeat allocation elimination of the churn garbage. + static volatile Object sink; + + static final long ITERS = 2_000_000L; + + /** Allocate short-lived garbage so the next young GC relocates our surviving object. */ + static void churn() { + Object[] junk = new Object[64]; + for (int k = 0; k < 64; k++) { + junk[k] = new byte[256]; + } + sink = junk; + } + + /** + * @return a non-null description of the first observed violation, or null if stable. + */ + static String probe(long iters, boolean installBefore) { + for (long i = 0; i < iters; i++) { + Object o = new Object(); + int h0, h1; + if (installBefore) { + h0 = System.identityHashCode(o); // install hash, THEN relocate + churn(); + h1 = System.identityHashCode(o); + } else { + churn(); // relocate, THEN first read installs/loads hash + h0 = System.identityHashCode(o); + h1 = System.identityHashCode(o); + } + if (h1 != h0) { + int settled = System.identityHashCode(o); // re-read returns the TRUE (frozen) hash + return "identityHashCode returned inconsistent values for a live object" + + " at iter=" + i + ": h0=" + h0 + " h1=" + h1 + + " settledReread=" + settled; + } + } + return null; + } + + public static void main(String[] args) { + // Warm up with the safe ordering first so that probe() is compiled by C2 + // with the installBefore branch profiled -- the shape that exposed the bug. + String warmup = probe(ITERS, true); + if (warmup != null) { + throw new RuntimeException("[warmup/installBefore] " + warmup); + } + + // Detection run: read the identity hash only after relocation. + String result = probe(ITERS, false); + if (result != null) { + throw new RuntimeException("[installAfter] " + result); + } + + System.out.println("PASSED: identity hash codes stable across relocation"); + } +}