-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Runtime code implementing lazy val should not use sun.misc.Unsafe on Java 9+ (JEP-471) #9013
Comments
Here's some prototype target code @smarter paired with me to write: /*
* Decompiled with CFR 0.152.
*
* Could not load the following classes:
* scala.runtime.LazyVals$
*/
import scala.runtime.LazyVals$;
import java.lang.invoke.VarHandle;
import java.lang.invoke.MethodHandles;
/*
* Illegal identifiers - consider using --renameillegalidents true
*/
public class Decompiled {
// public static final long OFFSET$0 = LazyVals$.MODULE$.getOffsetStatic(LazyVal.package.Foo.1.class.getDeclaredField("bitmap$1"));
// get a varhandle instead of an offset
private static final VarHandle handle;
static {
try {
MethodHandles.Lookup l = MethodHandles.lookup();
handle = l.findVarHandle(Decompiled.class, "bitmap$1", long.class);
} catch (ReflectiveOperationException e) {
throw new ExceptionInInitializerError(e);
}
}
public long bitmap$1;
public int value$lzy1;
static final long LAZY_VAL_MASK = 3;
static final long BITS_PER_LAZY_VAL = 2;
public long casHelper(long current, long newState, int ord) {
long mask = ~(LAZY_VAL_MASK << ord * BITS_PER_LAZY_VAL);
return (current & mask) | (newState << (ord * BITS_PER_LAZY_VAL));
}
private boolean CAS(Object t, long current, long newState, int ord) {
return handle.compareAndSet(t, current, casHelper(current, newState, ord));
}
public static void setFlag(Object t, int v, int ord) {
boolean retry = true;
while (retry) {
long cur = handle.getVolatile(t);
if (LazyVals$.MODULE$.STATE(cur, ord) == 1)
retry = !CAS(t, cur, v, ord);
else {
// cur == 2, somebody is waiting on monitor
if (CAS(t, cur, v, ord)) {
Class<?> clazz = LazyVals$.class;
var privateMethod = clazz.getDeclaredMethod("getMonitor", Object.class, int.class);
privateMethod.setAccessible(true);
Object monitor = privateMethod.invokew(LazyVals$.MODULE$, t, ord);
synchronized (monitor) {
monitor.notifyAll();
}
retry = false;
}
}
}
}
public static void wait4Notification(Object t, long cur, int ord) {
boolean retry = true;
while (retry) {
long current = handle.getVolatile(t);
int state = LazyVals$.MODULE$.STATE(current, ord);
if (state == 1)
CAS(t, current, 2, ord);
else if (state == 2) {
Class<?> clazz = LazyVals$.class;
var privateMethod = clazz.getDeclaredMethod("getMonitor", Object.class, int.class);
privateMethod.setAccessible(true);
Object monitor = privateMethod.invokew(LazyVals$.MODULE$, t, ord);
synchronized (monitor) {
if (LazyVal$.MODULE$.STATE(handle.getVolatile(t), ord) == 2) // make sure notification did not happen yet.
monitor.wait();
}
} else {
retry = false;
}
}
}
public int value() {
long l;
long l2;
while ((l2 = LazyVals$.MODULE$.STATE(l = handle.getVolatile(this), 0)) != 3L) {
if (l2 == 0L) {
if (!handle.compareAndSet(this, l, casHelper(l, 1, 0))) continue;
try {
int n;
this.value$lzy1 = n = 13;
handle.
setFlag((Object)this, 3, 0);
return n;
}
catch (Throwable throwable) {
setFlag((Object)this, 0, 0);
throw throwable;
}
}
wait4Notification((Object)this, l, 0);
}
return this.value$lzy1;
}
} |
This occurs for me in another context: using This occurs with Scala 3.3.3. |
Also: in response to this from the issue description:
I'd be happy if Scala declared a minimum JVM version to the version that supports adding static fields in any class. Didn't the JVM just add its own bytecode manipulation API? I'm okay with JDK 21 as the minimum for Scala, and I'm sure others are not. |
Noting that also comes up when using the new |
OOC, why is this not a problem with lazy vals in 2.12 and 2.13? Is there something fundamentally different about lazy vals in 3.x? |
@alexklibisz yes, see #15296 (and linked history stretching at least as far back as #6979) |
Note: JEP-471 deprecates memory access methods in |
so I guess you can use some static methodhandles that resolve to the right thing at runtime? |
BTW, Kotlin uses AtomicReferenceFieldUpdater. Oracle did some work to improve its performance. And for me personally most important that this ABI depends on the idea that object is located somewhere in flat memory with fields available by some offsets. For some JVM implementations this not necessarily true. So it would be a good step forward if at least at ABI level there was something more high-level, at least similar to interface provided by |
AtomicReferenceFieldUpdater is nice, but not sure why AtomicReference uses the VarHandle. |
Currently, any usage of a lazy val requires getitng an instance of sun.misc.Unsafe at runtime: https://github.com/lampepfl/dotty/blob/43e4bfa5598e9cdbebb1dc56ed25a319d5aa8fbe/library/src/dotty/runtime/LazyVals.scala#L7 which is problematic for various reasons (e.g. usage of a security manager, using Graal Native (#13985)).
On Java 8, there's no good alternative, but on Java 9+ we should be able to replace that using VarHandle.
We should be able to use the same trick used in scala.runtime.Statics to check once at runtime if VarHandle is available to stay compatible with Java 8: https://github.com/scala/scala/blob/a8a726118d06c90b5506f907b1524457c0d401a7/src/library/scala/runtime/Statics.java#L158-L173(EDIT: actually I don't think this is good enough: using VarHandle would require adding static fields in any class that has a lazy val, so we need to know at compile-time the version of Java we support (via -release/-Xtarget)The text was updated successfully, but these errors were encountered: