view graal/com.oracle.graal.hotspot/src/com/oracle/graal/hotspot/snippets/MonitorSnippets.java @ 6389:2d84f74e394c

enabled type-filter based logging of (snippet-based) monitor operations with the "graal.monitorsnippets.log" system property
author Doug Simon <doug.simon@oracle.com>
date Fri, 14 Sep 2012 10:52:30 +0200
parents abeeb57b655d
children b74402a7079b
line wrap: on
line source

/*
 * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
 * 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 com.oracle.graal.hotspot.snippets;

import static com.oracle.graal.hotspot.nodes.BeginLockScopeNode.*;
import static com.oracle.graal.hotspot.nodes.DirectCompareAndSwapNode.*;
import static com.oracle.graal.hotspot.nodes.EndLockScopeNode.*;
import static com.oracle.graal.hotspot.nodes.RegisterNode.*;
import static com.oracle.graal.hotspot.snippets.HotSpotSnippetUtils.*;
import static com.oracle.graal.nodes.extended.UnsafeLoadNode.*;
import static com.oracle.graal.snippets.SnippetTemplate.Arguments.*;
import static com.oracle.graal.snippets.nodes.DirectObjectStoreNode.*;

import java.util.*;

import com.oracle.graal.api.code.*;
import com.oracle.graal.api.meta.*;
import com.oracle.graal.graph.*;
import com.oracle.graal.hotspot.nodes.*;
import com.oracle.graal.nodes.*;
import com.oracle.graal.nodes.java.*;
import com.oracle.graal.nodes.spi.*;
import com.oracle.graal.snippets.*;
import com.oracle.graal.snippets.Snippet.ConstantParameter;
import com.oracle.graal.snippets.Snippet.Parameter;
import com.oracle.graal.snippets.SnippetTemplate.AbstractTemplates;
import com.oracle.graal.snippets.SnippetTemplate.Arguments;
import com.oracle.graal.snippets.SnippetTemplate.Key;

/**
 * Snippets used for implementing the monitorenter and monitorexit instructions.
 *
 * The locking algorithm used is described in the paper <a href="http://dl.acm.org/citation.cfm?id=1167515.1167496">
 * Eliminating synchronization-related atomic operations with biased locking and bulk rebiasing</a>
 * by Kenneth Russell and David Detlefs.
 */
public class MonitorSnippets implements SnippetsInterface {

    /**
     * Monitor operations on objects whose type contains this substring will be logged.
     */
    private static final String LOG_TYPE = System.getProperty("graal.monitorsnippets.log");

    private static void log(boolean enabled, String action, Object object) {
        if (enabled) {
            Log.print(action);
            Log.print(' ');
            Log.printlnObject(object);
        }
    }

