8257189: Handle concurrent updates of MH.form better

Reviewed-by: redestad, psandoz
This commit is contained in:
Vladimir Ivanov 2020-12-02 17:35:41 +00:00
parent 670426646d
commit 692b273ec5
5 changed files with 69 additions and 53 deletions

View File

@ -36,6 +36,7 @@ import sun.invoke.util.Wrapper;
import java.lang.ref.WeakReference;
import java.util.Arrays;
import java.util.Objects;
import java.util.function.Function;
import static java.lang.invoke.LambdaForm.*;
import static java.lang.invoke.LambdaForm.Kind.*;
@ -387,10 +388,12 @@ class DirectMethodHandle extends MethodHandle {
private void ensureInitialized() {
if (checkInitialized(member)) {
// The coast is clear. Delete the <clinit> barrier.
if (member.isField())
updateForm(preparedFieldLambdaForm(member));
else
updateForm(preparedLambdaForm(member));
updateForm(new Function<>() {
public LambdaForm apply(LambdaForm oldForm) {
return (member.isField() ? preparedFieldLambdaForm(member)
: preparedLambdaForm(member));
}
});
}
}
private static boolean checkInitialized(MemberName member) {

View File

@ -596,21 +596,17 @@ class Invokers {
@ForceInline
/*non-public*/
static void checkCustomized(MethodHandle mh) {
if (MethodHandleImpl.isCompileConstant(mh)) return;
if (mh.form.customized == null) {
maybeCustomize(mh);
if (MethodHandleImpl.isCompileConstant(mh)) {
return; // no need to customize a MH when the instance is known to JIT
}
if (mh.form.customized == null) { // fast approximate check that the underlying form is already customized
maybeCustomize(mh); // marked w/ @DontInline
}
}
@DontInline
/*non-public*/
static void maybeCustomize(MethodHandle mh) {
byte count = mh.customizationCount;
if (count >= CUSTOMIZE_THRESHOLD) {
mh.customize();
} else {
mh.customizationCount = (byte)(count+1);
}
mh.maybeCustomize();
}
// Local constant functions:

View File

@ -501,6 +501,9 @@ class LambdaForm {
/** Customize LambdaForm for a particular MethodHandle */
LambdaForm customize(MethodHandle mh) {
if (customized == mh) {
return this;
}
LambdaForm customForm = new LambdaForm(arity, names, result, forceInline, mh, kind);
if (COMPILE_THRESHOLD >= 0 && isCompiled) {
// If shared LambdaForm has been compiled, compile customized version as well.

View File

@ -30,13 +30,13 @@ import jdk.internal.vm.annotation.IntrinsicCandidate;
import java.lang.constant.ClassDesc;
import java.lang.constant.Constable;
import java.lang.constant.ConstantDesc;
import java.lang.constant.DirectMethodHandleDesc;
import java.lang.constant.MethodHandleDesc;
import java.lang.constant.MethodTypeDesc;
import java.util.Arrays;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import static java.lang.invoke.MethodHandleInfo.*;
import static java.lang.invoke.MethodHandleStatics.*;
@ -455,9 +455,8 @@ public abstract class MethodHandle implements Constable {
/*private*/
MethodHandle asTypeCache;
// asTypeCache is not private so that invokers can easily fetch it
/*non-public*/
byte customizationCount;
// customizationCount should be accessible from invokers
private byte customizationCount;
/**
* Reports the type of this method handle.
@ -1733,6 +1732,30 @@ assertEquals("[three, thee, tee]", asListFix.invoke((Object)argv).toString());
*/
abstract BoundMethodHandle rebind();
/* non-public */
void maybeCustomize() {
if (form.customized == null) {
byte count = customizationCount;
if (count >= CUSTOMIZE_THRESHOLD) {
customize();
} else {
customizationCount = (byte) (count + 1);
}
}
}
/** Craft a LambdaForm customized for this particular MethodHandle. */
/*non-public*/
void customize() {
updateForm(new Function<>() {
public LambdaForm apply(LambdaForm oldForm) {
return oldForm.customize(MethodHandle.this);
}
});
}
private volatile boolean updateInProgress; // = false;
/**
* Replace the old lambda form of this method handle with a new one.
* The new one must be functionally equivalent to the old one.
@ -1741,26 +1764,26 @@ assertEquals("[three, thee, tee]", asListFix.invoke((Object)argv).toString());
* Use with discretion.
*/
/*non-public*/
void updateForm(LambdaForm newForm) {
assert(newForm.customized == null || newForm.customized == this);
if (form == newForm) return;
newForm.prepare(); // as in MethodHandle.<init>
UNSAFE.putReference(this, FORM_OFFSET, newForm);
UNSAFE.fullFence();
}
/** Craft a LambdaForm customized for this particular MethodHandle */
/*non-public*/
void customize() {
final LambdaForm form = this.form;
if (form.customized == null) {
LambdaForm newForm = form.customize(this);
updateForm(newForm);
void updateForm(Function<LambdaForm, LambdaForm> updater) {
if (UNSAFE.compareAndSetBoolean(this, UPDATE_OFFSET, false, true)) { // updateInProgress = true
// Only 1 thread wins the race and updates MH.form field.
try {
LambdaForm oldForm = form;
LambdaForm newForm = updater.apply(oldForm);
if (oldForm != newForm) {
assert (newForm.customized == null || newForm.customized == this);
newForm.prepare(); // as in MethodHandle.<init>
UNSAFE.putReference(this, FORM_OFFSET, newForm);
UNSAFE.fullFence();
}
} finally {
updateInProgress = false;
}
} else {
assert(form.customized == this);
// Update got lost due to concurrent update. But callers don't care.
}
}
private static final long FORM_OFFSET
= UNSAFE.objectFieldOffset(MethodHandle.class, "form");
private static final long FORM_OFFSET = UNSAFE.objectFieldOffset(MethodHandle.class, "form");
private static final long UPDATE_OFFSET = UNSAFE.objectFieldOffset(MethodHandle.class, "updateInProgress");
}

View File

@ -841,21 +841,9 @@ abstract class MethodHandleImpl {
return (asTypeCache = wrapper);
}
// Customize target if counting happens for too long.
private int invocations = CUSTOMIZE_THRESHOLD;
private void maybeCustomizeTarget() {
int c = invocations;
if (c >= 0) {
if (c == 1) {
target.customize();
}
invocations = c - 1;
}
}
boolean countDown() {
int c = count;
maybeCustomizeTarget();
target.maybeCustomize(); // customize if counting happens for too long
if (c <= 1) {
// Try to limit number of updates. MethodHandle.updateForm() doesn't guarantee LF update visibility.
if (isCounting) {
@ -872,12 +860,15 @@ abstract class MethodHandleImpl {
@Hidden
static void maybeStopCounting(Object o1) {
CountingWrapper wrapper = (CountingWrapper) o1;
final CountingWrapper wrapper = (CountingWrapper) o1;
if (wrapper.countDown()) {
// Reached invocation threshold. Replace counting behavior with a non-counting one.
LambdaForm lform = wrapper.nonCountingFormProducer.apply(wrapper.target);
lform.compileToBytecode(); // speed up warmup by avoiding LF interpretation again after transition
wrapper.updateForm(lform);
wrapper.updateForm(new Function<>() {
public LambdaForm apply(LambdaForm oldForm) {
LambdaForm lform = wrapper.nonCountingFormProducer.apply(wrapper.target);
lform.compileToBytecode(); // speed up warmup by avoiding LF interpretation again after transition
return lform;
}});
}
}