| /* | |
| * Copyright (c) 2009, 2023, 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 jdk.vm.ci.code; | |
| import java.util.Arrays; | |
| import java.util.Objects; | |
| import jdk.vm.ci.common.JVMCIError; | |
| import jdk.vm.ci.meta.JavaKind; | |
| import jdk.vm.ci.meta.JavaValue; | |
| import jdk.vm.ci.meta.ResolvedJavaMethod; | |
| import jdk.vm.ci.meta.Value; | |
| /** | |
| * Represents the Java bytecode frame state(s) at a given position including {@link Value locations} | |
| * where to find the local variables, operand stack values and locked objects of the bytecode | |
| * frame(s). | |
| */ | |
| public final class BytecodeFrame extends BytecodePosition { | |
| /** | |
| * An array of values representing how to reconstruct the state of the Java frame. This is array | |
| * is partitioned as follows: | |
| * <p> | |
| * <table summary="" border="1" cellpadding="5" frame="void" rules="all"> | |
| * <tr> | |
| * <th>Start index (inclusive)</th> | |
| * <th>End index (exclusive)</th> | |
| * <th>Description</th> | |
| * </tr> | |
| * <tr> | |
| * <td>0</td> | |
| * <td>numLocals</td> | |
| * <td>Local variables</td> | |
| * </tr> | |
| * <tr> | |
| * <td>numLocals</td> | |
| * <td>numLocals + numStack</td> | |
| * <td>Operand stack</td> | |
| * </tr> | |
| * <tr> | |
| * <td>numLocals + numStack</td> | |
| * <td>values.length</td> | |
| * <td>Locked objects</td> | |
| * </tr> | |
| * </table> | |
| * <p> | |
| * Note that the number of locals and the number of stack slots may be smaller than the maximum | |
| * number of locals and stack slots as specified in the compiled method. | |
| * | |
| * This field is intentionally exposed as a mutable array that a compiler may modify (e.g. | |
| * during register allocation). | |
| */ | |
| @SuppressFBWarnings(value = "EI_EXPOSE_REP2", justification = "field is intentionally mutable")// | |
| public final JavaValue[] values; | |
| /** | |
| * An array describing the Java kinds in {@link #values}. It records a kind for the locals and | |
| * the operand stack. | |
| */ | |
| private final JavaKind[] slotKinds; | |
| /** | |
| * The number of locals in the values array. | |
| */ | |
| public final int numLocals; | |
| /** | |
| * The number of stack slots in the values array. | |
| */ | |
| public final int numStack; | |
| /** | |
| * The number of locks in the values array. | |
| */ | |
| public final int numLocks; | |
| /** | |
| * True if this is a position inside an exception handler before the exception object has been | |
| * consumed. In this case, {@link #numStack} {@code == 1} and {@link #getStackValue(int) | |
| * getStackValue(0)} is the location of the exception object. If deoptimization happens at this | |
| * position, the interpreter will rethrow the exception instead of executing the bytecode | |
| * instruction at this position. | |
| */ | |
| public final boolean rethrowException; | |
| /** | |
| * Specifies if this object represents a frame state in the middle of executing a call. If true, | |
| * the arguments to the call have been popped from the stack and the return value (for a | |
| * non-void call) has not yet been pushed. | |
| */ | |
| public final boolean duringCall; | |
| /** | |
| * This BCI should be used for frame states that are built for code with no meaningful BCI. | |
| */ | |
| public static final int UNKNOWN_BCI = -5; | |
| /** | |
| * The BCI for exception unwind. This is synthetic code and has no representation in bytecode. | |
| * In contrast with {@link #AFTER_EXCEPTION_BCI}, at this point, if the method is synchronized, | |
| * the monitor is still held. | |
| */ | |
| public static final int UNWIND_BCI = -1; | |
| /** | |
| * The BCI for the state before starting to execute a method. Note that if the method is | |
| * synchronized, the monitor is not yet held. | |
| */ | |
| public static final int BEFORE_BCI = -2; | |
| /** | |
| * The BCI for the state after finishing the execution of a method and returning normally. Note | |
| * that if the method was synchronized the monitor is already released. | |
| */ | |
| public static final int AFTER_BCI = -3; | |
| /** | |
| * The BCI for exception unwind. This is synthetic code and has no representation in bytecode. | |
| * In contrast with {@link #UNWIND_BCI}, at this point, if the method is synchronized, the | |
| * monitor is already released. | |
| */ | |
| public static final int AFTER_EXCEPTION_BCI = -4; | |
| /** | |
| * This BCI should be used for states that cannot be the target of a deoptimization, like | |
| * snippet frame states. | |
| */ | |
| public static final int INVALID_FRAMESTATE_BCI = -6; | |
| /** | |
| * Determines if a given BCI matches one of the placeholder BCI constants defined in this class. | |
| */ | |
| public static boolean isPlaceholderBci(int bci) { | |
| return bci < 0; | |
| } | |
| /** | |
| * Gets the name of a given placeholder BCI. | |
| */ | |
| public static String getPlaceholderBciName(int bci) { | |
| assert isPlaceholderBci(bci); | |
| if (bci == BytecodeFrame.AFTER_BCI) { | |
| return "AFTER_BCI"; | |
| } else if (bci == BytecodeFrame.AFTER_EXCEPTION_BCI) { | |
| return "AFTER_EXCEPTION_BCI"; | |
| } else if (bci == BytecodeFrame.INVALID_FRAMESTATE_BCI) { | |
| return "INVALID_FRAMESTATE_BCI"; | |
| } else if (bci == BytecodeFrame.BEFORE_BCI) { | |
| return "BEFORE_BCI"; | |
| } else if (bci == BytecodeFrame.UNKNOWN_BCI) { | |
| return "UNKNOWN_BCI"; | |
| } else { | |
| assert bci == BytecodeFrame.UNWIND_BCI; | |
| return "UNWIND_BCI"; | |
| } | |
| } | |
| /** | |
| * Creates a new frame object. A well formed frame has the following invariants: | |
| * <ul> | |
| * <li>{@code values != null}</li> | |
| * <li>{@code slotKinds != null}</li> | |
| * <li>{@code numLocals + numStack + numLocks == values.length}</li> | |
| * <li>{@code numLocals + numStack + numLocks == values.length}</li> | |
| * <li>{@code numLocals + numStack == slotKinds.length}</li> | |
| * <li>all entries in {@code values} starting at index {@code numLocals + numStack} must be of | |
| * type {@link StackLockValue}</li> | |
| * <li>for each index {@code i} between 0 (inclusive) and {@code numLocals + numStack} | |
| * (exclusive), if {@code slotKinds[i].needsTwoSlots()} then | |
| * {@code values[i + 1] == Value.ILLEGAL}.</li> | |
| * </ul> | |
| * | |
| * These invariants are not checked in this constructor but by {@link #verifyInvariants()}. | |
| * | |
| * @param caller the caller frame (which may be {@code null}) | |
| * @param method the method | |
| * @param bci a BCI within the method | |
| * @param rethrowException specifies if the VM should re-throw the pending exception when | |
| * deopt'ing using this frame | |
| * @param values the frame state {@link #values} | |
| * @param slotKinds the kinds in {@code values}. This array is now owned by this object and must | |
| * not be mutated by the caller. | |
| * @param numLocals the number of local variables | |
| * @param numStack the depth of the stack | |
| * @param numLocks the number of locked objects | |
| */ | |
| @SuppressFBWarnings(value = "EI_EXPOSE_REP2", justification = "caller transfers ownership of `slotKinds`") | |
| public BytecodeFrame(BytecodeFrame caller, | |
| ResolvedJavaMethod method, | |
| int bci, | |
| boolean rethrowException, | |
| boolean duringCall, | |
| JavaValue[] values, | |
| JavaKind[] slotKinds, | |
| int numLocals, | |
| int numStack, | |
| int numLocks) { | |
| super(caller, method, bci); | |
| assert values != null; | |
| this.rethrowException = rethrowException; | |
| this.duringCall = duringCall; | |
| this.values = values; | |
| this.slotKinds = slotKinds; | |
| this.numLocals = numLocals; | |
| this.numStack = numStack; | |
| this.numLocks = numLocks; | |
| assert !rethrowException || numStack == 1 : "must have exception on top of the stack"; | |
| } | |
| /** | |
| * Checks the invariants described in {@link #BytecodeFrame}. | |
| * | |
| * @throws NullPointerException if {@code values == null || slotKinds == null} or any of the | |
| * entries in {@code values} is null | |
| * @throws JVMCIError if any of the other invariants are violated | |
| */ | |
| public void verifyInvariants() { | |
| if (values.length != numLocals + numStack + numLocks) { | |
| throw new JVMCIError("unexpected values length %d in frame (%d locals, %d stack slots, %d locks)", values.length, numLocals, numStack, numLocks); | |
| } | |
| if (slotKinds.length != numLocals + numStack) { | |
| throw new JVMCIError("unexpected slotKinds length %d in frame (%d locals, %d stack slots)", values.length, numLocals, numStack); | |
| } | |
| for (int i = 0; i < slotKinds.length; i++) { | |
| Objects.requireNonNull(values[i]); | |
| JavaKind kind = slotKinds[i]; | |
| if (kind.needsTwoSlots()) { | |
| if (i + 1 >= values.length || values[i + 1] != Value.ILLEGAL) { | |
| throw new JVMCIError("2 slot value at index %d not followed by Value.ILLEGAL", i); | |
| } | |
| } | |
| } | |
| for (int i = slotKinds.length; i < values.length; i++) { | |
| JavaValue lock = values[i]; | |
| Objects.requireNonNull(lock); | |
| if (!(lock instanceof StackLockValue)) { | |
| throw new JVMCIError("Lock at %d must be of type StackLockValue, got %s", i, lock.getClass().getName()); | |
| } | |
| } | |
| } | |
| /** | |
| * Ensure that the frame state is formatted as expected by the JVM, with null or Illegal in the | |
| * slot following a double word item. This should really be checked in FrameState itself but | |
| * because of Word type rewriting and alternative backends that can't be done. | |
| */ | |
| public boolean validateFormat() { | |
| if (caller() != null) { | |
| caller().validateFormat(); | |
| } | |
| for (int i = 0; i < numLocals + numStack; i++) { | |
| if (values[i] != null) { | |
| JavaKind kind = slotKinds[i]; | |
| if (kind.needsTwoSlots()) { | |
| assert slotKinds.length > i + 1 : String.format("missing second word %s", this); | |
| assert slotKinds[i + 1] == JavaKind.Illegal : this; | |
| } | |
| } | |
| } | |
| return true; | |
| } | |
| /** | |
| * Gets the kind of a local variable. | |
| * | |
| * @param i the local variable to query | |
| * @return the kind of local variable {@code i} | |
| * @throw {@link IndexOutOfBoundsException} if {@code i < 0 || i >= this.numLocals} | |
| */ | |
| public JavaKind getLocalValueKind(int i) { | |
| Objects.checkIndex(i, numLocals); | |
| return slotKinds[i]; | |
| } | |
| /** | |
| * Gets the kind of a stack slot. | |
| * | |
| * @param i the local variable to query | |
| * @return the kind of stack slot {@code i} | |
| * @throw {@link IndexOutOfBoundsException} if {@code i < 0 || i >= this.numStack} | |
| */ | |
| public JavaKind getStackValueKind(int i) { | |
| Objects.checkIndex(i, numStack); | |
| return slotKinds[i + numLocals]; | |
| } | |
| /** | |
| * Gets the value representing the specified local variable. | |
| * | |
| * @param i the local variable index | |
| * @return the value that can be used to reconstruct the local's current value | |
| * @throw {@link IndexOutOfBoundsException} if {@code i < 0 || i >= this.numLocals} | |
| */ | |
| public JavaValue getLocalValue(int i) { | |
| Objects.checkIndex(i, numLocals); | |
| return values[i]; | |
| } | |
| /** | |
| * Gets the value representing the specified stack slot. | |
| * | |
| * @param i the stack index | |
| * @return the value that can be used to reconstruct the stack slot's current value | |
| * @throw {@link IndexOutOfBoundsException} if {@code i < 0 || i >= this.numStack} | |
| */ | |
| public JavaValue getStackValue(int i) { | |
| Objects.checkIndex(i, numStack); | |
| return values[i + numLocals]; | |
| } | |
| /** | |
| * Gets the value representing the specified lock. | |
| * | |
| * @param i the lock index | |
| * @return the value that can be used to reconstruct the lock's current value | |
| * @throw {@link IndexOutOfBoundsException} if {@code i < 0 || i >= this.numLocks} | |
| */ | |
| public JavaValue getLockValue(int i) { | |
| Objects.checkIndex(i, numLocks); | |
| return values[i + numLocals + numStack]; | |
| } | |
| /** | |
| * Gets the caller of this frame. | |
| * | |
| * @return {@code null} if this frame has no caller | |
| */ | |
| public BytecodeFrame caller() { | |
| return (BytecodeFrame) getCaller(); | |
| } | |
| @Override | |
| public int hashCode() { | |
| return Objects.hash(super.hashCode(), | |
| duringCall, | |
| numLocals, | |
| numLocks, | |
| numStack, | |
| rethrowException, | |
| Arrays.hashCode(slotKinds), | |
| Arrays.hashCode(values)); | |
| } | |
| @Override | |
| public boolean equals(Object obj) { | |
| if (this == obj) { | |
| return true; | |
| } | |
| if (!super.equals(obj)) { | |
| return false; | |
| } | |
| if (getClass() != obj.getClass()) { | |
| return false; | |
| } | |
| BytecodeFrame that = (BytecodeFrame) obj; | |
| return duringCall == that.duringCall && | |
| numLocals == that.numLocals && | |
| numLocks == that.numLocks && | |
| numStack == that.numStack && | |
| rethrowException == that.rethrowException && | |
| Arrays.equals(slotKinds, that.slotKinds) && | |
| Arrays.equals(values, that.values); | |
| } | |
| @Override | |
| public String toString() { | |
| return CodeUtil.append(new StringBuilder(100), this).toString(); | |
| } | |
| } |