-
Notifications
You must be signed in to change notification settings - Fork 326
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
Conversation
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.
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. |
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 |
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. |
Done. LMK if there's anything that needs more clarification. |
This looks better, thank you. |
/azp run |
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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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!
llvmlite/binding/ffi.py
Outdated
# 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. |
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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.
llvmlite/binding/ffi.py
Outdated
contextlib.nullcontext(None)): | ||
try: | ||
with lib_context as lib_path: | ||
lib = ctypes.CDLL(lib_path and str(lib_path)) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
@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. |
… to a temp file, and delete it after loading.
@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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
@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? |
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. |
@folded it's been a while since I last heard from you, how do you want to proceed with this one? |
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! |
I've split this into 3 PRs, starting with folded:refactor-native-library-loading |
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.