-
Notifications
You must be signed in to change notification settings - Fork 537
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
Thunk.asFunction0
is broken in Scala 3 nightly
#2816
Comments
This reads to me a lot like the optimization that Scala 2 has and that Scala 3 was previously lacking as identified in #2225. But I don't understand why our trick broke. |
Yeah I'm perplexed as well. I think this merits some more experimentation. We should try to figure out what's going on so we can give them feedback before they release. At a minimum, the fact that this is detectably different between versions of Scala 3 is frustrating, since it means that our users on one or the other version will have a significantly degraded experience. |
3.1.3-RC1-bin-20220207-74c2954-NIGHTLY
3.1.1
Not sure if we can really read anything into it. |
Yep, this is what got my attention too. Is there no way to check the Scala 3 version at runtime and use that to choose an implementation? |
I tried restoring the original Java-based workaround from #2226, but the test still fails. cats-effect/core/jvm/src/main/java/cats/effect/ThunkImpl.java Lines 24 to 29 in 6d59eeb
So it must be that the thunk is being wrapped earlier than it used to be? |
I think we may have to do some bytecode dumpster diving to figure this one out. |
Decompiled Java is about as much as I'll tolerate 😤 "Thunk.asFunction0" should {
"return the same function" in {
var i = 0
val f = () => i += 1
Thunk.asFunction0(f()) eq f
}
} 3.0.2 private static final boolean $init$$$anonfun$2$$anonfun$1() {
IntRef i = IntRef.create(0);
Function0 f = () -> {
int var1 = i.elem + 1;
i.elem = var1;
};
return cats.effect.Thunk..MODULE$.asFunction0(f) == f;
} 3.1.3-RC1-bin-20220207-74c2954-NIGHTLY private static final void $init$$$anonfun$1$$anonfun$1$$anonfun$1(final Function0 f$1) {
f$1.apply$mcV$sp();
}
private static final boolean $init$$$anonfun$1$$anonfun$1() {
IntRef i = IntRef.create(0);
Function0 f = () -> {
int var1 = i.elem + 1;
i.elem = var1;
};
return cats.effect.Thunk..MODULE$.asFunction0(() -> {
$init$$$anonfun$1$$anonfun$1$$anonfun$1(f);
return BoxedUnit.UNIT;
}) == f;
} |
Uh. That looks like a regression honestly. Unless im misreading the above, it would seem that the change is causing unnecessary wrapping of the thunk. |
This should be enough to file an issue upstream, no? |
Let's dig into the raw bytecode first, but it is very suggestive of a bug upstream. It honestly looks like the compiler is doing the opposite of what Martin's change was intended to achieve. |
Putting aside the performance regression (which FTR my benchmarks were unable to pick up), I realized that actually this change doesn't break tracing. The relevant test for that continues to pass. cats-effect/tests/shared/src/test/scala/cats/effect/tracing/TracingSpec.scala Lines 35 to 42 in 4f01f5f
Even more interesting, actually you could argue the accuracy of cached tracing is improved. This test started to fail, but actually it encodes a less-than-desirable behavior, which is that two independent cats-effect/tests/shared/src/test/scala/cats/effect/tracing/TracingSpec.scala Lines 25 to 32 in 4f01f5f
Because each of those thunk s now generates a unique wrapping lambda, they get unique traces.
|
One more observation: what has changed in the compiler is actually the original 2.13->3 regression that caused us to introduce If I restore the code to how it used to be: def delay[A](thunk: => A): IO[A] = {
- val fn = Thunk.asFunction0(thunk)
+ val fn = () => thunk
Delay(fn, Tracing.calculateTracingEvent(fn))
} These are the test results: 3.1.3-RC1-bin-20220207-74c2954-NIGHTLY
3.0.1
Essentially, there's been a sort of flip-flop. In the code: def delay[A](thunk: => A): IO[A] = {
val fn = () => thunk
Delay(fn, Tracing.calculateTracingEvent(fn))
} In 3.0.2, the In the nightly, the However, it's the opposite story at the callsite: var i = 0
val f = () => i += 1
IO.delay(f()) In 3.0.2, the thunk passed to In the nightly, the |
This is the bit that I think constitutes a regression. Definitely the improvement of the "thunk to Realistically, we're probably going to have to upgrade to the latest Scala 3 in order to force call sites into this particular pattern, otherwise tracing breaks. |
Why would tracing break? |
Doesn't the |
Hmm. I wouldn't say "misassociated". It does break somewhat the caching, but that actually improves the accuracy as I noted above. |
FWIW neither @vasilmkd nor I have been able to pick this regression up in benchmarks yet. |
I'm pretty sure this is because the JVM inlining is quite good, particularly in those cases. It's possible that it's good in all regressed cases but I'm not entirely convinced that it would be, and at the very least, it seems implausible that more wrapping is better. :-)
Oh so the trick is unnecessary on the nightlies, but it doesn't in and of itself break anything? I was concerned that we needed |
It's better for accurate cached tracing :P
The trick has been unnecessary in Scala 2 all along and we've been using it anyway. The only reason it would break is if one day |
LOL yes, but I'm more worried about general performance. |
@armanbilge Do we need to make any adjustments on our end for this? Or is this just suppressing the test? |
Yes, I think it's just a matter of suppressing tests. We were testing more than we need to establish that tracing is working correctly, and fortunately there is no regression there. Arguably, the ThunkSpec was actually testing that lambdas passed to delay are not de-optimized, and rightfully failed. This is unrelated to the correctness of tracing but a meaningful regression nonetheless. |
@armanbilge I'm going to leave this open to track any adjustments to the unit tests. |
I just tried Let's get these tests re-enabled in the Scala 3 community build and we can close here. |
This was identified in scala/scala3#14395 (comment), apparently due to scala/scala3#14295.
The text was updated successfully, but these errors were encountered: