From 69a78d0a5401ef555ede756dd85b17bcf23c9fcf Mon Sep 17 00:00:00 2001 From: Siu Kwan Lam Date: Mon, 30 Sep 2013 16:24:17 -0500 Subject: [PATCH] Test ABI for structures and reorganize tests a bit. --- llvm/__init__.py | 2 +- llvm/tests/__init__.py | 32 +++ llvm/{ => tests}/test_llvmpy.py | 23 +-- llvm/tests/test_struct_args.py | 356 ++++++++++++++++++++++++++++++++ setup.py | 3 +- 5 files changed, 392 insertions(+), 24 deletions(-) create mode 100644 llvm/tests/__init__.py rename llvm/{ => tests}/test_llvmpy.py (98%) create mode 100644 llvm/tests/test_struct_args.py diff --git a/llvm/__init__.py b/llvm/__init__.py index 2bb3c02..dc9852f 100644 --- a/llvm/__init__.py +++ b/llvm/__init__.py @@ -37,7 +37,7 @@ def test(verbosity=1): Run self-test, and return the number of failures + errors """ - from llvm.test_llvmpy import run + from llvm.tests import run result = run(verbosity=verbosity) diff --git a/llvm/tests/__init__.py b/llvm/tests/__init__.py new file mode 100644 index 0000000..e6967fe --- /dev/null +++ b/llvm/tests/__init__.py @@ -0,0 +1,32 @@ +from __future__ import print_function +import sys +import os +import unittest +import llvm + +tests = [] + +def run(verbosity=1): + print('llvmpy is installed in: ' + os.path.dirname(__file__)) + print('llvmpy version: ' + llvm.__version__) + print(sys.version) + + files = filter(lambda s: s.startswith('test_') and s.endswith('.py'), + os.listdir(os.path.dirname(__file__))) + + for f in files: + fname = f.split('.', 1)[0] + __import__('.'.join([__name__, fname])) + + suite = unittest.TestSuite() + for cls in tests: + suite.addTest(unittest.makeSuite(cls)) + + # The default stream fails in IPython qtconsole on Windows, + # so just using sys.stdout + runner = unittest.TextTestRunner(verbosity=verbosity, stream=sys.stdout) + return runner.run(suite) + +if __name__ == '__main__': + unittest.main() + diff --git a/llvm/test_llvmpy.py b/llvm/tests/test_llvmpy.py similarity index 98% rename from llvm/test_llvmpy.py rename to llvm/tests/test_llvmpy.py index 2efd5a4..b7961e0 100644 --- a/llvm/test_llvmpy.py +++ b/llvm/tests/test_llvmpy.py @@ -10,6 +10,7 @@ import subprocess import tempfile import contextlib from distutils.spawn import find_executable +from . import tests is_py3k = sys.version_info[0] >= 3 BITS = tuple.__itemsize__ * 8 @@ -30,8 +31,6 @@ import llvm.ee as le import llvmpy -tests = [] - # --------------------------------------------------------------------------- if sys.version_info[:2] <= (2, 6): @@ -1582,23 +1581,3 @@ class TestExact(TestCase): tests.append(TestExact) - -# --------------------------------------------------------------------------- - -def run(verbosity=1): - print('llvmpy is installed in: ' + os.path.dirname(__file__)) - print('llvmpy version: ' + llvm.__version__) - print(sys.version) - - suite = unittest.TestSuite() - for cls in tests: - suite.addTest(unittest.makeSuite(cls)) - - # The default stream fails in IPython qtconsole on Windows, - # so just using sys.stdout - runner = unittest.TextTestRunner(verbosity=verbosity, stream=sys.stdout) - return runner.run(suite) - - -if __name__ == '__main__': - unittest.main() diff --git a/llvm/tests/test_struct_args.py b/llvm/tests/test_struct_args.py new file mode 100644 index 0000000..ec6c70c --- /dev/null +++ b/llvm/tests/test_struct_args.py @@ -0,0 +1,356 @@ +from __future__ import print_function +from . import tests +import unittest +from ctypes import Structure, c_float, c_double, c_uint8, CFUNCTYPE +from llvm import core as lc +from llvm import ee as le + +class TwoDoubleOneByte(Structure): + _fields_ = ('x', c_double), ('y', c_double), ('z', c_uint8) + + def __repr__(self): + return '' % (self.x, self.y, self.z) + +class TwoDouble(Structure): + _fields_ = ('x', c_double), ('y', c_double) + + def __repr__(self): + return '' % (self.x, self.y) + +class TwoFloat(Structure): + _fields_ = ('x', c_float), ('y', c_float) + + def __repr__(self): + return '' % (self.x, self.y) + +class OneByte(Structure): + _fields_ = [('x', c_uint8)] + + def __repr__(self): + return '' % (self.x,) + +TM = le.TargetMachine.new() +POINTER_BITSIZE = TM.target_data.pointer_size * 8 + +def skip_if_not_64bits(fn): + if POINTER_BITSIZE == 64: + return fn + +def skip_if_not_32bits(fn): + if POINTER_BITSIZE == 32: + return fn + +class FloatTestMixin(object): + def assertClose(self, got, expect): + rel = abs(got - expect) / float(expect) + self.assertTrue(rel < 1e-6, 'relative error = %f' % rel) + +class TestStructABI(unittest.TestCase, FloatTestMixin): + @skip_if_not_64bits + def test_bigger_than_two_words_64(self): + m = lc.Module.new('test_struct_arg') + + double_type = lc.Type.double() + uint8_type = lc.Type.int(8) + struct_type = lc.Type.struct([double_type, double_type, uint8_type]) + struct_ptr_type = lc.Type.pointer(struct_type) + func_type = lc.Type.function(lc.Type.void(), + [struct_ptr_type, struct_ptr_type]) + func = m.add_function(func_type, name='foo') + + # return value pointer + func.args[0].add_attribute(lc.ATTR_STRUCT_RET) + + # pass structure by value + func.args[1].add_attribute(lc.ATTR_BY_VAL) + + # define function body + builder = lc.Builder.new(func.append_basic_block('')) + + arg = builder.load(func.args[1]) + e1, e2, e3 = [builder.extract_value(arg, i) for i in range(3)] + se1 = builder.fmul(e1, e2) + se2 = builder.fdiv(e1, e2) + ret = builder.insert_value(lc.Constant.undef(struct_type), se1, 0) + ret = builder.insert_value(ret, se2, 1) + ret = builder.insert_value(ret, e3, 2) + builder.store(ret, func.args[0]) + builder.ret_void() + + del builder + + # verify + m.verify() + + print(m) + # use with ctypes + engine = le.EngineBuilder.new(m).create() + ptr = engine.get_pointer_to_function(func) + + cfunctype = CFUNCTYPE(TwoDoubleOneByte, TwoDoubleOneByte) + cfunc = cfunctype(ptr) + + arg = TwoDoubleOneByte(x=1.321321, y=6.54352, z=128) + ret = cfunc(arg) + print(arg) + print(ret) + + self.assertClose(arg.x * arg.y, ret.x) + self.assertClose(arg.x / arg.y, ret.y) + self.assertEqual(arg.z, ret.z) + + @skip_if_not_64bits + def test_just_two_words_64(self): + m = lc.Module.new('test_struct_arg') + + double_type = lc.Type.double() + struct_type = lc.Type.struct([double_type, double_type]) + func_type = lc.Type.function(struct_type, [struct_type]) + func = m.add_function(func_type, name='foo') + + # define function body + builder = lc.Builder.new(func.append_basic_block('')) + + arg = func.args[0] + e1, e2 = [builder.extract_value(arg, i) for i in range(2)] + se1 = builder.fmul(e1, e2) + se2 = builder.fdiv(e1, e2) + ret = builder.insert_value(lc.Constant.undef(struct_type), se1, 0) + ret = builder.insert_value(ret, se2, 1) + builder.ret(ret) + + del builder + + # verify + m.verify() + + print(m) + # use with ctypes + engine = le.EngineBuilder.new(m).create() + ptr = engine.get_pointer_to_function(func) + + cfunctype = CFUNCTYPE(TwoDouble, TwoDouble) + cfunc = cfunctype(ptr) + + arg = TwoDouble(x=1.321321, y=6.54352) + ret = cfunc(arg) + print(arg) + print(ret) + + self.assertClose(arg.x * arg.y, ret.x) + self.assertClose(arg.x / arg.y, ret.y) + + @skip_if_not_64bits + def test_two_halfwords(self): + '''Arguments smaller or equal to a word is packed into a word. + + Passing as struct { float, float } occupies two XMM registers instead + of one. + The output must be in XMM. + ''' + m = lc.Module.new('test_struct_arg') + + float_type = lc.Type.float() + struct_type = lc.Type.vector(float_type, 2) + print('ABI size', + TM.target_data.abi_size(lc.Type.struct([float_type, float_type]))) + func_type = lc.Type.function(struct_type, [struct_type]) + func = m.add_function(func_type, name='foo') + + # define function body + builder = lc.Builder.new(func.append_basic_block('')) + + arg = func.args[0] + constint = lambda x: lc.Constant.int(lc.Type.int(), x) + e1, e2 = [builder.extract_element(arg, constint(i)) + for i in range(2)] + se1 = builder.fmul(e1, e2) + se2 = builder.fdiv(e1, e2) + ret = builder.insert_element(lc.Constant.undef(struct_type), se1, + constint(0)) + ret = builder.insert_element(ret, se2, constint(1)) + builder.ret(ret) + + del builder + + # verify + m.verify() + + print(m) + # use with ctypes + engine = le.EngineBuilder.new(m).create() + ptr = engine.get_pointer_to_function(func) + + cfunctype = CFUNCTYPE(TwoFloat, TwoFloat) + cfunc = cfunctype(ptr) + + arg = TwoFloat(x=1.321321, y=6.54352) + ret = cfunc(arg) + print(arg) + print(ret) + + self.assertClose(arg.x * arg.y, ret.x) + self.assertClose(arg.x / arg.y, ret.y) + + @skip_if_not_32bits + def test_structure_abi_32_1(self): + '''x86 is simple. Always pass structure as memory. + ''' + m = lc.Module.new('test_struct_arg') + + double_type = lc.Type.double() + uint8_type = lc.Type.int(8) + struct_type = lc.Type.struct([double_type, double_type, uint8_type]) + struct_ptr_type = lc.Type.pointer(struct_type) + func_type = lc.Type.function(lc.Type.void(), + [struct_ptr_type, struct_ptr_type]) + func = m.add_function(func_type, name='foo') + + # return value pointer + func.args[0].add_attribute(lc.ATTR_STRUCT_RET) + + # pass structure by value + func.args[1].add_attribute(lc.ATTR_BY_VAL) + + # define function body + builder = lc.Builder.new(func.append_basic_block('')) + + arg = builder.load(func.args[1]) + e1, e2, e3 = [builder.extract_value(arg, i) for i in range(3)] + se1 = builder.fmul(e1, e2) + se2 = builder.fdiv(e1, e2) + ret = builder.insert_value(lc.Constant.undef(struct_type), se1, 0) + ret = builder.insert_value(ret, se2, 1) + ret = builder.insert_value(ret, e3, 2) + builder.store(ret, func.args[0]) + builder.ret_void() + + del builder + + # verify + m.verify() + + print(m) + # use with ctypes + engine = le.EngineBuilder.new(m).create() + ptr = engine.get_pointer_to_function(func) + + cfunctype = CFUNCTYPE(TwoDoubleOneByte, TwoDoubleOneByte) + cfunc = cfunctype(ptr) + + arg = TwoDoubleOneByte(x=1.321321, y=6.54352, z=128) + ret = cfunc(arg) + print(arg) + print(ret) + + self.assertClose(arg.x * arg.y, ret.x) + self.assertClose(arg.x / arg.y, ret.y) + self.assertEqual(arg.z, ret.z) + + + @skip_if_not_32bits + def test_structure_abi_32_2(self): + '''x86 is simple. Always pass structure as memory. + ''' + m = lc.Module.new('test_struct_arg') + + float_type = lc.Type.float() + struct_type = lc.Type.struct([float_type, float_type]) + struct_ptr_type = lc.Type.pointer(struct_type) + func_type = lc.Type.function(lc.Type.void(), + [struct_ptr_type, struct_ptr_type]) + func = m.add_function(func_type, name='foo') + + # return value pointer + func.args[0].add_attribute(lc.ATTR_STRUCT_RET) + + # pass structure by value + func.args[1].add_attribute(lc.ATTR_BY_VAL) + + # define function body + builder = lc.Builder.new(func.append_basic_block('')) + + arg = builder.load(func.args[1]) + e1, e2 = [builder.extract_value(arg, i) for i in range(2)] + se1 = builder.fmul(e1, e2) + se2 = builder.fdiv(e1, e2) + ret = builder.insert_value(lc.Constant.undef(struct_type), se1, 0) + ret = builder.insert_value(ret, se2, 1) + builder.store(ret, func.args[0]) + builder.ret_void() + + del builder + + # verify + m.verify() + + print(m) + # use with ctypes + engine = le.EngineBuilder.new(m).create() + ptr = engine.get_pointer_to_function(func) + + cfunctype = CFUNCTYPE(TwoFloat, TwoFloat) + cfunc = cfunctype(ptr) + + arg = TwoFloat(x=1.321321, y=6.54352) + ret = cfunc(arg) + print(arg) + print(ret) + + self.assertClose(arg.x * arg.y, ret.x) + self.assertClose(arg.x / arg.y, ret.y) + + + @skip_if_not_32bits + def test_structure_abi_32_3(self): + '''x86 is simple. Always pass structure as memory. + ''' + m = lc.Module.new('test_struct_arg') + + uint8_type = lc.Type.int(8) + struct_type = lc.Type.struct([uint8_type]) + struct_ptr_type = lc.Type.pointer(struct_type) + func_type = lc.Type.function(lc.Type.void(), + [struct_ptr_type, struct_ptr_type]) + func = m.add_function(func_type, name='foo') + + # return value pointer + func.args[0].add_attribute(lc.ATTR_STRUCT_RET) + + # pass structure by value + func.args[1].add_attribute(lc.ATTR_BY_VAL) + + # define function body + builder = lc.Builder.new(func.append_basic_block('')) + + arg = builder.load(func.args[1]) + e1 = builder.extract_value(arg, 0) + se1 = builder.mul(e1, e1) + ret = builder.insert_value(lc.Constant.undef(struct_type), se1, 0) + builder.store(ret, func.args[0]) + builder.ret_void() + + del builder + + # verify + m.verify() + + print(m) + # use with ctypes + engine = le.EngineBuilder.new(m).create() + ptr = engine.get_pointer_to_function(func) + + cfunctype = CFUNCTYPE(OneByte, OneByte) + cfunc = cfunctype(ptr) + + arg = OneByte(x=8) + ret = cfunc(arg) + print(arg) + print(ret) + + self.assertEqual(arg.x * arg.x, ret.x) + +tests.append(TestStructABI) + +if __name__ == "__main__": + unittest.main() diff --git a/setup.py b/setup.py index db4f75e..1e920f8 100644 --- a/setup.py +++ b/setup.py @@ -188,7 +188,8 @@ setup( 'llvm_cbuilder', 'llpython', 'llvm_array', - 'llvmpy.api', 'llvmpy.api.llvm',], + 'llvmpy.api', 'llvmpy.api.llvm', + 'llvm.tests'], package_data = {'llvm': ['llrt/*.ll']}, py_modules = ['llvmpy', 'llvmpy._capsule',