diff --git a/docs/source/user-guide/binding/value-references.rst b/docs/source/user-guide/binding/value-references.rst index c579884e1..7f2a5ced4 100644 --- a/docs/source/user-guide/binding/value-references.rst +++ b/docs/source/user-guide/binding/value-references.rst @@ -52,6 +52,38 @@ Enumerations * .. data:: dllexport +.. class:: ValueKind + + The value kinds allowed are: + + * .. data:: argument + * .. data:: basic_block + * .. data:: memory_use + * .. data:: memory_def + * .. data:: memory_phi + * .. data:: function + * .. data:: global_alias + * .. data:: global_ifunc + * .. data:: global_variable + * .. data:: block_address + * .. data:: constant_expr + * .. data:: constant_array + * .. data:: constant_struct + * .. data:: constant_vector + * .. data:: undef_value + * .. data:: constant_aggregate_zero + * .. data:: constant_data_array + * .. data:: constant_data_vector + * .. data:: constant_int + * .. data:: constant_fp + * .. data:: constant_pointer_null + * .. data:: constant_token_none + * .. data:: metadata_as_value + * .. data:: inline_asm + * .. data:: instruction + * .. data:: poison_value + + The ValueRef class ------------------ @@ -110,6 +142,11 @@ The ValueRef class style---a :class:`Visibility` instance---for this value. This attribute can be set. + * .. attribute:: value_kind + + The LLVM value kind---a :class:`ValueKind` instance---for + this value. + * .. attribute:: blocks An iterator over the basic blocks in this function. @@ -164,3 +201,19 @@ The ValueRef class * .. attribute:: is_operand The value is a instruction's operand. + + * .. attribute:: is_constant + + The value is a constant. + + * .. method:: get_constant_value(self, signed_int=False, round_fp=False) + + Return the constant value, either as a literal (for example, int + or float) when supported, or as a string otherwise. Keyword arguments + specify the preferences during conversion: + + * If ``signed_int`` is True and the constant is an integer, returns a + signed integer. + * If ``round_fp`` True and the constant is a floating point value, + rounds the result upon accuracy loss (e.g., when querying an fp128 + value). By default, raises an exception on accuracy loss. diff --git a/ffi/value.cpp b/ffi/value.cpp index 4750b4c6e..f29ea1df6 100644 --- a/ffi/value.cpp +++ b/ffi/value.cpp @@ -304,6 +304,43 @@ LLVMPY_DisposeOperandsIter(LLVMOperandsIteratorRef GI) { delete llvm::unwrap(GI); } +API_EXPORT(bool) +LLVMPY_IsConstant(LLVMValueRef Val) { return LLVMIsConstant(Val); } + +API_EXPORT(const uint64_t *) +LLVMPY_GetConstantIntRawValue(LLVMValueRef Val, bool *littleEndian) { + if (littleEndian) { + *littleEndian = llvm::sys::IsLittleEndianHost; + } + if (llvm::ConstantInt *CI = + llvm::dyn_cast((llvm::Value *)Val)) { + return CI->getValue().getRawData(); + } + return nullptr; +} + +API_EXPORT(unsigned) +LLVMPY_GetConstantIntNumWords(LLVMValueRef Val) { + if (llvm::ConstantInt *CI = + llvm::dyn_cast((llvm::Value *)Val)) { + return CI->getValue().getNumWords(); + } + return 0; +} + +API_EXPORT(double) +LLVMPY_GetConstantFPValue(LLVMValueRef Val, bool *losesInfo) { + LLVMBool losesInfo_internal; + double result = LLVMConstRealGetDouble(Val, &losesInfo_internal); + if (losesInfo) { + *losesInfo = losesInfo_internal; + } + return result; +} + +API_EXPORT(int) +LLVMPY_GetValueKind(LLVMValueRef Val) { return (int)LLVMGetValueKind(Val); } + API_EXPORT(void) LLVMPY_PrintValueToString(LLVMValueRef Val, const char **outstr) { *outstr = LLVMPrintValueToString(Val); diff --git a/llvmlite/binding/value.py b/llvmlite/binding/value.py index 4e21b3ee5..9411c7b23 100644 --- a/llvmlite/binding/value.py +++ b/llvmlite/binding/value.py @@ -1,4 +1,5 @@ -from ctypes import POINTER, c_char_p, c_int, c_size_t, c_uint, c_bool, c_void_p +from ctypes import (POINTER, byref, cast, c_char_p, c_double, c_int, c_size_t, + c_uint, c_uint64, c_bool, c_void_p) import enum from llvmlite.binding import ffi @@ -43,6 +44,41 @@ class StorageClass(enum.IntEnum): dllexport = 2 +class ValueKind(enum.IntEnum): + # The LLVMValueKind enum from llvm-c/Core.h + + argument = 0 + basic_block = 1 + memory_use = 2 + memory_def = 3 + memory_phi = 4 + + function = 5 + global_alias = 6 + global_ifunc = 7 + global_variable = 8 + block_address = 9 + constant_expr = 10 + constant_array = 11 + constant_struct = 12 + constant_vector = 13 + + undef_value = 14 + constant_aggregate_zero = 15 + constant_data_array = 16 + constant_data_vector = 17 + constant_int = 18 + constant_fp = 19 + constant_pointer_null = 20 + constant_token_none = 21 + + metadata_as_value = 22 + inline_asm = 23 + + instruction = 24 + poison_value = 25 + + class TypeRef(ffi.ObjectRef): """A weak reference to a LLVM type """ @@ -140,6 +176,14 @@ def is_instruction(self): def is_operand(self): return self._kind == 'operand' + @property + def is_constant(self): + return bool(ffi.lib.LLVMPY_IsConstant(self)) + + @property + def value_kind(self): + return ValueKind(ffi.lib.LLVMPY_GetValueKind(self)) + @property def name(self): return _decode_string(ffi.lib.LLVMPY_GetValueName(self)) @@ -299,6 +343,51 @@ def opcode(self): % (self._kind,)) return ffi.ret_string(ffi.lib.LLVMPY_GetOpcodeName(self)) + def get_constant_value(self, signed_int=False, round_fp=False): + """ + Return the constant value, either as a literal (when supported) + or as a string. + + Parameters + ----------- + signed_int : bool + if True and the constant is an integer, returns a signed version + round_fp : bool + if True and the constant is a floating point value, rounds the + result upon accuracy loss (e.g., when querying an fp128 value). + By default, raises an exception on accuracy loss + """ + if not self.is_constant: + raise ValueError('expected constant value, got %s' + % (self._kind,)) + + if self.value_kind == ValueKind.constant_int: + # Python integers are also arbitrary-precision + little_endian = c_bool(False) + words = ffi.lib.LLVMPY_GetConstantIntNumWords(self) + ptr = ffi.lib.LLVMPY_GetConstantIntRawValue( + self, byref(little_endian)) + asbytes = bytes(cast(ptr, POINTER(c_uint64 * words)).contents) + return int.from_bytes( + asbytes, + ('little' if little_endian.value else 'big'), + signed=signed_int, + ) + elif self.value_kind == ValueKind.constant_fp: + # Convert floating-point values to double-precision (Python float) + accuracy_loss = c_bool(False) + value = ffi.lib.LLVMPY_GetConstantFPValue(self, + byref(accuracy_loss)) + if accuracy_loss.value and not round_fp: + raise ValueError( + 'Accuracy loss encountered in conversion of constant ' + f'value {str(self)}') + + return value + + # Otherwise, return the IR string + return str(self) + class _ValueIterator(ffi.ObjectRef): @@ -516,3 +605,20 @@ def _next(self): ffi.lib.LLVMPY_GetOpcodeName.argtypes = [ffi.LLVMValueRef] ffi.lib.LLVMPY_GetOpcodeName.restype = c_void_p + +ffi.lib.LLVMPY_IsConstant.argtypes = [ffi.LLVMValueRef] +ffi.lib.LLVMPY_IsConstant.restype = c_bool + +ffi.lib.LLVMPY_GetValueKind.argtypes = [ffi.LLVMValueRef] +ffi.lib.LLVMPY_GetValueKind.restype = c_int + +ffi.lib.LLVMPY_GetConstantIntRawValue.argtypes = [ffi.LLVMValueRef, + POINTER(c_bool)] +ffi.lib.LLVMPY_GetConstantIntRawValue.restype = POINTER(c_uint64) + +ffi.lib.LLVMPY_GetConstantIntNumWords.argtypes = [ffi.LLVMValueRef] +ffi.lib.LLVMPY_GetConstantIntNumWords.restype = c_uint + +ffi.lib.LLVMPY_GetConstantFPValue.argtypes = [ffi.LLVMValueRef, + POINTER(c_bool)] +ffi.lib.LLVMPY_GetConstantFPValue.restype = c_double diff --git a/llvmlite/tests/test_binding.py b/llvmlite/tests/test_binding.py index 0c3f2a179..d0955508d 100644 --- a/llvmlite/tests/test_binding.py +++ b/llvmlite/tests/test_binding.py @@ -64,6 +64,18 @@ def no_de_locale(): }} """ +asm_sum3 = r""" + ; ModuleID = '' + target triple = "{triple}" + + define i64 @sum(i64 %.1, i64 %.2) {{ + %.3 = add i64 %.1, %.2 + %.4 = add i64 5, %.3 + %.5 = add i64 -5, %.4 + ret i64 %.5 + }} + """ + asm_mul = r""" ; ModuleID = '' target triple = "{triple}" @@ -129,6 +141,15 @@ def no_de_locale(): declare i32 @sum(i32 %.1, i32 %.2) """ +asm_double_inaccurate = r""" + ; ModuleID = '' + target triple = "{triple}" + + define void @foo() {{ + %const = fadd fp128 0xLF3CB1CCF26FBC178452FB4EC7F91DEAD, 0xL00000000000000000000000000000001 + ret void + }} + """ # noqa E501 asm_double_locale = r""" ; ModuleID = '' @@ -352,6 +373,21 @@ def no_de_locale(): """ # noqa W291 # trailing space needed for match later +asm_null_constant = r""" + ; ModuleID = '' + target triple = "{triple}" + + define void @foo(i64* %.1) {{ + ret void + }} + + define void @bar() {{ + call void @foo(i64* null) + ret void + }} +""" + + riscv_asm_ilp32 = [ 'addi\tsp, sp, -16', 'sw\ta1, 8(sp)', @@ -1551,6 +1587,96 @@ def test_function_attributes(self): self.assertEqual(list(args[0].attributes), [b'returned']) self.assertEqual(list(args[1].attributes), []) + def test_value_kind(self): + mod = self.module() + self.assertEqual(mod.get_global_variable('glob').value_kind, + llvm.ValueKind.global_variable) + func = mod.get_function('sum') + self.assertEqual(func.value_kind, llvm.ValueKind.function) + block = list(func.blocks)[0] + self.assertEqual(block.value_kind, llvm.ValueKind.basic_block) + inst = list(block.instructions)[1] + self.assertEqual(inst.value_kind, llvm.ValueKind.instruction) + self.assertEqual(list(inst.operands)[0].value_kind, + llvm.ValueKind.constant_int) + self.assertEqual(list(inst.operands)[1].value_kind, + llvm.ValueKind.instruction) + + iasm_func = self.module(asm_inlineasm).get_function('foo') + iasm_inst = list(list(iasm_func.blocks)[0].instructions)[0] + self.assertEqual(list(iasm_inst.operands)[0].value_kind, + llvm.ValueKind.inline_asm) + + def test_is_constant(self): + mod = self.module() + self.assertTrue(mod.get_global_variable('glob').is_constant) + constant_operands = 0 + for func in mod.functions: + self.assertTrue(func.is_constant) + for block in func.blocks: + self.assertFalse(block.is_constant) + for inst in block.instructions: + self.assertFalse(inst.is_constant) + for op in inst.operands: + if op.is_constant: + constant_operands += 1 + + self.assertEqual(constant_operands, 1) + + def test_constant_int(self): + mod = self.module() + func = mod.get_function('sum') + insts = list(list(func.blocks)[0].instructions) + self.assertEqual(insts[1].opcode, 'add') + operands = list(insts[1].operands) + self.assertTrue(operands[0].is_constant) + self.assertFalse(operands[1].is_constant) + self.assertEqual(operands[0].get_constant_value(), 0) + with self.assertRaises(ValueError): + operands[1].get_constant_value() + + mod = self.module(asm_sum3) + func = mod.get_function('sum') + insts = list(list(func.blocks)[0].instructions) + posint64 = list(insts[1].operands)[0] + negint64 = list(insts[2].operands)[0] + self.assertEqual(posint64.get_constant_value(), 5) + self.assertEqual(negint64.get_constant_value(signed_int=True), -5) + + # Convert from unsigned arbitrary-precision integer to signed i64 + as_u64 = negint64.get_constant_value(signed_int=False) + as_i64 = int.from_bytes(as_u64.to_bytes(8, 'little'), 'little', + signed=True) + self.assertEqual(as_i64, -5) + + def test_constant_fp(self): + mod = self.module(asm_double_locale) + func = mod.get_function('foo') + insts = list(list(func.blocks)[0].instructions) + self.assertEqual(len(insts), 2) + self.assertEqual(insts[0].opcode, 'fadd') + operands = list(insts[0].operands) + self.assertTrue(operands[0].is_constant) + self.assertAlmostEqual(operands[0].get_constant_value(), 0.0) + self.assertTrue(operands[1].is_constant) + self.assertAlmostEqual(operands[1].get_constant_value(), 3.14) + + mod = self.module(asm_double_inaccurate) + func = mod.get_function('foo') + inst = list(list(func.blocks)[0].instructions)[0] + operands = list(inst.operands) + with self.assertRaises(ValueError): + operands[0].get_constant_value() + self.assertAlmostEqual(operands[1].get_constant_value(round_fp=True), 0) + + def test_constant_as_string(self): + mod = self.module(asm_null_constant) + func = mod.get_function('bar') + inst = list(list(func.blocks)[0].instructions)[0] + arg = list(inst.operands)[0] + self.assertTrue(arg.is_constant) + self.assertEqual(arg.get_constant_value(), 'i64* null') + class TestTarget(BaseTest):