Skip to content
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/hotspot/share/interpreter/oopMapCache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -194,14 +194,14 @@ InterpreterOopMap::~InterpreterOopMap() {
bool InterpreterOopMap::is_empty() const {
bool result = _method == nullptr;
assert(_method != nullptr || (_bci == 0 &&
(_mask_size == 0 || _mask_size == INT_MAX) &&
(_mask_size == 0 || _mask_size == USHRT_MAX) &&
_bit_mask[0] == 0), "Should be completely empty");
return result;
}

void InterpreterOopMap::initialize() {
_method = nullptr;
_mask_size = INT_MAX; // This value should cause a failure quickly
_mask_size = USHRT_MAX; // This value should cause a failure quickly
_bci = 0;
_expression_stack_size = 0;
_num_oops = 0;
Expand Down
4 changes: 2 additions & 2 deletions src/hotspot/share/interpreter/oopMapCache.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ class InterpreterOopMap: ResourceObj {
private:
Method* _method; // the method for which the mask is valid
unsigned short _bci; // the bci for which the mask is valid
int _mask_size; // the mask size in bits (INT_MAX if invalid)
int _mask_size; // the mask size in bits (USHRT_MAX if invalid)
int _expression_stack_size; // the size of the expression stack in slots

protected:
Expand Down Expand Up @@ -147,7 +147,7 @@ class InterpreterOopMap: ResourceObj {
int expression_stack_size() const { return _expression_stack_size; }

// Determines if a valid mask has been computed
bool has_valid_mask() const { return _mask_size != INT_MAX; }
bool has_valid_mask() const { return _mask_size != USHRT_MAX; }
};

class OopMapCache : public CHeapObj<mtClass> {
Expand Down
49 changes: 31 additions & 18 deletions src/hotspot/share/jvmci/jvmciCompilerToVM.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3076,33 +3076,46 @@ C2V_VMENTRY_0(jlong, getThreadLocalLong, (JNIEnv* env, jobject, jint id))
}
C2V_END

C2V_VMENTRY_0(jlong, getLiveObjectLocalsAt, (JNIEnv* env, jobject, ARGUMENT_PAIR(method),
jint bci, jlong buffer))
C2V_VMENTRY(void, getOopMapAt, (JNIEnv* env, jobject, ARGUMENT_PAIR(method),
jint bci, jlongArray oop_map_handle))
methodHandle method(THREAD, UNPACK_PAIR(Method, method));
if (bci < 0 || bci >= method->code_size()) {
JVMCI_THROW_MSG_0(IllegalArgumentException,
JVMCI_THROW_MSG(IllegalArgumentException,
err_msg("bci %d is out of bounds [0 .. %d)", bci, method->code_size()));
}
InterpreterOopMap mask;
OopMapCache::compute_one_oop_map(method, bci, &mask);
if (!mask.has_valid_mask()) {
JVMCI_THROW_MSG_0(IllegalArgumentException, err_msg("bci %d is not valid", bci));
}
int nlocals = method->max_locals();
jlong liveness = 0L;
// stringStream st;
// st.print("BitMap[%s@%d, nlocals:%d]:", method->name_and_sig_as_C_string(), bci, nlocals);
BitMapView bm = BitMapView((BitMap::bm_word_t*) (nlocals <= 64 ? (jlong) &liveness : buffer), nlocals);
for (int i = 0; i < nlocals ; i++ ) {
JVMCI_THROW_MSG(IllegalArgumentException, err_msg("bci %d is not valid", bci));
}
if (mask.number_of_entries() == 0) {
return;
}

int nslots = method->max_locals() + method->max_stack();
int nwords = ((nslots - 1) / 64) + 1;
JVMCIPrimitiveArray oop_map = JVMCIENV->wrap(oop_map_handle);
int oop_map_len = JVMCIENV->get_length(oop_map);
if (nwords > oop_map_len) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we sanity check against mask.number_of_entries()? One wrinkle here is that compute_one_oop_map also computes information about the stack so the mask it computes can be larger than just max_locals. For the purposes of OSR this doesn't matter as none of the JITs support OSR with a non-empty stack, so we would never call it for a bci with a non-empty stack. So should we disallow calling it with a non-empty stack or just properly handle it by passing in an array long enough to contain max_locals + max_stack?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We only look up the mask for locals and so ignore stack indexes in the mask altogether. I'm assuming therefore that mask.is_oop(i) can never hit any problems.
Note that this API should be safe when called for any valid BCI, not just those for an OSR entry point. Even if called for a BCI with a non-empty stack, the current implementation simply ignores that part of the mask.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes that's implied by the name of the method. It would make me happy if there was a comment pointing out that we're explicitly ignoring whether the stack is non-empty and contains oops.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead, I generalized getLiveObjectLocalsAt to getOopMapAt since the VM computation is for both locals and operand stack anyway.
When called for OSR entry points, the result will be the same since (currently) HotSpot requires the stack to be empty.

JVMCI_THROW_MSG(IllegalArgumentException,
err_msg("oop map too short: %d > %d", nwords, oop_map_len));
}

jlong* oop_map_buf = NEW_RESOURCE_ARRAY_IN_THREAD_RETURN_NULL(THREAD, jlong, nwords);
if (oop_map_buf == nullptr) {
JVMCI_THROW_MSG(InternalError, err_msg("could not allocate %d longs", nwords));
}
for (int i = 0; i < nwords; i++) {
oop_map_buf[i] = 0L;
}

BitMapView oop_map_view = BitMapView((BitMap::bm_word_t*) oop_map_buf, nwords * BitsPerLong);
for (int i = 0; i < nslots; i++) {
if (mask.is_oop(i)) {
bm.set_bit(i);
// st.print(" %d", i);
oop_map_view.set_bit(i);
}
}
// st.print(" [liveness: 0x" JULONG_FORMAT_X "]", (julong) liveness);
// tty->print_raw_cr(st.as_string());
// tty->flush();
return liveness;
JVMCIENV->copy_longs_from((jlong*)oop_map_buf, oop_map, 0, nwords);
C2V_END

#define CC (char*) /*cast a literal from (const char*)*/
Expand Down Expand Up @@ -3262,7 +3275,7 @@ JNINativeMethod CompilerToVM::methods[] = {
{CC "registerCompilerPhase", CC "(" STRING ")I", FN_PTR(registerCompilerPhase)},
{CC "notifyCompilerPhaseEvent", CC "(JIII)V", FN_PTR(notifyCompilerPhaseEvent)},
{CC "notifyCompilerInliningEvent", CC "(I" HS_METHOD2 HS_METHOD2 "ZLjava/lang/String;I)V", FN_PTR(notifyCompilerInliningEvent)},
{CC "getLiveObjectLocalsAt", CC "(" HS_METHOD2 "IJ)J", FN_PTR(getLiveObjectLocalsAt)},
{CC "getOopMapAt", CC "(" HS_METHOD2 "I[J)V", FN_PTR(getOopMapAt)},
};

int CompilerToVM::methods_count() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1474,10 +1474,12 @@ public void close() {
}
}


long getLiveObjectLocalsAt(HotSpotResolvedJavaMethodImpl method, int bci, long buffer) {
return getLiveObjectLocalsAt(method, method.getMethodPointer(), bci, buffer);
/**
* @see HotSpotResolvedJavaMethod#getOopMapAt
*/
void getOopMapAt(HotSpotResolvedJavaMethodImpl method, int bci, long[] oopMap) {
getOopMapAt(method, method.getMethodPointer(), bci, oopMap);
}

native long getLiveObjectLocalsAt(HotSpotResolvedJavaMethodImpl method, long methodPointer, int bci, long buffer);
native void getOopMapAt(HotSpotResolvedJavaMethodImpl method, long methodPointer, int bci, long[] oopMap);
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
package jdk.vm.ci.hotspot;

import java.lang.reflect.Modifier;
import java.util.BitSet;

import jdk.vm.ci.meta.JavaMethod;
import jdk.vm.ci.meta.ResolvedJavaMethod;
Expand Down Expand Up @@ -127,4 +128,18 @@ default boolean isDefault() {
boolean hasCodeAtLevel(int entryBCI, int level);

int methodIdnum();


/**
* Computes which local variables and operand stack slots in {@code method} contain
* live object values at the instruction denoted by {@code bci}. This is the "oop map"
* used by the garbage collector for interpreter frames.
*
* @param bci the index of an instruction in this method's bytecodes
* @return the computed oop map. The first {@link #getMaxLocals} bits are for
* the local variables, the remaining bits are for the stack slots.
* @throws IllegalArgumentException if this method has no bytecode or
* {@code bci} is not the index of a bytecode instruction
*/
BitSet getOopMapAt(int bci);
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,12 @@
import java.lang.annotation.Annotation;
import java.lang.reflect.Executable;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;
import java.lang.reflect.Type;
import java.util.BitSet;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

import jdk.internal.misc.Unsafe;
import jdk.internal.vm.VMSupport;
import jdk.vm.ci.common.JVMCIError;
import jdk.vm.ci.hotspot.HotSpotJVMCIRuntime.Option;
Expand Down Expand Up @@ -771,43 +769,14 @@ private List<AnnotationData> getAnnotationData0(ResolvedJavaType... filter) {
return VMSupport.decodeAnnotations(encoded, AnnotationDataDecoder.INSTANCE);
}

/*
* BitSets are packed into arrays of "words." Currently a word is
* a long, which consists of 64 bits, requiring 6 address bits.
* The choice of word size is determined purely by performance concerns.
*/
private static final int ADDRESS_BITS_PER_WORD = 6;
private static final int BITS_PER_WORD = 1 << ADDRESS_BITS_PER_WORD;
private static final int BIT_INDEX_MASK = BITS_PER_WORD - 1;

private static int wordIndex(int bitIndex) {
return bitIndex >> ADDRESS_BITS_PER_WORD;
}

@Override
public long getLiveObjectLocalsAt(int bci, BitSet bigOopMap) {
int locals = getMaxLocals();
if (locals == 0) {
throw new IllegalArgumentException("cannot compute oop map for method with no local variables");
}
if (locals > 64) {
int nwords = ((locals - 1) / 64) + 1;
Unsafe unsafe = UnsafeAccess.UNSAFE;
long buffer = unsafe.allocateMemory(nwords);
try {
compilerToVM().getLiveObjectLocalsAt(this, bci, buffer);
long liveness[] = new long[nwords];
for (int i = 0; i < nwords; i++) {
liveness[i] = unsafe.getLong(buffer + i);
}
bigOopMap.or(BitSet.valueOf(liveness));
} finally {
unsafe.freeMemory(buffer);
}
return 0;
} else {
return compilerToVM().getLiveObjectLocalsAt(this, bci, 0);
public BitSet getOopMapAt(int bci) {
if (getCodeSize() == 0) {
throw new IllegalArgumentException("has no bytecode");
}
int nwords = ((getMaxLocals() + getMaxStackSize() - 1) / 64) + 1;
long[] oopMap = new long[nwords];
compilerToVM().getOopMapAt(this, bci, oopMap);
return BitSet.valueOf(oopMap);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -470,28 +470,4 @@ default boolean isJavaLangObjectInit() {
* responsibility to ensure the same speculation log is used throughout a compilation.
*/
SpeculationLog getSpeculationLog();

/**
* Computes which local variables contain live object values
* at the instruction denoted by {@code bci}. This is the "oop map" used
* by the garbage collector.
*
* If {@link #getMaxLocals()} {@code <= 64}, then the oop map is encoded
* in the return value. Otherwise, it is copied into {@code bigOopMap}.
*
* @param bci the index of an instruction in this method's bytecodes
* @param bigOopMap the bit set in which the oop map is returned for
* methods whose max number of local variables is {@code > 64}. It's up
* to the caller to ensure this bit set is large enough and initially has
* all bits set to 0.
* @return the oop map for methods with 64 or less max locals otherwise 0
* @throws NullPointerException if {@link #getMaxLocals()} {@code > 64 && bigOopMap == null}
* @throws IllegalArgumentException if {@link #getMaxLocals()} {@code == 0} or if
* {@code bci} is not the index of a bytecode instruction
* @throws UnsupportedOperationException if local variable liveness is not provided
* by the current JVMCI runtime
*/
default long getLiveObjectLocalsAt(int bci, BitSet bigOopMap) {
throw new UnsupportedOperationException();
}
}
Loading