Skip to content
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

Regression with Scala 2.13: cannot compile GraalVM native-image #11634

Closed
plokhotnyuk opened this issue Jul 15, 2019 · 24 comments
Closed

Regression with Scala 2.13: cannot compile GraalVM native-image #11634

plokhotnyuk opened this issue Jul 15, 2019 · 24 comments
Labels

Comments

@plokhotnyuk
Copy link

@plokhotnyuk plokhotnyuk commented Jul 15, 2019

An example which successfully compiles to native binaries from Scala 2.11 & 2.12 uber-jars cannot be compiled from Scala 2.13 uber-jar due some internal changes in Scala collection implementation.

Error output from the native-image tool is:

Error: Unsupported features in 5 methods
Detailed message:
Error: com.oracle.graal.pointsto.constraints.UnresolvedElementException: Discovered unresolved method during parsing: java.lang.Object[].clone(). To diagnose the issue you can use the --allow-incomplete-classpath option. The missing method is then reported at run time when it is accessed the first time.
Trace: 
	at parsing scala.collection.immutable.VectorPointer.gotoPosWritable1(Vector.scala:1243)
Call path from entry point to scala.collection.immutable.VectorPointer.gotoPosWritable1(int, int, int, Object[]): 
	at scala.collection.immutable.VectorPointer.gotoPosWritable1(Vector.scala:1242)
	at scala.collection.immutable.VectorPointer.gotoPosWritable1$(Vector.scala:1241)
	at scala.collection.immutable.VectorBuilder.addAll(Vector.scala:840)
	at scala.collection.immutable.Vector$.from(Vector.scala:840)
	at scala.collection.immutable.Vector$.from(Vector.scala:27)
	at scala.collection.IterableFactory.apply(Factory.scala:104)
	at scala.collection.IterableFactory.apply$(Factory.scala:104)
	at scala.collection.immutable.List$.apply(List.scala:618)
	at scala.collection.SeqFactory$Delegate.apply(Factory.scala:305)
	at com.github.plokhotnyuk.jsoniter_scala.examples.Example01$.main(Example01.scala:18)
	at com.github.plokhotnyuk.jsoniter_scala.examples.Example01.main(Example01.scala)
	at com.oracle.svm.core.JavaMainWrapper.run(JavaMainWrapper.java:147)
	at com.oracle.svm.core.code.IsolateEnterStub.JavaMainWrapper_run_5087f5482cc9a6abc971913ece43acb471d2631b(generated:0)
Error: com.oracle.graal.pointsto.constraints.UnresolvedElementException: Discovered unresolved method during parsing: java.lang.Object[][].clone(). To diagnose the issue you can use the --allow-incomplete-classpath option. The missing method is then reported at run time when it is accessed the first time.
Trace: 
	at parsing scala.collection.immutable.VectorPointer.stabilize(Vector.scala:1197)
Call path from entry point to scala.collection.immutable.VectorPointer.stabilize(int): 
	at scala.collection.immutable.VectorPointer.stabilize(Vector.scala:1163)
	at scala.collection.immutable.VectorPointer.stabilize$(Vector.scala:1163)
	at scala.collection.immutable.VectorBuilder.addAll(Vector.scala:840)
	at scala.collection.immutable.Vector$.from(Vector.scala:840)
	at scala.collection.immutable.Vector$.from(Vector.scala:27)
	at scala.collection.IterableFactory.apply(Factory.scala:104)
	at scala.collection.IterableFactory.apply$(Factory.scala:104)
	at scala.collection.immutable.List$.apply(List.scala:618)
	at scala.collection.SeqFactory$Delegate.apply(Factory.scala:305)
	at com.github.plokhotnyuk.jsoniter_scala.examples.Example01$.main(Example01.scala:18)
	at com.github.plokhotnyuk.jsoniter_scala.examples.Example01.main(Example01.scala)
	at com.oracle.svm.core.JavaMainWrapper.run(JavaMainWrapper.java:147)
	at com.oracle.svm.core.code.IsolateEnterStub.JavaMainWrapper_run_5087f5482cc9a6abc971913ece43acb471d2631b(generated:0)
Error: com.oracle.svm.hosted.substitute.DeletedElementException: Unsupported type java.lang.invoke.MemberName is reachable: All methods from java.lang.invoke should have been replaced during image building.
To diagnose the issue, you can add the option --report-unsupported-elements-at-runtime. The unsupported element is then reported at run time when it is accessed the first time.
Trace: 
	at parsing java.lang.invoke.MethodHandles$Lookup.findVirtual(MethodHandles.java:861)
Call path from entry point to java.lang.invoke.MethodHandles$Lookup.findVirtual(Class, String, MethodType): 
	no path found from entry point to target method

Error: type is not available in this platform: com.oracle.svm.hosted.ClassValueFeature
Trace: 	object java.lang.Class[]
	object java.lang.invoke.MethodType
	object java.lang.invoke.MethodType$ConcurrentWeakInternSet$WeakEntry
	object java.util.concurrent.ConcurrentHashMap$Node
	object java.util.concurrent.ConcurrentHashMap$Node[]
	object java.util.concurrent.ConcurrentHashMap
	object java.lang.invoke.MethodType$ConcurrentWeakInternSet
	method java.lang.invoke.MethodType.makeImpl(Class, Class[], boolean)
Call path from entry point to java.lang.invoke.MethodType.makeImpl(Class, Class[], boolean): 
	no path found from entry point to target method

Error: type is not available in this platform: com.oracle.svm.hosted.substitute.ComputedValueField
Trace: 	object java.lang.invoke.MethodType
	object java.lang.invoke.MethodType$ConcurrentWeakInternSet$WeakEntry
	object java.util.concurrent.ConcurrentHashMap$Node
	object java.util.concurrent.ConcurrentHashMap$Node[]
	object java.util.concurrent.ConcurrentHashMap
	object java.lang.invoke.MethodType$ConcurrentWeakInternSet
	method java.lang.invoke.MethodType.makeImpl(Class, Class[], boolean)
Call path from entry point to java.lang.invoke.MethodType.makeImpl(Class, Class[], boolean): 
	no path found from entry point to target method

Steps to reproduce:

  1. Install or make sure all those things are installed: Sbt, GraalVM CE/EE and its native-image tool
  2. Clone the following repo: https://github.com/plokhotnyuk/jsoniter-scala
  3. Checkout the switch-examples-to-scala-2.13 branch
  4. Run the following commands:
cd jsoniter-scala-examples
sbt clean +assembly
/usr/lib/jvm/graalvm-ce-19/bin/native-image --no-server --no-fallback -H:UnsafeAutomaticSubstitutionsLogLevel=3 -jar target/scala-2.13/jsoniter-scala-examples-assembly-0.1.0-SNAPSHOT.jar
@SethTisue
Copy link
Member

@SethTisue SethTisue commented Jul 15, 2019

is this a bug in Scala?

@plokhotnyuk
Copy link
Author

@plokhotnyuk plokhotnyuk commented Jul 15, 2019

IMHO, yes.

Some of these errors appear due to releaseFence calls, as reporred here. Others also could be prevented in case of opening some kind of community build for tools and libs which going to support AOT compilation mode, starting from scalac and core libraries.

@SethTisue
Copy link
Member

@SethTisue SethTisue commented Jul 15, 2019

are you saying that in order for this to work, we can't use releaseFence at all?

@plokhotnyuk
Copy link
Author

@plokhotnyuk plokhotnyuk commented Jul 15, 2019

There are implementation options to make Unsafe usage recognizable for the Graal compiler.

And for edge cases some cooperation with the GraalVM team will be required for sure.

I have managed to fix compilation error for releaseFence calls by the following commit: plokhotnyuk/jsoniter-scala@e089f06

Others calls to java.lang.Object[].clone() and java.lang.Object[][].clone() are still not substituted, and if they are not called in runtime then possible W/A is to use the --allow-incomplete-classpath option of the GraalVM native-image compiler.

@SethTisue
Copy link
Member

@SethTisue SethTisue commented Oct 2, 2019

Tentatively milestoned for 2.13.2, since it should certainly at least be assessed further.

@adriaanm
Copy link
Contributor

@adriaanm adriaanm commented Oct 3, 2019

Great that you managed to override the call to releaseFence. This (allowing other platforms to customize low level stuff) is why we provide that hook through scala.runtime.Statics. Maybe we can do something similar for the array clone methods?

@sjrd
Copy link
Member

@sjrd sjrd commented Oct 3, 2019

What's the problem with Array.clone()? They're not low-level at all; they're straightforward reflection-free user code:
https://github.com/scala/scala/blob/4ec48ff29755fc01fdde880dbb4548085420c601/src/library/scala/runtime/ScalaRunTime.scala#L94-L105

@adriaanm
Copy link
Contributor

@adriaanm adriaanm commented Oct 3, 2019

I meant java.lang.Object[].clone()

@adriaanm
Copy link
Contributor

@adriaanm adriaanm commented Oct 3, 2019

Anyway, that still seems a little too common for graal not to support natively? Quick google led to a similar, resolved issue: oracle/graal#877

@plokhotnyuk
Copy link
Author

@plokhotnyuk plokhotnyuk commented Oct 3, 2019

@adriaanm checked with GraalVM CE 19.2.0.1 and still got the same error...

now I'm waiting for the next release: 19.2.1 or 19.3.0 to check again...

@adriaanm
Copy link
Contributor

@adriaanm adriaanm commented Oct 3, 2019

Thanks for checking. Looks like the bug I linked to was reported fixed on the Java 11 version, which would be the upcoming 19.3, IUC.

@ryanberckmans
Copy link

@ryanberckmans ryanberckmans commented Nov 20, 2019

GraalVM 19.3.0 fixed this for me.

@adriaanm
Copy link
Contributor

@adriaanm adriaanm commented Nov 20, 2019

Sweet! Thanks for reporting :-)

@dwijnand
Copy link
Member

@dwijnand dwijnand commented Jan 31, 2020

I have managed to fix compilation error for releaseFence calls by the following commit: plokhotnyuk/jsoniter-scala@e089f06

Btw, I've found that it isn't necessary to substitute the scala.runtime.Statics class, for instance with your Target_scala_runtime_Statics class.

It's simply necessary to initialize the underlying scala.runtime.Statics.VM class, by specifying --initialize-at-build-time=scala.runtime.Statics$VM. You can do that either with graalVMNativeImageOptions, if using sbt-native-packager, or with a src/main/resources/META-INF/native-image/org.scala-lang/scala-lang/native-image.properties file containing

Args = --initialize-at-build-time=scala.runtime.Statics$VM

I'm thinking we should consider adding that file ourselves in the library jar, so it's pre-configured for all native-image users.

@ignasi35
Copy link

@ignasi35 ignasi35 commented Jan 31, 2020

I'm thinking we should consider adding that file ourselves in the library jar, so it's pre-configured for all native-image users.

Should the config files for native-image be on a separate artifact instead of polluting the library jar?

@dwijnand
Copy link
Member

@dwijnand dwijnand commented Jan 31, 2020

Yeah, that's the consideration: should Graal native-image support come out the box or rely on some other artifact?

In terms of pollution, creating a separate artifact likely pollutes much more than the 59 bytes that native-image.properties adds. 😃

@plokhotnyuk
Copy link
Author

@plokhotnyuk plokhotnyuk commented Feb 1, 2020

@dwijnand I've tried to build an image with your file in this branch and it doesn't work for me:

[jsoniter-scala-examples-assembly-0.1.0-SNAPSHOT:24882]    classlist:   3,006.85 ms
[jsoniter-scala-examples-assembly-0.1.0-SNAPSHOT:24882]        (cap):     611.87 ms
[jsoniter-scala-examples-assembly-0.1.0-SNAPSHOT:24882]        setup:   1,539.92 ms
warning: unknown anonymous info of class scala.collection.immutable.LazyList$State$Empty$, assuming class is not anonymous. To remove the warning report an issue to the library or language author. The issue is caused by scala.collection.immutable.LazyList$State$Empty$ which is not following the naming convention.
[jsoniter-scala-examples-assembly-0.1.0-SNAPSHOT:24882]   (typeflow):   4,817.32 ms
[jsoniter-scala-examples-assembly-0.1.0-SNAPSHOT:24882]    (objects):   4,055.61 ms
[jsoniter-scala-examples-assembly-0.1.0-SNAPSHOT:24882]   (features):     149.16 ms
[jsoniter-scala-examples-assembly-0.1.0-SNAPSHOT:24882]     analysis:   9,139.04 ms
Error: com.oracle.graal.pointsto.constraints.UnsupportedFeatureException: Invoke with MethodHandle argument could not be reduced to at most a single call: java.lang.invoke.LambdaForm$MH/1637474956.invoke_MT(Object, Object)
To diagnose the issue, you can add the option --report-unsupported-elements-at-runtime. The error is then reported at run time when the invoke is executed.
Detailed message:
Trace: 
	at parsing scala.runtime.Statics.releaseFence(Statics.java:148)
