-
Notifications
You must be signed in to change notification settings - Fork 319
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 support for importing symbols from static library #902
base: main
Are you sure you want to change the base?
Changes from 11 commits
9b891d0
ede2555
ca0abbf
6a334a1
88114fc
77b6f89
e56a4c7
9d07856
e77b239
90043d4
cb9eb40
0ee2687
6f2a5ef
96f2766
f9ccb45
476099a
fcf38e6
a159f31
18b6aba
ecfe984
0db31a7
5e51921
405b85c
c68001b
ab90410
af07004
22ddfc2
f49b699
608b6b8
bc2023c
281a9d3
2e7d474
c9a59e5
37182a7
269cd6d
d42498d
0effd26
28e5ef7
9f53121
674ca5f
488dd58
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -7,10 +7,12 @@ | |||||||||||
#include "llvm/ExecutionEngine/JITEventListener.h" | ||||||||||||
#include "llvm/ExecutionEngine/ObjectCache.h" | ||||||||||||
#include "llvm/IR/Module.h" | ||||||||||||
#include "llvm/Object/Archive.h" | ||||||||||||
#include "llvm/Object/Binary.h" | ||||||||||||
#include "llvm/Object/ObjectFile.h" | ||||||||||||
#include "llvm/Support/Errc.h" | ||||||||||||
#include "llvm/Support/Memory.h" | ||||||||||||
|
||||||||||||
#include "llvm/Support/MemoryBuffer.h" | ||||||||||||
#include <cstdio> | ||||||||||||
#include <memory> | ||||||||||||
|
||||||||||||
|
@@ -167,6 +169,42 @@ LLVMPY_MCJITAddObjectFile(LLVMExecutionEngineRef EE, LLVMObjectFileRef ObjF) { | |||||||||||
{std::move(binary_tuple.first), std::move(binary_tuple.second)}); | ||||||||||||
} | ||||||||||||
|
||||||||||||
API_EXPORT(int) | ||||||||||||
LLVMPY_MCJITAddArchive(LLVMExecutionEngineRef EE, const char *ArchiveName, | ||||||||||||
const char **OutError) { | ||||||||||||
using namespace llvm; | ||||||||||||
using namespace llvm::object; | ||||||||||||
auto engine = unwrap(EE); | ||||||||||||
|
||||||||||||
ErrorOr<std::unique_ptr<MemoryBuffer>> ArBufOrErr = | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe I'm a little lazy (as a C++ programmer) but it seems like we could simplify some of these declarations by using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @gmarkall it's up to you, I find "auto" can obfuscate in a situation like this especially when you need to determine what to unpack such as getting the error message. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @gmarkall to your comment "... which will probably be linked statically", a static archive can only be linked statically. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would also prefer these to consistently be |
||||||||||||
MemoryBuffer::getFile(ArchiveName); | ||||||||||||
|
||||||||||||
std::error_code EC = ArBufOrErr.getError(); | ||||||||||||
if (EC) { | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is more idiomatically written as:
without the creation of |
||||||||||||
*OutError = LLVMPY_CreateString(EC.message().c_str()); | ||||||||||||
return 1; | ||||||||||||
} | ||||||||||||
|
||||||||||||
Expected<std::unique_ptr<object::Archive>> ArchiveOrError = | ||||||||||||
object::Archive::create(ArBufOrErr.get()->getMemBufferRef()); | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The
Suggested change
|
||||||||||||
|
||||||||||||
if (!ArchiveOrError) { | ||||||||||||
auto takeErr = ArchiveOrError.takeError(); | ||||||||||||
std::string archiveErrStr = | ||||||||||||
"Unable to load archive: " + std::string(ArchiveName); | ||||||||||||
*OutError = LLVMPY_CreateString(archiveErrStr.c_str()); | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rather than providing a generic error message, passing along the error provided by LLVM will probably be more helpful to the user. I'm not sure of the cleanest way to do it (I often have some trouble picking and choosing between LLVM C++ and C APIs, for example, but here's one way to do it:
Suggested change
|
||||||||||||
return 1; | ||||||||||||
} | ||||||||||||
|
||||||||||||
std::unique_ptr<MemoryBuffer> &ArBuf = ArBufOrErr.get(); | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These can also be simplified to:
|
||||||||||||
std::unique_ptr<object::Archive> &Ar = ArchiveOrError.get(); | ||||||||||||
object::OwningBinary<object::Archive> owningBinaryArchive(std::move(Ar), | ||||||||||||
std::move(ArBuf)); | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||
engine->addArchive(std::move(owningBinaryArchive)); | ||||||||||||
*OutError = LLVMPY_CreateString(""); | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We don't need to create an empty string if we're returning success.
Suggested change
|
||||||||||||
return 0; | ||||||||||||
} | ||||||||||||
|
||||||||||||
// | ||||||||||||
// Object cache | ||||||||||||
// | ||||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
|
||
int __multiply_accumulate(int a, int b, int c) | ||
{ | ||
return (a * b) + c; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
int __multiply_subtract(int a, int b, int c) | ||
{ | ||
return (a * b) - c; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,12 +10,58 @@ | |
import sys | ||
import unittest | ||
from contextlib import contextmanager | ||
from tempfile import mkstemp | ||
from tempfile import mkstemp, TemporaryDirectory, mkdtemp | ||
from pathlib import Path | ||
|
||
from llvmlite import ir | ||
from llvmlite import binding as llvm | ||
from llvmlite.binding import ffi | ||
from llvmlite.tests import TestCase | ||
import llvmlite.tests | ||
from setuptools._distutils.ccompiler import new_compiler | ||
from setuptools._distutils.sysconfig import customize_compiler | ||
import functools | ||
|
||
|
||
@contextmanager | ||
def _gentmpfile(suffix): | ||
# windows locks the tempfile so use a tempdir + file, see | ||
# https://github.com/numba/numba/issues/3304 | ||
try: | ||
tmpdir = mkdtemp() | ||
ntf = open(os.path.join(tmpdir, "temp%s" % suffix), 'wt') | ||
yield ntf | ||
finally: | ||
try: | ||
ntf.close() | ||
os.remove(ntf) | ||
except Exception: | ||
pass | ||
else: | ||
os.rmdir(tmpdir) | ||
|
||
|
||
@functools.lru_cache(maxsize=1) | ||
def external_compiler_works(): | ||
""" | ||
Returns True if the "external compiler" bound in setuptools._distutil | ||
is present and working, False otherwise. | ||
""" | ||
compiler = new_compiler() | ||
customize_compiler(compiler) | ||
for suffix in ['.c', '.cxx']: | ||
try: | ||
with _gentmpfile(suffix) as ntf: | ||
simple_c = "int main(void) { return 0; }" | ||
ntf.write(simple_c) | ||
ntf.flush() | ||
ntf.close() | ||
# *output_dir* is set to avoid the compiler putting temp files | ||
# in the current directory. | ||
compiler.compile([ntf.name], output_dir=Path(ntf.name).anchor) | ||
except Exception: # likely CompileError or file system issue | ||
return False | ||
return True | ||
|
||
|
||
# arvm7l needs extra ABI symbols to link successfully | ||
|
@@ -1935,6 +1981,60 @@ def test_get_section_content(self): | |
self.assertEqual(s.data().hex(), issue_632_text) | ||
|
||
|
||
class TestArchiveFile(BaseTest): | ||
mod_archive_asm = """ | ||
;ModuleID = <string> | ||
target triple = "{triple}" | ||
|
||
declare i32 @__multiply_accumulate(i32 %0, i32 %1, i32 %2) | ||
declare i32 @__multiply_subtract(i32 %0, i32 %1, i32 %2) | ||
""" | ||
|
||
@unittest.skipUnless(sys.platform.startswith('linux'), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is this Linux specific? It needs to be tested on all platforms. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @apmasell thanks I modified the code to test on all platforms. I am now getting errors on all versions of Windows and a single version of macOS. Do you have any feedback you can offer? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. One thing that stands out to me is that the library is called There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @gmarkall thanks that got me further on Windows, still more Windows errors to debug. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. On Windows you can't delete an open file. I wonder if the file is still open when you attempt to clean up the temp dir, causing the issue you see - perhaps deleting |
||
"Linux-specific test") | ||
def test_add_archive(self): | ||
if not external_compiler_works(): | ||
self.skipTest() | ||
|
||
tmpdir = TemporaryDirectory() | ||
try: | ||
target_machine = self.target_machine(jit=False) | ||
|
||
jit = llvm.create_mcjit_compiler(self.module(self.mod_archive_asm), | ||
target_machine) | ||
|
||
# Create compiler with default options | ||
c = new_compiler() | ||
customize_compiler(c) | ||
workdir = tmpdir.name | ||
srcdir = os.path.dirname(llvmlite.tests.__file__) + "/" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please use |
||
|
||
# Compile into .o files | ||
file1 = srcdir + "a.c" | ||
file2 = srcdir + "b.c" | ||
|
||
objects = c.compile([file1, file2], output_dir=workdir) | ||
library_name = "foo" | ||
|
||
c.create_static_lib(objects, library_name, output_dir=workdir) | ||
|
||
static_library_name = workdir + "/" + "lib" + library_name + ".a" | ||
jit.add_archive(static_library_name) | ||
|
||
mac_func = CFUNCTYPE(c_int, c_int, c_int, c_int)( | ||
jit.get_function_address("__multiply_accumulate")) | ||
|
||
msub_func = CFUNCTYPE(c_int, c_int, c_int, c_int)( | ||
jit.get_function_address("__multiply_subtract")) | ||
|
||
self.assertEqual(mac_func(10, 10, 20), 120) | ||
self.assertEqual(msub_func(10, 10, 20), 80) | ||
finally: | ||
# Manually invoke cleanup to remove tmpdir, objects | ||
# and static library created during test | ||
tmpdir.cleanup() | ||
|
||
|
||
class TestTimePasses(BaseTest): | ||
def test_reporting(self): | ||
mp = llvm.create_module_pass_manager() | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -100,6 +100,7 @@ def run(self): | |
from llvmlite.utils import get_library_files | ||
self.distribution.package_data = { | ||
"llvmlite.binding": get_library_files(), | ||
"llvmlite.tests": ["*.c"] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When you inline the |
||
} | ||
|
||
|
||
|
@@ -110,6 +111,7 @@ def run(self): | |
from llvmlite.utils import get_library_files | ||
self.distribution.package_data = { | ||
"llvmlite.binding": get_library_files(), | ||
"llvmlite.tests": ["*.c"] | ||
} | ||
install.run(self) | ||
|
||
|
@@ -155,6 +157,7 @@ def run(self): | |
build_library_files(self.dry_run) | ||
self.distribution.package_data.update({ | ||
"llvmlite.binding": get_library_files(), | ||
"llvmlite.tests": ["*.c"] | ||
}) | ||
# Run wheel build command | ||
bdist_wheel.run(self) | ||
|
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 does the word "static" mean here? My understanding is that in general, an archive contains unlinked objects, and those objects might eventually take part in a static or dynamic linking process (depending on how they were compiled, of course).
According to the LLVM docs, it looks like adding an archive uses
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.
@gmarkall static here means static archive (i.e libc.a) vs shared library (libc.so). I double checked the code on the LLVM C++ side of things and it is only expecting a static archive.
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.
It looks like somehow my comment got truncated, apologies - what I'm trying to suggest is that "static archive" feels like a weird term - it's an archive file of objects, which will probably be linked statically. So either "archive file" or "static library" seem like appropriate terms (but "archive file" is better here since the method name refers to archives), but "static archive" is a mashup of the two, which I can't find any reference to in common use.