/* * 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 * 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.graalvm.compiler.truffle.test; import java.lang.ref.WeakReference; import java.util.function.IntSupplier; import java.util.stream.IntStream; import org.junit.Assert; import org.junit.Test; import org.graalvm.compiler.truffle.OptimizedCallTarget; import org.graalvm.compiler.truffle.test.nodes.AbstractTestNode; import org.graalvm.compiler.truffle.test.nodes.RootTestNode; import com.oracle.truffle.api.CompilerAsserts; import com.oracle.truffle.api.CompilerDirectives.CompilationFinal; import com.oracle.truffle.api.frame.FrameDescriptor; import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.nodes.RootNode; public class CompilationFinalWeakReferencePartialEvaluationTest extends PartialEvaluationTest { public static Object constant42() { return 42; } private static class TestData implements IntSupplier { private final TestData left; private final TestData right; TestData() { this.left = null; this.right = null; } TestData(TestData left, TestData right) { this.left = left; this.right = right; } @Override public int getAsInt() { if (left == null && right == null) { return 1; } return (left == null ? 0 : left.getAsInt()) + (right == null ? 0 : right.getAsInt()); } } private static class CompilationFinalWeakReferenceTestNode extends AbstractTestNode { @CompilationFinal private WeakReference<Integer> finalWeakRefInteger; @CompilationFinal private WeakReference<Object> finalWeakRefNull; @CompilationFinal private WeakReference<IntSupplier> finalWeakRef; CompilationFinalWeakReferenceTestNode(IntSupplier data) { this.finalWeakRefInteger = new WeakReference<>(0); this.finalWeakRefNull = new WeakReference<>(null); this.finalWeakRef = new WeakReference<>(data); } @Override public int execute(VirtualFrame frame) { partialEvaluationConstantAndEquals(finalWeakRefInteger.get(), Integer.valueOf(0)); partialEvaluationConstantAndEquals(finalWeakRefNull.get(), null); IntSupplier supplier = finalWeakRef.get(); if (supplier != null) { return supplier.getAsInt(); } else { return 0xdead; } } } private static class CompilationFinalWeakReferenceTestGCNode extends AbstractTestNode { @CompilationFinal private WeakReference<IntSupplier> finalWeakRef; CompilationFinalWeakReferenceTestGCNode(IntSupplier data) { this.finalWeakRef = new WeakReference<>(data); } @Override public int execute(VirtualFrame frame) { IntSupplier supplier = finalWeakRef.get(); if (supplier == null) { return 0xdead; } else if (supplier == frame.getArguments()[0]) { return supplier.getAsInt(); } else { return -1; } } } private static void partialEvaluationConstantAndEquals(Object a, Object b) { CompilerAsserts.partialEvaluationConstant(a); CompilerAsserts.partialEvaluationConstant(b); if (a != b) { throw new AssertionError(); } } /** * {@link WeakReference} constant-folded but not embedded in compiled code. */ @Test public void compilationFinalWeakReferenceTest() { String name = "compilationFinalWeakReferenceTest"; FrameDescriptor fd = new FrameDescriptor(); IntSupplier data = generateTestData(); AbstractTestNode result = new CompilationFinalWeakReferenceTestNode(data); RootTestNode rootNode = new RootTestNode(fd, name, result); assertPartialEvalEquals("constant42", rootNode); OptimizedCallTarget callTarget = compileHelper(name, rootNode, new Object[0]); Assert.assertEquals(42, (int) callTarget.call(new Object[0])); assert data != null; WeakReference<IntSupplier> witness = new WeakReference<>(data); data = null; boolean cleared = false; for (int i = 1; i <= 5 && !cleared; i++) { System.gc(); cleared = witness.get() == null; } // Reference not embedded in compiled code Assert.assertEquals(42, (int) callTarget.call(new Object[0])); assertTrue(callTarget.isValid()); } /** * {@link WeakReference} constant-folded and embedded in compiled code. */ @Test public void compilationFinalWeakReferenceTestGC() { String name = "compilationFinalWeakReferenceTestGC"; FrameDescriptor fd = new FrameDescriptor(); IntSupplier data = generateTestData(); AbstractTestNode result = new CompilationFinalWeakReferenceTestGCNode(data); RootTestNode rootNode = new RootTestNode(fd, name, result); OptimizedCallTarget callTarget = compileHelper(name, rootNode, new Object[]{data}); Assert.assertEquals(42, (int) callTarget.call(new Object[]{data})); Assert.assertEquals(-1, (int) callTarget.call(new Object[]{null})); callTarget = compileHelper(name, rootNode, new Object[]{data}); assertTrue(callTarget.isValid()); assert data != null; clearDebugScopeTL(); WeakReference<IntSupplier> witness = new WeakReference<>(data); data = null; boolean cleared = false; for (int i = 1; i <= 5 && !cleared; i++) { System.gc(); cleared = witness.get() == null; } assertTrue("Test data should have been garbage collected at this point", cleared); // Compiled code had the collected reference embedded so it had to be invalidated assertFalse(callTarget.isValid()); Assert.assertEquals(0xdead, (int) callTarget.call(new Object[]{null})); } private static IntSupplier generateTestData() { return IntStream.range(0, 42).mapToObj(i -> new TestData()).reduce((l, r) -> new TestData(l, r)).get(); } /** * Perform a dummy compilation to ensure compilation result data of the last compilation kept * alive through DebugScope thread locals are freed. */ private void clearDebugScopeTL() { compileHelper("dummy", RootNode.createConstantNode(null), new Object[]{}); } }