Skip to content

Use Scala 3 threadUnsafe lazy vals#1818

Closed
igor-vovk wants to merge 1 commit intoscalapb:masterfrom
igor-vovk:threadunsafe-scala3-lazy-vals
Closed

Use Scala 3 threadUnsafe lazy vals#1818
igor-vovk wants to merge 1 commit intoscalapb:masterfrom
igor-vovk:threadunsafe-scala3-lazy-vals

Conversation

@igor-vovk
Copy link
Contributor

@igor-vovk igor-vovk commented Feb 15, 2025

Hi!

Scala 3 introduced faster mechanism of initialization for lazy vals: link to docs.

I want to propose adding it to some of the lazy val's in the generated outputs.

The reasoning is that there would be no harm if in the worst case scenario default values are initialized more than once and then GCed, but instead there will be a) no thread-locks during the initialization b) much less byte-code and faster compilation, since they are used a lot. Lazy vals for variables inside of the *Proto classes (decoding of the protobufs) left as is (no @ThreadUnsafe annotation added), since it may be more costly to initialize those values more than once.

Here is the example of 2 classes, with annotation and without:

class LazyVal {
  lazy val x = "test"
}

class ThreadUnsafeLazyVal {
  @scala.annotation.threadUnsafe
  lazy val x = "test"
}

And here is the bytecode that is produced (decompiled to Java code):

/// LazyVal.decompiled.java

import scala.runtime.LazyVals;
import scala.runtime.LazyVals.;

public class LazyVal {
    public static final long OFFSET$0;
    private volatile Object x$lzy1;

    public LazyVal() {
    }

    static {
        OFFSET$0 = .MODULE$.getOffsetStatic(LazyVal.class.getDeclaredField("x$lzy1"));
    }

    public String x() {
        Object var1 = this.x$lzy1;
        if (var1 instanceof String) {
            return (String)var1;
        } else {
            return var1 == scala.runtime.LazyVals.NullValue..MODULE$ ? null : (String)this.x$lzyINIT1();
        }
    }

    private Object x$lzyINIT1() {
        while(true) {
            Object var1 = this.x$lzy1;
            if (var1 == null) {
                if (.MODULE$.objCAS(this, OFFSET$0, (Object)null, scala.runtime.LazyVals.Evaluating..MODULE$)) {
                    Object var2 = null;
                    Object var3 = null;

                    try {
                        var8 = "test";
                        if (var8 == null) {
                            var2 = scala.runtime.LazyVals.NullValue..MODULE$;
                        } else {
                            var2 = var8;
                        }
                    } finally {
                        if (!.MODULE$.objCAS(this, OFFSET$0, scala.runtime.LazyVals.Evaluating..MODULE$, var2)) {
                            LazyVals.Waiting var5 = (LazyVals.Waiting)this.x$lzy1;
                            .MODULE$.objCAS(this, OFFSET$0, var5, var2);
                            var5.countDown();
                        }

                    }

                    return var8;
                }
            } else {
                if (var1 instanceof LazyVals.LazyValControlState) {
                    if (var1 == scala.runtime.LazyVals.Evaluating..MODULE$) {
                        .MODULE$.objCAS(this, OFFSET$0, var1, new LazyVals.Waiting());
                        continue;
                    }

                    if (var1 instanceof LazyVals.Waiting) {
                        ((LazyVals.Waiting)var1).await();
                        continue;
                    }

                    return null;
                }

                return var1;
            }
        }
    }
}

/// ThreadUnsafeLazyVal.decompiled.java

public class ThreadUnsafeLazyVal {
    private String x$lzy2;
    private boolean xbitmap$1;

    public ThreadUnsafeLazyVal() {
    }

    public String x() {
        if (!this.xbitmap$1) {
            this.x$lzy2 = "test";
            this.xbitmap$1 = true;
        }

        return this.x$lzy2;
    }
}

What do you think about this change?

@igor-vovk
Copy link
Contributor Author

Seems that sources emitted for Scala 3 are still tested against Scala 2.13. This change seems to break this test. Out of curiosity, could you please share, what is the idea behind it? That latest Scala 2.13 should work with Scala 3 sources?

@thesamet
Copy link
Contributor

It's been a while, but I found this statement in https://scalapb.github.io/docs/customizations/:

By default, ScalaPB generates Scala sources that are compatible with both Scala 2 and Scala 3. To generate sources that can be compiled error-free with -source feature on Scala 3 or with -Xsource:3 on Scala 2.13, set scala3_sources to true or pass the scala3_sources generator parameter.

See also #1316 - some users were (are) compiling their project with this flag.

@igor-vovk
Copy link
Contributor Author

I've been thinking about it for some time, and I had interesting observations:

I couldn't find any reasons why someone wants to generate Scala 2 sources when compilation target is set to Scala 3.
I do understand what is the purpose of enabling Scala 3 sources when compiling the library for Scala 2, this makes sense for me: to cross-compile or reuse the same sources for both versions of Scala.

That's why the question pops up: should Scala 3 source generation to be forced when compiling for Scala 3? So the option scala3_sources becomes obsolete for Scala 3 and starts making sense only for Scala 2.13.

This could then unblock optimizations like this, that will be enabled only for Scala 3. Also with time the scala3_sources option can become deprecated when (let's say that hopefully it will happen one day) Scala 2 support will be dropped.

If you see sense in making scala3_sources effective only when compiling for Scala 2, but to be always enabled for 3, I would then be happy to research how to check Scala target version during compilation and will prepare a PR.

@igor-vovk
Copy link
Contributor Author

igor-vovk commented Apr 2, 2025

Closing it, benefits doesn't cost amount of changes that are needed

@igor-vovk igor-vovk closed this Apr 2, 2025
@igor-vovk igor-vovk deleted the threadunsafe-scala3-lazy-vals branch April 2, 2025 19:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

Comments