    @Snippet
    public static void monitorenter(@Parameter("object") Object object, @ConstantParameter("logEnabled") boolean logEnabled) {
        verifyOop(object);

        // Load the mark word - this includes a null-check on object
        final Word mark = asWord(loadObject(object, 0, markOffset(), false));

        final Word lock = beginLockScope(object, false, wordKind());

        if (useBiasedLocking()) {
            // See whether the lock is currently biased toward our thread and
            // whether the epoch is still valid.
            // Note that the runtime guarantees sufficient alignment of JavaThread
            // pointers to allow age to be placed into low bits.
            final Word biasableLockBits = mark.and(biasedLockMaskInPlace());

            // First check to see whether biasing is enabled for this object
            if (biasableLockBits.toLong() != biasedLockPattern()) {
                // Biasing not enabled -> fall through to lightweight locking
            } else {
                // The bias pattern is present in the object's mark word. Need to check
                // whether the bias owner and the epoch are both still current.
                Object hub = loadHub(object);
                final Word prototypeMarkWord = asWord(loadObject(hub, 0, prototypeMarkWordOffset(), true));
                final Word thread = asWord(register(threadReg(), wordKind()));
                final Word tmp = prototypeMarkWord.or(thread).xor(mark).and(~ageMaskInPlace());
                if (tmp == Word.zero()) {
                    // Object is already biased to current thread -> done
                    log(logEnabled, "+lock{bias}", object);
                    return;
                }

                // At this point we know that the mark word has the bias pattern and
                // that we are not the bias owner in the current epoch. We need to
                // figure out more details about the state of the mark word in order to
                // know what operations can be legally performed on the object's
                // mark word.
                if (tmp.and(biasedLockMaskInPlace()) == Word.zero()) {
                    // Biasing is still enabled for object's type. See whether the
                    // epoch of the current bias is still valid, meaning that the epoch
                    // bits of the mark word are equal to the epoch bits of the
                    // prototype mark word. (Note that the prototype mark word's epoch bits
                    // only change at a safepoint.) If not, attempt to rebias the object
                    // toward the current thread. Note that we must be absolutely sure
                    // that the current epoch is invalid in order to do this because
                    // otherwise the manipulations it performs on the mark word are
                    // illegal.
                    if (tmp.and(epochMaskInPlace()) != Word.zero()) {
                        // The epoch of the current bias is still valid but we know nothing
                        // about the owner; it might be set or it might be clear. Try to
                        // acquire the bias of the object using an atomic operation. If this
                        // fails we will go in to the runtime to revoke the object's bias.
                        // Note that we first construct the presumed unbiased header so we
                        // don't accidentally blow away another thread's valid bias.
                        Word unbiasedMark = mark.and(biasedLockMaskInPlace() | ageMaskInPlace() | epochMaskInPlace());
                        Word biasedMark = unbiasedMark.or(thread);
                        if (compareAndSwap(object, markOffset(), unbiasedMark, biasedMark) == unbiasedMark) {
                            // Object is now biased to current thread -> done
                            log(logEnabled, "+lock{bias}", object);
                            return;
                        }
                        // If the biasing toward our thread failed, this means that
                        // another thread succeeded in biasing it toward itself and we
                        // need to revoke that bias. The revocation will occur in the
                        // interpreter runtime in the slow case.
                        log(logEnabled, "+lock{stub}", object);
                        MonitorEnterStubCall.call(object, lock);
                        return;
                    } else {
                        // At this point we know the epoch has expired, meaning that the
                        // current "bias owner", if any, is actually invalid. Under these
                        // circumstances _only_, we are allowed to use the current mark word
                        // value as the comparison value when doing the CAS to acquire the
                        // bias in the current epoch. In other words, we allow transfer of
                        // the bias from one thread to another directly in this situation.
                        Word biasedMark = prototypeMarkWord.or(thread);
                        if (compareAndSwap(object, markOffset(), mark, biasedMark) == mark) {
                            // Object is now biased to current thread -> done
                            log(logEnabled, "+lock{bias}", object);
                            return;
                        }
                        // If the biasing toward our thread failed, then another thread
                        // succeeded in biasing it toward itself and we need to revoke that
                        // bias. The revocation will occur in the runtime in the slow case.
                        log(logEnabled, "+lock{stub}", object);
                        MonitorEnterStubCall.call(object, lock);
                        return;
                    }
                } else {
                    // The prototype mark word doesn't have the bias bit set any
                    // more, indicating that objects of this data type are not supposed
                    // to be biased any more. We are going to try to reset the mark of
                    // this object to the prototype value and fall through to the
                    // CAS-based locking scheme. Note that if our CAS fails, it means
                    // that another thread raced us for the privilege of revoking the
                    // bias of this particular object, so it's okay to continue in the
                    // normal locking code.
                    compareAndSwap(object, markOffset(), mark, tmp);

                    // Fall through to the normal CAS-based lock, because no matter what
                    // the result of the above CAS, some thread must have succeeded in
                    // removing the bias bit from the object's header.
                }
            }
        }

        // Create the unlocked mark word pattern
        Word unlockedMark = mark.or(unlockedMask());

        // Copy this unlocked mark word into the lock slot on the stack
        storeWord(lock, lockDisplacedMarkOffset(), 0, unlockedMark);

        // Test if the object's mark word is unlocked, and if so, store the
        // (address of) the lock slot into the object's mark word.
        Word currentMark = compareAndSwap(object, markOffset(), unlockedMark, lock);
        if (currentMark != unlockedMark) {
            // The mark word in the object header was not the same.
            // Either the object is locked by another thread or is already locked
            // by the current thread. The latter is true if the mark word
            // is a stack pointer into the current thread's stack, i.e.:
            //
            //   1) (currentMark & aligned_mask) == 0
            //   2)  rsp <= currentMark
            //   3)  currentMark <= rsp + page_size
            //
            // These 3 tests can be done by evaluating the following expression:
            //
            //   (currentMark - rsp) & (aligned_mask - page_size)
            //
            // assuming both the stack pointer and page_size have their least
            // significant 2 bits cleared and page_size is a power of 2
            final Word alignedMask = Word.fromInt(wordSize() - 1);
            final Word stackPointer = asWord(register(stackPointerReg(), wordKind()));
            if (currentMark.minus(stackPointer).and(alignedMask.minus(pageSize())) != Word.zero()) {
                // Most likely not a recursive lock, go into a slow runtime call
                log(logEnabled, "+lock{stub}", object);
                MonitorEnterStubCall.call(object, lock);
                return;
            } else {
                // Recursively locked => write 0 to the lock slot
                storeWord(lock, lockDisplacedMarkOffset(), 0, Word.zero());
                log(logEnabled, "+lock{recursive}", object);
            }
        } else {
            log(logEnabled, "+lock{cas}", object);
        }
    }