Call path from entry point to scala.runtime.Statics.releaseFence(): 
	at scala.runtime.Statics.releaseFence(Statics.java:148)
	at scala.collection.immutable.Vector$.from(Vector.scala:65)
	at scala.collection.immutable.Vector$.from(Vector.scala:27)
	at scala.collection.IterableFactory.apply(Factory.scala:103)
	at scala.collection.IterableFactory.apply$(Factory.scala:103)
	at scala.collection.immutable.List$.apply(List.scala:611)
	at scala.collection.SeqFactory$Delegate.apply(Factory.scala:304)
	at com.github.plokhotnyuk.jsoniter_scala.examples.Example01$.main(Example01.scala:18)
	at com.github.plokhotnyuk.jsoniter_scala.examples.Example01.main(Example01.scala)
	at com.oracle.svm.core.JavaMainWrapper.runCore(JavaMainWrapper.java:151)
	at com.oracle.svm.core.JavaMainWrapper.run(JavaMainWrapper.java:186)
	at com.oracle.svm.core.code.IsolateEnterStub.JavaMainWrapper_run_5087f5482cc9a6abc971913ece43acb471d2631b(generated:0)

Error: Use -H:+ReportExceptionStackTraces to print stacktrace of underlying exception
Error: Image build request failed with exit status 1

Steps to reproduce:

$ sbt clean +assembly
$ /usr/lib/jvm/graalvm-ce-java8/bin/java -version
openjdk version "1.8.0_232"
OpenJDK Runtime Environment (build 1.8.0_232-b07)
OpenJDK 64-Bit GraalVM CE 19.3.0.2 (build 25.232-b07-jvmci-19.3-b06, mixed mode)
$ /usr/lib/jvm/graalvm-ce-java8/bin/native-image --no-server --no-fallback --allow-incomplete-classpath -jar target/scala-2.13/jsoniter-scala-examples-assembly-0.1.0-SNAPSHOT.jar

Have I missed something?

@dwijnand
Copy link
Member

@dwijnand dwijnand commented Feb 1, 2020

@plokhotnyuk I'm not sure, it worked for me:

$ native-image --no-server --no-fallback --allow-incomplete-classpath -jar target/scala-2.13/jsoniter-scala-examples-assembly-0.1.0-SNAPSHOT.jar
[jsoniter-scala-examples-assembly-0.1.0-SNAPSHOT:36803]    classlist:   2,055.92 ms
[jsoniter-scala-examples-assembly-0.1.0-SNAPSHOT:36803]        (cap):   1,925.99 ms
[jsoniter-scala-examples-assembly-0.1.0-SNAPSHOT:36803]        setup:   2,948.79 ms
[jsoniter-scala-examples-assembly-0.1.0-SNAPSHOT:36803]   (typeflow):   5,549.69 ms
[jsoniter-scala-examples-assembly-0.1.0-SNAPSHOT:36803]    (objects):   5,066.50 ms
[jsoniter-scala-examples-assembly-0.1.0-SNAPSHOT:36803]   (features):     344.68 ms
[jsoniter-scala-examples-assembly-0.1.0-SNAPSHOT:36803]     analysis:  11,164.62 ms
[jsoniter-scala-examples-assembly-0.1.0-SNAPSHOT:36803]     (clinit):     427.08 ms
[jsoniter-scala-examples-assembly-0.1.0-SNAPSHOT:36803]     universe:     712.27 ms
[jsoniter-scala-examples-assembly-0.1.0-SNAPSHOT:36803]      (parse):     537.21 ms
[jsoniter-scala-examples-assembly-0.1.0-SNAPSHOT:36803]     (inline):   1,287.31 ms
[jsoniter-scala-examples-assembly-0.1.0-SNAPSHOT:36803]    (compile):   5,211.85 ms
[jsoniter-scala-examples-assembly-0.1.0-SNAPSHOT:36803]      compile:   7,470.92 ms
[jsoniter-scala-examples-assembly-0.1.0-SNAPSHOT:36803]        image:     795.07 ms
[jsoniter-scala-examples-assembly-0.1.0-SNAPSHOT:36803]        write:     206.90 ms
[jsoniter-scala-examples-assembly-0.1.0-SNAPSHOT:36803]      [total]:  25,544.13 ms
$ ./jsoniter-scala-examples-assembly-0.1.0-SNAPSHOT
User(John,List(Device(1,HTC One X)))
{"name":"John","devices":[{"id":2,"model":"iPhone X"}]}

I'm running graalvm-ce-java11-19.3.1, maybe it's because of that?

$ java -version
openjdk version "11.0.6" 2020-01-14
OpenJDK Runtime Environment GraalVM CE 19.3.1 (build 11.0.6+9-jvmci-19.3-b07)
OpenJDK 64-Bit Server VM GraalVM CE 19.3.1 (build 11.0.6+9-jvmci-19.3-b07, mixed mode, sharing)

@plokhotnyuk
Copy link
Author

@plokhotnyuk plokhotnyuk commented Feb 1, 2020

@dwijnand yeap, your native-image.properties works fine with graalvm-ce-java11-19.3.1 but the substitution is still required for graalvm-ce-java8-19.3.1.

@olafurpg
Copy link
Member

@olafurpg olafurpg commented Aug 7, 2020

In case anyone has hit on a similar issue with GraalVM 20.1.0, Scala 2.12.12 and scala.collection.immutable.VM, the following changes fixed the problem for me.

  // build.sbt
  lazy val myProject
    .in(file("my-project"))
    .settings(
+     libraryDependencies += "org.graalvm.nativeimage" % "svm" % "20.1.0" % "compile-internal",
    )
    .enablePlugins(GraalVMNativeImagePlugin)
+  // my-project/src/main/scala/myproject/internal/substites/Target_scala_collection_immutable_VM.java
+  package myproject.internal.substitutes;
+  
+  import com.oracle.svm.core.annotate.Substitute;
+  import com.oracle.svm.core.annotate.TargetClass;
+  
+  @TargetClass(className = "scala.collection.immutable.VM")
+  final class Target_scala_runtime_Statics {
+  
+      @Substitute
+      public static void releaseFence() {
+          UnsafeUtils.UNSAFE.storeFence();
+      }
+  }
+  // my-project/src/main/scala/myproject/internal/substites/UnsafeUtils.java
+  package myproject.internal.substitutes;+  
+  import java.lang.reflect.Field;+  
+  class UnsafeUtils {
+      static final sun.misc.Unsafe UNSAFE;+  
+      static {
+          try {
+              Field field = sun.misc.Unsafe.class.getDeclaredField("theUnsafe");
+              field.setAccessible(true);
+              UNSAFE = (sun.misc.Unsafe) field.get(null);
+          } catch (Throwable ex) {
+              throw new ExceptionInInitializerError(ex);
+          }
+      }
+  }

Big thanks to @plokhotnyuk for finding this workaround, which I found in plokhotnyuk/jsoniter-scala@e089f06

@olafurpg
Copy link
Member

@olafurpg olafurpg commented Aug 29, 2020

I published the workaround above as an independent library:

// Add this to build.sbt for the native-image project
libraryDependencies += "org.scalameta" %% "svm-subs" % "20.1.0" % "compile-internal"

The jar is 4kb and has no external dependencies besides scala-library. You can download the jar and add it manually to the classpath if you prefer https://repo1.maven.org/maven2/org/scalameta/svm-subs_2.13/19.3.2/svm-subs_2.13-19.3.2.jar

This workaround is only needed for 2.12.12+ and 2.13.3+

@phdoerfler
Copy link

@phdoerfler phdoerfler commented Aug 29, 2020

@olafurpg Very neat, thanks!

@olafurpg
Copy link
Member

@olafurpg olafurpg commented Aug 29, 2020

In case anyone is interested, I published a new plugin called sbt-native-image (https://github.com/scalameta/sbt-native-image) that automatically adds the correct svm-subs dependency and provides other nice features like automatic GraalVM installation

@huntc
Copy link

@huntc huntc commented Feb 11, 2021

Sweet! Thanks for reporting :-)

Hey everyone - are we sure this issue should be closed? My first experience with GraalVM and Scala hit upon it very quickly (using GraalVM 21.0.0). I was able to circumvent issues by using the sbt-native-image plugin, but I'd say that's working around the issue. Thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
10 participants