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

Add Standard.Tableau to native-image #12204

Open
wants to merge 14 commits into
base: develop
Choose a base branch
from

Conversation

hubertp
Copy link
Collaborator

@hubertp hubertp commented Jan 31, 2025

Pull Request Description

Closes #12194.

Checklist

Please ensure that the following checklist has been satisfied before submitting the PR:

screencast is preferred.

  • All code follows the
    Scala,
    Java,
    TypeScript,
    and
    Rust
    style guides. In case you are using a language not listed above, follow the Rust style guide.

```
[warn] there's a key that's not used by any other settings/tasks:
[warn]
[warn] * runtime-compiler-dump / javaModuleName
[warn]   +- /home/hubert/tmp/enso/build.sbt:3425
[warn]
[warn] note: a setting might still be used by a command; to exclude a key from this `lintUnused` check
[warn] either append it to `Global / excludeLintKeys` or call .withRank(KeyRanks.Invisible) on the key
```
@JaroslavTulach
Copy link
Member

70MB in resources!? Are we distributing another .so or .dll library?

Copy link
Member

@JaroslavTulach JaroslavTulach left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tableauhperapi library should be extracted from the JAR into polyglot/lib directory.

This change extracts `Tableau`'s Hyper shared library from the jar.
Unlike `Standard.Image`, had to provide a custom classloader that
manages to find requested library - HyperAPI used a custom JNA logic for
that.
return libPath;
}
}
throw new UnsupportedOperationException("unable to find library " + libname);
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: I'm not entirely sure this is the right way but RuntimeException didn't feel right either.
Maybe a PanicException or a simple null?

Open for suggestions.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method is called only from HyperReader. I suggested implementing finding native library this way, because HyperReader is in std-bits and, therefore, has no (direct) access to EnsoContext and its NativeLibraryFinder. I don't have a strong opinion about the exception. But I suggest you append additional test for it inside NativeLibraryFinderTest.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we are exposing this for the Enso code, then:

  • ctx.nothing() might be a reasonable value.
  • PanicException is a good alternative as well
  • UnsupportedMessageException is a poor's man solution as well

Certainly not java.lang.UnsupportedOperationException - that would mean crash of the interpreter!

@hubertp hubertp added the CI: No changelog needed Do not require a changelog entry for this PR. label Feb 3, 2025
@hubertp
Copy link
Collaborator Author

hubertp commented Feb 3, 2025

70MB in resources!? Are we distributing another .so or .dll library?

Yes.

return libPath;
}
}
throw new UnsupportedOperationException("unable to find library " + libname);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method is called only from HyperReader. I suggested implementing finding native library this way, because HyperReader is in std-bits and, therefore, has no (direct) access to EnsoContext and its NativeLibraryFinder. I don't have a strong opinion about the exception. But I suggest you append additional test for it inside NativeLibraryFinderTest.

@enso-bot
Copy link

enso-bot bot commented Feb 3, 2025

Hubert Plociniczak reports a new STANDUP for the provided date (2025-01-31):

Progress: Created PR with Standard.Tableau integration. Looking into false positive log failures reported when closing project. It should be finished by 2025-02-01.

Next Day: Next day I will be working on the #12204 task. Continue investigating the issue.

}

@Override
public InputStream getResourceAsStream(String name) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the caller stack trace? What the tableu code is trying to achieve? Why does it need InputStream? Can we prevent it to ask for input stream?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can't prevent it. It's part of hyper initialization.

Copy link
Member

@JaroslavTulach JaroslavTulach Feb 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My investigation suggests there is a way to prevent the call and skip the "extraction of the JNI library" completely.