    @Snippet
    public static void monitorenterEliminated(@Parameter("object") Object object) {
        beginLockScope(object, true, wordKind());
    }

    /**
     * Calls straight out to the monitorenter stub.
     */
    @Snippet
    public static void monitorenterStub(@Parameter("object") Object object, @ConstantParameter("checkNull") boolean checkNull, @ConstantParameter("logEnabled") boolean logEnabled) {
        verifyOop(object);
        if (checkNull && object == null) {
            DeoptimizeNode.deopt(DeoptimizationAction.InvalidateReprofile, DeoptimizationReason.NullCheckException);
        }
        // BeginLockScope nodes do not read from object so a use of object
        // cannot float about the null check above
        final Word lock = beginLockScope(object, false, wordKind());
        log(logEnabled, "+lock{stub}", object);
        MonitorEnterStubCall.call(object, lock);
    }

    @Snippet
    public static void monitorexit(@Parameter("object") Object object, @ConstantParameter("logEnabled") boolean logEnabled) {
        if (useBiasedLocking()) {
            // Check for biased locking unlock case, which is a no-op
            // Note: we do not have to check the thread ID for two reasons.
            // First, the interpreter checks for IllegalMonitorStateException at
            // a higher level. Second, if the bias was revoked while we held the
            // lock, the object could not be rebiased toward another thread, so
            // the bias bit would be clear.
            final Word mark = asWord(loadObject(object, 0, markOffset(), true));
            if (mark.and(biasedLockMaskInPlace()).toLong() == biasedLockPattern()) {
                endLockScope(object, false);
                log(logEnabled, "-lock{bias}", object);
                return;
            }
        }

        final Word lock = CurrentLockNode.currentLock(wordKind());

        // Load displaced mark
        final Word displacedMark = loadWord(lock, lockDisplacedMarkOffset());

        if (displacedMark == Word.zero()) {
            // Recursive locking => done
            log(logEnabled, "-lock{recursive}", object);
        } else {
            verifyOop(object);
            // Test if object's mark word is pointing to the displaced mark word, and if so, restore
            // the displaced mark in the object - if the object's mark word is not pointing to
            // the displaced mark word, do unlocking via runtime call.
            if (DirectCompareAndSwapNode.compareAndSwap(object, markOffset(), lock, displacedMark) != lock) {
              // The object's mark word was not pointing to the displaced header,
              // we do unlocking via runtime call.
                log(logEnabled, "-lock{stub}", object);
                MonitorExitStubCall.call(object);
            } else {
                log(logEnabled, "-lock{cas}", object);
            }
        }
        endLockScope(object, false);
    }

