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

Support loading llvmlite from the main executable. #829

Closed
wants to merge 13 commits into from

Conversation

folded
Copy link
Contributor

@folded folded commented Mar 25, 2022

Change library loading code to also check for the presence of llvmlite
statically linked into the main executable if the dynamic library fails
to load. This allows llvmlite to be used in hermetic and embedded
build cases.

Check that the version function can be dynamically resolved before
declaring success.

Exit the resource context manager correctly so that any extracted
files are unlinked correctly.

On failure, raise OSError with the first error chained.

Change library loading code to also check for the presence of a
statically linked version of llvmlite if the dynamic library fails
to load. This allows llvmlite to be used in hermetic and embedded
build cases.

Check that the version function can be dynamically resolved before
declaring success.

Exit the resource context manager correctly so that any extracted
files are unlinked correctly.

On failure, raise OSError with the first error chained.
@esc
Copy link
Member

esc commented Mar 27, 2022

Change library loading code to also check for the presence of a statically linked version of llvmlite if the dynamic library fails to load. This allows llvmlite to be used in hermetic and embedded build cases.

Thank you for adding this. There are a few ways to build llvmlite. By default the Numba team will statically link LLVM in both wheels and conda packages. See also:

https://llvmlite.readthedocs.io/en/latest/admin-guide/install.html#pre-built-binaries

Hence I am a little confused as to what benefit this PR brings. May I kindly request, that you describe in slightly more detail: what problems did you encounter and how does this help with the build? Thank you.

@folded
Copy link
Contributor Author

folded commented Mar 28, 2022

I modified the PR description slightly to make the intention hopefully more clear.

This CL addresses the case where you link llvmlite (and llvm) directly into the main executable. You might do this, for example, if you're using an embedded python runtime and don't want to distribute lots of additional shared libraries. It's admittedly a niche use-case (although an important one for us).

I'm happy to maintain this patch downstream if you feel like it's not sufficiently general,. but I do think there are some other small quality of life improvements in this code and it would be pretty easy to just drop the contextlib.nullcontext(None) that supports looking up the symbols in the main executable.

@esc
Copy link
Member

esc commented Mar 28, 2022

I modified the PR description slightly to make the intention hopefully more clear.

This CL addresses the case where you link llvmlite (and llvm) directly into the main executable. You might do this, for example, if you're using an embedded python runtime and don't want to distribute lots of additional shared libraries. It's admittedly a niche use-case (although an important one for us).

I'm happy to maintain this patch downstream if you feel like it's not sufficiently general,. but I do think there are some other small quality of life improvements in this code and it would be pretty easy to just drop the contextlib.nullcontext(None) that supports looking up the symbols in the main executable.

OK, if this patch has value for you I don't mind considering it. It's just important that this does not impact what is currently working. Also, would you be open to adding more comments to the source code? There are a few lines where it isn't immediately obvious to me what is going on and they could IMHO do with some additional comments.

@folded
Copy link
Contributor Author

folded commented Mar 28, 2022

Done. LMK if there's anything that needs more clarification.

@esc
Copy link
Member

esc commented Mar 29, 2022

Done. LMK if there's anything that needs more clarification.

This looks better, thank you.

@esc
Copy link
Member

esc commented Mar 29, 2022

/azp run

@azure-pipelines
Copy link
Contributor

Azure Pipelines successfully started running 1 pipeline(s).

try:
_lib_handle = importlib.resources.path(pkgname, _lib_name)
lib = ctypes.CDLL(str(_lib_handle.__enter__()))
# on windows file handles to the dll file remain open after
Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure about this change yet. The comment seems to indicate that the context manager can not be used on Windows.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Isn't it ok to delete a file that has open file handles on Windows? This is definitely the way you would want this to behave on posix systems, as it's totally fine to keep a file descriptor open and unlink the file. This has the advantage that the filesystem is cleaned up.

I'm investigating this to check the windows behaviour.

Copy link
Member

Choose a reason for hiding this comment

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

@folded thank you for looking into this, if you can work out if or if not this comment is/was relevant and can explain it too, that would be most helpful!

# 1) In a shared library available as a resource of the llvmlite package.
# This may involve unpacking the shared library from an archive.
# 2) Linked directly into the main binary. Symbols may be resolved from the
# main binary by passing None as the argument to ctypes.CDLL.
Copy link
Member

Choose a reason for hiding this comment

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

Under what circumstances do we pass None?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

None is the second value in the iteration, as produced when the context contextlib.nullcontext(None) is entered.

contextlib.nullcontext(None)):
try:
with lib_context as lib_path:
lib = ctypes.CDLL(lib_path and str(lib_path))
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 intent of using and here. Is this something special about the type of `lib_context?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

lib_path is either a pathlib.Path or None, lib_path and str(lib_path) evaluates to None if lib_path is None, or converts it to a string otherwise, which copes with the fact that ctypes.CDLL (on windows, at least) requires a string path, not a pathlib.Path

@esc
Copy link
Member

esc commented Mar 29, 2022

@folded thank you again for the PR. CI seems green now and I have given the patch a more thorough review. There are a few questions left to address. Oh, and also: do you have any thoughts on how I could test this patch to verify it does indeed work in the kinds of environments you describe.

@esc
Copy link
Member

esc commented Jun 1, 2022

@folded thank you again for submitting this. I will now continue to review this.

try:
with lib_context as lib_path:
if os.name == 'nt' and not lib_path:
# The Windows implementation of ctypes.CDLL does not
Copy link
Member

Choose a reason for hiding this comment

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

Question here: does this mean that Windows doesn't support the kind of static linking?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Unfortunately yes. It turns out that although Windows does have the ability to get a handle to the main binary, ctypes failed to implement that in a way that was consistent with MacOS/*nix. The only two solutions would be to send a PR to cpython to fix it or to use ctypes to dig into the win32 API and do it manually, neither of which I'm terribly keen on doing.

@esc
Copy link
Member

esc commented Jun 1, 2022

@folded do you, by any chance, have some simple instructions for creating they type of binary that has llvmlite statically linked so that I can test that this works?

@folded
Copy link
Contributor Author

folded commented Jun 1, 2022

I haven't had time to look at this for a while, but I think it might be sensible to split it into logically separate components. Dealing with all the separate issues that came up had quite a few follow-on effects.

Would that be helpful?

@esc
Copy link
Member

esc commented Jun 1, 2022

I haven't had time to look at this for a while, but I think it might be sensible to split it into logically separate components. Dealing with all the separate issues that came up had quite a few follow-on effects.

Would that be helpful?

Yes, that sounds useful.

@esc
Copy link
Member

esc commented Aug 11, 2022

@folded it's been a while since I last heard from you, how do you want to proceed with this one?

@folded
Copy link
Contributor Author

folded commented Aug 22, 2022

My work for this week is to do the internal upgrade to 0.39.0, and as a result I'll be going through these patches again and cleaning them up. I'll get back to you soon.

@esc
Copy link
Member

esc commented Aug 22, 2022

My work for this week is to do the internal upgrade to 0.39.0, and as a result I'll be going through these patches again and cleaning them up. I'll get back to you soon.

Thank you for the update!

@folded
Copy link
Contributor Author

folded commented Aug 22, 2022

I've split this into 3 PRs, starting with folded:refactor-native-library-loading

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Archived in project
Development

Successfully merging this pull request may close these issues.

2 participants