int dotIdx = name.indexOf(".");
if (libIdx != -1 && dotIdx != -1) {
var libName = name.substring(libIdx + 3, dotIdx);
var bindings = Context.getCurrent().getBindings("enso");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Calling org.graalvm.polyglot from ClassLoader - that's wild.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only other option how to get reference to the NativeLibraryFinder functionality is to move the NativeLibraryFinder out of org.enso.runtime into some module that is shared with std-bits. The approach via Context.getCurrent().getBindings("enso") is what I have suggested to @hubertp as the "best" solution that I can think of. I am open to other suggestions of course.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Care to expand on what's so wild about it?

Copy link
Member

@JaroslavTulach JaroslavTulach Feb 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's so wild about it?

ClassLoader is a low level JVM mechanism. Calling from such a level into a Truffle language implemented on top of Truffle framework written in Java is just wild. It's like calling from a Linux kernel driver into X11 paint method and waiting for a result.

Doing something like this is a request to get a deadlock in internal JVM classloading locks.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#yolo

Jokes aside, it would be best to suggest some improvements upstream as those jars are not integration-friendly. But that can take a lot of time and has unknown chances of success.

Copy link
Member

@JaroslavTulach JaroslavTulach Feb 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To avoid this "kernel to graphics" dependency, I'd like to point your attention to NetBeans translate support:

  • it provides a way to instruct the classloader to do something else
  • by having a special file that provides a mapping
  • all NetBeans ClassLoaders support this translating

We could let the Enso tableau.jar provide such a file to instruct the HostClassLoader to return the FileInputStream from getResourceAsStream when a particular resource cannot be found.

The difference to current state is that we don't need to call into polyglot context from a ClassLoading code.

@@ -88,7 +91,7 @@ private static HyperProcess getProcess() throws IOException {
if (process == null || !process.isOpen()) {
var contextClassLoader = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(HyperReader.class.getClassLoader());
Thread.currentThread().setContextClassLoader(new TableauClassLoader());
Copy link
Member

@JaroslavTulach JaroslavTulach Feb 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There seems to be:

$ javap -cp built-distribution/enso-engine-*/enso-*/lib/Standard/Tableau/0.0.0-dev/polyglot/java/tableauhyperapi.jar com.tableau.hyperapi.impl.SharedLibraryProvider
Compiled from "SharedLibraryProvider.java"
public interface com.tableau.hyperapi.impl.SharedLibraryProvider {
  public abstract java.lang.String getSharedLibraryBaseName();
}

cannot it be used for loading the JNI library without these classloading tricks? Looks like the code checks jna.nounpack property:

  private static void registerJNA();
    Code:
       0: ldc           #52                 // String jna.nounpack
       2: invokestatic  #53                 // Method java/lang/Boolean.getBoolean:(Ljava/lang/String;)Z
       5: ifeq          17
       8: invokestatic  #54                 // Method getLibraryBaseName:()Ljava/lang/String;
      11: invokestatic  #55                 // Method com/sun/jna/Native.register:(Ljava/lang/String;)V

With a proper value provided, the code should be able to skip this extracting step completely.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I saw that. Sadly I couldn't make it to work that way; JNA gets in the way.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, thanks.

@JaroslavTulach JaroslavTulach self-requested a review February 4, 2025 13:19
Copy link
Member

@JaroslavTulach JaroslavTulach left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me approve in the name of pragmatism. Since my previous review comment this PR managed to:

  • make the distribution smaller:
  • avoid duplicating the library between JAR and native executable

Only the

  • makes the startup faster (no extraction of a resource on start)

remains open. That's probably good enough for now - especially when it is unlikely many people use Tableau to begin with.

I still don't like the "kernel to graphics call", but I can live with it. Moreover I am going to make another proposal to solve it and stay in the kernel space.

@hubertp hubertp added the CI: Clean build required CI runners will be cleaned before and after this PR is built. label Feb 4, 2025
Seems to be causing problems on CI.
Extraction logic had to be extended to allow for:
- different prefixes (e.g, `linux-86-64` instead of `linux-86_64)
- classes being present on the prefix should be included in the thin
  jar; we cannot assume that extraction prefix is unique
@enso-bot
Copy link

enso-bot bot commented Feb 5, 2025

Hubert Plociniczak reports a new STANDUP for the provided date (2025-02-03):

Progress: Addressing PR review - extracting native libs from Tableau's jars. Most problems come from discrepancies between different OS/Architecture. It should be finished by 2025-02-05.

Next Day: Next day I will be working on the #12204 task. Continue investigating the issue.

Alternative approach to deal with Windows/Mac failures on CI where
`jnidispatch` native library somehow could not be identified. Linux
worked just fine with the previous approach.

This change enhances custom classloader to identify the desired library.
That way we can provide the path to it, as specified in the
implementation of JNA.
@enso-bot
Copy link

enso-bot bot commented Feb 5, 2025

Hubert Plociniczak reports a new STANDUP for the provided date (2025-02-04):

Progress: Continued fighting with native library loading - Tableau relies on JNA which brings its own set of problems. Solved for Linux but Windows and Mac are problematic. Dealing with some CI failures. It should be finished by 2025-02-05.

Next Day: Next day I will be working on the #12204 task. Continue investigating the issue.

@enso-bot
Copy link

enso-bot bot commented Feb 6, 2025

Hubert Plociniczak reports a new STANDUP for yesterday (2025-02-05):

Progress: Previous approach worked on linux because it was loading the library from jna-wrapper, failed to identify the reasons for failures for mac or windows due to setup issues. Attempting to hardcode jna boot dir path to fix the problem. Reviewing builtins PR. It should be finished by 2025-02-05.

Next Day: Next day I will be working on the #12204 task. Continue investigating the issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CI: Clean build required CI runners will be cleaned before and after this PR is built. CI: No changelog needed Do not require a changelog entry for this PR.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Include Standard.Tableau in native image
4 participants