All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.oracle.truffle.api.instrumentation.AllocationReporter Maven / Gradle / Ivy

Go to download

Truffle is a multi-language framework for executing dynamic languages that achieves high performance when combined with Graal.

There is a newer version: 1.0.0-rc7
Show newest version
/*
 * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
 * 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 com.oracle.truffle.api.instrumentation;

import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.util.Arrays;
import java.util.LinkedList;

import com.oracle.truffle.api.Assumption;
import com.oracle.truffle.api.CompilerAsserts;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.Truffle;
import com.oracle.truffle.api.TruffleLanguage;
import com.oracle.truffle.api.TruffleLanguage.Env;
import com.oracle.truffle.api.nodes.ExplodeLoop;
import com.oracle.truffle.api.nodes.LanguageInfo;

/**
 * Reporter of guest language value allocations. Language implementation ought to use this class to
 * report all allocations and re-allocations of guest language values. An instance of this class can
 * be obtained from {@link Env#lookup(java.lang.Class) Env.lookup(AllocationReporter.class)}. If
 * used from compiled code paths, then the allocation reporter must be stored in a compilation final
 * or final field.
 * 

* Usage example: {@link AllocationReporterSnippets#example} * * @since 0.27 */ public final class AllocationReporter { /** * Constant specifying an unknown size. Use it when it's not possible to estimate size of the * memory being allocated. * * @since 0.27 */ public static final long SIZE_UNKNOWN = Long.MIN_VALUE; /** * Name of a property that is fired when an {@link #isActive() active} property of this reporter * changes. * * @since 0.27 * @see #isActive() * @see #addPropertyChangeListener(java.beans.PropertyChangeListener) */ public static final String PROPERTY_ACTIVE = "active"; final LanguageInfo language; private final PropChangeSupport propSupport = new PropChangeSupport(this); private final ThreadLocal>> valueCheck; @CompilationFinal private volatile Assumption listenersNotChangedAssumption = Truffle.getRuntime().createAssumption(); @CompilationFinal(dimensions = 1) private volatile AllocationListener[] listeners = null; AllocationReporter(LanguageInfo language) { this.language = language; boolean assertions = false; assert (assertions = true) == true; valueCheck = (assertions) ? new ThreadLocal<>() : null; } /** * Add a property change listener that is notified when a property of this reporter changes. Use * it to get notified when {@link #isActive()} changes. * * @since 0.27 * @see #PROPERTY_ACTIVE */ public void addPropertyChangeListener(java.beans.PropertyChangeListener listener) { // Using FQN to avoid mx to generate dependency on java.desktop JDK9 module propSupport.addPropertyChangeListener(listener); } /** * Remove a property change listener that is notified when state of this reporter changes. * * @since 0.27 * @see #addPropertyChangeListener(java.beans.PropertyChangeListener) */ public void removePropertyChangeListener(java.beans.PropertyChangeListener listener) { // Using FQN to avoid mx to generate dependency on java.desktop JDK9 module propSupport.removePropertyChangeListener(listener); } /** * Test if the reporter instance is actually doing some reporting when notify methods are * called. Methods {@link #onEnter(java.lang.Object, long, long)} and * {@link #onReturnValue(java.lang.Object, long, long)} have no effect when this method returns * false. A {@link java.beans.PropertyChangeListener} can be * {@link #addPropertyChangeListener(java.beans.PropertyChangeListener) added} to listen on * changes of this property. * * @return true when there are some {@link AllocationListener}s attached, * false otherwise. * @since 0.27 */ public boolean isActive() { if (!listenersNotChangedAssumption.isValid()) { CompilerDirectives.transferToInterpreterAndInvalidate(); } return listeners != null; } void addListener(AllocationListener l) { CompilerAsserts.neverPartOfCompilation(); boolean hadListeners; synchronized (this) { if (listeners == null) { listeners = new AllocationListener[]{l}; hadListeners = false; } else { int index = listeners.length; AllocationListener[] newListeners = Arrays.copyOf(listeners, index + 1); newListeners[index] = l; listeners = newListeners; hadListeners = true; } Assumption assumption = listenersNotChangedAssumption; listenersNotChangedAssumption = Truffle.getRuntime().createAssumption(); assumption.invalidate(); } if (!hadListeners) { propSupport.firePropertyChange(PROPERTY_ACTIVE, false, true); } } void removeListener(AllocationListener l) { CompilerAsserts.neverPartOfCompilation(); boolean hasListeners = true; synchronized (this) { final int len = listeners.length; if (len == 1) { if (listeners[0] == l) { listeners = null; hasListeners = false; } } else { for (int i = 0; i < len; i++) { if (listeners[i] == l) { if (i == (len - 1)) { listeners = Arrays.copyOf(listeners, i); } else if (i == 0) { listeners = Arrays.copyOfRange(listeners, 1, len); } else { AllocationListener[] newListeners = new AllocationListener[len - 1]; System.arraycopy(listeners, 0, newListeners, 0, i); System.arraycopy(listeners, i + 1, newListeners, i, len - i - 1); listeners = newListeners; } break; } } } Assumption assumption = listenersNotChangedAssumption; listenersNotChangedAssumption = Truffle.getRuntime().createAssumption(); assumption.invalidate(); } if (!hasListeners) { propSupport.firePropertyChange(PROPERTY_ACTIVE, true, false); } } /** * Report an intent to allocate a new guest language value, or re-allocate an existing one. This * method delegates to all registered listeners * {@link AllocationListener#onEnter(com.oracle.truffle.api.instrumentation.AllocationEvent)}. * Only primitive types, String and {@link com.oracle.truffle.api.interop.TruffleObject} are * accepted value types. The change in memory consumption caused by the allocation is going to * be newSizeEstimate - oldSize when both old size and new size are known. The * change can be either positive or negative. *

* A call to this method needs to be followed by a call to * {@link #onReturnValue(java.lang.Object, long, long)} with the actual allocated value, or with * the same (re-allocated) value. Nested allocations are supported, several calls to * onEnter prior every sub-value allocation can be followed by the appropriate * number of onReturnValue calls after the sub-values are allocated, in the * opposite order. * * @param valueToReallocate null in case of a new allocation, or the value that is * to be re-allocated. * @param oldSize 0 in case of a new allocation, or the size in bytes of value to * be re-allocated. Can be {@link #SIZE_UNKNOWN} when the value size is not known. * @param newSizeEstimate an estimate of the allocation size of the value which is to be created * or re-allocated, in bytes. Can be {@link #SIZE_UNKNOWN} when the allocation size * is not known. * @since 0.27 */ public void onEnter(Object valueToReallocate, long oldSize, long newSizeEstimate) { if (valueCheck != null) { onEnterCheck(valueToReallocate, oldSize, newSizeEstimate); } notifyAllocateOrReallocate(valueToReallocate, oldSize, newSizeEstimate); } @TruffleBoundary private void onEnterCheck(Object valueToReallocate, long oldSize, long newSizeEstimate) { enterSizeCheck(valueToReallocate, oldSize, newSizeEstimate); if (valueToReallocate != null) { allocateValueCheck(valueToReallocate); } setValueCheck(valueToReallocate); } @ExplodeLoop private void notifyAllocateOrReallocate(Object value, long oldSize, long newSizeEstimate) { CompilerAsserts.partialEvaluationConstant(this); if (!listenersNotChangedAssumption.isValid()) { CompilerDirectives.transferToInterpreterAndInvalidate(); } AllocationListener[] ls = this.listeners; if (ls != null) { AllocationEvent event = new AllocationEvent(language, value, oldSize, newSizeEstimate); for (AllocationListener l : ls) { l.onEnter(event); } } } /** * Report an allocation of a new one or re-allocation of an existing guest language value. This * method notifies all registered listeners * {@link AllocationListener#onReturnValue(com.oracle.truffle.api.instrumentation.AllocationEvent)} * . Only primitive types, String and {@link com.oracle.truffle.api.interop.TruffleObject} are * accepted value types. The change in memory consumption caused by the allocation is * newSize - oldSize when both old size and new size are known. The change can be * either positive or negative. *

* A call to {@link #onEnter(java.lang.Object, long, long)} must precede this call. In case of * re-allocation, the value object passed to {@link #onEnter(java.lang.Object, long, long)} must * be the same instance as the value passed to this method. * * @param value the value that was newly allocated, or the re-allocated value. Must not be * null. * @param oldSize size in bytes of an old value, if any. Must be 0 for newly * allocated values. In case of re-allocation it's the size of the original value * before re-allocation. Can be {@link #SIZE_UNKNOWN} when not known. * @param newSize the size of the allocated value in bytes. In case of re-allocation, it's the * size of the object after re-allocation. The newSize may be less than * oldSize when the object size shrinks. Can be {@link #SIZE_UNKNOWN} * when not known. * @since 0.27 */ public void onReturnValue(Object value, long oldSize, long newSize) { if (valueCheck != null) { onReturnValueCheck(value, oldSize, newSize); } notifyAllocated(value, oldSize, newSize); } @TruffleBoundary private void onReturnValueCheck(Object value, long oldSize, long newSize) { allocateValueCheck(value); allocatedCheck(value, oldSize, newSize); } @ExplodeLoop private void notifyAllocated(Object value, long oldSize, long newSize) { CompilerAsserts.partialEvaluationConstant(this); if (!listenersNotChangedAssumption.isValid()) { CompilerDirectives.transferToInterpreterAndInvalidate(); } AllocationListener[] ls = this.listeners; if (ls != null) { AllocationEvent event = new AllocationEvent(language, value, oldSize, newSize); for (AllocationListener l : ls) { l.onReturnValue(event); } } } private static void enterSizeCheck(Object valueToReallocate, long oldSize, long newSizeEstimate) { CompilerAsserts.neverPartOfCompilation(); assert (newSizeEstimate == SIZE_UNKNOWN || newSizeEstimate > 0) : "Wrong new size estimate = " + newSizeEstimate; assert valueToReallocate != null || oldSize == 0 : "Old size must be 0 for new allocations. Was: " + oldSize; assert valueToReallocate == null || (oldSize > 0 || oldSize == SIZE_UNKNOWN) : "Old size of a re-allocated value must be positive or unknown. Was: " + oldSize; } private boolean setValueCheck(Object value) { CompilerAsserts.neverPartOfCompilation(); LinkedList> list = valueCheck.get(); if (list == null) { list = new LinkedList<>(); valueCheck.set(list); } list.add(new WeakReference<>(value)); return true; } private static void allocateValueCheck(Object value) { CompilerAsserts.neverPartOfCompilation(); if (value == null) { throw new NullPointerException("No allocated value."); } // Strings are O.K. if (value instanceof String) { return; } // Primitive types are O.K. if (value instanceof Boolean || value instanceof Byte || value instanceof Character || value instanceof Short || value instanceof Integer || value instanceof Long || value instanceof Float || value instanceof Double) { return; } // TruffleObject is O.K. boolean isTO = InstrumentationHandler.ACCESSOR.isTruffleObject(value); assert isTO : "Wrong value class, TruffleObject is required. Was: " + value.getClass().getName(); } private void allocatedCheck(Object value, long oldSize, long newSize) { CompilerAsserts.neverPartOfCompilation(); assert value != null : "Allocated value must not be null."; LinkedList> list = valueCheck.get(); assert list != null && !list.isEmpty() : "onEnter() was not called"; Object orig = list.removeLast().get(); assert orig == null || orig == value : "A different reallocated value. Was: " + orig + " now is: " + value; assert orig == null && oldSize == 0 || orig != null : "Old size must be 0 for new allocations. Was: " + oldSize; assert orig != null && (oldSize > 0 || oldSize == SIZE_UNKNOWN) || orig == null : "Old size of a re-allocated value must be positive or unknown. Was: " + oldSize; assert newSize == SIZE_UNKNOWN || newSize > 0 : "New value size must be positive or unknown. Was: " + newSize; } } class AllocationReporterSnippets extends TruffleLanguage { void example() { } // @formatter:off // BEGIN: AllocationReporterSnippets#example @Override protected ContextObject createContext(Env env) { AllocationReporter reporter = env.lookup(AllocationReporter.class); return new ContextObject(reporter); } Object allocateNew() { AllocationReporter reporter = getContextReference().get().getReporter(); // Test if the reporter is active, we should compute the size estimate if (reporter.isActive()) { long size = findSizeEstimate(); reporter.onEnter(null, 0, size); } // Do the allocation itself Object newObject = new MyTruffleObject(); // Test if the reporter is active, // we should compute the allocated object size if (reporter.isActive()) { long size = findSize(newObject); reporter.onReturnValue(newObject, 0, size); } return newObject; } Object allocateComplex() { AllocationReporter reporter = getContextReference().get().getReporter(); // If the allocated size is a constant, onEnter() and onReturnValue() // can be called without a fast-path performance penalty when not active reporter.onEnter(null, 0, 16); // Do the allocation itself Object newObject = createComplexObject(); // Report the allocation reporter.onReturnValue(newObject, 0, 16); return newObject; } // END: AllocationReporterSnippets#example // @formatter:on @Override protected boolean isObjectOfLanguage(Object object) { return false; } private static long findSizeEstimate() { return 0L; } private static long findSize(Object newObject) { assert newObject != null; return 0L; } private static Object createComplexObject() { return null; } private static class MyTruffleObject { } } class ContextObject { private final AllocationReporter reporter; ContextObject(AllocationReporter reporter) { this.reporter = reporter; } public AllocationReporter getReporter() { return reporter; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy