diff --git a/llvm/_dwarf.cpp b/llvm/_dwarf.cpp new file mode 100644 index 0000000..43f75aa --- /dev/null +++ b/llvm/_dwarf.cpp @@ -0,0 +1,86 @@ +#include +#include + +namespace llvmpy { +namespace dwarf { + +using namespace llvm::dwarf; + +const char *constant_names[] = { + "LLVMDebugVersion", + + #define define(x) #x, + #include "_dwarf.h" + #undef define +}; + +int constant_values[] = { + llvm::LLVMDebugVersion, + + #define define(x) x, + #include "_dwarf.h" + #undef define +}; + +enum enum_values { + __llvmpy_LLVMDebugVersion, + + #define define(x) __llvmpy_##x, + #include "_dwarf.h" + #undef define + + NVALUES /* Get the number of constants */ +}; + +} // End namespace dwarf + +int +set_dwarf_constants(PyObject *module) +{ + int i; + for (i = 0; i < dwarf::NVALUES; i++) { + if (PyModule_AddIntConstant(module, dwarf::constant_names[i], + dwarf::constant_values[i]) > 0) + return -1; + } + + return 0; +} + +} // End namespace llvmpy + +#if (PY_MAJOR_VERSION >= 3) + struct PyModuleDef module_def = { + PyModuleDef_HEAD_INIT, + "_dwarf", + NULL, + -1, + NULL, /* m_methods */ + NULL, /* m_reload */ + NULL, /* m_traverse */ + NULL, /* m_clear */ + NULL, /* m_free */ + }; + + #define INITERROR return NULL + PyObject *PyInit__dwarf(void) +#else + #define INITERROR return + PyMODINIT_FUNC init_dwarf(void) +#endif +{ +#if PY_MAJOR_VERSION >= 3 + PyObject *module = PyModule_Create( &module_def ); +#else + PyObject *module = Py_InitModule("_dwarf", NULL); +#endif + if (module == NULL) + INITERROR; + + if (llvmpy::set_dwarf_constants(module) < 0) + INITERROR; + +#if PY_MAJOR_VERSION >= 3 + return module; +#endif +} diff --git a/llvm/_dwarf.h b/llvm/_dwarf.h new file mode 100644 index 0000000..78eada0 --- /dev/null +++ b/llvm/_dwarf.h @@ -0,0 +1,101 @@ +/* Dwarf constant wrapping. See include/llvm/Support/Dwarf.h */ + +define(DWARF_VERSION) + +/* Dwarf tags */ +define(DW_TAG_array_type) +define(DW_TAG_class_type) +define(DW_TAG_entry_point) +define(DW_TAG_enumeration_type) +define(DW_TAG_formal_parameter) +define(DW_TAG_imported_declaration) +define(DW_TAG_label) +define(DW_TAG_lexical_block) +define(DW_TAG_member) +define(DW_TAG_pointer_type) +define(DW_TAG_reference_type) +define(DW_TAG_compile_unit) +define(DW_TAG_string_type) +define(DW_TAG_structure_type) +define(DW_TAG_subroutine_type) +define(DW_TAG_typedef) +define(DW_TAG_union_type) +define(DW_TAG_unspecified_parameters) +define(DW_TAG_variant) +define(DW_TAG_common_block) +define(DW_TAG_common_inclusion) +define(DW_TAG_inheritance) +define(DW_TAG_inlined_subroutine) +define(DW_TAG_module) +define(DW_TAG_ptr_to_member_type) +define(DW_TAG_set_type) +define(DW_TAG_subrange_type) +define(DW_TAG_with_stmt) +define(DW_TAG_access_declaration) +define(DW_TAG_base_type) +define(DW_TAG_catch_block) +define(DW_TAG_const_type) +define(DW_TAG_constant) +define(DW_TAG_enumerator) +define(DW_TAG_file_type) +define(DW_TAG_friend) +define(DW_TAG_namelist) +define(DW_TAG_namelist_item) +define(DW_TAG_packed_type) +define(DW_TAG_subprogram) +define(DW_TAG_template_type_parameter) +define(DW_TAG_template_value_parameter) +define(DW_TAG_thrown_type) +define(DW_TAG_try_block) +define(DW_TAG_variant_part) +define(DW_TAG_variable) +define(DW_TAG_volatile_type) +define(DW_TAG_dwarf_procedure) +define(DW_TAG_restrict_type) +define(DW_TAG_interface_type) +define(DW_TAG_namespace) +define(DW_TAG_imported_module) +define(DW_TAG_unspecified_type) +define(DW_TAG_partial_unit) +define(DW_TAG_imported_unit) +define(DW_TAG_condition) +define(DW_TAG_shared_type) +define(DW_TAG_type_unit) +define(DW_TAG_rvalue_reference_type) +define(DW_TAG_template_alias) +define(DW_TAG_MIPS_loop) +define(DW_TAG_format_label) +define(DW_TAG_function_template) +define(DW_TAG_class_template) +define(DW_TAG_GNU_template_template_param) +define(DW_TAG_GNU_template_parameter_pack) +define(DW_TAG_GNU_formal_parameter_pack) +define(DW_TAG_lo_user) +define(DW_TAG_APPLE_property) +define(DW_TAG_hi_user) + +/* Dwarf language constants */ +define(DW_LANG_C89) +define(DW_LANG_C) +define(DW_LANG_Ada83) +define(DW_LANG_C_plus_plus) +define(DW_LANG_Cobol74) +define(DW_LANG_Cobol85) +define(DW_LANG_Fortran77) +define(DW_LANG_Fortran90) +define(DW_LANG_Pascal83) +define(DW_LANG_Modula2) +define(DW_LANG_Java) +define(DW_LANG_C99) +define(DW_LANG_Ada95) +define(DW_LANG_Fortran95) +define(DW_LANG_PLI) +define(DW_LANG_ObjC) +define(DW_LANG_ObjC_plus_plus) +define(DW_LANG_UPC) +define(DW_LANG_D) +define(DW_LANG_Python) +define(DW_LANG_lo_user) +define(DW_LANG_Mips_Assembler) +define(DW_LANG_hi_user) + diff --git a/llvm/debuginfo.py b/llvm/debuginfo.py new file mode 100644 index 0000000..0f18b33 --- /dev/null +++ b/llvm/debuginfo.py @@ -0,0 +1,398 @@ +""" +Support for debug info metadata. +""" + +import functools + +import llvm.core +from llvm import _dwarf + +#---------------------------------------------------------------------------- +# Some types and type checking functions +#---------------------------------------------------------------------------- + +int32_t = llvm.core.Type.int(32) +bool_t = llvm.core.Type.int(1) +p_int32_t = llvm.core.Type.pointer(int32_t) +null = llvm.core.Constant.null(p_int32_t) + +i32 = functools.partial(llvm.core.Constant.int, int32_t) +i1 = functools.partial(llvm.core.Constant.int, bool_t) + +def is_md(value): + return isinstance(value, (llvm.core.MetaData, llvm.core.NamedMetaData, + llvm.core.Value)) + +def is_mdstr(value): + return isinstance(value, (llvm.core.MetaDataString, llvm.core.Value)) + +#---------------------------------------------------------------------------- +# Callbacks for debug type descriptors +#---------------------------------------------------------------------------- + +def get_i32(llvm_module, value): + return i32(value) + +def get_i1(llvm_module, value): + return i1(value) + +def get_md(llvm_module, value): + if is_md(value): + return value + return value.get_metadata(llvm_module) + +def get_mdstr(llvm_module, value): + if is_mdstr(value): + return value + return llvm.core.MetaDataString.get(llvm_module, value) + +def get_mdlist(llvm_module, value): + if isinstance(value, list): + value = MDList([value]) + return get_md(llvm_module, value) + +def get_lfunc(llvm_module, lfunc): + assert lfunc.module is llvm_module + return lfunc + +#---------------------------------------------------------------------------- +# Debug descriptors that generate LLVM Metadata +#---------------------------------------------------------------------------- + +class desc(object): + """ + Descriptor of a debug field. + + field_name: + name of the field (keyword argument name) + build_metadata: + metadata callback :: (llvm_module, Python value) -> LLVM Value + default: + default value for this field + """ + + def __init__(self, field_name, build_metadata, default=None): + self.field_name = field_name + self.build_metadata = build_metadata + self.default = default + + +def build_operand_list(type_descriptors, idx2name, operands, kwargs): + """ + Build the list of operands from the positional and keyword arguments + to a DebugInfoDescriptor. + + E.g. FileDescriptor("foo.c", "/path/to/file", compile_unit=compile_unit) + + idx2name: {0 : 'source_filename', + 1 : 'source_filedir', + 2 : 'compile_unit' } + operands: ["foo.c", "/path/to/file"] + kwargs: { 'compile_unit' : compile_unit } + """ + for i in range(len(operands), len(type_descriptors)): + name = idx2name[i] + if name in kwargs: + op = kwargs[name] + else: + typedesc = type_descriptors[i] + assert typedesc.default is not None, ( + "No value found for field %s" % typedesc.field_name) + op = typedesc.default + + operands.append(op) + + return operands + + +class DebugInfoDescriptor(object): + """ + Base class to describe a debug info descriptor. + + See http://llvm.org/docs/SourceLevelDebugging.html + """ + + type_descriptors = None # [desc(...)] + idx2name = None # { "field_idx" : field_name } + name2idx = None # { "field_name" : field_idx } + + # Whether the debug descriptor is allowed to be incomplete + # The policy seems unclear ? + accept_optional_data = False + + def __init__(self, *args, **kwargs): + self.class_init() + self.metadata_cache = {} + + operands = list(args) + if kwargs or not self.accept_optional_data: + operands = build_operand_list(self.type_descriptors, + self.idx2name, operands, kwargs) + + self.operands = operands + self.operands_dict = dict( + (typedesc.field_name, operand) + for typedesc, operand in zip(self.type_descriptors, operands)) + + @classmethod + def class_init(cls): + if cls.idx2name is None and cls.type_descriptors is not None: + cls.name2idx = {} + cls.idx2name = {} + + for i, typedesc in enumerate(cls.type_descriptors): + cls.name2idx[typedesc.field_name] = i + cls.idx2name[i] = typedesc.field_name + + def add_metadata(self, metadata_name, metadata): + """ + Replace a metadata value in the operand list. + """ + idx = self.name2idx[metadata_name] + self.operands[idx] = metadata + + #------------------------------------------------------------------------ + # Define metadata in a module + #------------------------------------------------------------------------ + + def get_metadata(self, llvm_module): + """ + Get an existing metadata node for the given LLVM module, or build + one from this instance. + """ + if llvm_module in self.metadata_cache: + node = self.metadata_cache[llvm_module] + else: + node = self.build_metadata(llvm_module) + self.metadata_cache[llvm_module] = node + + return node + + def build_metadata(self, llvm_module): + "Build a metadata node for the given LLVM module" + mdops = [] + for type_desc, operand in zip(self.type_descriptors, self.operands): + try: + field_value = type_desc.build_metadata(llvm_module, operand) + except Exception, e: + if type_desc.build_metadata is get_md: + raise + + raise ValueError("Invalid value for field %r: %s" % ( + type_desc.field_name, e)) + + mdops.append(field_value) + + return llvm.core.MetaData.get(llvm_module, mdops) + + def define(self, llvm_module): + """ + Define this debug descriptor as named debug metadata for the module. + """ + md = self.get_metadata(llvm_module) + llvm.core.MetaData.add_named_operand(llvm_module, "dbg", md) + +#---------------------------------------------------------------------------- +# Convenience descriptors +#---------------------------------------------------------------------------- + +class MDList(DebugInfoDescriptor): + """ + [MetaData] + """ + + def __init__(self, operands): + self.type_descriptors = [desc("op", get_md)] * len(operands) + super(MDList, self).__init__(*operands) + +empty = MDList([]) + +#---------------------------------------------------------------------------- +# Dwarf Debug descriptors +#---------------------------------------------------------------------------- + +class CompileUnitDescriptor(DebugInfoDescriptor): + """ + !0 = metadata !{ + i32, ;; Tag = 17 + LLVMDebugVersion (DW_TAG_compile_unit) + i32, ;; Unused field. + i32, ;; DWARF language identifier (ex. DW_LANG_C89) + metadata, ;; Source file name + metadata, ;; Source file directory (includes trailing slash) + metadata ;; Producer (ex. "4.0.1 LLVM (LLVM research group)") + i1, ;; True if this is a main compile unit. + i1, ;; True if this is optimized. + metadata, ;; Flags + i32 ;; Runtime version + metadata ;; List of enums types + metadata ;; List of retained types + metadata ;; List of subprograms + metadata ;; List of global variables + } + """ + + accept_optional_data = True + + type_descriptors = [ + desc("tag", get_i32), + desc("unused", get_i32), + + # Positional argument list starts here: + desc("langid", get_i32), + desc("source_filename", get_mdstr), + desc("source_filedir", get_mdstr), + desc("producer", get_mdstr), + desc("is_main", get_i1, default=False), + desc("is_optimized", get_i1, default=False), + desc("compile_flags", get_mdstr, default=""), + desc("runtime_version", get_i32, default=0), + desc("enum_types", get_mdlist, default=empty), + desc("retained_types", get_mdlist, default=empty), + desc("subprograms", get_mdlist, default=empty), + desc("global_vars", get_mdlist, default=empty), + ] + + def __init__(self, *args, **kwargs): + super(CompileUnitDescriptor, self).__init__( + _dwarf.DW_TAG_compile_unit + _dwarf.LLVMDebugVersion, # tag + 0, # unused + *args, **kwargs) + + def define(self, llvm_module): + """ + Define this debug descriptor as named debug metadata for the module. + """ + md = self.get_metadata(llvm_module) + llvm.core.MetaData.add_named_operand(llvm_module, "llvm.dbg.cu", md) + + +class FileDescriptor(DebugInfoDescriptor): + """ + !0 = metadata !{ + i32, ;; Tag = 41 + LLVMDebugVersion (DW_TAG_file_type) + metadata, ;; Source file name + metadata, ;; Source file directory (includes trailing slash) + metadata ;; Unused + } + """ + + type_descriptors = [ + desc("tag", get_i32), + + # Positional argument list starts here: + desc("source_filename", get_mdstr), + desc("source_filedir", get_mdstr), + desc("compile_unit", get_md, default=null), + ] + + def __init__(self, *args, **kwargs): + super(FileDescriptor, self).__init__( + _dwarf.DW_TAG_file_type + _dwarf.LLVMDebugVersion, # Tag + *args, **kwargs) + + @classmethod + def from_compileunit(cls, compile_unit_descriptor): + return cls(compile_unit_descriptor.operands_dict["source_filename"], + compile_unit_descriptor.operands_dict["source_filedir"], + compile_unit_descriptor) + + +class SubprogramDescriptor(DebugInfoDescriptor): + """ + !2 = metadata !{ + i32, ;; Tag = 46 + LLVMDebugVersion (DW_TAG_subprogram) + i32, ;; Unused field. + metadata, ;; Reference to context descriptor + metadata, ;; Name + metadata, ;; Display name (fully qualified C++ name) + metadata, ;; MIPS linkage name (for C++) + metadata, ;; Reference to file where defined + i32, ;; Line number where defined + metadata, ;; Reference to type descriptor + i1, ;; True if the global is local to compile unit (static) + i1, ;; True if the global is defined in the compile unit (not extern) + i32, ;; Line number where the scope of the subprogram begins + i32, ;; Virtuality, e.g. dwarf::DW_VIRTUALITY__virtual + i32, ;; Index into a virtual function + metadata, ;; indicates which base type contains the vtable pointer for the + ;; derived class + i32, ;; Flags - Artifical, Private, Protected, Explicit, Prototyped. + i1, ;; isOptimized + Function * , ;; Pointer to LLVM function + metadata, ;; Lists function template parameters + metadata, ;; Function declaration descriptor + metadata ;; List of function variables + } + """ + + accept_optional_data = True + + type_descriptors = [ + desc("tag", get_i32), + desc("unused", get_i32), + + # Positional argument list starts here: + desc("file_desc", get_md), + desc("name", get_mdstr), + desc("display_name", get_mdstr), + desc("mips_linkage_name", get_mdstr), + desc("source_file_ref", get_md), + desc("line_number", get_i32), + desc("signature", get_md), + desc("is_local", get_i1, default=False), + desc("is_definition", get_i1, default=True), + desc("virtual_attribute", get_i32, default=0), + desc("virtual_index", get_i32, default=0), + desc("virttab", get_i32, default=0), + desc("flags", get_i32, default=0), + desc("is_optimized", get_i1, default=False), + desc("llvm_func", get_lfunc), + # Template params + # Func decl + # Func vars + ] + + def __init__(self, *args, **kwargs): + super(SubprogramDescriptor, self).__init__( + _dwarf.DW_TAG_subprogram + _dwarf.LLVMDebugVersion, # Tag + 0, # Unused + *args, **kwargs) + +class BlockDescriptor(DebugInfoDescriptor): + """ + !3 = metadata !{ + i32, ;; Tag = 11 + LLVMDebugVersion (DW_TAG_lexical_block) + metadata,;; Reference to context descriptor + i32, ;; Line number + i32 ;; Column number + } + """ + + type_descriptors = [ + desc("tag", get_i32), + + # Positional argument list starts here: + desc("context_descr", get_md), # FileDescr | BlockDescr | + # SubprogDescr | ComputeUnit + desc("line_number", get_i32), + desc("col_number", get_i32), + ] + + def __init__(self, *args, **kwargs): + super(BlockDescriptor, self).__init__( + _dwarf.DW_TAG_lexical_block + _dwarf.LLVMDebugVersion, + *args, **kwargs) + +class PositionInfoDescriptor(DebugInfoDescriptor): + """ + line number, column number, scope, and original scope + """ + + type_descriptors = [ + desc("line_number", get_i32), + desc("col_number", get_i32), + desc("context_descr", get_md), # Scope of instruction + # (FileDescr etc) + desc("original_context_descr", get_md, # The original scope if inlined + default=null), + ] diff --git a/setup.py b/setup.py index eeaf162..25b69ee 100644 --- a/setup.py +++ b/setup.py @@ -116,7 +116,12 @@ kwds = dict(ext_modules = [Extension( library_dirs = [libdir], libraries = libs_core, extra_objects = objs_core, - extra_link_args = extra_link_args)]) + extra_link_args = extra_link_args), + + Extension(name='llvm._dwarf', + sources=['llvm/_dwarf.cpp'], + include_dirs=[incdir]) +]) def run_2to3(): import lib2to3.refactor diff --git a/test/test_debuginfo.py b/test/test_debuginfo.py new file mode 100644 index 0000000..68ab29c --- /dev/null +++ b/test/test_debuginfo.py @@ -0,0 +1,104 @@ +import os +import unittest + +from llvm.core import * +from llvm import _dwarf, debuginfo + +class TestDebugInfo(unittest.TestCase): + + def test_dwarf_constants(self): + dwarf_constants = vars(_dwarf) + + # Version numbers + self.assertIn("LLVMDebugVersion", dwarf_constants) + self.assertIn("DWARF_VERSION", dwarf_constants) + + # Tags + self.assertIn("DW_TAG_compile_unit", dwarf_constants) + + # Language identifiers + self.assertIn("DW_LANG_Python", dwarf_constants) + self.assertIn("DW_LANG_C89", dwarf_constants) + self.assertIn("DW_LANG_C99", dwarf_constants) + + # print sorted([constname for constname in dwarf_constants + # if constname.startswith("DW_LANG_")]) + + def test_debug_info_compile_unit(self): + mod = Module.new('test_debug_info') + fty = Type.function(Type.float(), [Type.float()]) + square = mod.add_function(fty, 'square') + bb = square.append_basic_block('entry') + bldr = Builder.new(bb) + + source_filename = "test_debug_info.py" + source_filedir = os.path.expanduser("~") + + # Debug info for our file + filedesc = debuginfo.FileDescriptor(source_filename, source_filedir) + + # Debug info for our function + subprogram = debuginfo.SubprogramDescriptor( + filedesc, + "some_function", + "some_function", + "some_function", + filedesc, + 1, # line number + debuginfo.empty, # Type descriptor + llvm_func=square, + ) + subprograms = debuginfo.MDList([subprogram]) + + # Debug info for our basic blocks + blockdescr1 = debuginfo.BlockDescriptor(subprogram, 2, 4) # lineno, col + blockdescr2 = debuginfo.BlockDescriptor(blockdescr1, 3, 4) # lineno, col + + # Debug info for our instructions + posinfo1 = debuginfo.PositionInfoDescriptor(2, 4, blockdescr1) + posinfo2 = debuginfo.PositionInfoDescriptor(2, 4, blockdescr2) + + # Debug info for our module + compile_unit = debuginfo.CompileUnitDescriptor( + # DW_LANG_Python segfaults: + # llvm/ADT/StringRef.h:79: llvm::StringRef::StringRef(const char*): + # Assertion `Str && "StringRef cannot be built from a NULL argument"' + # _dwarf.DW_LANG_Python, + _dwarf.DW_LANG_C89, + source_filename, + source_filedir, + "my_cool_compiler", + subprograms=subprograms, + ) + + # Define our module debug data + compile_unit.define(mod) + + # Build some instructions + value = square.args[0] + result = bldr.fmul(value, value) + ret = bldr.ret(result) + + # Annotate instructions with source position + result.set_metadata("dbg", posinfo1.get_metadata(mod)) + ret.set_metadata("dbg", posinfo2.get_metadata(mod)) + + # ... Aaaand, test... + # print mod + + modstr = str(mod) + + # Test compile unit + self.assertIn("my_cool_compiler", modstr) + self.assertIn(source_filename, modstr) + self.assertIn(source_filedir, modstr) + self.assertIn("my_cool_compiler", modstr) + + # Test subprogram + self.assertIn("some_function", modstr) + self.assertIn("float (float)* @square", modstr) + +if __name__ == '__main__': +# TestDebugInfo("test_dwarf_constants").debug() +# TestDebugInfo("test_debug_info_compile_unit").debug() + unittest.main() \ No newline at end of file