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 support for importing symbols from static library #902

Open
wants to merge 41 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
9b891d0
Add test files to be compiled for add_archive test case
testhound Feb 3, 2023
ede2555
Add support for add_archive, test case and documentation
testhound Feb 3, 2023
ca0abbf
Fix flake8 errors
testhound Feb 3, 2023
6a334a1
Fix test_add_archive to create object files and static library in a t…
testhound Feb 6, 2023
88114fc
Fix formatting according to clang-format
testhound Feb 6, 2023
77b6f89
Merge branch 'main' into testhound/addArchive
testhound Feb 10, 2023
e56a4c7
Migrate from using deprecated distutils to setuptools
testhound Feb 10, 2023
9d07856
Add code to test if calls to external compiler works and potentially …
testhound Feb 10, 2023
e77b239
Fix flake8 errors
testhound Feb 10, 2023
90043d4
Add C test files
testhound Feb 14, 2023
cb9eb40
Add *.c files to all uses of pacakge data
testhound Feb 14, 2023
0ee2687
Incorporate upstream comments
testhound Feb 14, 2023
6f2a5ef
Merge branch 'main' into testhound/addArchive
testhound Apr 18, 2023
96f2766
Write out .c .files instead of storing them in the test directory
testhound Apr 21, 2023
f9ccb45
Add use of auto varibles and simplify some code sequences
testhound Apr 21, 2023
476099a
Remove test .c files and write them to temp directory instead, use os…
testhound Apr 21, 2023
fcf38e6
Remove .c files from setup as they have are no longer needed for testing
testhound Apr 21, 2023
a159f31
Format with clang-format
testhound Apr 21, 2023
18b6aba
Revert use of 'auto' as it causes an error
testhound Apr 21, 2023
ecfe984
Remove final use of .c test files
testhound Apr 21, 2023
0db31a7
Allow test to run on Windows
testhound Apr 24, 2023
5e51921
Add debug print statement
testhound Apr 24, 2023
405b85c
Use CCompiler.library_filename to get platform specific name
testhound Apr 25, 2023
c68001b
Try instantiating unix compiler regardless of platform
testhound Apr 25, 2023
ab90410
Make sure function names are not mangled and use C calling convention
testhound Apr 25, 2023
af07004
Fix flake8 issue
testhound Apr 25, 2023
22ddfc2
Test that the jit found the function address
testhound Apr 26, 2023
f49b699
Use try .. finally block to clean up temp directory
testhound Apr 27, 2023
608b6b8
Delete jit object in case it is keeping archive open
testhound May 4, 2023
bc2023c
Delete all files created during test
testhound May 4, 2023
281a9d3
Don't use Path
testhound May 4, 2023
2e7d474
Return and use tmpdir
testhound May 7, 2023
c9a59e5
Close file to resolve Windows open file issue
testhound May 8, 2023
37182a7
Remove __ from from function names and document why the testcase does…
testhound May 10, 2023
269cd6d
Skip test on macOS and Python 3.9 due to CI issue
testhound May 11, 2023
d42498d
Add string reason to skipTest
testhound May 11, 2023
0effd26
Don't skip Windows to expose error
testhound May 24, 2023
28e5ef7
Add Windows specific compilation
testhound May 30, 2023
9f53121
Convert test case to a .c file
testhound Jun 2, 2023
674ca5f
Set debug=True
testhound Jun 2, 2023
488dd58
Fix flake8 and remove unused funtion
testhound Jun 2, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions docs/source/user-guide/binding/execution-engine.rst
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,13 @@ The ExecutionEngine class
or a object file instance. Object file instance is not usable after this
call.

* .. method:: add_archive(archive_file)

Add the symbols from the specified static library file to the execution
engine. It is a fatal error in LLVM if the *archive_file* does not exist.

* *archive_file* str: a path to the static library file

* .. method:: set_object_cache(notify_func=None, getbuffer_func=None)

Set the object cache callbacks for this engine.
Expand Down
33 changes: 32 additions & 1 deletion ffi/executionengine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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>

Expand Down Expand Up @@ -167,6 +169,35 @@ 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);

auto ArBufOrErr = MemoryBuffer::getFile(ArchiveName);

if (!ArBufOrErr) {
std::error_code EC = ArBufOrErr.getError();
*OutError = LLVMPY_CreateString(EC.message().c_str());
return 1;
}

auto ArchiveOrError = Archive::create(ArBufOrErr.get()->getMemBufferRef());

if (!ArchiveOrError) {
LLVMErrorRef errorRef = wrap(ArchiveOrError.takeError());
*OutError = LLVMPY_CreateString(LLVMGetErrorMessage(errorRef));
return 1;
}

OwningBinary<object::Archive> owningBinaryArchive(
std::move(*ArchiveOrError), std::move(*ArBufOrErr));
engine->addArchive(std::move(owningBinaryArchive));
return 0;
}

//
// Object cache
//
Expand Down
20 changes: 20 additions & 0 deletions llvmlite/binding/executionengine.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,18 @@ def add_object_file(self, obj_file):

ffi.lib.LLVMPY_MCJITAddObjectFile(self, obj_file)

def add_archive(self, archive_file):
"""
Add archive file to the jit. archive_file is a string
representing the file system path to the archive file.
"""

with ffi.OutputString() as outerr:
res = ffi.lib.LLVMPY_MCJITAddArchive(self, archive_file.encode(),
outerr)
if res:
raise RuntimeError(str(outerr))

def set_object_cache(self, notify_func=None, getbuffer_func=None):
"""
Set the object cache "notifyObjectCompiled" and "getBuffer"
Expand Down Expand Up @@ -286,6 +298,14 @@ def _dispose(self):
ffi.LLVMObjectFileRef
]

ffi.lib.LLVMPY_MCJITAddArchive.argtypes = [
ffi.LLVMExecutionEngineRef,
c_char_p,
POINTER(c_char_p)
]

ffi.lib.LLVMPY_MCJITAddArchive.restype = c_int


class _ObjectCacheData(Structure):
_fields_ = [
Expand Down
160 changes: 159 additions & 1 deletion llvmlite/tests/test_binding.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,81 @@
import os
import platform
import re
import shutil
import subprocess
import sys
import unittest
from contextlib import contextmanager
from tempfile import mkstemp
from tempfile import mkstemp, 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
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)


@contextmanager
def _gentmpfiles2(suffix):
try:
tmpdir = mkdtemp()
ntf1 = open(os.path.join(tmpdir, "temp1%s" % suffix), 'wt')
ntf2 = open(os.path.join(tmpdir, "temp2%s" % suffix), 'wt')
yield ntf1, ntf2, tmpdir
finally:
try:
ntf1.close()
ntf2.close()
os.remove(ntf1.name)
os.remove(ntf2.name)
except Exception:
pass
else:
shutil.rmtree(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
Expand Down Expand Up @@ -1941,6 +2006,99 @@ def test_get_section_content(self):
self.assertEqual(s.data().hex(), issue_632_text)


# Skip this test on Windows due to the fact that
# the /GL cross-module optimization has the side-effect
# of removing external symbols in this test case.
Comment on lines +2009 to +2011
Copy link
Member

Choose a reason for hiding this comment

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

Maybe adding a DEF file can fix this; see https://devblogs.microsoft.com/oldnewthing/20140321-00/?p=1433

Copy link
Author

Choose a reason for hiding this comment

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

@sklam I have explored using a DEF file, however DEF files can only be provided to the "LINK" command for creating a dynamic library not the 'LIB" command that creates a static library. Additionally I have not found any way to modify the command line options to the compiler via the compile options. If I could modify the command line I would simply remove the "/GL" option.

#
# The /GL option is used when compiling and 'debug=0' (default)
# is provided on the the c.compile method invocation.
#
# Setting 'debug=1' preserves the debug information but
# the jit.add_archive command fails because the llvm
# libraries do not recognize the library object file format
# and generate the error:
#
# LLVM ERROR: Incompatible object format
#
# Although the llvm libraries does not recognize the library
# format, it is recognized by the Windows dumpbin command.
# so this appears to be a bug in the llvm libraries
#@unittest.skipIf(sys.platform.startswith('win32'),
# "Windows and LLVM static library issues")
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)
"""

def test_add_archive(self):
# macOS and Python 3.9 on the Anaconda CI has been configured
# incorrectly and has an issue with ocating llvm-ar
# which is used to create the static library.
if sys.platform.startswith('darwin') and sys.version_info.major == 3 \
and sys.version_info.minor == 9:
self.skipTest("Test fails in bad macOS/python 3.9 environment")

if not external_compiler_works():
self.skipTest("External compiler does not work")

with _gentmpfiles2(".c") as (f1, f2, temp_dir):
target_machine = self.target_machine(jit=False)

jit = llvm.create_mcjit_compiler(
self.module(self.mod_archive_asm),
target_machine
)

c = new_compiler()
customize_compiler(c)

code1 = "int multiply_accumulate(int a, int b, int c) " \
"{return (a * b) + c;}"
code2 = "int multiply_subtract(int a, int b, int c) " \
"{return (a * b) - c;}"

f1.write(code1)
f2.write(code2)
f1.flush()
f2.flush()
f1.close()
f2.close()

objects = c.compile([f1.name, f2.name], output_dir=temp_dir,
debug=True)

library_name = "foo"
c.create_static_lib(objects, library_name, output_dir=temp_dir)

platform_library_name = c.library_filename(library_name)
static_library_name = os.path.join(temp_dir,
platform_library_name)

jit.add_archive(static_library_name)

mac_func_addr = jit.get_function_address('multiply_accumulate')
self.assertTrue(mac_func_addr)

mac_func = CFUNCTYPE(c_int, c_int, c_int, c_int)(mac_func_addr)

msub_func_addr = jit.get_function_address('multiply_subtract')
self.assertTrue(msub_func_addr)

msub_func = CFUNCTYPE(c_int, c_int, c_int, c_int)(msub_func_addr)

self.assertEqual(mac_func(10, 10, 20), 120)
self.assertEqual(msub_func(10, 10, 20), 80)

del jit
os.remove(static_library_name)
for file in objects:
os.remove(Path(file))


class TestTimePasses(BaseTest):
def test_reporting(self):
mp = llvm.create_module_pass_manager()
Expand Down