    /**
     * Calls straight out to the monitorexit stub.
     */
    @Snippet
    public static void monitorexitStub(@Parameter("object") Object object, @ConstantParameter("logEnabled") boolean logEnabled) {
        verifyOop(object);
        log(logEnabled, "-lock{stub}", object);
        MonitorExitStubCall.call(object);
        endLockScope(object, false);
    }

    @Snippet
    public static void monitorexitEliminated(@Parameter("object") Object object) {
        endLockScope(object, true);
    }

    public static class Templates extends AbstractTemplates<MonitorSnippets> {

        private final ResolvedJavaMethod monitorenter;
        private final ResolvedJavaMethod monitorexit;
        private final ResolvedJavaMethod monitorenterStub;
        private final ResolvedJavaMethod monitorexitStub;
        private final ResolvedJavaMethod monitorenterEliminated;
        private final ResolvedJavaMethod monitorexitEliminated;
        private final boolean useFastLocking;

        public Templates(CodeCacheProvider runtime, boolean useFastLocking) {
            super(runtime, MonitorSnippets.class);
            monitorenter = snippet("monitorenter", Object.class, boolean.class);
            monitorexit = snippet("monitorexit", Object.class, boolean.class);
            monitorenterStub = snippet("monitorenterStub", Object.class, boolean.class, boolean.class);
            monitorexitStub = snippet("monitorexitStub", Object.class, boolean.class);
            monitorenterEliminated = snippet("monitorenterEliminated", Object.class);
            monitorexitEliminated = snippet("monitorexitEliminated", Object.class);
            this.useFastLocking = useFastLocking;
        }

        static boolean isLoggingEnabledFor(ValueNode object) {
            ResolvedJavaType type = object.objectStamp().type();
            if (LOG_TYPE == null) {
                return false;
            } else {
                if (LOG_TYPE.length() == 0) {
                    return true;
                }
                if (type == null) {
                    return false;
                }
                return (type.name().contains(LOG_TYPE));
            }
        }

        public void lower(MonitorEnterNode monitorenterNode, @SuppressWarnings("unused") LoweringTool tool) {
            FrameState stateAfter = monitorenterNode.stateAfter();
            ResolvedJavaMethod method = monitorenterNode.eliminated() ? monitorenterEliminated : useFastLocking ? monitorenter : monitorenterStub;
            boolean checkNull = !monitorenterNode.object().stamp().nonNull();
            Key key = new Key(method);
            if (method == monitorenterStub) {
                key.add("checkNull", checkNull);
            }
            if (!monitorenterNode.eliminated()) {
                key.add("logEnabled", isLoggingEnabledFor(monitorenterNode.object()));
            }
            Arguments arguments = arguments("object", monitorenterNode.object());
            SnippetTemplate template = cache.get(key);
            Map<Node, Node> nodes = template.instantiate(runtime, monitorenterNode, arguments);
            for (Node n : nodes.values()) {
                if (n instanceof BeginLockScopeNode) {
                    BeginLockScopeNode begin = (BeginLockScopeNode) n;
                    begin.setStateAfter(stateAfter);
                }
            }
        }

        public void lower(MonitorExitNode monitorexitNode, @SuppressWarnings("unused") LoweringTool tool) {
            FrameState stateAfter = monitorexitNode.stateAfter();
            ResolvedJavaMethod method = monitorexitNode.eliminated() ? monitorexitEliminated : useFastLocking ? monitorexit : monitorexitStub;
            Key key = new Key(method);
            if (!monitorexitNode.eliminated()) {
                key.add("logEnabled", isLoggingEnabledFor(monitorexitNode.object()));
            }
            Arguments arguments = arguments("object", monitorexitNode.object());
            SnippetTemplate template = cache.get(key);
            Map<Node, Node> nodes = template.instantiate(runtime, monitorexitNode, arguments);
            for (Node n : nodes.values()) {
                if (n instanceof EndLockScopeNode) {
                    EndLockScopeNode end = (EndLockScopeNode) n;
                    end.setStateAfter(stateAfter);
                }
            }
        }
    }
}