diff --git a/substratevm/CHANGELOG.md b/substratevm/CHANGELOG.md index bd900af80a42..9691d3dc7690 100644 --- a/substratevm/CHANGELOG.md +++ b/substratevm/CHANGELOG.md @@ -9,6 +9,7 @@ This changelog summarizes major changes to GraalVM Native Image. * (GR-59864) Added JVM version check to the Native Image agent. The agent will abort execution if the JVM major version does not match the version it was built with, and warn if the full JVM version is different. * (GR-59135) Verify if hosted options passed to `native-image` exist prior to starting the builder. Provide suggestions how to fix unknown options early on. * (GR-61492) The experimental JDWP option is now present in standard GraalVM builds. +* (GR-54697) Parallelize debug info generation and add support for run-time debug info generation. `-H:+RuntimeDebugInfo` adds a run-time debug info generator into a native image for use with GDB. ## GraalVM for JDK 24 (Internal Version 24.2.0) * (GR-59717) Added `DuringSetupAccess.registerObjectReachabilityHandler` to allow registering a callback that is executed when an object of a specified type is marked as reachable during heap scanning. diff --git a/substratevm/debug/gdbpy/gdb-debughelpers.py b/substratevm/debug/gdbpy/gdb-debughelpers.py index 77c70eced249..f2e72cd86a50 100644 --- a/substratevm/debug/gdbpy/gdb-debughelpers.py +++ b/substratevm/debug/gdbpy/gdb-debughelpers.py @@ -67,17 +67,6 @@ def trace(msg: str) -> None: svm_debug_tracing.tracefile.flush() -def adr(obj: gdb.Value) -> int: - # use null as fallback if we cannot find the address value - adr_val = 0 - if obj.type.code == gdb.TYPE_CODE_PTR: - if int(obj) != 0: - adr_val = int(obj.dereference().address) - elif obj.address is not None: - adr_val = int(obj.address) - return adr_val - - def try_or_else(success, failure, *exceptions): try: return success() @@ -94,27 +83,12 @@ def __init__(self, static: bool, name: str, gdb_sym: gdb.Symbol): class SVMUtil: + # class fields pretty_printer_name = "SubstrateVM" hub_field_name = "hub" compressed_ref_prefix = '_z_.' - use_heap_base = try_or_else(lambda: bool(gdb.parse_and_eval('(int)__svm_use_heap_base')), True, gdb.error) - compression_shift = try_or_else(lambda: int(gdb.parse_and_eval('(int)__svm_compression_shift')), 0, gdb.error) - reserved_bits_mask = try_or_else(lambda: int(gdb.parse_and_eval('(int)__svm_reserved_bits_mask')), 0, gdb.error) - object_alignment = try_or_else(lambda: int(gdb.parse_and_eval('(int)__svm_object_alignment')), 0, gdb.error) - heap_base_regnum = try_or_else(lambda: int(gdb.parse_and_eval('(int)__svm_heap_base_regnum')), 0, gdb.error) - - string_type = gdb.lookup_type("java.lang.String") - enum_type = gdb.lookup_type("java.lang.Enum") - object_type = gdb.lookup_type("java.lang.Object") - hub_type = gdb.lookup_type("java.lang.Class") - null = gdb.Value(0).cast(object_type.pointer()) - classloader_type = gdb.lookup_type("java.lang.ClassLoader") - wrapper_types = [gdb.lookup_type(f'java.lang.{x}') for x in - ["Byte", "Short", "Integer", "Long", "Float", "Double", "Boolean", "Character"] - if gdb.lookup_global_symbol(f'java.lang.{x}') is not None] - pretty_print_objfiles = set() current_print_depth = 0 @@ -122,68 +96,195 @@ class SVMUtil: selfref_cycles = set() hlreps = dict() - deopt_stub_adr = 0 + # static methods + @staticmethod + def get_eager_deopt_stub_adr() -> int: + sym = gdb.lookup_global_symbol('com.oracle.svm.core.deopt.Deoptimizer::eagerDeoptStub', gdb.SYMBOL_VAR_DOMAIN) + return sym.value().address if sym is not None else -1 + + @staticmethod + def get_lazy_deopt_stub_primitive_adr() -> int: + sym = gdb.lookup_global_symbol('com.oracle.svm.core.deopt.Deoptimizer::lazyDeoptStubPrimitiveReturn', gdb.SYMBOL_VAR_DOMAIN) + return sym.value().address if sym is not None else -1 + + @staticmethod + def get_lazy_deopt_stub_object_adr() -> int: + sym = gdb.lookup_global_symbol('com.oracle.svm.core.deopt.Deoptimizer::lazyDeoptStubObjectReturn', gdb.SYMBOL_VAR_DOMAIN) + return sym.value().address if sym is not None else -1 + + @staticmethod + def get_unqualified_type_name(qualified_type_name: str) -> str: + result = qualified_type_name.split('.')[-1] + result = result.split('$')[-1] + trace(f' - get_unqualified_type_name({qualified_type_name}) = {result}') + return result + + @staticmethod + def get_symbol_adr(symbol: str) -> int: + trace(f' - get_symbol_adr({symbol})') + return gdb.parse_and_eval(symbol).address + + @staticmethod + def execout(cmd: str) -> str: + trace(f' - execout({cmd})') + return gdb.execute(cmd, False, True) + + @staticmethod + def get_basic_type(t: gdb.Type) -> gdb.Type: + trace(f' - get_basic_type({t})') + while t.code == gdb.TYPE_CODE_PTR: + t = t.target() + return t + + # class methods @classmethod - def get_heap_base(cls) -> gdb.Value: - try: - return gdb.selected_frame().read_register(cls.heap_base_regnum) - except gdb.error: - # no frame available - return cls.null + def prompt_hook(cls, current_prompt: str = None): + cls.current_print_depth = 0 + cls.parents.clear() + cls.selfref_cycles.clear() + SVMCommandPrint.cache.clear() @classmethod - def is_null(cls, obj: gdb.Value) -> bool: - return adr(obj) == 0 or (cls.use_heap_base and adr(obj) == int(cls.get_heap_base())) + # checks if node this is reachable from node other (this node is parent of other node) + def is_reachable(cls, this: hex, other: hex) -> bool: + test_nodes = [other] + trace(f' - is_reachable(this={this}, other={other}') + while True: + if len(test_nodes) == 0: + return False + if any(this == node for node in test_nodes): + return True + # create a flat list of all ancestors of each tested node + test_nodes = [parent for node in test_nodes for parent in cls.parents.get(node, [])] @classmethod - def get_uncompressed_type(cls, t: gdb.Type) -> gdb.Type: - # compressed types only exist for java type which are either struct or union - if t.code != gdb.TYPE_CODE_STRUCT and t.code != gdb.TYPE_CODE_UNION: - return t - result = cls.get_base_class(t) if cls.is_compressed(t) else t - trace(f' - get_uncompressed_type({t}) = {result}') + def is_compressed(cls, t: gdb.Type) -> bool: + type_name = cls.get_basic_type(t).name + if type_name is None: + # fallback to the GDB type printer for t + type_name = str(t) + # compressed types from a different classLoader have the format ::_z_. + result = type_name.startswith(cls.compressed_ref_prefix) or ('::' + cls.compressed_ref_prefix) in type_name + trace(f' - is_compressed({type_name}) = {result}') return result @classmethod - def get_compressed_type(cls, t: gdb.Type) -> gdb.Type: + def is_primitive(cls, t: gdb.Type) -> bool: + result = cls.get_basic_type(t).is_scalar + trace(f' - is_primitive({t}) = {result}') + return result + + @classmethod + def get_all_fields(cls, t: gdb.Type, include_static: bool) -> list: # list[gdb.Field]: t = cls.get_basic_type(t) - # compressed types only exist for java types which are either struct or union - # do not compress types that already have the compressed prefix - if not cls.is_java_type(t) or cls.is_compressed(t): - return t + while t.code == gdb.TYPE_CODE_TYPEDEF: + t = t.target() + t = cls.get_basic_type(t) + if t.code != gdb.TYPE_CODE_STRUCT and t.code != gdb.TYPE_CODE_UNION: + return [] + for f in t.fields(): + if not include_static: + try: + f.bitpos # bitpos attribute is not available for static fields + except AttributeError: # use bitpos access exception to skip static fields + continue + if f.is_base_class: + yield from cls.get_all_fields(f.type, include_static) + else: + yield f - type_name = t.name - # java types only contain '::' if there is a classloader namespace - if '::' in type_name: - loader_namespace, _, type_name = type_name.partition('::') - type_name = loader_namespace + '::' + cls.compressed_ref_prefix + type_name - else: - type_name = cls.compressed_ref_prefix + type_name + @classmethod + def get_all_member_functions(cls, t: gdb.Type, include_static: bool, include_constructor: bool) -> set: # set[Function]: + syms = set() + try: + basic_type = cls.get_basic_type(t) + type_name = basic_type.name + members = cls.execout(f"ptype '{type_name}'") + for member in members.split('\n'): + parts = member.strip().split(' ') + is_static = parts[0] == 'static' + if not include_static and is_static: + continue + for part in parts: + if '(' in part: + func_name = part[:part.find('(')] + if include_constructor or func_name != cls.get_unqualified_type_name(type_name): + sym = gdb.lookup_global_symbol(f"{type_name}::{func_name}") + # check if symbol exists and is a function + if sym is not None and sym.type.code == gdb.TYPE_CODE_FUNC: + syms.add(Function(is_static, func_name, sym)) + break + for f in basic_type.fields(): + if f.is_base_class: + syms = syms.union(cls.get_all_member_functions(f.type, include_static, include_constructor)) + except Exception as ex: + trace(f' - get_all_member_function_names({t}) exception: {ex}') + return syms - trace(f' - get_compressed_type({t}) = {type_name}') - return gdb.lookup_type(type_name) + # instance initializer + # there will be one instance of SVMUtil per objfile that is registered + # each objfile has its own types, thus this is necessary to compare against the correct types in memory + # when reloading e.g. a shared library, the addresses of debug info in the relocatable objfile might change + def __init__(self): + self.use_heap_base = try_or_else(lambda: bool(gdb.parse_and_eval('(int)__svm_use_heap_base')), True, gdb.error) + self.compression_shift = try_or_else(lambda: int(gdb.parse_and_eval('(int)__svm_compression_shift')), 0, gdb.error) + self.reserved_bits_mask = try_or_else(lambda: int(gdb.parse_and_eval('(int)__svm_reserved_bits_mask')), 0, gdb.error) + self.object_alignment = try_or_else(lambda: int(gdb.parse_and_eval('(int)__svm_object_alignment')), 0, gdb.error) + self.heap_base_regnum = try_or_else(lambda: int(gdb.parse_and_eval('(int)__svm_heap_base_regnum')), 0, gdb.error) + self.frame_size_status_mask = try_or_else(lambda: int(gdb.parse_and_eval('(int)__svm_frame_size_status_mask')), 0, gdb.error) + self.object_type = gdb.lookup_type("java.lang.Object") + self.stack_type = gdb.lookup_type("long") + self.hub_type = gdb.lookup_type("java.lang.Class") + self.classloader_type = gdb.lookup_type("java.lang.ClassLoader") + self.wrapper_types = [gdb.lookup_type(f'java.lang.{x}') for x in + ["Byte", "Short", "Integer", "Long", "Float", "Double", "Boolean", "Character"] + if gdb.lookup_global_symbol(f'java.lang.{x}') is not None] + self.null = gdb.Value(0).cast(self.object_type.pointer()) + + # instance methods + def get_heap_base(self) -> gdb.Value: + try: + return gdb.selected_frame().read_register(self.heap_base_regnum) + except gdb.error: + # no frame available, return 0 + return 0 + + def get_adr(self, obj: gdb.Value) -> int: + # use null as fallback if we cannot find the address value or obj is null + adr_val = 0 + if obj.type.code == gdb.TYPE_CODE_PTR: + if int(obj) == 0 or (self.use_heap_base and int(obj) == int(self.get_heap_base())): + # obj is null + pass + else: + adr_val = int(obj.dereference().address) + elif obj.address is not None: + adr_val = int(obj.address) + return adr_val - @classmethod - def get_compressed_oop(cls, obj: gdb.Value) -> int: + def is_null(self, obj: gdb.Value) -> bool: + return self.get_adr(obj) == 0 + + def get_compressed_oop(self, obj: gdb.Value) -> int: # use compressed ref if available - only compute it if necessary - if obj.type.code == gdb.TYPE_CODE_PTR and cls.is_compressed(obj.type): + if obj.type.code == gdb.TYPE_CODE_PTR and SVMUtil.is_compressed(obj.type): return int(obj) - obj_adr = adr(obj) + obj_adr = self.get_adr(obj) if obj_adr == 0: return obj_adr # recreate compressed oop from the object address # this reverses the uncompress expression from # com.oracle.objectfile.elf.dwarf.DwarfInfoSectionImpl#writeIndirectOopConversionExpression - is_hub = cls.get_rtt(obj) == cls.hub_type - compression_shift = cls.compression_shift - num_reserved_bits = int.bit_count(cls.reserved_bits_mask) - num_alignment_bits = int.bit_count(cls.object_alignment - 1) + is_hub = self.get_rtt(obj) == self.hub_type + compression_shift = self.compression_shift + num_reserved_bits = int.bit_count(self.reserved_bits_mask) + num_alignment_bits = int.bit_count(self.object_alignment - 1) compressed_oop = obj_adr - if cls.use_heap_base: - compressed_oop -= int(SVMUtil.get_heap_base()) + if self.use_heap_base: + compressed_oop -= int(self.get_heap_base()) assert compression_shift >= 0 compressed_oop = compressed_oop >> compression_shift if is_hub and num_reserved_bits != 0: @@ -196,88 +297,46 @@ def get_compressed_oop(cls, obj: gdb.Value) -> int: return compressed_oop - @classmethod - def get_unqualified_type_name(cls, qualified_type_name: str) -> str: - result = qualified_type_name.split('.')[-1] - result = result.split('$')[-1] - trace(f' - get_unqualified_type_name({qualified_type_name}) = {result}') - return result - - @classmethod - def is_compressed(cls, t: gdb.Type) -> bool: - type_name = cls.get_basic_type(t).name - if type_name is None: - # fallback to the GDB type printer for t - type_name = str(t) - # compressed types from a different classLoader have the format ::_z_. - result = type_name.startswith(cls.compressed_ref_prefix) or ('::' + cls.compressed_ref_prefix) in type_name - trace(f' - is_compressed({type_name}) = {result}') - return result - - @classmethod - def adr_str(cls, obj: gdb.Value) -> str: - if not svm_print_address.absolute_adr and cls.is_compressed(obj.type): - result = f' @z({hex(cls.get_compressed_oop(obj))})' + def adr_str(self, obj: gdb.Value) -> str: + if not svm_print_address.absolute_adr and SVMUtil.is_compressed(obj.type): + result = f' @z({hex(self.get_compressed_oop(obj))})' else: - result = f' @({hex(adr(obj))})' - trace(f' - adr_str({hex(adr(obj))}) = {result}') + result = f' @({hex(self.get_adr(obj))})' + trace(f' - adr_str({hex(self.get_adr(obj))}) = {result}') return result - @classmethod - def prompt_hook(cls, current_prompt: str = None): - cls.current_print_depth = 0 - cls.parents.clear() - cls.selfref_cycles.clear() - SVMCommandPrint.cache.clear() - - @classmethod - def is_selfref(cls, obj: gdb.Value) -> bool: + def is_selfref(self, obj: gdb.Value) -> bool: result = (svm_check_selfref.value and - not cls.is_primitive(obj.type) and - adr(obj) in cls.selfref_cycles) - trace(f' - is_selfref({hex(adr(obj))}) = {result}') + not SVMUtil.is_primitive(obj.type) and + self.get_adr(obj) in SVMUtil.selfref_cycles) + trace(f' - is_selfref({hex(self.get_adr(obj))}) = {result}') return result - @classmethod - def add_selfref(cls, parent: gdb.Value, child: gdb.Value) -> gdb.Value: + def add_selfref(self, parent: gdb.Value, child: gdb.Value) -> gdb.Value: # filter out null references and primitives - if (child.type.code == gdb.TYPE_CODE_PTR and cls.is_null(child)) or cls.is_primitive(child.type): + if (child.type.code == gdb.TYPE_CODE_PTR and self.is_null(child)) or SVMUtil.is_primitive(child.type): return child - child_adr = adr(child) - parent_adr = adr(parent) + child_adr = self.get_adr(child) + parent_adr = self.get_adr(parent) trace(f' - add_selfref(parent={hex(parent_adr)}, child={hex(child_adr)})') - if svm_check_selfref.value and cls.is_reachable(child_adr, parent_adr): + if svm_check_selfref.value and SVMUtil.is_reachable(child_adr, parent_adr): trace(f' ') - cls.selfref_cycles.add(child_adr) + SVMUtil.selfref_cycles.add(child_adr) else: trace(f' {hex(parent_adr)}>') - if child_adr in cls.parents: - cls.parents[child_adr].append(parent_adr) + if child_adr in SVMUtil.parents: + SVMUtil.parents[child_adr].append(parent_adr) else: - cls.parents[child_adr] = [parent_adr] + SVMUtil.parents[child_adr] = [parent_adr] return child - @classmethod - # checks if node this is reachable from node other (this node is parent of other node) - def is_reachable(cls, this: hex, other: hex) -> bool: - test_nodes = [other] - trace(f' - is_reachable(this={this}, other={other}') - while True: - if len(test_nodes) == 0: - return False - if any(this == node for node in test_nodes): - return True - # create a flat list of all ancestors of each tested node - test_nodes = [parent for node in test_nodes for parent in cls.parents.get(node, [])] - - @classmethod - def get_java_string(cls, obj: gdb.Value, gdb_output_string: bool = False) -> str: - if cls.is_null(obj): + def get_java_string(self, obj: gdb.Value, gdb_output_string: bool = False) -> str: + if self.is_null(obj): return "" - trace(f' - get_java_string({hex(adr(obj))})') - coder = cls.get_int_field(obj, 'coder', None) + trace(f' - get_java_string({hex(self.get_adr(obj))})') + coder = self.get_int_field(obj, 'coder', None) if coder is None: codec = 'utf-16' # Java 8 has a char[] with utf-16 bytes_per_char = 2 @@ -290,13 +349,13 @@ def get_java_string(cls, obj: gdb.Value, gdb_output_string: bool = False) -> str }.get(coder) bytes_per_char = 1 - value = cls.get_obj_field(obj, 'value') - if cls.is_null(value): + value = self.get_obj_field(obj, 'value') + if self.is_null(value): return "" - value_content = cls.get_obj_field(value, 'data') - value_length = cls.get_int_field(value, 'len') - if cls.is_null(value_content) or value_length == 0: + value_content = self.get_obj_field(value, 'data') + value_length = self.get_int_field(value, 'len') + if self.is_null(value_content) or value_length == 0: return "" string_data = bytearray() @@ -310,72 +369,68 @@ def get_java_string(cls, obj: gdb.Value, gdb_output_string: bool = False) -> str if gdb_output_string and 0 < svm_print_string_limit.value < value_length: result += "..." - trace(f' - get_java_string({hex(adr(obj))}) = {result}') + trace(f' - get_java_string({hex(self.get_adr(obj))}) = {result}') return result - @classmethod - def get_obj_field(cls, obj: gdb.Value, field_name: str, default: gdb.Value = null) -> gdb.Value: + def get_obj_field(self, obj: gdb.Value, field_name: str, default: gdb.Value = None) -> gdb.Value: try: return obj[field_name] except gdb.error: - return default + return self.null if default is None else default - @classmethod - def get_int_field(cls, obj: gdb.Value, field_name: str, default: int = 0) -> int: - field = cls.get_obj_field(obj, field_name) + def get_int_field(self, obj: gdb.Value, field_name: str, default: int = 0) -> int: + field = self.get_obj_field(obj, field_name) try: return int(field) except (gdb.error, TypeError): # TypeError if field is None already return default - @classmethod - def get_classloader_namespace(cls, obj: gdb.Value) -> str: + def get_classloader_namespace(self, obj: gdb.Value) -> str: try: - hub = cls.get_obj_field(obj, cls.hub_field_name) - if cls.is_null(hub): + hub = self.get_obj_field(obj, SVMUtil.hub_field_name) + if self.is_null(hub): return "" - hub_companion = cls.get_obj_field(hub, 'companion') - if cls.is_null(hub_companion): + hub_companion = self.get_obj_field(hub, 'companion') + if self.is_null(hub_companion): return "" - loader = cls.get_obj_field(hub_companion, 'classLoader') - if cls.is_null(loader): + loader = self.get_obj_field(hub_companion, 'classLoader') + if self.is_null(loader): return "" - loader = cls.cast_to(loader, cls.classloader_type) + loader = self.cast_to(loader, self.classloader_type) - loader_name = cls.get_obj_field(loader, 'nameAndId') - if cls.is_null(loader_name): + loader_name = self.get_obj_field(loader, 'nameAndId') + if self.is_null(loader_name): return "" - loader_namespace = cls.get_java_string(loader_name) + loader_namespace = self.get_java_string(loader_name) trace(f' - get_classloader_namespace loader_namespace: {loader_namespace}') # replicate steps in 'com.oracle.svm.hosted.image.NativeImageBFDNameProvider::uniqueShortLoaderName' # for recreating the loader name stored in the DWARF debuginfo - loader_namespace = cls.get_unqualified_type_name(loader_namespace) + loader_namespace = SVMUtil.get_unqualified_type_name(loader_namespace) loader_namespace = loader_namespace.replace(' @', '_').replace("'", '').replace('"', '') return loader_namespace except gdb.error: pass # ignore gdb errors here and try to continue with no classLoader return "" - @classmethod - def get_rtt(cls, obj: gdb.Value) -> gdb.Type: - static_type = cls.get_basic_type(obj.type) + def get_rtt(self, obj: gdb.Value) -> gdb.Type: + static_type = SVMUtil.get_basic_type(obj.type) # check for interfaces and cast them to Object to make the hub accessible - if cls.get_uncompressed_type(cls.get_basic_type(obj.type)).code == gdb.TYPE_CODE_UNION: - obj = cls.cast_to(obj, cls.object_type) + if self.get_uncompressed_type(SVMUtil.get_basic_type(obj.type)).code == gdb.TYPE_CODE_UNION: + obj = self.cast_to(obj, self.object_type) - hub = cls.get_obj_field(obj, cls.hub_field_name) - if cls.is_null(hub): + hub = self.get_obj_field(obj, SVMUtil.hub_field_name) + if self.is_null(hub): return static_type - name_field = cls.get_obj_field(hub, 'name') - if cls.is_null(name_field): + name_field = self.get_obj_field(hub, 'name') + if self.is_null(name_field): return static_type - rtt_name = cls.get_java_string(name_field) + rtt_name = self.get_java_string(name_field) if rtt_name.startswith('['): array_dimension = rtt_name.count('[') if array_dimension > 0: @@ -397,7 +452,7 @@ def get_rtt(cls, obj: gdb.Value) -> gdb.Type: for _ in range(array_dimension): rtt_name += '[]' - loader_namespace = cls.get_classloader_namespace(obj) + loader_namespace = self.get_classloader_namespace(obj) if loader_namespace != "": try: # try to apply loader namespace @@ -407,134 +462,63 @@ def get_rtt(cls, obj: gdb.Value) -> gdb.Type: else: rtt = gdb.lookup_type(rtt_name) - if cls.is_compressed(obj.type) and not cls.is_compressed(rtt): - rtt = cls.get_compressed_type(rtt) + if SVMUtil.is_compressed(obj.type) and not SVMUtil.is_compressed(rtt): + rtt = self.get_compressed_type(rtt) - trace(f' - get_rtt({hex(adr(obj))}) = {rtt_name}') + trace(f' - get_rtt({hex(self.get_adr(obj))}) = {rtt_name}') return rtt - @classmethod - def cast_to(cls, obj: gdb.Value, t: gdb.Type) -> gdb.Value: + def cast_to(self, obj: gdb.Value, t: gdb.Type) -> gdb.Value: if t is None: return obj # get objects address, take care of compressed oops - if cls.is_compressed(t): - obj_oop = cls.get_compressed_oop(obj) + if SVMUtil.is_compressed(t): + obj_oop = self.get_compressed_oop(obj) else: - obj_oop = adr(obj) + obj_oop = self.get_adr(obj) - trace(f' - cast_to({hex(adr(obj))}, {t})') + trace(f' - cast_to({hex(self.get_adr(obj))}, {t})') if t.code != gdb.TYPE_CODE_PTR: t = t.pointer() - trace(f' - cast_to({hex(adr(obj))}, {t}) returned') + trace(f' - cast_to({hex(self.get_adr(obj))}, {t}) returned') # just use the raw pointer value and cast it instead the obj # casting the obj directly results in issues with compressed oops return obj if t == obj.type else gdb.Value(obj_oop).cast(t) - @classmethod - def get_symbol_adr(cls, symbol: str) -> int: - trace(f' - get_symbol_adr({symbol})') - return gdb.parse_and_eval(symbol).address - - @classmethod - def execout(cls, cmd: str) -> str: - trace(f' - execout({cmd})') - return gdb.execute(cmd, False, True) - - @classmethod - def get_basic_type(cls, t: gdb.Type) -> gdb.Type: - trace(f' - get_basic_type({t})') - while t.code == gdb.TYPE_CODE_PTR: - t = t.target() - return t - - @classmethod - def is_primitive(cls, t: gdb.Type) -> bool: - result = cls.get_basic_type(t).is_scalar - trace(f' - is_primitive({t}) = {result}') + def get_uncompressed_type(self, t: gdb.Type) -> gdb.Type: + # compressed types only exist for java type which are either struct or union + if t.code != gdb.TYPE_CODE_STRUCT and t.code != gdb.TYPE_CODE_UNION: + return t + result = self.get_base_class(t) if SVMUtil.is_compressed(t) else t + trace(f' - get_uncompressed_type({t}) = {result}') return result - @classmethod - def is_primitive_wrapper(cls, t: gdb.Type) -> bool: - result = t in cls.wrapper_types + def is_primitive_wrapper(self, t: gdb.Type) -> bool: + result = t in self.wrapper_types trace(f' - is_primitive_wrapper({t}) = {result}') return result - @classmethod - def is_enum_type(cls, t: gdb.Type) -> bool: - return cls.get_base_class(t) == cls.enum_type + def get_base_class(self, t: gdb.Type) -> gdb.Type: + return t if t == self.object_type else \ + next((f.type for f in t.fields() if f.is_base_class), self.object_type) - @classmethod - def get_base_class(cls, t: gdb.Type) -> gdb.Type: - return t if t == cls.object_type else \ - next((f.type for f in t.fields() if f.is_base_class), cls.object_type) - - @classmethod - def find_shared_types(cls, type_list: list, t: gdb.Type) -> list: # list[gdb.Type] + def find_shared_types(self, type_list: list, t: gdb.Type) -> list: # list[gdb.Type] if len(type_list) == 0: - while t != cls.object_type: + while t != self.object_type: type_list += [t] - t = cls.get_base_class(t) + t = self.get_base_class(t) return type_list else: - while t != cls.object_type: + while t != self.object_type: if t in type_list: return type_list[type_list.index(t):] - t = cls.get_base_class(t) - return [cls.object_type] - - @classmethod - def get_all_fields(cls, t: gdb.Type, include_static: bool) -> list: # list[gdb.Field]: - t = cls.get_basic_type(t) - while t.code == gdb.TYPE_CODE_TYPEDEF: - t = t.target() - t = cls.get_basic_type(t) - if t.code != gdb.TYPE_CODE_STRUCT and t.code != gdb.TYPE_CODE_UNION: - return [] - for f in t.fields(): - if not include_static: - try: - f.bitpos # bitpos attribute is not available for static fields - except AttributeError: # use bitpos access exception to skip static fields - continue - if f.is_base_class: - yield from cls.get_all_fields(f.type, include_static) - else: - yield f + t = self.get_base_class(t) + return [self.object_type] - @classmethod - def get_all_member_functions(cls, t: gdb.Type, include_static: bool, include_constructor: bool) -> set: # set[Function]: - syms = set() - try: - basic_type = cls.get_basic_type(t) - type_name = basic_type.name - members = SVMUtil.execout(f"ptype '{type_name}'") - for member in members.split('\n'): - parts = member.strip().split(' ') - is_static = parts[0] == 'static' - if not include_static and is_static: - continue - for part in parts: - if '(' in part: - func_name = part[:part.find('(')] - if include_constructor or func_name != cls.get_unqualified_type_name(type_name): - sym = gdb.lookup_global_symbol(f"{type_name}::{func_name}") - # check if symbol exists and is a function - if sym is not None and sym.type.code == gdb.TYPE_CODE_FUNC: - syms.add(Function(is_static, func_name, sym)) - break - for f in basic_type.fields(): - if f.is_base_class: - syms = syms.union(cls.get_all_member_functions(f.type, include_static, include_constructor)) - except Exception as ex: - trace(f' - get_all_member_function_names({t}) exception: {ex}') - return syms - - @classmethod - def is_java_type(cls, t: gdb.Type) -> bool: - t = cls.get_uncompressed_type(cls.get_basic_type(t)) + def is_java_type(self, t: gdb.Type) -> bool: + t = self.get_uncompressed_type(SVMUtil.get_basic_type(t)) # Check for existing ".class" symbol (which exists for every java type in a native image) # a java class is represented by a struct, interfaces are represented by a union # only structs contain a "hub" field, thus just checking for a hub field does not work for interfaces @@ -544,45 +528,73 @@ def is_java_type(cls, t: gdb.Type) -> bool: trace(f' - is_java_obj({t}) = {result}') return result + # returns the compressed variant of t if available, otherwise returns the basic type of t (without pointers) + def get_compressed_type(self, t: gdb.Type) -> gdb.Type: + t = SVMUtil.get_basic_type(t) + # compressed types only exist for java types which are either struct or union + # do not compress types that already have the compressed prefix + if not self.is_java_type(t) or SVMUtil.is_compressed(t): + return t + + type_name = t.name + # java types only contain '::' if there is a classloader namespace + if '::' in type_name: + loader_namespace, _, type_name = type_name.partition('::') + type_name = loader_namespace + '::' + SVMUtil.compressed_ref_prefix + type_name + else: + type_name = SVMUtil.compressed_ref_prefix + type_name + + try: + result_type = gdb.lookup_type(type_name) + trace(f' - could not find compressed type "{type_name}" using uncompressed type') + except gdb.error as ex: + trace(ex) + result_type = t + + trace(f' - get_compressed_type({t}) = {t.name}') + return result_type + class SVMPPString: - def __init__(self, obj: gdb.Value, java: bool = True): - trace(f' - __init__({hex(adr(obj))})') + def __init__(self, svm_util: SVMUtil, obj: gdb.Value, java: bool = True): + trace(f' - __init__({hex(svm_util.get_adr(obj))})') self.__obj = obj self.__java = java + self.__svm_util = svm_util def to_string(self) -> str: trace(' - to_string') if self.__java: try: - value = '"' + SVMUtil.get_java_string(self.__obj, True) + '"' + value = '"' + self.__svm_util.get_java_string(self.__obj, True) + '"' except gdb.error: return SVMPPConst(None) else: value = str(self.__obj) value = value[value.index('"'):] if svm_print_address.with_adr: - value += SVMUtil.adr_str(self.__obj) + value += self.__svm_util.adr_str(self.__obj) trace(f' - to_string = {value}') return value class SVMPPArray: - def __init__(self, obj: gdb.Value, java_array: bool = True): - trace(f' - __init__(obj={obj.type} @ {hex(adr(obj))}, java_array={java_array})') + def __init__(self, svm_util: SVMUtil, obj: gdb.Value, java_array: bool = True): + trace(f' - __init__(obj={obj.type} @ {hex(svm_util.get_adr(obj))}, java_array={java_array})') if java_array: - self.__length = SVMUtil.get_int_field(obj, 'len') - self.__array = SVMUtil.get_obj_field(obj, 'data', None) - if SVMUtil.is_null(self.__array): + self.__length = svm_util.get_int_field(obj, 'len') + self.__array = svm_util.get_obj_field(obj, 'data', None) + if svm_util.is_null(self.__array): self.__array = None else: self.__length = obj.type.range()[-1] + 1 self.__array = obj self.__obj = obj self.__java_array = java_array - self.__skip_children = SVMUtil.is_selfref(obj) or 0 <= svm_print_depth_limit.value <= SVMUtil.current_print_depth + self.__skip_children = svm_util.is_selfref(obj) or 0 <= svm_print_depth_limit.value <= SVMUtil.current_print_depth if not self.__skip_children: SVMUtil.current_print_depth += 1 + self.__svm_util = svm_util def display_hint(self) -> str: trace(' - display_hint = array') @@ -591,15 +603,15 @@ def display_hint(self) -> str: def to_string(self) -> str: trace(' - to_string') if self.__java_array: - rtt = SVMUtil.get_rtt(self.__obj) - value = str(SVMUtil.get_uncompressed_type(rtt)) + rtt = self.__svm_util.get_rtt(self.__obj) + value = str(self.__svm_util.get_uncompressed_type(rtt)) value = value.replace('[]', f'[{self.__length}]') else: value = str(self.__obj.type) if self.__skip_children: value += ' = {...}' if svm_print_address.with_adr: - value += SVMUtil.adr_str(self.__obj) + value += self.__svm_util.adr_str(self.__obj) trace(f' - to_string = {value}') return value @@ -619,22 +631,23 @@ def children(self) -> Iterable[object]: yield str(index), '...' return trace(f' - children[{index}]') - yield str(index), SVMUtil.add_selfref(self.__obj, elem) + yield str(index), self.__svm_util.add_selfref(self.__obj, elem) SVMUtil.current_print_depth -= 1 class SVMPPClass: - def __init__(self, obj: gdb.Value, java_class: bool = True): - trace(f' - __init__({obj.type} @ {hex(adr(obj))})') + def __init__(self, svm_util: SVMUtil, obj: gdb.Value, java_class: bool = True): + trace(f' - __init__({obj.type} @ {hex(svm_util.get_adr(obj))})') self.__obj = obj self.__java_class = java_class - self.__skip_children = SVMUtil.is_selfref(obj) or 0 <= svm_print_depth_limit.value <= SVMUtil.current_print_depth + self.__skip_children = svm_util.is_selfref(obj) or 0 <= svm_print_depth_limit.value <= SVMUtil.current_print_depth if not self.__skip_children: SVMUtil.current_print_depth += 1 + self.__svm_util = svm_util def __getitem__(self, key: str) -> gdb.Value: trace(f' - __getitem__({key})') - item = SVMUtil.get_obj_field(self.__obj, key, None) + item = self.__svm_util.get_obj_field(self.__obj, key, None) if item is None: return None pp_item = gdb.default_visualizer(item) @@ -644,14 +657,14 @@ def to_string(self) -> str: trace(' - to_string') try: if self.__java_class: - rtt = SVMUtil.get_rtt(self.__obj) - result = SVMUtil.get_uncompressed_type(rtt).name + rtt = self.__svm_util.get_rtt(self.__obj) + result = self.__svm_util.get_uncompressed_type(rtt).name else: result = "object" if self.__obj.type.name is None else self.__obj.type.name if self.__skip_children: result += ' = {...}' if svm_print_address.with_adr: - result += SVMUtil.adr_str(self.__obj) + result += self.__svm_util.adr_str(self.__obj) trace(f' - to_string = {result}') return result except gdb.error as ex: @@ -669,35 +682,37 @@ def children(self) -> Iterable[object]: if self.__java_class and 0 <= svm_print_field_limit.value <= index: yield f, '...' return - yield f, SVMUtil.add_selfref(self.__obj, self.__obj[f]) + yield f, self.__svm_util.add_selfref(self.__obj, self.__obj[f]) SVMUtil.current_print_depth -= 1 class SVMPPEnum: - def __init__(self, obj: gdb.Value): - trace(f' - __init__({hex(adr(obj))})') + def __init__(self, svm_util: SVMUtil, obj: gdb.Value): + trace(f' - __init__({hex(svm_util.get_adr(obj))})') self.__obj = obj - self.__name = SVMUtil.get_obj_field(self.__obj, 'name', "") - self.__ordinal = SVMUtil.get_int_field(self.__obj, 'ordinal', None) + self.__name = svm_util.get_obj_field(self.__obj, 'name', "") + self.__ordinal = svm_util.get_int_field(self.__obj, 'ordinal', None) + self.__svm_util = svm_util def to_string(self) -> str: - result = SVMUtil.get_java_string(self.__name) + f"({self.__ordinal})" + result = self.__svm_util.get_java_string(self.__name) + f"({self.__ordinal})" if svm_print_address.with_adr: - result += SVMUtil.adr_str(self.__obj) + result += self.__svm_util.adr_str(self.__obj) trace(f' - to_string = {result}') return result class SVMPPBoxedPrimitive: - def __init__(self, obj: gdb.Value): - trace(f' - __init__({obj.type} @ {hex(adr(obj))})') + def __init__(self, svm_util: SVMUtil, obj: gdb.Value): + trace(f' - __init__({obj.type} @ {hex(svm_util.get_adr(obj))})') self.__obj = obj - self.__value = SVMUtil.get_obj_field(self.__obj, 'value', obj.type.name) + self.__value = svm_util.get_obj_field(self.__obj, 'value', obj.type.name) + self.__svm_util = svm_util def to_string(self) -> str: result = str(self.__value) if svm_print_address.with_adr: - result += SVMUtil.adr_str(self.__obj) + result += self.__svm_util.adr_str(self.__obj) trace(f' - to_string = {result}') return result @@ -714,52 +729,55 @@ def to_string(self) -> str: class SVMPrettyPrinter(gdb.printing.PrettyPrinter): - def __init__(self): + def __init__(self, svm_util: SVMUtil): super().__init__(SVMUtil.pretty_printer_name) + self.enum_type = gdb.lookup_type("java.lang.Enum") + self.string_type = gdb.lookup_type("java.lang.String") + self.svm_util = svm_util def __call__(self, obj: gdb.Value): - trace(f' - __call__({obj.type} @ {hex(adr(obj))})') + trace(f' - __call__({obj.type} @ {hex(self.svm_util.get_adr(obj))})') - if not SVMUtil.is_primitive(obj.type) and SVMUtil.is_java_type(obj.type): + if not SVMUtil.is_primitive(obj.type) and self.svm_util.is_java_type(obj.type): # Filter out references to the null literal - if SVMUtil.is_null(obj): + if self.svm_util.is_null(obj): return SVMPPConst(None) - rtt = SVMUtil.get_rtt(obj) - uncompressed_rtt = SVMUtil.get_uncompressed_type(rtt) - obj = SVMUtil.cast_to(obj, rtt) + rtt = self.svm_util.get_rtt(obj) + uncompressed_rtt = self.svm_util.get_uncompressed_type(rtt) + obj = self.svm_util.cast_to(obj, rtt) # filter for primitive wrappers - if SVMUtil.is_primitive_wrapper(uncompressed_rtt): - return SVMPPBoxedPrimitive(obj) + if self.svm_util.is_primitive_wrapper(uncompressed_rtt): + return SVMPPBoxedPrimitive(self.svm_util, obj) # filter for strings - if uncompressed_rtt == SVMUtil.string_type: - return SVMPPString(obj) + if uncompressed_rtt == self.string_type: + return SVMPPString(self.svm_util, obj) # filter for arrays if uncompressed_rtt.name.endswith("[]"): - return SVMPPArray(obj) + return SVMPPArray(self.svm_util, obj) # filter for enum values - if SVMUtil.is_enum_type(uncompressed_rtt): - return SVMPPEnum(obj) + if self.svm_util.get_base_class(uncompressed_rtt) == self.enum_type: + return SVMPPEnum(self.svm_util, obj) # Any other Class ... if svm_use_hlrep.value: - pp = make_high_level_object(obj, uncompressed_rtt.name) + pp = make_high_level_object(self.svm_util, obj, uncompressed_rtt.name) else: - pp = SVMPPClass(obj) + pp = SVMPPClass(self.svm_util, obj) return pp # no complex java type -> handle foreign types for selfref checks elif obj.type.code == gdb.TYPE_CODE_PTR and obj.type.target().code != gdb.TYPE_CODE_VOID: # Filter out references to the null literal - if SVMUtil.is_null(obj): + if self.svm_util.is_null(obj): return SVMPPConst(None) return self.__call__(obj.dereference()) elif obj.type.code == gdb.TYPE_CODE_ARRAY: - return SVMPPArray(obj, False) + return SVMPPArray(self.svm_util, obj, False) elif obj.type.code == gdb.TYPE_CODE_TYPEDEF: # try to expand foreign c structs try: @@ -768,7 +786,7 @@ def __call__(self, obj: gdb.Value): except gdb.error as err: return None elif obj.type.code == gdb.TYPE_CODE_STRUCT: - return SVMPPClass(obj, False) + return SVMPPClass(self.svm_util, obj, False) elif SVMUtil.is_primitive(obj.type): if obj.type.name == "char" and obj.type.sizeof == 2: return SVMPPConst(repr(chr(obj))) @@ -792,20 +810,21 @@ def HLRep(original_class): class ArrayList: target_type = 'java.util.ArrayList' - def __init__(self, obj: gdb.Value): - trace(f' - __init__({obj.type} @ {hex(adr(obj))})') - self.__size = SVMUtil.get_int_field(obj, 'size') - element_data = SVMUtil.get_obj_field(obj, 'elementData') - if SVMUtil.is_null(element_data): + def __init__(self, svm_util: SVMUtil, obj: gdb.Value): + trace(f' - __init__({obj.type} @ {hex(svm_util.get_adr(obj))})') + self.__size = svm_util.get_int_field(obj, 'size') + element_data = svm_util.get_obj_field(obj, 'elementData') + if svm_util.is_null(element_data): self.__data = None else: - self.__data = SVMUtil.get_obj_field(element_data, 'data', None) - if self.__data is not None and SVMUtil.is_null(self.__data): + self.__data = svm_util.get_obj_field(element_data, 'data', None) + if self.__data is not None and svm_util.is_null(self.__data): self.__data = None self.__obj = obj - self.__skip_children = SVMUtil.is_selfref(obj) or 0 <= svm_print_depth_limit.value <= SVMUtil.current_print_depth + self.__skip_children = svm_util.is_selfref(obj) or 0 <= svm_print_depth_limit.value <= SVMUtil.current_print_depth if not self.__skip_children: SVMUtil.current_print_depth += 1 + self.__svm_util = svm_util def to_string(self) -> str: trace(' - to_string') @@ -818,7 +837,7 @@ def to_string(self) -> str: if self.__skip_children: res += ' = {...}' if svm_print_address.with_adr: - res += SVMUtil.adr_str(self.__obj) + res += self.__svm_util.adr_str(self.__obj) trace(f' - to_string = {res}') return res @@ -826,9 +845,9 @@ def infer_generic_types(self) -> str: elem_type: list = [] # list[gdb.Type] for i, elem in enumerate(self, 1): - if not SVMUtil.is_null(elem): # check for null values - elem_type = SVMUtil.find_shared_types(elem_type, SVMUtil.get_rtt(elem)) - if (len(elem_type) > 0 and elem_type[0] == SVMUtil.object_type) or (0 <= svm_infer_generics.value <= i): + if not self.__svm_util.is_null(elem): # check for null values + elem_type = self.__svm_util.find_shared_types(elem_type, self.__svm_util.get_rtt(elem)) + if (len(elem_type) > 0 and elem_type[0] == self.__svm_util.object_type) or (0 <= svm_infer_generics.value <= i): break return None if len(elem_type) == 0 else SVMUtil.get_unqualified_type_name(elem_type[0].name) @@ -844,15 +863,15 @@ def __iter__(self) -> gdb.Value: yield self.__data[i] def children(self) -> Iterable[object]: - trace(f' - children({self.__obj.type} @ {hex(adr(self.__obj))})') + trace(f' - children({self.__obj.type} @ {hex(self.__svm_util.get_adr(self.__obj))})') if self.__skip_children: return for index, elem in enumerate(self): if 0 <= svm_print_element_limit.value <= index: yield str(index), '...' return - trace(f' - children({self.__obj.type} @ {hex(adr(self.__obj))})[{index}]') - yield str(index), SVMUtil.add_selfref(self.__obj, elem) + trace(f' - children({self.__obj.type} @ {hex(self.__svm_util.get_adr(self.__obj))})[{index}]') + yield str(index), self.__svm_util.add_selfref(self.__obj, elem) SVMUtil.current_print_depth -= 1 @@ -860,24 +879,25 @@ def children(self) -> Iterable[object]: class HashMap: target_type = 'java.util.HashMap' - def __init__(self, obj: gdb.Value): - trace(f' - __init__({obj.type} @ {hex(adr(obj))})') + def __init__(self, svm_util: SVMUtil, obj: gdb.Value): + trace(f' - __init__({obj.type} @ {hex(svm_util.get_adr(obj))})') - self.__size = SVMUtil.get_int_field(obj, 'size') - table = SVMUtil.get_obj_field(obj, 'table') - if SVMUtil.is_null(table): + self.__size = svm_util.get_int_field(obj, 'size') + table = svm_util.get_obj_field(obj, 'table') + if svm_util.is_null(table): self.__data = None self.__table_len = 0 else: - self.__data = SVMUtil.get_obj_field(table, 'data', None) - if self.__data is not None and SVMUtil.is_null(self.__data): + self.__data = svm_util.get_obj_field(table, 'data', None) + if self.__data is not None and svm_util.is_null(self.__data): self.__data = None - self.__table_len = SVMUtil.get_int_field(table, 'len') + self.__table_len = svm_util.get_int_field(table, 'len') self.__obj = obj - self.__skip_children = SVMUtil.is_selfref(obj) or 0 <= svm_print_depth_limit.value <= SVMUtil.current_print_depth + self.__skip_children = svm_util.is_selfref(obj) or 0 <= svm_print_depth_limit.value <= SVMUtil.current_print_depth if not self.__skip_children: SVMUtil.current_print_depth += 1 + self.__svm_util = svm_util def to_string(self) -> str: trace(' - to_string') @@ -889,7 +909,7 @@ def to_string(self) -> str: if self.__skip_children: res += ' = {...}' if svm_print_address.with_adr: - res += SVMUtil.adr_str(self.__obj) + res += self.__svm_util.adr_str(self.__obj) trace(f' - to_string = {res}') return res @@ -900,12 +920,12 @@ def infer_generic_types(self) -> tuple: # (str, str): for i, kv in enumerate(self, 1): key, value = kv # if len(*_type) = 1 we could just infer the type java.lang.Object, ignore null values - if not SVMUtil.is_null(key) and (len(key_type) == 0 or key_type[0] != SVMUtil.object_type): - key_type = SVMUtil.find_shared_types(key_type, SVMUtil.get_rtt(key)) - if not SVMUtil.is_null(value) and (len(value_type) == 0 or value_type[0] != SVMUtil.object_type): - value_type = SVMUtil.find_shared_types(value_type, SVMUtil.get_rtt(value)) - if (0 <= svm_infer_generics.value <= i) or (len(key_type) > 0 and key_type[0] == SVMUtil.object_type and - len(value_type) > 0 and value_type[0] == SVMUtil.object_type): + if not self.__svm_util.is_null(key) and (len(key_type) == 0 or key_type[0] != self.__svm_util.object_type): + key_type = self.__svm_util.find_shared_types(key_type, self.__svm_util.get_rtt(key)) + if not self.__svm_util.is_null(value) and (len(value_type) == 0 or value_type[0] != self.__svm_util.object_type): + value_type = self.__svm_util.find_shared_types(value_type, self.__svm_util.get_rtt(value)) + if (0 <= svm_infer_generics.value <= i) or (len(key_type) > 0 and key_type[0] == self.__svm_util.object_type and + len(value_type) > 0 and value_type[0] == self.__svm_util.object_type): break key_type_name = '?' if len(key_type) == 0 else SVMUtil.get_unqualified_type_name(key_type[0].name) @@ -921,34 +941,122 @@ def __iter__(self) -> tuple: # (gdb.Value, gdb.Value): trace(' - __iter__') for i in range(self.__table_len): obj = self.__data[i] - while not SVMUtil.is_null(obj): - key = SVMUtil.get_obj_field(obj, 'key') - value = SVMUtil.get_obj_field(obj, 'value') + while not self.__svm_util.is_null(obj): + key = self.__svm_util.get_obj_field(obj, 'key') + value = self.__svm_util.get_obj_field(obj, 'value') + yield key, value + obj = self.__svm_util.get_obj_field(obj, 'next') + + def children(self) -> Iterable[object]: + trace(f' - children({self.__obj.type} @ {hex(self.__svm_util.get_adr(self.__obj))})') + if self.__skip_children: + return + for index, (key, value) in enumerate(self): + if 0 <= svm_print_element_limit.value <= index: + yield str(index), '...' + return + trace(f' - children({self.__obj.type} @ {hex(self.__svm_util.get_adr(self.__obj))})[{index}]') + yield f"key{index}", self.__svm_util.add_selfref(self.__obj, key) + yield f"value{index}", self.__svm_util.add_selfref(self.__obj, value) + SVMUtil.current_print_depth -= 1 + + +@HLRep +class EconomicMapImpl: + target_type = 'org.graalvm.collections.EconomicMapImpl' + + def __init__(self, svm_util: SVMUtil, obj: gdb.Value): + trace(f' - __init__({obj.type} @ {hex(svm_util.get_adr(obj))})') + + self.__size = svm_util.get_int_field(obj, 'totalEntries') - svm_util.get_int_field(obj, 'deletedEntries') + entries = svm_util.get_obj_field(obj, 'entries') + if svm_util.is_null(entries): + self.__data = None + self.__array_len = 0 + else: + self.__data = svm_util.get_obj_field(entries, 'data', None) + if self.__data is not None and svm_util.is_null(self.__data): + self.__data = None + self.__array_len = svm_util.get_int_field(entries, 'len') + + self.__obj = obj + self.__skip_children = svm_util.is_selfref(obj) or 0 <= svm_print_depth_limit.value <= SVMUtil.current_print_depth + if not self.__skip_children: + SVMUtil.current_print_depth += 1 + self.__svm_util = svm_util + + def to_string(self) -> str: + trace(' - to_string') + res = self.target_type + if svm_infer_generics.value != 0: + key_type, value_type = self.infer_generic_types() + res += f"<{key_type}, {value_type}>" + res += f'({self.__size})' + if self.__skip_children: + res += ' = {...}' + if svm_print_address.with_adr: + res += self.__svm_util.adr_str(self.__obj) + trace(f' - to_string = {res}') + return res + + def infer_generic_types(self) -> tuple: # (str, str): + key_type: list = [] # list[gdb.Type] + value_type: list = [] # list[gdb.Type] + + for i, kv in enumerate(self, 1): + key, value = kv + # if len(*_type) = 1 we could just infer the type java.lang.Object, ignore null values + if not self.__svm_util.is_null(key) and (len(key_type) == 0 or key_type[0] != self.__svm_util.object_type): + key_type = self.__svm_util.find_shared_types(key_type, self.__svm_util.get_rtt(key)) + if not self.__svm_util.is_null(value) and (len(value_type) == 0 or value_type[0] != self.__svm_util.object_type): + value_type = self.__svm_util.find_shared_types(value_type, self.__svm_util.get_rtt(value)) + if (0 <= svm_infer_generics.value <= i) or (len(key_type) > 0 and key_type[0] == self.__svm_util.object_type and + len(value_type) > 0 and value_type[0] == self.__svm_util.object_type): + break + + key_type_name = '?' if len(key_type) == 0 else SVMUtil.get_unqualified_type_name(key_type[0].name) + value_type_name = '?' if len(value_type) == 0 else SVMUtil.get_unqualified_type_name(value_type[0].name) + + return key_type_name, value_type_name + + def display_hint(self) -> str: + trace(' - display_hint = map') + return "map" + + def __iter__(self) -> tuple: # (gdb.Value, gdb.Value): + trace(' - __iter__') + key = 0 + for i in range(self.__array_len): + if i % 2 == 0: + if self.__svm_util.is_null(self.__data[i]): + break + key = self.__data[i] + else: + value = self.__data[i] yield key, value - obj = SVMUtil.get_obj_field(obj, 'next') def children(self) -> Iterable[object]: - trace(f' - children({self.__obj.type} @ {hex(adr(self.__obj))})') + trace(f' - children({self.__obj.type} @ {hex(self.__svm_util.get_adr(self.__obj))})') if self.__skip_children: return for index, (key, value) in enumerate(self): if 0 <= svm_print_element_limit.value <= index: yield str(index), '...' return - trace(f' - children({self.__obj.type} @ {hex(adr(self.__obj))})[{index}]') - yield f"key{index}", SVMUtil.add_selfref(self.__obj, key) - yield f"value{index}", SVMUtil.add_selfref(self.__obj, value) + trace(f' - children({self.__obj.type} @ {hex(self.__svm_util.get_adr(self.__obj))})[{index}]') + yield f"key{index}", self.__svm_util.add_selfref(self.__obj, key) + yield f"value{index}", self.__svm_util.add_selfref(self.__obj, value) SVMUtil.current_print_depth -= 1 -def make_high_level_object(obj: gdb.Value, rtt_name: str) -> gdb.Value: +def make_high_level_object(svm_util: SVMUtil, obj: gdb.Value, rtt_name: str) -> gdb.Value: try: trace(f'try makeHighLevelObject for {rtt_name}') hl_rep_class = SVMUtil.hlreps[rtt_name] - return hl_rep_class(obj) + return hl_rep_class(svm_util, obj) except Exception as ex: trace(f' exception: {ex}') - return SVMPPClass(obj) + return SVMPPClass(svm_util, obj) class SVMPrintParam(gdb.Parameter): @@ -1207,16 +1315,16 @@ def __init__(self, complete): # complete: list[str] | int def __init__(self): super().__init__('p', gdb.COMMAND_DATA) + self.svm_util = SVMUtil() - @staticmethod - def cast_to_rtt(obj: gdb.Value, obj_str: str) -> tuple: # tuple[gdb.Value, str]: + def cast_to_rtt(self, obj: gdb.Value, obj_str: str) -> tuple: # tuple[gdb.Value, str]: static_type = SVMUtil.get_basic_type(obj.type) - rtt = SVMUtil.get_rtt(obj) - obj = SVMUtil.cast_to(obj, rtt) + rtt = self.svm_util.get_rtt(obj) + obj = self.svm_util.cast_to(obj, rtt) if static_type.name == rtt.name: return obj, obj_str else: - obj_oop = SVMUtil.get_compressed_oop(obj) if SVMUtil.is_compressed(rtt) else adr(obj) + obj_oop = self.svm_util.get_compressed_oop(obj) if SVMUtil.is_compressed(rtt) else self.svm_util.get_adr(obj) return obj, f"(('{rtt.name}' *)({obj_oop}))" # Define the token specifications @@ -1295,8 +1403,8 @@ def object(self, completion: bool = False) -> str: # could not parse obj_str as obj -> let gdb deal with it later return self.t.val base_obj_str = obj_str - if not SVMUtil.is_primitive(obj.type) and SVMUtil.is_java_type(obj.type): - obj, obj_str = SVMCommandPrint.cast_to_rtt(obj, obj_str) + if not SVMUtil.is_primitive(obj.type) and self.svm_util.is_java_type(obj.type): + obj, obj_str = self.cast_to_rtt(obj, obj_str) self.cache[base_obj_str] = (obj, obj_str) while self.sym == "FA" or self.sym == "LPAREN" or self.sym == "LBRACK": @@ -1319,8 +1427,8 @@ def object(self, completion: bool = False) -> str: obj = obj[self.t.val] obj_str += "." + self.t.val base_obj_str = obj_str - if not SVMUtil.is_primitive(obj.type) and SVMUtil.is_java_type(obj.type): - obj, obj_str = SVMCommandPrint.cast_to_rtt(obj, obj_str) + if not SVMUtil.is_primitive(obj.type) and self.svm_util.is_java_type(obj.type): + obj, obj_str = self.cast_to_rtt(obj, obj_str) self.cache[base_obj_str] = (obj, obj_str) elif self.sym == "LPAREN": if obj.type.code != gdb.TYPE_CODE_METHOD: @@ -1338,8 +1446,8 @@ def object(self, completion: bool = False) -> str: obj_str += f"({param_str})" obj = gdb.parse_and_eval(obj_str) base_obj_str = obj_str - if not SVMUtil.is_primitive(obj.type) and SVMUtil.is_java_type(obj.type): - obj, obj_str = SVMCommandPrint.cast_to_rtt(obj, obj_str) + if not SVMUtil.is_primitive(obj.type) and self.svm_util.is_java_type(obj.type): + obj, obj_str = self.cast_to_rtt(obj, obj_str) self.cache[base_obj_str] = (obj, obj_str) elif self.sym == "LBRACK": is_array = obj.type.is_array_like or isinstance(gdb.default_visualizer(obj), SVMPPArray) @@ -1351,9 +1459,9 @@ def object(self, completion: bool = False) -> str: i_obj_str = self.array_index(completion) if self.sym == "" and completion: # handle autocompletion for array index - if SVMUtil.is_java_type(obj.type) and (i_obj_str == '' or i_obj_str.isnumeric()): + if self.svm_util.is_java_type(obj.type) and (i_obj_str == '' or i_obj_str.isnumeric()): index = 0 if i_obj_str == '' else int(i_obj_str) - length = SVMUtil.get_int_field(obj, 'len') + length = self.svm_util.get_int_field(obj, 'len') complete = [] if index < length: complete.append(f'{index}]') @@ -1369,19 +1477,19 @@ def object(self, completion: bool = False) -> str: else: i_obj = gdb.parse_and_eval(i_obj_str) self.check('RBRACK') - if is_array and SVMUtil.is_java_type(obj.type): + if is_array and self.svm_util.is_java_type(obj.type): obj_str += ".data" - obj = SVMUtil.get_obj_field(obj, 'data', obj) + obj = self.svm_util.get_obj_field(obj, 'data', obj) if isinstance(gdb.default_visualizer(i_obj), SVMPPBoxedPrimitive) or SVMUtil.is_primitive(i_obj.type): if isinstance(gdb.default_visualizer(i_obj), SVMPPBoxedPrimitive): - index = SVMUtil.get_int_field(i_obj, 'value') + index = self.svm_util.get_int_field(i_obj, 'value') else: index = int(i_obj) obj_str += f"[{index}]" obj = obj[index] base_obj_str = obj_str - if not SVMUtil.is_primitive(obj.type) and SVMUtil.is_java_type(obj.type): - obj, obj_str = SVMCommandPrint.cast_to_rtt(obj, obj_str) + if not SVMUtil.is_primitive(obj.type) and self.svm_util.is_java_type(obj.type): + obj, obj_str = self.cast_to_rtt(obj, obj_str) self.cache[base_obj_str] = (obj, obj_str) else: # let gdb figure out what to do @@ -1391,8 +1499,8 @@ def object(self, completion: bool = False) -> str: else: obj = gdb.parse_and_eval(obj_str) base_obj_str = obj_str - if not SVMUtil.is_primitive(obj.type) and SVMUtil.is_java_type(obj.type): - obj, obj_str = SVMCommandPrint.cast_to_rtt(obj, obj_str) + if not SVMUtil.is_primitive(obj.type) and self.svm_util.is_java_type(obj.type): + obj, obj_str = self.cast_to_rtt(obj, obj_str) self.cache[base_obj_str] = (obj, obj_str) if isinstance(gdb.default_visualizer(obj), SVMPPBoxedPrimitive): @@ -1412,9 +1520,9 @@ def params(self, completion: bool = False) -> str: obj_str += self.t.val obj = gdb.parse_and_eval(obj_str) # check if gdb can handle the current param - if SVMUtil.is_java_type(obj.type) and SVMUtil.is_compressed(obj.type): + if self.svm_util.is_java_type(obj.type) and SVMUtil.is_compressed(obj.type): # uncompress compressed java params - obj_str = f"(('{SVMUtil.get_uncompressed_type(SVMUtil.get_basic_type(obj.type)).name}' *)({adr(obj)}))" + obj_str = f"(('{self.svm_util.get_uncompressed_type(SVMUtil.get_basic_type(obj.type)).name}' *)({self.svm_util.get_adr(obj)}))" param_str += obj_str if self.sym == "COMMA": self.scan() @@ -1438,6 +1546,8 @@ def array_index(self, completion: bool = False) -> str: return i_obj_str def complete(self, text: str, word: str): # -> list[str] | int: + self.svm_util = SVMUtil() + if not svm_print.value: return gdb.COMPLETE_EXPRESSION @@ -1451,6 +1561,8 @@ def complete(self, text: str, word: str): # -> list[str] | int: return gdb.COMPLETE_NONE def invoke(self, arg: str, from_tty: bool) -> None: + self.svm_util = SVMUtil() + if not svm_print.value: gdb.execute(f"print {arg}") return @@ -1488,59 +1600,86 @@ def invoke(self, arg: str, from_tty: bool) -> None: class SVMFrameUnwinder(gdb.unwinder.Unwinder): - AMD64_RBP = 6 - AMD64_RSP = 7 - AMD64_RIP = 16 - def __init__(self): + def __init__(self, svm_util: SVMUtil): super().__init__('SubstrateVM FrameUnwinder') - self.stack_type = gdb.lookup_type('long') - self.deopt_frame_type = gdb.lookup_type('com.oracle.svm.core.deopt.DeoptimizedFrame') - - def __call__(self, pending_frame): - if SVMUtil.deopt_stub_adr == 0: - # find deopt stub after its properly loaded - SVMUtil.deopt_stub_adr = gdb.lookup_global_symbol('com.oracle.svm.core.deopt.Deoptimizer::deoptStub', - gdb.SYMBOL_VAR_DOMAIN).value().address - + self.eager_deopt_stub_adr = None + self.lazy_deopt_stub_primitive_adr = None + self.lazy_deopt_stub_object_adr = None + self.svm_util = svm_util + + def __call__(self, pending_frame: gdb.Frame): + if self.eager_deopt_stub_adr is None: + self.eager_deopt_stub_adr = SVMUtil.get_eager_deopt_stub_adr() + self.lazy_deopt_stub_primitive_adr = SVMUtil.get_lazy_deopt_stub_primitive_adr() + self.lazy_deopt_stub_object_adr = SVMUtil.get_lazy_deopt_stub_object_adr() + + sp = 0 try: - rsp = pending_frame.read_register('sp') - rip = pending_frame.read_register('pc') - if int(rip) == SVMUtil.deopt_stub_adr: - deopt_frame_stack_slot = rsp.cast(self.stack_type.pointer()).dereference() - deopt_frame = deopt_frame_stack_slot.cast(self.deopt_frame_type.pointer()) - source_frame_size = deopt_frame['sourceTotalFrameSize'] + sp = pending_frame.read_register('sp') + pc = pending_frame.read_register('pc') + if int(pc) == self.eager_deopt_stub_adr: + deopt_frame_stack_slot = sp.cast(self.svm_util.stack_type.pointer()).dereference() + deopt_frame = deopt_frame_stack_slot.cast(self.svm_util.get_compressed_type(self.svm_util.object_type).pointer()) + rtt = self.svm_util.get_rtt(deopt_frame) + deopt_frame = self.svm_util.cast_to(deopt_frame, rtt) + encoded_frame_size = self.svm_util.get_int_field(deopt_frame, 'sourceEncodedFrameSize') + source_frame_size = encoded_frame_size & ~self.svm_util.frame_size_status_mask + + # Now find the register-values for the caller frame + unwind_info = pending_frame.create_unwind_info(gdb.unwinder.FrameId(sp, pc)) + caller_sp = sp + int(source_frame_size) + unwind_info.add_saved_register('sp', gdb.Value(caller_sp)) + # try to fetch return address directly from stack + caller_pc = gdb.Value(caller_sp - 8).cast(self.svm_util.stack_type.pointer()).dereference() + unwind_info.add_saved_register('pc', gdb.Value(caller_pc)) + return unwind_info + elif int(pc) == self.lazy_deopt_stub_primitive_adr or int(pc) == self.lazy_deopt_stub_object_adr: # Now find the register-values for the caller frame - unwind_info = pending_frame.create_unwind_info(gdb.unwinder.FrameId(rsp, rip)) - caller_rsp = rsp + int(source_frame_size) - unwind_info.add_saved_register(self.AMD64_RSP, gdb.Value(caller_rsp)) - caller_rip = gdb.Value(caller_rsp - 8).cast(self.stack_type.pointer()).dereference() - unwind_info.add_saved_register(self.AMD64_RIP, gdb.Value(caller_rip)) + # We only have the original pc for lazy deoptimization -> unwind to original pc with same sp + # This is the best guess we can make without knowing the return address and frame size of the lazily deoptimized frame + unwind_info = pending_frame.create_unwind_info(gdb.unwinder.FrameId(sp, pc)) + unwind_info.add_saved_register('sp', gdb.Value(sp)) + caller_pc = sp.cast(self.svm_util.stack_type.pointer()).dereference() + unwind_info.add_saved_register('pc', gdb.Value(caller_pc)) return unwind_info - except Exception as e: - print(e) + except Exception as ex: + trace(f' - Failed to unwind frame at {hex(sp)}') + trace(ex) # Fallback to default frame unwinding via debug_frame (dwarf) return None -class SVMFrameFilter(): - def __init__(self): +class SVMFrameFilter: + def __init__(self, svm_util: SVMUtil): self.name = "SubstrateVM FrameFilter" self.priority = 100 self.enabled = True + self.eager_deopt_stub_adr = None + self.lazy_deopt_stub_primitive_adr = None + self.lazy_deopt_stub_object_adr = None + self.svm_util = svm_util + + def filter(self, frame_iter: Iterable) -> FrameDecorator: + if self.eager_deopt_stub_adr is None: + self.eager_deopt_stub_adr = SVMUtil.get_eager_deopt_stub_adr() + self.lazy_deopt_stub_primitive_adr = SVMUtil.get_lazy_deopt_stub_primitive_adr() + self.lazy_deopt_stub_object_adr = SVMUtil.get_lazy_deopt_stub_object_adr() - def filter(self, frame_iter): for frame in frame_iter: frame = frame.inferior_frame() - if SVMUtil.deopt_stub_adr and frame.pc() == SVMUtil.deopt_stub_adr: - yield SVMFrameDeopt(frame) + pc = int(frame.pc()) + if pc == self.eager_deopt_stub_adr: + yield SVMFrameEagerDeopt(self.svm_util, frame) + elif pc == self.lazy_deopt_stub_primitive_adr or pc == self.lazy_deopt_stub_object_adr: + yield SVMFrameLazyDeopt(self.svm_util, frame) else: yield SVMFrame(frame) class SVMFrame(FrameDecorator): - def function(self): + def function(self) -> str: frame = self.inferior_frame() if not frame.name(): return 'Unknown Frame at ' + hex(int(frame.read_register('sp'))) @@ -1560,17 +1699,124 @@ def function(self): return func_name + eclipse_filename -class SVMFrameDeopt(SVMFrame): - def function(self): - return '[DEOPT FRAMES ...]' +class SymValueWrapper: + + def __init__(self, symbol, value): + self.sym = symbol + self.val = value + + def value(self): + return self.val + + def symbol(self): + return self.sym + + +class SVMFrameEagerDeopt(SVMFrame): + + def __init__(self, svm_util: SVMUtil, frame: gdb.Frame): + super().__init__(frame) + + # fetch deoptimized frame from stack + sp = frame.read_register('sp') + deopt_frame_stack_slot = sp.cast(svm_util.stack_type.pointer()).dereference() + deopt_frame = deopt_frame_stack_slot.cast(svm_util.get_compressed_type(svm_util.object_type).pointer()) + rtt = svm_util.get_rtt(deopt_frame) + deopt_frame = svm_util.cast_to(deopt_frame, rtt) + self.__virtual_frame = svm_util.get_obj_field(deopt_frame, 'topFrame') + self.__frame_info = svm_util.get_obj_field(self.__virtual_frame, 'frameInfo') + self.__svm_util = svm_util + + def function(self) -> str: + if self.__frame_info is None or self.__svm_util.is_null(self.__frame_info): + # we have no more information about the frame + return '[EAGER DEOPT FRAME ...]' + + # read from deoptimized frame + source_class = self.__svm_util.get_obj_field(self.__frame_info, 'sourceClass') + if self.__svm_util.is_null(source_class): + source_class_name = '' + else: + source_class_name = str(self.__svm_util.get_obj_field(source_class, 'name'))[1:-1] + if len(source_class_name) > 0: + source_class_name = source_class_name + '::' + + source_file_name = self.filename() + if source_file_name is None or len(source_file_name) == 0: + source_file_name = '' + else: + line = self.line() + if line is not None and line != 0: + source_file_name = source_file_name + ':' + str(line) + source_file_name = '(' + source_file_name + ')' + + func_name = str(self.__svm_util.get_obj_field(self.__frame_info, 'sourceMethodName'))[1:-1] + + return '[EAGER DEOPT FRAME] ' + source_class_name + func_name + source_file_name + + def filename(self): + if self.__frame_info is None or self.__svm_util.is_null(self.__frame_info): + return None + + source_class = self.__svm_util.get_obj_field(self.__frame_info, 'sourceClass') + companion = self.__svm_util.get_obj_field(source_class, 'companion') + source_file_name = self.__svm_util.get_obj_field(companion, 'sourceFileName') + + if self.__svm_util.is_null(source_file_name): + source_file_name = '' + else: + source_file_name = str(source_file_name)[1:-1] + + return source_file_name + + def line(self): + if self.__frame_info is None or self.__svm_util.is_null(self.__frame_info): + return None + return self.__svm_util.get_int_field(self.__frame_info, 'sourceLineNumber') def frame_args(self): - return None + if self.__frame_info is None or self.__svm_util.is_null(self.__frame_info): + return None + + values = self.__svm_util.get_obj_field(self.__virtual_frame, 'values') + data = self.__svm_util.get_obj_field(values, 'data') + length = self.__svm_util.get_int_field(values, 'len') + args = [SymValueWrapper('deoptFrameValues', length)] + if self.__svm_util.is_null(data) or length == 0: + return args + + for i in range(length): + elem = data[i] + rtt = self.__svm_util.get_rtt(elem) + elem = self.__svm_util.cast_to(elem, rtt) + value = self.__svm_util.get_obj_field(elem, 'value') + args.append(SymValueWrapper(f'__{i}', value)) + + return args def frame_locals(self): return None +class SVMFrameLazyDeopt(SVMFrame): + + def __init__(self, svm_util: SVMUtil, frame: gdb.Frame): + super().__init__(frame) + + # fetch deoptimized frame from stack + sp = frame.read_register('sp') + real_pc = sp.cast(svm_util.stack_type.pointer()).dereference().cast(svm_util.stack_type.pointer()) + self.__gdb_text = str(real_pc) + self.__svm_util = svm_util + + def function(self) -> str: + if self.__gdb_text is None: + # we have no more information about the frame + return '[LAZY DEOPT FRAME ...]' + + return '[LAZY DEOPT FRAME] at ' + self.__gdb_text + + try: svminitfile = os.path.expandvars('${SVMGDBINITFILE}') exec(open(svminitfile).read()) @@ -1578,35 +1824,39 @@ def frame_locals(self): except Exception as e: trace(f'') + +def register_objfile(objfile: gdb.Objfile): + svm_util = SVMUtil() + gdb.printing.register_pretty_printer(objfile, SVMPrettyPrinter(svm_util), True) + + # deopt stub points to the wrong address at first -> fill later when needed + deopt_stub_available = gdb.lookup_global_symbol('com.oracle.svm.core.deopt.Deoptimizer', + gdb.SYMBOL_VAR_DOMAIN) is not None + if deopt_stub_available: + gdb.unwinder.register_unwinder(objfile, SVMFrameUnwinder(svm_util)) + + frame_filter = SVMFrameFilter(svm_util) + objfile.frame_filters[frame_filter.name] = frame_filter + + try: gdb.prompt_hook = SVMUtil.prompt_hook svm_objfile = gdb.current_objfile() # Only if we have an objfile and an SVM specific symbol we consider this an SVM objfile if svm_objfile and svm_objfile.lookup_global_symbol("com.oracle.svm.core.Isolates"): - gdb.printing.register_pretty_printer(svm_objfile, SVMPrettyPrinter(), True) - - # deopt stub points to the wrong address at first -> set dummy value to fill later (0 from SVMUtil) - deopt_stub_available = gdb.lookup_global_symbol('com.oracle.svm.core.deopt.Deoptimizer::deoptStub', - gdb.SYMBOL_VAR_DOMAIN) - - if deopt_stub_available: - SVMUtil.frame_unwinder = SVMFrameUnwinder() - gdb.unwinder.register_unwinder(svm_objfile, SVMUtil.frame_unwinder) - - SVMUtil.frame_filter = SVMFrameFilter() - svm_objfile.frame_filters[SVMUtil.frame_filter.name] = SVMUtil.frame_filter + register_objfile(svm_objfile) else: print(f'Warning: Load {os.path.basename(__file__)} only in the context of an SVM objfile') # fallback (e.g. if loaded manually -> look through all objfiles and attach pretty printer) - for of in gdb.objfiles(): - if of.lookup_global_symbol("com.oracle.svm.core.Isolates"): - gdb.printing.register_pretty_printer(of, SVMPrettyPrinter(), True) + for objfile in gdb.objfiles(): + if objfile.lookup_global_symbol("com.oracle.svm.core.Isolates"): + register_objfile(objfile) # save and restore SVM pretty printer for reloaded objfiles (e.g. shared libraries) def new_objectfile(new_objfile_event): objfile = new_objfile_event.new_objfile if objfile.filename in SVMUtil.pretty_print_objfiles: - gdb.printing.register_pretty_printer(objfile, SVMPrettyPrinter(), True) + register_objfile(objfile) def free_objectfile(free_objfile_event): objfile = free_objfile_event.objfile diff --git a/substratevm/debug/include/gdb_jit_compilation_interface.h b/substratevm/debug/include/gdb_jit_compilation_interface.h new file mode 100644 index 000000000000..ed4cc2659b1f --- /dev/null +++ b/substratevm/debug/include/gdb_jit_compilation_interface.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#ifndef SVM_NATIVE_GDBJITCOMPILATIONINTERFACE_H +#define SVM_NATIVE_GDBJITCOMPILATIONINTERFACE_H + +#include + +typedef enum +{ + JIT_NOACTION = 0, + JIT_REGISTER, + JIT_UNREGISTER +} jit_actions_t; + +struct jit_code_entry +{ + struct jit_code_entry *next_entry; + struct jit_code_entry *prev_entry; + const char *symfile_addr; + uint64_t symfile_size; +}; + +struct jit_descriptor +{ + uint32_t version; + /* This type should be jit_actions_t, but we use uint32_t + to be explicit about the bitwidth. */ + uint32_t action_flag; + struct jit_code_entry *relevant_entry; + struct jit_code_entry *first_entry; +}; + +#endif diff --git a/substratevm/mx.substratevm/gdb_utils.py b/substratevm/mx.substratevm/gdb_utils.py index 1499bf28678f..47d7d9c33af6 100644 --- a/substratevm/mx.substratevm/gdb_utils.py +++ b/substratevm/mx.substratevm/gdb_utils.py @@ -110,6 +110,8 @@ def check(self, text, skip_fails=True): for i in range(0, num_rexps): rexp = rexps[i] match = None + if skip_fails: + line_idx = 0 while line_idx < num_lines and match is None: line = lines[line_idx] match = rexp.match(line) diff --git a/substratevm/mx.substratevm/mx_substratevm.py b/substratevm/mx.substratevm/mx_substratevm.py index 551ceff88ae3..d00a6546a6a9 100644 --- a/substratevm/mx.substratevm/mx_substratevm.py +++ b/substratevm/mx.substratevm/mx_substratevm.py @@ -1122,7 +1122,7 @@ def run_debug_test(image_name: str, testfile: str, source_path: str, with_isolat else: build_args += ['-o', join(build_dir, image_name)] - mx.log(f"native_image {' '.join(build_args)}") + mx.log(f"native-image {' '.join(build_args)}") native_image(build_args) if build_cinterfacetutorial: @@ -1139,14 +1139,14 @@ def run_debug_test(image_name: str, testfile: str, source_path: str, with_isolat mx.run(c_command, cwd=build_dir) if mx.get_os() == 'linux': logfile = join(path, pathlib.Path(testfile).stem + ('' if with_isolates else '_no_isolates') + '.log') - os.environ.update({'gdbdebughelperstest_logfile': logfile}) + os.environ.update({'gdb_logfile': logfile}) gdb_command = gdb_args + [ '-iex', f"set logging file {logfile}", '-iex', 'set logging enabled on', '-iex', f"set auto-load safe-path {join(build_dir, 'gdb-debughelpers.py')}", '-x', testfile, join(build_dir, image_name) ] - mx.log(' '.join(gdb_command)) + log_gdb_command(gdb_command) # unittest may result in different exit code, nonZeroIsFatal ensures that we can go on with other test return mx.run(gdb_command, cwd=build_dir, nonZeroIsFatal=False) return 0 @@ -1173,6 +1173,97 @@ def run_debug_test(image_name: str, testfile: str, source_path: str, with_isolat mx.abort(status) +def log_gdb_command(gdb_command): + mx.log(' '.join([(f"'{c}'" if ' ' in c else c) for c in gdb_command])) + + +def _runtimedebuginfotest(native_image, output_path, args=None): + """Build and run the runtimedebuginfotest""" + + args = [] if args is None else args + + test_proj = mx.dependency('com.oracle.svm.test') + test_source_path = test_proj.source_dirs()[0] + + test_python_source_dir = join(test_source_path, 'com', 'oracle', 'svm', 'test', 'debug', 'helper') + test_runtime_compilation_py = join(test_python_source_dir, 'test_runtime_compilation.py') + test_runtime_deopt_py = join(test_python_source_dir, 'test_runtime_deopt.py') + testdeopt_js = join(suite.dir, 'mx.substratevm', 'testdeopt.js') + + gdb_args = [ + os.environ.get('GDB_BIN', 'gdb'), + '--nx', + '-q', # do not print the introductory and copyright messages + '-iex', "set pagination off", # messages from enabling logging could already cause pagination, so this must be done first + '-iex', "set logging redirect on", + '-iex', "set logging overwrite off", + ] + + # clean / create output directory + if exists(output_path): + mx.rmtree(output_path) + mx_util.ensure_dir_exists(output_path) + + # Build the native image from Java code + build_args = [ + '-g', '-O0', + '-o', join(output_path, 'runtimedebuginfotest'), + '-cp', classpath('com.oracle.svm.test'), + # We do not want to step into class initializer, so initialize everything at build time. + '--initialize-at-build-time=com.oracle.svm.test.debug.helper', + '--features=com.oracle.svm.test.debug.helper.RuntimeCompileDebugInfoTest$RegisterMethodsFeature', + 'com.oracle.svm.test.debug.helper.RuntimeCompileDebugInfoTest', + ] + svm_experimental_options([ + '-H:DebugInfoSourceSearchPath=' + test_source_path, + '-H:+SourceLevelDebug', + '-H:+RuntimeDebugInfo', + ]) + args + + mx.log(f"native-image {' '.join(build_args)}") + runtime_compile_binary = native_image(build_args) + + logfile = join(output_path, 'test_runtime_compilation.log') + os.environ.update({'gdb_logfile': logfile}) + gdb_command = gdb_args + [ + '-iex', f"set logging file {logfile}", + '-iex', "set logging enabled on", + '-iex', f"set auto-load safe-path {join(output_path, 'gdb-debughelpers.py')}", + '-x', test_runtime_compilation_py, runtime_compile_binary + ] + log_gdb_command(gdb_command) + # unittest may result in different exit code, nonZeroIsFatal ensures that we can go on with other test + status = mx.run(gdb_command, cwd=output_path, nonZeroIsFatal=False) + + def run_js_test(eager: bool = False): + jslib = mx.add_lib_suffix(native_image( + args + + svm_experimental_options([ + '-H:+SourceLevelDebug', + '-H:+RuntimeDebugInfo', + '-H:+LazyDeoptimization' if eager else '-H:-LazyDeoptimization', + ]) + + ['-g', '-O0', '--macro:jsvm-library'] + )) + js_launcher = get_js_launcher(jslib) + logfile = join(output_path, 'test_runtime_deopt_' + ('eager' if eager else 'lazy') + '.log') + os.environ.update({'gdb_logfile': logfile}) + gdb_command = gdb_args + [ + '-iex', f"set logging file {logfile}", + '-iex', "set logging enabled on", + '-iex', f"set auto-load safe-path {join(output_path, 'gdb-debughelpers.py')}", + '-x', test_runtime_deopt_py, '--args', js_launcher, testdeopt_js + ] + log_gdb_command(gdb_command) + # unittest may result in different exit code, nonZeroIsFatal ensures that we can go on with other test + return mx.run(gdb_command, cwd=output_path, nonZeroIsFatal=False) + + status |= run_js_test() + status |= run_js_test(True) + + if status != 0: + mx.abort(status) + + def _javac_image(native_image, path, args=None): args = [] if args is None else args mx_util.ensure_dir_exists(path) @@ -1763,6 +1854,26 @@ def gdbdebughelperstest(args, config=None): config=config ) + +@mx.command(suite.name, 'runtimedebuginfotest', 'Runs the runtime debuginfo generation test') +def runtimedebuginfotest(args, config=None): + """ + runs a native image that compiles code and creates debug info at runtime. + """ + parser = ArgumentParser(prog='mx runtimedebuginfotest') + all_args = ['--output-path', '--with-isolates-only'] + masked_args = [_mask(arg, all_args) for arg in args] + parser.add_argument(all_args[0], metavar='', nargs=1, help='Path of the generated image', default=[join(svmbuild_dir(), "runtimedebuginfotest")]) + parser.add_argument('image_args', nargs='*', default=[]) + parsed = parser.parse_args(masked_args) + output_path = unmask(parsed.output_path)[0] + native_image_context_run( + lambda native_image, a: + _runtimedebuginfotest(native_image, output_path, a), unmask(parsed.image_args), + config=config + ) + + @mx.command(suite_name=suite.name, command_name='helloworld', usage_msg='[options]') def helloworld(args, config=None): """ @@ -1855,6 +1966,7 @@ def build_and_test_java_agent_image(native_image, args): native_image_context_run(build_and_test_java_agent_image, args) + @mx.command(suite.name, 'clinittest', 'Runs the ') def clinittest(args): def build_and_test_clinittest_image(native_image, args): diff --git a/substratevm/mx.substratevm/suite.py b/substratevm/mx.substratevm/suite.py index b035719dcd8b..5a7d99b26182 100644 --- a/substratevm/mx.substratevm/suite.py +++ b/substratevm/mx.substratevm/suite.py @@ -309,6 +309,7 @@ "dependencies": [ "com.oracle.svm.common", "com.oracle.svm.shaded.org.objectweb.asm", + "com.oracle.objectfile", ], "requires" : [ "java.compiler", @@ -680,7 +681,6 @@ "subDir": "src", "sourceDirs": ["src"], "dependencies": [ - "com.oracle.objectfile", "com.oracle.graal.reachability", "com.oracle.svm.core.graal.amd64", ], @@ -1077,6 +1077,10 @@ "jdk.internal.misc", "sun.security.jca", ], + "jdk.internal.vm.ci" : [ + "jdk.vm.ci.code", + "jdk.vm.ci.meta", + ], }, "checkstyle": "com.oracle.svm.test", "checkstyleVersion" : "10.21.0", @@ -1864,6 +1868,8 @@ "com.oracle.objectfile", "com.oracle.objectfile.io", "com.oracle.objectfile.debuginfo", + "com.oracle.objectfile.debugentry", + "com.oracle.objectfile.debugentry.range", "com.oracle.objectfile.macho", ], @@ -1994,6 +2000,7 @@ "dependency:com.oracle.svm.native.libchelper/*", "dependency:com.oracle.svm.native.jvm.posix/*", "dependency:com.oracle.svm.native.libcontainer/*", + "file:debug/include", ], }, }, @@ -2002,6 +2009,7 @@ # on all other os's we don't want libc specific subdirectories "include/": [ "dependency:com.oracle.svm.native.libchelper/include/*", + "file:debug/include/*", ], "-/": [ "dependency:com.oracle.svm.native.libchelper/-/default/*", diff --git a/substratevm/mx.substratevm/testdeopt.js b/substratevm/mx.substratevm/testdeopt.js new file mode 100644 index 000000000000..05e3e991619c --- /dev/null +++ b/substratevm/mx.substratevm/testdeopt.js @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +function add(a, b, test) { + if (test) { + a += b; + } + return a + b; +} + +// trigger compilation add for ints and test = true +for (let i = 0; i < 1000 * 1000; i++) { + add(i, i, true); +} + +// deoptimize with failed assumption in compiled method +// then trigger compilation again +console.log("deopt1") +for (let i = 0; i < 1000 * 1000; i++) { + add(i, i, false); +} + +// deoptimize with different parameter types +console.log("deopt2"); +add({f1: "test1", f2: 2}, {x: "x", y: {test: 42}}, false); diff --git a/substratevm/mx.substratevm/testhello.py b/substratevm/mx.substratevm/testhello.py index c81fc7c4e560..b38b7c3a8e40 100644 --- a/substratevm/mx.substratevm/testhello.py +++ b/substratevm/mx.substratevm/testhello.py @@ -147,25 +147,25 @@ def test(): # check incoming parameters are bound to sensible values exec_string = execute("info args") - rexp = [fr"__0 = {digits_pattern}", - fr"__1 = 0x{hex_digits_pattern}"] + rexp = [fr"__int0 = {digits_pattern}", + fr"__long1 = 0x{hex_digits_pattern}"] checker = Checker(f"info args : {method_name}", rexp) checker.check(exec_string) - exec_string = execute("p __0") + exec_string = execute("p __int0") rexp = [fr"\${digits_pattern} = 1"] - checker = Checker("p __0", rexp) + checker = Checker("p __int0", rexp) checker.check(exec_string) - exec_string = execute("p __1") + exec_string = execute("p __long1") rexp = [fr"\${digits_pattern} = \(org\.graalvm\.nativeimage\.c\.type\.CCharPointerPointer\) 0x{hex_digits_pattern}"] - checker = Checker("p __1", rexp) + checker = Checker("p __long1", rexp) checker.check(exec_string) - exec_string = execute("p __1[0]") + exec_string = execute("p __long1[0]") rexp = [ fr'\${digits_pattern} = \(org\.graalvm\.nativeimage\.c\.type\.CCharPointer\) 0x{hex_digits_pattern} "{wildcard_pattern}/hello_image"'] - checker = Checker("p __1[0]", rexp) + checker = Checker("p __long1[0]", rexp) checker.check(exec_string) # set a break point at hello.Hello::main diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/ObjectFile.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/ObjectFile.java index 734c93a025e1..c758f06eaa8d 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/ObjectFile.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/ObjectFile.java @@ -219,16 +219,12 @@ public static Format getNativeFormat() { } private static ObjectFile getNativeObjectFile(int pageSize, boolean runtimeDebugInfoGeneration) { - switch (ObjectFile.getNativeFormat()) { - case ELF: - return new ELFObjectFile(pageSize, runtimeDebugInfoGeneration); - case MACH_O: - return new MachOObjectFile(pageSize); - case PECOFF: - return new PECoffObjectFile(pageSize); - default: - throw new AssertionError("Unreachable"); - } + return switch (ObjectFile.getNativeFormat()) { + case ELF -> new ELFObjectFile(pageSize, runtimeDebugInfoGeneration); + case MACH_O -> new MachOObjectFile(pageSize); + case PECOFF -> new PECoffObjectFile(pageSize); + case LLVM -> throw new AssertionError("Unsupported NativeObjectFile for format " + ObjectFile.getNativeFormat()); + }; } public static ObjectFile getNativeObjectFile(int pageSize) { @@ -1828,7 +1824,7 @@ public final SymbolTable getOrCreateSymbolTable() { * Temporary storage for a debug context installed in a nested scope under a call. to * {@link #withDebugContext} */ - private DebugContext debugContext = null; + protected DebugContext debugContext = DebugContext.disabled(null); /** * Allows a task to be executed with a debug context in a named subscope bound to the object diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ArrayTypeEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ArrayTypeEntry.java index 114c585637ea..cde997d753b7 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ArrayTypeEntry.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ArrayTypeEntry.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -26,40 +26,21 @@ package com.oracle.objectfile.debugentry; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugArrayTypeInfo; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo.DebugTypeKind; - -import jdk.graal.compiler.debug.DebugContext; -import jdk.vm.ci.meta.ResolvedJavaType; - public class ArrayTypeEntry extends StructureTypeEntry { - private TypeEntry elementType; - private int baseSize; - private int lengthOffset; - - public ArrayTypeEntry(String typeName, int size) { - super(typeName, size); - } - - @Override - public DebugTypeKind typeKind() { - return DebugTypeKind.ARRAY; + private final TypeEntry elementType; + private final LoaderEntry loader; + + public ArrayTypeEntry(String typeName, int size, long classOffset, long typeSignature, + long compressedTypeSignature, long layoutTypeSignature, + TypeEntry elementType, LoaderEntry loader) { + super(typeName, size, classOffset, typeSignature, compressedTypeSignature, layoutTypeSignature); + this.elementType = elementType; + this.loader = loader; } @Override - public void addDebugInfo(DebugInfoBase debugInfoBase, DebugTypeInfo debugTypeInfo, DebugContext debugContext) { - super.addDebugInfo(debugInfoBase, debugTypeInfo, debugContext); - DebugArrayTypeInfo debugArrayTypeInfo = (DebugArrayTypeInfo) debugTypeInfo; - ResolvedJavaType eltType = debugArrayTypeInfo.elementType(); - this.elementType = debugInfoBase.lookupTypeEntry(eltType); - this.baseSize = debugArrayTypeInfo.baseSize(); - this.lengthOffset = debugArrayTypeInfo.lengthOffset(); - /* Add details of fields and field types */ - debugArrayTypeInfo.fieldInfoProvider().forEach(debugFieldInfo -> this.processField(debugFieldInfo, debugInfoBase, debugContext)); - if (debugContext.isLogEnabled()) { - debugContext.log("typename %s element type %s base size %d length offset %d%n", typeName, this.elementType.getTypeName(), baseSize, lengthOffset); - } + public boolean isArray() { + return true; } public TypeEntry getElementType() { @@ -67,13 +48,6 @@ public TypeEntry getElementType() { } public String getLoaderId() { - TypeEntry type = elementType; - while (type.isArray()) { - type = ((ArrayTypeEntry) type).elementType; - } - if (type.isClass()) { - return ((ClassEntry) type).getLoaderId(); - } - return ""; + return (loader != null ? loader.loaderId() : ""); } } diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ClassEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ClassEntry.java index f9747acaf638..0883dcc45e05 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ClassEntry.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ClassEntry.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -26,27 +26,13 @@ package com.oracle.objectfile.debugentry; -import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; import java.util.List; -import java.util.stream.Stream; +import java.util.Map; +import java.util.concurrent.ConcurrentSkipListSet; -import org.graalvm.collections.EconomicMap; - -import com.oracle.objectfile.debugentry.range.PrimaryRange; import com.oracle.objectfile.debugentry.range.Range; -import com.oracle.objectfile.debugentry.range.SubRange; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugFieldInfo; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugFrameSizeChange; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugInstanceTypeInfo; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugLocalInfo; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugMethodInfo; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugRangeInfo; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo.DebugTypeKind; - -import jdk.graal.compiler.debug.DebugContext; -import jdk.vm.ci.meta.ResolvedJavaMethod; -import jdk.vm.ci.meta.ResolvedJavaType; /** * Track debug info associated with a Java class. @@ -55,11 +41,7 @@ public class ClassEntry extends StructureTypeEntry { /** * Details of this class's superclass. */ - protected ClassEntry superClass; - /** - * Details of this class's interfaces. - */ - protected final List interfaces = new ArrayList<>(); + private final ClassEntry superClass; /** * Details of the associated file. */ @@ -67,116 +49,99 @@ public class ClassEntry extends StructureTypeEntry { /** * Details of the associated loader. */ - private LoaderEntry loader; + private final LoaderEntry loader; /** * Details of methods located in this instance. */ - protected final List methods = new ArrayList<>(); - /** - * An index of all currently known methods keyed by the unique, associated, identifying - * ResolvedJavaMethod. - */ - private final EconomicMap methodsIndex = EconomicMap.create(); + private final ConcurrentSkipListSet methods; /** * A list recording details of all normal compiled methods included in this class sorted by * ascending address range. Note that the associated address ranges are disjoint and contiguous. */ - private final List compiledEntries = new ArrayList<>(); - /** - * An index identifying ranges for compiled method which have already been encountered. - */ - private final EconomicMap compiledMethodIndex = EconomicMap.create(); + private final ConcurrentSkipListSet compiledMethods; /** * A list of all files referenced from info associated with this class, including info detailing * inline method ranges. */ - private final ArrayList files; + private final ConcurrentSkipListSet files; + private final Map indexedFiles = new HashMap<>(); + /** * A list of all directories referenced from info associated with this class, including info * detailing inline method ranges. */ - private final ArrayList dirs; - /** - * An index identifying the file table position of every file referenced from info associated - * with this class, including info detailing inline method ranges. - */ - private EconomicMap fileIndex; - /** - * An index identifying the dir table position of every directory referenced from info - * associated with this class, including info detailing inline method ranges. - */ - private EconomicMap dirIndex; - - public ClassEntry(String className, FileEntry fileEntry, int size) { - super(className, size); + private final ConcurrentSkipListSet dirs; + private final Map indexedDirs = new HashMap<>(); + + public ClassEntry(String typeName, int size, long classOffset, long typeSignature, + long compressedTypeSignature, long layoutTypeSignature, + ClassEntry superClass, FileEntry fileEntry, LoaderEntry loader) { + super(typeName, size, classOffset, typeSignature, compressedTypeSignature, layoutTypeSignature); + this.superClass = superClass; this.fileEntry = fileEntry; - this.loader = null; - // file and dir lists/indexes are populated after all DebugInfo API input has - // been received and are only created on demand - files = new ArrayList<>(); - dirs = new ArrayList<>(); - // create these on demand using the size of the file and dir lists - this.fileIndex = null; - this.dirIndex = null; + this.loader = loader; + this.methods = new ConcurrentSkipListSet<>(Comparator.comparingInt(MethodEntry::getModifiers).thenComparingInt(MethodEntry::getLine).thenComparing(MethodEntry::getSymbolName)); + this.compiledMethods = new ConcurrentSkipListSet<>(Comparator.comparing(CompiledMethodEntry::primary)); + this.files = new ConcurrentSkipListSet<>(Comparator.comparing(FileEntry::fileName).thenComparing(file -> file.dirEntry().path())); + this.dirs = new ConcurrentSkipListSet<>(Comparator.comparing(DirEntry::path)); + + addFile(fileEntry); } - @Override - public DebugTypeKind typeKind() { - return DebugTypeKind.INSTANCE; + private void addFile(FileEntry addFileEntry) { + if (addFileEntry != null && !addFileEntry.fileName().isEmpty()) { + files.add(addFileEntry); + DirEntry addDirEntry = addFileEntry.dirEntry(); + if (addDirEntry != null && !addDirEntry.getPathString().isEmpty()) { + dirs.add(addDirEntry); + } + } } + /** + * Add a field to the class entry and store its file entry. + * + * @param field the {@code FieldEntry} to add + */ @Override - public void addDebugInfo(DebugInfoBase debugInfoBase, DebugTypeInfo debugTypeInfo, DebugContext debugContext) { - super.addDebugInfo(debugInfoBase, debugTypeInfo, debugContext); - assert debugTypeInfo.typeName().equals(typeName); - DebugInstanceTypeInfo debugInstanceTypeInfo = (DebugInstanceTypeInfo) debugTypeInfo; - /* Add details of super and interface classes */ - ResolvedJavaType superType = debugInstanceTypeInfo.superClass(); - if (debugContext.isLogEnabled()) { - debugContext.log("typename %s adding super %s%n", typeName, superType != null ? superType.toJavaName() : ""); - } - if (superType != null) { - this.superClass = debugInfoBase.lookupClassEntry(superType); - } - String loaderName = debugInstanceTypeInfo.loaderName(); - if (!loaderName.isEmpty()) { - this.loader = debugInfoBase.ensureLoaderEntry(loaderName); - } - debugInstanceTypeInfo.interfaces().forEach(interfaceType -> processInterface(interfaceType, debugInfoBase, debugContext)); - /* Add details of fields and field types */ - debugInstanceTypeInfo.fieldInfoProvider().forEach(debugFieldInfo -> this.processField(debugFieldInfo, debugInfoBase, debugContext)); - /* Add details of methods and method types */ - debugInstanceTypeInfo.methodInfoProvider().forEach(debugMethodInfo -> this.processMethod(debugMethodInfo, debugInfoBase, debugContext)); + public void addField(FieldEntry field) { + addFile(field.getFileEntry()); + super.addField(field); } - public CompiledMethodEntry indexPrimary(PrimaryRange primary, List frameSizeInfos, int frameSize) { - assert compiledMethodIndex.get(primary) == null : "repeat of primary range [0x%x, 0x%x]!".formatted(primary.getLo(), primary.getHi()); - CompiledMethodEntry compiledEntry = new CompiledMethodEntry(primary, frameSizeInfos, frameSize, this); - compiledMethodIndex.put(primary, compiledEntry); - compiledEntries.add(compiledEntry); - return compiledEntry; + /** + * Add a method to the class entry and store its file entry. + * + * @param methodEntry the {@code MethodEntry} to add + */ + public void addMethod(MethodEntry methodEntry) { + addFile(methodEntry.getFileEntry()); + methods.add(methodEntry); } - public void indexSubRange(SubRange subrange) { - Range primary = subrange.getPrimary(); - /* The subrange should belong to a primary range. */ - assert primary != null; - CompiledMethodEntry compiledEntry = compiledMethodIndex.get(primary); - /* We should already have seen the primary range. */ - assert compiledEntry != null; - assert compiledEntry.getClassEntry() == this; + /** + * Add a compiled method to the class entry and store its file entry and the file entries of + * inlined methods. + * + * @param compiledMethodEntry the {@code CompiledMethodEntry} to add + */ + public void addCompiledMethod(CompiledMethodEntry compiledMethodEntry) { + addFile(compiledMethodEntry.primary().getFileEntry()); + for (Range range : compiledMethodEntry.topDownRangeStream().toList()) { + addFile(range.getFileEntry()); + } + compiledMethods.add(compiledMethodEntry); } - private void indexMethodEntry(MethodEntry methodEntry, ResolvedJavaMethod idMethod) { - assert methodsIndex.get(idMethod) == null : methodEntry.getSymbolName(); - methods.add(methodEntry); - methodsIndex.put(idMethod, methodEntry); + @Override + public boolean isInstance() { + return true; } public String getFileName() { if (fileEntry != null) { - return fileEntry.getFileName(); + return fileEntry.fileName(); } else { return ""; } @@ -207,18 +172,44 @@ public int getFileIdx() { return getFileIdx(this.getFileEntry()); } + public int getDirIdx() { + return getDirIdx(this.getFileEntry()); + } + + /** + * Returns the file index of a given file entry within this class entry. + * + *

+ * The first time a file entry is fetched, this produces a file index that is used for further + * index lookups. The file index is only created once. Therefore, this method must be used only + * after debug info generation is finished and no more file entries can be added to this class + * entry. + * + * @param file the given file entry + * @return the index of the file entry + */ public int getFileIdx(FileEntry file) { - if (file == null || fileIndex == null) { + if (file == null || files.isEmpty() || !files.contains(file)) { return 0; } - return fileIndex.get(file); + + // Create a file index for all files in this class entry + if (indexedFiles.isEmpty()) { + int index = 1; + for (FileEntry f : getFiles()) { + indexedFiles.put(f, index); + index++; + } + } + + return indexedFiles.get(file); } - public DirEntry getDirEntry(FileEntry file) { + private static DirEntry getDirEntry(FileEntry file) { if (file == null) { return null; } - return file.getDirEntry(); + return file.dirEntry(); } public int getDirIdx(FileEntry file) { @@ -226,136 +217,69 @@ public int getDirIdx(FileEntry file) { return getDirIdx(dirEntry); } - public int getDirIdx(DirEntry dir) { - if (dir == null || dir.getPathString().isEmpty() || dirIndex == null) { - return 0; - } - return dirIndex.get(dir); - } - - public String getLoaderId() { - return (loader != null ? loader.getLoaderId() : ""); - } - /** - * Retrieve a stream of all compiled method entries for this class. + * Returns the dir index of a given dir entry within this class entry. * - * @return a stream of all compiled method entries for this class. + *

+ * The first time a dir entry is fetched, this produces a dir index that is used for further + * index lookups. The dir index is only created once. Therefore, this method must be used only + * after debug info generation is finished and no more dir entries can be added to this class + * entry. + * + * @param dir the given dir entry + * @return the index of the dir entry */ - public Stream compiledEntries() { - return compiledEntries.stream(); - } - - protected void processInterface(ResolvedJavaType interfaceType, DebugInfoBase debugInfoBase, DebugContext debugContext) { - if (debugContext.isLogEnabled()) { - debugContext.log("typename %s adding interface %s%n", typeName, interfaceType.toJavaName()); - } - ClassEntry entry = debugInfoBase.lookupClassEntry(interfaceType); - assert entry instanceof InterfaceClassEntry || (entry instanceof ForeignTypeEntry && this instanceof ForeignTypeEntry); - InterfaceClassEntry interfaceClassEntry = (InterfaceClassEntry) entry; - interfaces.add(interfaceClassEntry); - interfaceClassEntry.addImplementor(this, debugContext); - } - - protected MethodEntry processMethod(DebugMethodInfo debugMethodInfo, DebugInfoBase debugInfoBase, DebugContext debugContext) { - String methodName = debugMethodInfo.name(); - int line = debugMethodInfo.line(); - ResolvedJavaType resultType = debugMethodInfo.valueType(); - int modifiers = debugMethodInfo.modifiers(); - DebugLocalInfo[] paramInfos = debugMethodInfo.getParamInfo(); - DebugLocalInfo thisParam = debugMethodInfo.getThisParamInfo(); - int paramCount = paramInfos.length; - if (debugContext.isLogEnabled()) { - String resultTypeName = resultType.toJavaName(); - debugContext.log("typename %s adding %s method %s %s(%s)%n", - typeName, memberModifiers(modifiers), resultTypeName, methodName, formatParams(paramInfos)); - } - TypeEntry resultTypeEntry = debugInfoBase.lookupTypeEntry(resultType); - TypeEntry[] typeEntries = new TypeEntry[paramCount]; - for (int i = 0; i < paramCount; i++) { - typeEntries[i] = debugInfoBase.lookupTypeEntry(paramInfos[i].valueType()); + public int getDirIdx(DirEntry dir) { + if (dir == null || dir.getPathString().isEmpty() || dirs.isEmpty() || !dirs.contains(dir)) { + return 0; } - /* - * n.b. the method file may differ from the owning class file when the method is a - * substitution - */ - FileEntry methodFileEntry = debugInfoBase.ensureFileEntry(debugMethodInfo); - MethodEntry methodEntry = new MethodEntry(debugInfoBase, debugMethodInfo, methodFileEntry, line, methodName, - this, resultTypeEntry, typeEntries, paramInfos, thisParam); - indexMethodEntry(methodEntry, debugMethodInfo.idMethod()); - - return methodEntry; - } - - @Override - protected FieldEntry addField(DebugFieldInfo debugFieldInfo, DebugInfoBase debugInfoBase, DebugContext debugContext) { - FieldEntry fieldEntry = super.addField(debugFieldInfo, debugInfoBase, debugContext); - return fieldEntry; - } - private static String formatParams(DebugLocalInfo[] paramInfo) { - if (paramInfo.length == 0) { - return ""; - } - StringBuilder builder = new StringBuilder(); - for (int i = 0; i < paramInfo.length; i++) { - if (i > 0) { - builder.append(", "); + // Create a dir index for all dirs in this class entry + if (indexedDirs.isEmpty()) { + int index = 1; + for (DirEntry d : getDirs()) { + indexedDirs.put(d, index); + index++; } - builder.append(paramInfo[i].typeName()); - builder.append(' '); - builder.append(paramInfo[i].name()); } - return builder.toString(); + return indexedDirs.get(dir); } - public int compiledEntryCount() { - return compiledEntries.size(); + public String getLoaderId() { + return (loader != null ? loader.loaderId() : ""); } - public boolean hasCompiledEntries() { - return compiledEntryCount() != 0; + /** + * Retrieve a list of all compiled method entries for this class. + * + * @return a list of all compiled method entries for this class. + */ + public List compiledMethods() { + return List.copyOf(compiledMethods); } - public int compiledEntriesBase() { - assert hasCompiledEntries(); - return compiledEntries.get(0).getPrimary().getLo(); + public boolean hasCompiledMethods() { + return !compiledMethods.isEmpty(); } public ClassEntry getSuperClass() { return superClass; } - public MethodEntry ensureMethodEntryForDebugRangeInfo(DebugRangeInfo debugRangeInfo, DebugInfoBase debugInfoBase, DebugContext debugContext) { - - MethodEntry methodEntry = methodsIndex.get(debugRangeInfo.idMethod()); - if (methodEntry == null) { - methodEntry = processMethod(debugRangeInfo, debugInfoBase, debugContext); - } else { - methodEntry.updateRangeInfo(debugInfoBase, debugRangeInfo); - } - return methodEntry; - } - public List getMethods() { - return methods; + return List.copyOf(methods); } - /* - * Accessors for lo and hi bounds of this class's compiled method code ranges. See comments in - * class DebugInfoBase for an explanation of the layout of compiled method code. - */ - /** * Retrieve the lowest code section offset for compiled method code belonging to this class. It * is an error to call this for a class entry which has no compiled methods. * * @return the lowest code section offset for compiled method code belonging to this class */ - public int lowpc() { - assert hasCompiledEntries(); - return compiledEntries.get(0).getPrimary().getLo(); + public long lowpc() { + assert hasCompiledMethods(); + return compiledMethods.first().primary().getLo(); } /** @@ -365,84 +289,28 @@ public int lowpc() { * * @return the highest code section offset for compiled method code belonging to this class */ - public int hipc() { - assert hasCompiledEntries(); - return compiledEntries.get(compiledEntries.size() - 1).getPrimary().getHi(); + public long hipc() { + assert hasCompiledMethods(); + return compiledMethods.last().primary().getHi(); } /** - * Add a file to the list of files referenced from info associated with this class. - * - * @param file The file to be added. - */ - public void includeFile(FileEntry file) { - assert !files.contains(file) : "caller should ensure file is only included once"; - assert fileIndex == null : "cannot include files after index has been created"; - files.add(file); - } - - /** - * Add a directory to the list of firectories referenced from info associated with this class. - * - * @param dirEntry The directory to be added. - */ - public void includeDir(DirEntry dirEntry) { - assert !dirs.contains(dirEntry) : "caller should ensure dir is only included once"; - assert dirIndex == null : "cannot include dirs after index has been created"; - dirs.add(dirEntry); - } - - /** - * Populate the file and directory indexes that track positions in the file and dir tables for - * this class's line info section. - */ - public void buildFileAndDirIndexes() { - // this is a one-off operation - assert fileIndex == null && dirIndex == null : "file and indexes can only be generated once"; - if (files.isEmpty()) { - assert dirs.isEmpty() : "should not have included any dirs if we have no files"; - } - int idx = 1; - fileIndex = EconomicMap.create(files.size()); - for (FileEntry file : files) { - fileIndex.put(file, idx++); - } - dirIndex = EconomicMap.create(dirs.size()); - idx = 1; - for (DirEntry dir : dirs) { - if (!dir.getPathString().isEmpty()) { - dirIndex.put(dir, idx++); - } else { - assert idx == 1; - } - } - } - - /** - * Retrieve a stream of all files referenced from debug info for this class in line info file + * Retrieve a list of all files referenced from debug info for this class in line info file * table order, starting with the file at index 1. - * - * @return a stream of all referenced files + * + * @return a list of all referenced files */ - public Stream fileStream() { - if (!files.isEmpty()) { - return files.stream(); - } else { - return Stream.empty(); - } + public List getFiles() { + return List.copyOf(files); } /** - * Retrieve a stream of all directories referenced from debug info for this class in line info + * Retrieve a list of all directories referenced from debug info for this class in line info * directory table order, starting with the directory at index 1. * - * @return a stream of all referenced directories + * @return a list of all referenced directories */ - public Stream dirStream() { - if (!dirs.isEmpty()) { - return dirs.stream(); - } else { - return Stream.empty(); - } + public List getDirs() { + return List.copyOf(dirs); } } diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/CompiledMethodEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/CompiledMethodEntry.java index b8d34393677a..2c154c0f54b2 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/CompiledMethodEntry.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/CompiledMethodEntry.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -26,139 +26,53 @@ package com.oracle.objectfile.debugentry; -import com.oracle.objectfile.debugentry.range.PrimaryRange; -import com.oracle.objectfile.debugentry.range.SubRange; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugFrameSizeChange; - -import java.util.ArrayDeque; -import java.util.Iterator; import java.util.List; +import java.util.stream.Stream; + +import com.oracle.objectfile.debugentry.range.PrimaryRange; +import com.oracle.objectfile.debugentry.range.Range; /** * Tracks debug info associated with a top level compiled method. + * + * @param primary The primary range detailed by this object. + * @param classEntry Details of the class owning this range. + * @param frameSizeInfos Details of compiled method frame size changes. + * @param frameSize Size of compiled method frame. */ -public class CompiledMethodEntry { - /** - * The primary range detailed by this object. - */ - private final PrimaryRange primary; - /** - * Details of the class owning this range. - */ - private final ClassEntry classEntry; - /** - * Details of of compiled method frame size changes. - */ - private final List frameSizeInfos; - /** - * Size of compiled method frame. - */ - private final int frameSize; - - public CompiledMethodEntry(PrimaryRange primary, List frameSizeInfos, int frameSize, ClassEntry classEntry) { - this.primary = primary; - this.classEntry = classEntry; - this.frameSizeInfos = frameSizeInfos; - this.frameSize = frameSize; - } - - public PrimaryRange getPrimary() { - return primary; - } - - public ClassEntry getClassEntry() { - return classEntry; - } +public record CompiledMethodEntry(PrimaryRange primary, List frameSizeInfos, int frameSize, + ClassEntry classEntry) { /** - * Returns an iterator that traverses all the callees of the method associated with this entry. - * The iterator performs a depth-first pre-order traversal of the call tree. + * Returns a stream that traverses all the callees of the method associated with this entry. The + * stream performs a depth-first pre-order traversal of the call tree. * - * @return the iterator + * @return the stream of all ranges */ - public Iterator topDownRangeIterator() { - return new Iterator<>() { - final ArrayDeque workStack = new ArrayDeque<>(); - SubRange current = primary.getFirstCallee(); - - @Override - public boolean hasNext() { - return current != null; - } - - @Override - public SubRange next() { - assert hasNext(); - SubRange result = current; - forward(); - return result; - } - - private void forward() { - SubRange sibling = current.getSiblingCallee(); - assert sibling == null || (current.getHi() <= sibling.getLo()) : current.getHi() + " > " + sibling.getLo(); - if (!current.isLeaf()) { - /* save next sibling while we process the children */ - if (sibling != null) { - workStack.push(sibling); - } - current = current.getFirstCallee(); - } else if (sibling != null) { - current = sibling; - } else { - /* - * Return back up to parents' siblings, use pollFirst instead of pop to return - * null in case the work stack is empty - */ - current = workStack.pollFirst(); - } - } - }; + public Stream topDownRangeStream() { + // skip the root of the range stream which is the primary range + return primary.rangeStream().skip(1); } /** - * Returns an iterator that traverses the callees of the method associated with this entry and - * returns only the leafs. The iterator performs a depth-first pre-order traversal of the call + * Returns a stream that traverses the callees of the method associated with this entry and + * returns only the leafs. The stream performs a depth-first pre-order traversal of the call * tree returning only ranges with no callees. * - * @return the iterator + * @return the stream of leaf ranges */ - public Iterator leafRangeIterator() { - final Iterator iter = topDownRangeIterator(); - return new Iterator<>() { - SubRange current = forwardLeaf(iter); - - @Override - public boolean hasNext() { - return current != null; - } - - @Override - public SubRange next() { - assert hasNext(); - SubRange result = current; - current = forwardLeaf(iter); - return result; - } - - private SubRange forwardLeaf(Iterator t) { - if (t.hasNext()) { - SubRange next = t.next(); - while (next != null && !next.isLeaf()) { - next = t.next(); - } - return next; - } - return null; - } - }; - } - - public List getFrameSizeInfos() { - return frameSizeInfos; + public Stream leafRangeStream() { + return topDownRangeStream().filter(Range::isLeaf); } - public int getFrameSize() { - return frameSize; + /** + * Returns a stream that traverses the callees of the method associated with this entry and + * returns only the call ranges. The stream performs a depth-first pre-order traversal of the + * call tree returning only ranges with callees. + * + * @return the stream of call ranges + */ + public Stream callRangeStream() { + return topDownRangeStream().filter(range -> !range.isLeaf()); } } diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ConstantValueEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ConstantValueEntry.java new file mode 100644 index 000000000000..8388f3369dca --- /dev/null +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ConstantValueEntry.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.objectfile.debugentry; + +import jdk.vm.ci.meta.JavaConstant; + +public record ConstantValueEntry(long heapOffset, JavaConstant constant) implements LocalValueEntry { + + @Override + public String toString() { + return "CONST:" + (constant != null ? constant.toValueString() : "null") + "[" + Long.toHexString(heapOffset) + "]"; + } +} diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/DebugInfoBase.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/DebugInfoBase.java index 47a68ad22445..9be7a2c45ce8 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/DebugInfoBase.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/DebugInfoBase.java @@ -27,29 +27,10 @@ package com.oracle.objectfile.debugentry; import java.nio.ByteOrder; -import java.nio.file.Path; -import java.nio.file.Paths; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; -import org.graalvm.collections.EconomicMap; -import org.graalvm.collections.EconomicSet; - -import com.oracle.objectfile.debugentry.range.PrimaryRange; -import com.oracle.objectfile.debugentry.range.Range; -import com.oracle.objectfile.debugentry.range.SubRange; import com.oracle.objectfile.debuginfo.DebugInfoProvider; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugCodeInfo; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugFileInfo; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugLocalValueInfo; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugLocationInfo; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo.DebugTypeKind; -import com.oracle.objectfile.elf.dwarf.DwarfDebugInfo; - -import jdk.graal.compiler.debug.DebugContext; -import jdk.vm.ci.meta.ResolvedJavaType; /** * An abstract class which indexes the information presented by the DebugInfoProvider in an @@ -90,39 +71,31 @@ public abstract class DebugInfoBase { protected ByteOrder byteOrder; /** - * A table listing all known strings, some of which may be marked for insertion into the - * debug_str section. - */ - private final StringTable stringTable = new StringTable(); - /** - * List of dirs in which files are found to reside. + * A table listing all known strings except strings in the debug line section. */ - private final List dirs = new ArrayList<>(); + private StringTable stringTable; + /** - * Index of all dirs in which files are found to reside either as part of substrate/compiler or - * user code. + * A table listing all known strings in the debug line section. */ - private final EconomicMap dirsIndex = EconomicMap.create(); + private StringTable lineStringTable; /** * List of all types present in the native image including instance classes, array classes, * primitive types and the one-off Java header struct. */ private final List types = new ArrayList<>(); - /** - * Index of already seen types keyed by the unique, associated, identifying ResolvedJavaType or, - * in the single special case of the TypeEntry for the Java header structure, by key null. - */ - private final Map typesIndex = new HashMap<>(); /** * List of all instance classes found in debug info. These classes do not necessarily have top * level or inline compiled methods. This list includes interfaces and enum types. */ private final List instanceClasses = new ArrayList<>(); - /** - * Index of already seen classes. - */ - private final EconomicMap instanceClassesIndex = EconomicMap.create(); + + private final List instanceClassesWithCompilation = new ArrayList<>(); + + private final List primitiveTypes = new ArrayList<>(); + + private final List arrayTypes = new ArrayList<>(); /** * Handle on type entry for header structure. */ @@ -140,30 +113,14 @@ public abstract class DebugInfoBase { * debug info API in ascending address range order. */ private final List compiledMethods = new ArrayList<>(); - /** - * List of of files which contain primary or secondary ranges. - */ - private final List files = new ArrayList<>(); - /** - * Index of files which contain primary or secondary ranges keyed by path. - */ - private final EconomicMap filesIndex = EconomicMap.create(); - - /** - * List of all loaders associated with classes included in the image. - */ - private final List loaders = new ArrayList<>(); - - /** - * Index of all loaders associated with classes included in the image. - */ - private final EconomicMap loaderIndex = EconomicMap.create(); /** * Flag set to true if heap references are stored as addresses relative to a heap base register * otherwise false. */ private boolean useHeapBase; + + private boolean isRuntimeCompilation; /** * Number of bits oops are left shifted by when using compressed oops. */ @@ -198,16 +155,27 @@ public abstract class DebugInfoBase { private String cachePath; /** - * The offset of the first byte beyond the end of the Java compiled code address range. + * The type entry for java.lang.Class. + */ + private ClassEntry hubClassEntry; + + /** + * A prefix used to label indirect types used to ensure gdb performs oop reference --> raw + * address translation. */ - private int compiledCodeMax; + public static final String COMPRESSED_PREFIX = "_z_."; + /** + * A prefix used for type signature generation to generate unique type signatures for type + * layout type units. + */ + public static final String LAYOUT_PREFIX = "_layout_."; /** - * The type entry for java.lang.Class. + * The name of the type for header field hub which needs special case processing to remove tag + * bits. */ - private ClassEntry hubClassEntry; + public static final String HUB_TYPE_NAME = "java.lang.Class"; - @SuppressWarnings("this-escape") public DebugInfoBase(ByteOrder byteOrder) { this.byteOrder = byteOrder; this.useHeapBase = true; @@ -219,34 +187,22 @@ public DebugInfoBase(ByteOrder byteOrder) { this.objectAlignment = 0; this.numAlignmentBits = 0; this.hubClassEntry = null; - this.compiledCodeMax = 0; - // create and index an empty dir with index 0. - ensureDirEntry(EMPTY_PATH); - } - - public int compiledCodeMax() { - return compiledCodeMax; } /** * Entry point allowing ELFObjectFile to pass on information about types, code and heap data. - * - * @param debugInfoProvider provider instance passed by ObjectFile client. */ @SuppressWarnings("try") public void installDebugInfo(DebugInfoProvider debugInfoProvider) { - /* - * This will be needed once we add support for type info: - * - * DebugTypeInfoProvider typeInfoProvider = debugInfoProvider.typeInfoProvider(); for - * (DebugTypeInfo debugTypeInfo : typeInfoProvider) { install types } - */ + debugInfoProvider.installDebugInfo(); /* * Track whether we need to use a heap base register. */ useHeapBase = debugInfoProvider.useHeapBase(); + this.isRuntimeCompilation = debugInfoProvider.isRuntimeCompilation(); + /* * Save count of low order tag bits that may appear in references. */ @@ -278,187 +234,40 @@ public void installDebugInfo(DebugInfoProvider debugInfoProvider) { /* Reference alignment must be 8 bytes. */ assert objectAlignment == 8; - /* retrieve limit for Java code address range */ - compiledCodeMax = debugInfoProvider.compiledCodeMax(); - - /* Ensure we have a null string and cachePath in the string section. */ - String uniqueNullString = stringTable.uniqueDebugString(""); - if (debugInfoProvider.getCachePath() != null) { - cachePath = stringTable.uniqueDebugString(debugInfoProvider.getCachePath().toString()); - } else { - cachePath = uniqueNullString; // fall back to null string - } - - /* Create all the types. */ - debugInfoProvider.typeInfoProvider().forEach(debugTypeInfo -> debugTypeInfo.debugContext((debugContext) -> { - ResolvedJavaType idType = debugTypeInfo.idType(); - String typeName = debugTypeInfo.typeName(); - typeName = stringTable.uniqueDebugString(typeName); - DebugTypeKind typeKind = debugTypeInfo.typeKind(); - int byteSize = debugTypeInfo.size(); - - if (debugContext.isLogEnabled(DebugContext.INFO_LEVEL)) { - debugContext.log(DebugContext.INFO_LEVEL, "Register %s type %s ", typeKind.toString(), typeName); - } - String fileName = debugTypeInfo.fileName(); - Path filePath = debugTypeInfo.filePath(); - addTypeEntry(idType, typeName, fileName, filePath, byteSize, typeKind); - })); - debugInfoProvider.recordActivity(); - - /* Now we can cross reference static and instance field details. */ - debugInfoProvider.typeInfoProvider().forEach(debugTypeInfo -> debugTypeInfo.debugContext((debugContext) -> { - ResolvedJavaType idType = debugTypeInfo.idType(); - String typeName = debugTypeInfo.typeName(); - DebugTypeKind typeKind = debugTypeInfo.typeKind(); - - if (debugContext.isLogEnabled(DebugContext.INFO_LEVEL)) { - debugContext.log(DebugContext.INFO_LEVEL, "Process %s type %s ", typeKind.toString(), typeName); - } - TypeEntry typeEntry = (idType != null ? lookupTypeEntry(idType) : lookupHeaderType()); - typeEntry.addDebugInfo(this, debugTypeInfo, debugContext); - })); - debugInfoProvider.recordActivity(); - - debugInfoProvider.codeInfoProvider().forEach(debugCodeInfo -> debugCodeInfo.debugContext((debugContext) -> { - /* - * Primary file name and full method name need to be written to the debug_str section. - */ - String fileName = debugCodeInfo.fileName(); - Path filePath = debugCodeInfo.filePath(); - ResolvedJavaType ownerType = debugCodeInfo.ownerType(); - String methodName = debugCodeInfo.name(); - int lo = debugCodeInfo.addressLo(); - int hi = debugCodeInfo.addressHi(); - int primaryLine = debugCodeInfo.line(); - - /* Search for a method defining this primary range. */ - ClassEntry classEntry = lookupClassEntry(ownerType); - MethodEntry methodEntry = classEntry.ensureMethodEntryForDebugRangeInfo(debugCodeInfo, this, debugContext); - PrimaryRange primaryRange = Range.createPrimary(methodEntry, lo, hi, primaryLine); - if (debugContext.isLogEnabled(DebugContext.INFO_LEVEL)) { - debugContext.log(DebugContext.INFO_LEVEL, "PrimaryRange %s.%s %s %s:%d [0x%x, 0x%x]", ownerType.toJavaName(), methodName, filePath, fileName, primaryLine, lo, hi); - } - addPrimaryRange(primaryRange, debugCodeInfo, classEntry); - /* - * Record all subranges even if they have no line or file so we at least get a symbol - * for them and don't see a break in the address range. - */ - EconomicMap subRangeIndex = EconomicMap.create(); - debugCodeInfo.locationInfoProvider().forEach(debugLocationInfo -> addSubrange(debugLocationInfo, primaryRange, classEntry, subRangeIndex, debugContext)); - debugInfoProvider.recordActivity(); - })); - - debugInfoProvider.dataInfoProvider().forEach(debugDataInfo -> debugDataInfo.debugContext((debugContext) -> { - if (debugContext.isLogEnabled(DebugContext.INFO_LEVEL)) { - String provenance = debugDataInfo.getProvenance(); - String typeName = debugDataInfo.getTypeName(); - String partitionName = debugDataInfo.getPartition(); - /* Offset is relative to heap-base register. */ - long offset = debugDataInfo.getOffset(); - long size = debugDataInfo.getSize(); - debugContext.log(DebugContext.INFO_LEVEL, "Data: offset 0x%x size 0x%x type %s partition %s provenance %s ", offset, size, typeName, partitionName, provenance); - } - })); - // populate a file and dir list and associated index for each class entry - getInstanceClasses().forEach(classEntry -> { - collectFilesAndDirs(classEntry); - }); - } - - private TypeEntry createTypeEntry(String typeName, String fileName, Path filePath, int size, DebugTypeKind typeKind) { - TypeEntry typeEntry = null; - switch (typeKind) { - case INSTANCE: { - FileEntry fileEntry = addFileEntry(fileName, filePath); - typeEntry = new ClassEntry(typeName, fileEntry, size); - if (typeEntry.getTypeName().equals(DwarfDebugInfo.HUB_TYPE_NAME)) { - hubClassEntry = (ClassEntry) typeEntry; - } - break; - } - case INTERFACE: { - FileEntry fileEntry = addFileEntry(fileName, filePath); - typeEntry = new InterfaceClassEntry(typeName, fileEntry, size); - break; - } - case ENUM: { - FileEntry fileEntry = addFileEntry(fileName, filePath); - typeEntry = new EnumClassEntry(typeName, fileEntry, size); - break; - } - case PRIMITIVE: - assert fileName.length() == 0; - assert filePath == null; - typeEntry = new PrimitiveTypeEntry(typeName, size); - break; - case ARRAY: - assert fileName.length() == 0; - assert filePath == null; - typeEntry = new ArrayTypeEntry(typeName, size); - break; - case HEADER: - assert fileName.length() == 0; - assert filePath == null; - typeEntry = new HeaderTypeEntry(typeName, size); - break; - case FOREIGN: { - FileEntry fileEntry = addFileEntry(fileName, filePath); - typeEntry = new ForeignTypeEntry(typeName, fileEntry, size); - break; - } - } - return typeEntry; - } - - private TypeEntry addTypeEntry(ResolvedJavaType idType, String typeName, String fileName, Path filePath, int size, DebugTypeKind typeKind) { - TypeEntry typeEntry = (idType != null ? typesIndex.get(idType) : null); - if (typeEntry == null) { - typeEntry = createTypeEntry(typeName, fileName, filePath, size, typeKind); + stringTable = new StringTable(); + lineStringTable = new StringTable(); + /* Ensure we have a null string at the start of the string table. */ + uniqueDebugString(""); + uniqueDebugLineString(""); + /* Create the cachePath string entry which serves as base directory for source files */ + cachePath = uniqueDebugString(debugInfoProvider.cachePath()); + uniqueDebugLineString(debugInfoProvider.cachePath()); + + compiledMethods.addAll(debugInfoProvider.compiledMethodEntries()); + debugInfoProvider.typeEntries().forEach(typeEntry -> { types.add(typeEntry); - if (idType != null) { - typesIndex.put(idType, typeEntry); - } - // track object type and header struct - if (idType == null) { - headerType = (HeaderTypeEntry) typeEntry; - } - if (typeName.equals("java.lang.Object")) { - objectClass = (ClassEntry) typeEntry; - } - if (typeName.equals("void")) { + if (typeEntry.getTypeName().equals("void")) { voidType = typeEntry; } - if (typeEntry instanceof ClassEntry) { - indexInstanceClass(idType, (ClassEntry) typeEntry); - } - } else { - if (!(typeEntry.isClass())) { - assert ((ClassEntry) typeEntry).getFileName().equals(fileName); + switch (typeEntry) { + case ArrayTypeEntry arrayTypeEntry -> arrayTypes.add(arrayTypeEntry); + case PrimitiveTypeEntry primitiveTypeEntry -> primitiveTypes.add(primitiveTypeEntry); + case HeaderTypeEntry headerTypeEntry -> headerType = headerTypeEntry; + case ClassEntry classEntry -> { + instanceClasses.add(classEntry); + if (classEntry.hasCompiledMethods()) { + instanceClassesWithCompilation.add(classEntry); + } + if (classEntry.getTypeName().equals("java.lang.Object")) { + objectClass = classEntry; + } else if (classEntry.getTypeName().equals(HUB_TYPE_NAME)) { + hubClassEntry = classEntry; + } + } + default -> { + } } - } - return typeEntry; - } - - public TypeEntry lookupTypeEntry(ResolvedJavaType type) { - TypeEntry typeEntry = typesIndex.get(type); - if (typeEntry == null) { - throw new RuntimeException("Type entry not found " + type.getName()); - } - return typeEntry; - } - - ClassEntry lookupClassEntry(ResolvedJavaType type) { - // lookup key should advertise itself as a resolved instance class or interface - assert type.isInstanceClass() || type.isInterface(); - // lookup target should already be included in the index - ClassEntry classEntry = instanceClassesIndex.get(type); - if (classEntry == null || !(classEntry.isClass())) { - throw new RuntimeException("Class entry not found " + type.getName()); - } - // lookup target should also be indexed in the types index - assert typesIndex.get(type) != null; - return classEntry; + }); } public HeaderTypeEntry lookupHeaderType() { @@ -479,166 +288,6 @@ public ClassEntry lookupObjectClass() { return objectClass; } - private void addPrimaryRange(PrimaryRange primaryRange, DebugCodeInfo debugCodeInfo, ClassEntry classEntry) { - CompiledMethodEntry compiledMethod = classEntry.indexPrimary(primaryRange, debugCodeInfo.getFrameSizeChanges(), debugCodeInfo.getFrameSize()); - indexCompiledMethod(compiledMethod); - } - - /** - * Recursively creates subranges based on DebugLocationInfo including, and appropriately - * linking, nested inline subranges. - * - * @param locationInfo - * @param primaryRange - * @param classEntry - * @param subRangeIndex - * @param debugContext - * @return the subrange for {@code locationInfo} linked with all its caller subranges up to the - * primaryRange - */ - @SuppressWarnings("try") - private Range addSubrange(DebugLocationInfo locationInfo, PrimaryRange primaryRange, ClassEntry classEntry, EconomicMap subRangeIndex, DebugContext debugContext) { - /* - * We still insert subranges for the primary method but they don't actually count as inline. - * we only need a range so that subranges for inline code can refer to the top level line - * number. - */ - DebugLocationInfo callerLocationInfo = locationInfo.getCaller(); - boolean isTopLevel = callerLocationInfo == null; - assert (!isTopLevel || (locationInfo.name().equals(primaryRange.getMethodName()) && - locationInfo.ownerType().toJavaName().equals(primaryRange.getClassName()))); - Range caller = (isTopLevel ? primaryRange : subRangeIndex.get(callerLocationInfo)); - // the frame tree is walked topdown so inline ranges should always have a caller range - assert caller != null; - - final String fileName = locationInfo.fileName(); - final Path filePath = locationInfo.filePath(); - final String fullPath = (filePath == null ? "" : filePath.toString() + "/") + fileName; - final ResolvedJavaType ownerType = locationInfo.ownerType(); - final String methodName = locationInfo.name(); - final int loOff = locationInfo.addressLo(); - final int hiOff = locationInfo.addressHi() - 1; - final int lo = primaryRange.getLo() + locationInfo.addressLo(); - final int hi = primaryRange.getLo() + locationInfo.addressHi(); - final int line = locationInfo.line(); - ClassEntry subRangeClassEntry = lookupClassEntry(ownerType); - MethodEntry subRangeMethodEntry = subRangeClassEntry.ensureMethodEntryForDebugRangeInfo(locationInfo, this, debugContext); - SubRange subRange = Range.createSubrange(subRangeMethodEntry, lo, hi, line, primaryRange, caller, locationInfo.isLeaf()); - classEntry.indexSubRange(subRange); - subRangeIndex.put(locationInfo, subRange); - if (debugContext.isLogEnabled(DebugContext.DETAILED_LEVEL)) { - debugContext.log(DebugContext.DETAILED_LEVEL, "SubRange %s.%s %d %s:%d [0x%x, 0x%x] (%d, %d)", - ownerType.toJavaName(), methodName, subRange.getDepth(), fullPath, line, lo, hi, loOff, hiOff); - } - assert (callerLocationInfo == null || (callerLocationInfo.addressLo() <= loOff && callerLocationInfo.addressHi() >= hiOff)) : "parent range should enclose subrange!"; - DebugLocalValueInfo[] localValueInfos = locationInfo.getLocalValueInfo(); - for (int i = 0; i < localValueInfos.length; i++) { - DebugLocalValueInfo localValueInfo = localValueInfos[i]; - if (debugContext.isLogEnabled(DebugContext.DETAILED_LEVEL)) { - debugContext.log(DebugContext.DETAILED_LEVEL, " locals[%d] %s:%s = %s", localValueInfo.slot(), localValueInfo.name(), localValueInfo.typeName(), localValueInfo); - } - } - subRange.setLocalValueInfo(localValueInfos); - return subRange; - } - - private void indexInstanceClass(ResolvedJavaType idType, ClassEntry classEntry) { - instanceClasses.add(classEntry); - instanceClassesIndex.put(idType, classEntry); - } - - private void indexCompiledMethod(CompiledMethodEntry compiledMethod) { - assert verifyMethodOrder(compiledMethod); - compiledMethods.add(compiledMethod); - } - - private boolean verifyMethodOrder(CompiledMethodEntry next) { - int size = compiledMethods.size(); - if (size > 0) { - CompiledMethodEntry last = compiledMethods.get(size - 1); - PrimaryRange lastRange = last.getPrimary(); - PrimaryRange nextRange = next.getPrimary(); - if (lastRange.getHi() > nextRange.getLo()) { - assert false : "methods %s [0x%x, 0x%x] and %s [0x%x, 0x%x] presented out of order".formatted(lastRange.getFullMethodName(), lastRange.getLo(), lastRange.getHi(), - nextRange.getFullMethodName(), nextRange.getLo(), nextRange.getHi()); - return false; - } - } - return true; - } - - static final Path EMPTY_PATH = Paths.get(""); - - private FileEntry addFileEntry(String fileName, Path filePath) { - assert fileName != null; - Path dirPath = filePath; - Path fileAsPath; - if (filePath != null) { - fileAsPath = dirPath.resolve(fileName); - } else { - fileAsPath = Paths.get(fileName); - dirPath = EMPTY_PATH; - } - FileEntry fileEntry = filesIndex.get(fileAsPath); - if (fileEntry == null) { - DirEntry dirEntry = ensureDirEntry(dirPath); - /* Ensure file and cachepath are added to the debug_str section. */ - uniqueDebugString(fileName); - uniqueDebugString(cachePath); - fileEntry = new FileEntry(fileName, dirEntry); - files.add(fileEntry); - /* Index the file entry by file path. */ - filesIndex.put(fileAsPath, fileEntry); - } else { - assert fileEntry.getDirEntry().getPath().equals(dirPath); - } - return fileEntry; - } - - protected FileEntry ensureFileEntry(DebugFileInfo debugFileInfo) { - String fileName = debugFileInfo.fileName(); - if (fileName == null || fileName.length() == 0) { - return null; - } - Path filePath = debugFileInfo.filePath(); - Path fileAsPath; - if (filePath == null) { - fileAsPath = Paths.get(fileName); - } else { - fileAsPath = filePath.resolve(fileName); - } - /* Reuse any existing entry. */ - FileEntry fileEntry = findFile(fileAsPath); - if (fileEntry == null) { - fileEntry = addFileEntry(fileName, filePath); - } - return fileEntry; - } - - private DirEntry ensureDirEntry(Path filePath) { - if (filePath == null) { - return null; - } - DirEntry dirEntry = dirsIndex.get(filePath); - if (dirEntry == null) { - /* Ensure dir path is entered into the debug_str section. */ - uniqueDebugString(filePath.toString()); - dirEntry = new DirEntry(filePath); - dirsIndex.put(filePath, dirEntry); - dirs.add(dirEntry); - } - return dirEntry; - } - - protected LoaderEntry ensureLoaderEntry(String loaderId) { - LoaderEntry loaderEntry = loaderIndex.get(loaderId); - if (loaderEntry == null) { - loaderEntry = new LoaderEntry(uniqueDebugString(loaderId)); - loaderIndex.put(loaderEntry.getLoaderId(), loaderEntry); - } - return loaderEntry; - } - /* Accessors to query the debug info model. */ public ByteOrder getByteOrder() { return byteOrder; @@ -648,40 +297,34 @@ public List getTypes() { return types; } - public List getInstanceClasses() { - return instanceClasses; - } - - public List getCompiledMethods() { - return compiledMethods; - } - - public List getFiles() { - return files; + public List getArrayTypes() { + return arrayTypes; } - public List getDirs() { - return dirs; + public List getPrimitiveTypes() { + return primitiveTypes; } - @SuppressWarnings("unused") - public FileEntry findFile(Path fullFileName) { - return filesIndex.get(fullFileName); + public List getInstanceClasses() { + return instanceClasses; } - public List getLoaders() { - return loaders; + public List getInstanceClassesWithCompilation() { + return instanceClassesWithCompilation; } - @SuppressWarnings("unused") - public LoaderEntry findLoader(String id) { - return loaderIndex.get(id); + public List getCompiledMethods() { + return compiledMethods; } public StringTable getStringTable() { return stringTable; } + public StringTable getLineStringTable() { + return lineStringTable; + } + /** * Indirects this call to the string table. * @@ -691,6 +334,15 @@ public String uniqueDebugString(String string) { return stringTable.uniqueDebugString(string); } + /** + * Indirects this call to the line string table. + * + * @param string the string whose index is required. + */ + public String uniqueDebugLineString(String string) { + return lineStringTable.uniqueDebugString(string); + } + /** * Indirects this call to the string table. * @@ -701,10 +353,24 @@ public int debugStringIndex(String string) { return stringTable.debugStringIndex(string); } + /** + * Indirects this call to the line string table. + * + * @param string the string whose index is required. + * @return the offset of the string in the .debug_line_str section. + */ + public int debugLineStringIndex(String string) { + return lineStringTable.debugStringIndex(string); + } + public boolean useHeapBase() { return useHeapBase; } + public boolean isRuntimeCompilation() { + return isRuntimeCompilation; + } + public int reservedBitsMask() { return reservedBitsMask; } @@ -738,67 +404,11 @@ public String getCachePath() { } public boolean isHubClassEntry(StructureTypeEntry typeEntry) { - return typeEntry.getTypeName().equals(DwarfDebugInfo.HUB_TYPE_NAME); + return typeEntry.getTypeName().equals(DebugInfoBase.HUB_TYPE_NAME); } public ClassEntry getHubClassEntry() { return hubClassEntry; } - private static void collectFilesAndDirs(ClassEntry classEntry) { - // track files and dirs we have already seen so that we only add them once - EconomicSet visitedFiles = EconomicSet.create(); - EconomicSet visitedDirs = EconomicSet.create(); - // add the class's file and dir - includeOnce(classEntry, classEntry.getFileEntry(), visitedFiles, visitedDirs); - // add files for fields (may differ from class file if we have a substitution) - for (FieldEntry fieldEntry : classEntry.fields) { - includeOnce(classEntry, fieldEntry.getFileEntry(), visitedFiles, visitedDirs); - } - // add files for declared methods (may differ from class file if we have a substitution) - for (MethodEntry methodEntry : classEntry.getMethods()) { - includeOnce(classEntry, methodEntry.getFileEntry(), visitedFiles, visitedDirs); - } - // add files for top level compiled and inline methods - classEntry.compiledEntries().forEachOrdered(compiledMethodEntry -> { - includeOnce(classEntry, compiledMethodEntry.getPrimary().getFileEntry(), visitedFiles, visitedDirs); - // we need files for leaf ranges and for inline caller ranges - // - // add leaf range files first because they get searched for linearly - // during line info processing - compiledMethodEntry.leafRangeIterator().forEachRemaining(subRange -> { - includeOnce(classEntry, subRange.getFileEntry(), visitedFiles, visitedDirs); - }); - // now the non-leaf range files - compiledMethodEntry.topDownRangeIterator().forEachRemaining(subRange -> { - if (!subRange.isLeaf()) { - includeOnce(classEntry, subRange.getFileEntry(), visitedFiles, visitedDirs); - } - }); - }); - // now all files and dirs are known build an index for them - classEntry.buildFileAndDirIndexes(); - } - - /** - * Ensure the supplied file entry and associated directory entry are included, but only once, in - * a class entry's file and dir list. - * - * @param classEntry the class entry whose file and dir list may need to be updated - * @param fileEntry a file entry which may need to be added to the class entry's file list or - * whose dir may need adding to the class entry's dir list - * @param visitedFiles a set tracking current file list entries, updated if a file is added - * @param visitedDirs a set tracking current dir list entries, updated if a dir is added - */ - private static void includeOnce(ClassEntry classEntry, FileEntry fileEntry, EconomicSet visitedFiles, EconomicSet visitedDirs) { - if (fileEntry != null && !visitedFiles.contains(fileEntry)) { - visitedFiles.add(fileEntry); - classEntry.includeFile(fileEntry); - DirEntry dirEntry = fileEntry.getDirEntry(); - if (dirEntry != null && !dirEntry.getPathString().isEmpty() && !visitedDirs.contains(dirEntry)) { - visitedDirs.add(dirEntry); - classEntry.includeDir(dirEntry); - } - } - } } diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/DirEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/DirEntry.java index 619e37d26cba..b39b8dc8ac2a 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/DirEntry.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/DirEntry.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -30,21 +30,12 @@ /** * Tracks the directory associated with one or more source files. - * + *

* This is identified separately from each FileEntry identifying files that reside in the directory. * That is necessary because the line info generator needs to collect and write out directory names * into directory tables once only rather than once per file. */ -public class DirEntry { - private final Path path; - - public DirEntry(Path path) { - this.path = path; - } - - public Path getPath() { - return path; - } +public record DirEntry(Path path) { public String getPathString() { return path.toString(); diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/EnumClassEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/EnumClassEntry.java index f46378d57486..ea4d757bd986 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/EnumClassEntry.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/EnumClassEntry.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -26,25 +26,20 @@ package com.oracle.objectfile.debugentry; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugEnumTypeInfo; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo.DebugTypeKind; - -import jdk.graal.compiler.debug.DebugContext; - public class EnumClassEntry extends ClassEntry { - public EnumClassEntry(String typeName, FileEntry fileEntry, int size) { - super(typeName, fileEntry, size); + public EnumClassEntry(String typeName, int size, long classOffset, long typeSignature, + long compressedTypeSignature, long layoutTypeSignature, + ClassEntry superClass, FileEntry fileEntry, LoaderEntry loader) { + super(typeName, size, classOffset, typeSignature, compressedTypeSignature, layoutTypeSignature, superClass, fileEntry, loader); } @Override - public DebugTypeKind typeKind() { - return DebugTypeKind.ENUM; + public boolean isEnum() { + return true; } @Override - public void addDebugInfo(DebugInfoBase debugInfoBase, DebugTypeInfo debugTypeInfo, DebugContext debugContext) { - assert debugTypeInfo instanceof DebugEnumTypeInfo; - super.addDebugInfo(debugInfoBase, debugTypeInfo, debugContext); + public boolean isInstance() { + return false; } } diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/FieldEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/FieldEntry.java index f6e7c6a0cd40..d23b4e61d63d 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/FieldEntry.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/FieldEntry.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -27,12 +27,13 @@ package com.oracle.objectfile.debugentry; public class FieldEntry extends MemberEntry { + private final int size; private final int offset; - private final boolean isEmbedded; - public FieldEntry(FileEntry fileEntry, String fieldName, StructureTypeEntry ownerType, TypeEntry valueType, int size, int offset, boolean isEmbedded, int modifiers) { + public FieldEntry(FileEntry fileEntry, String fieldName, StructureTypeEntry ownerType, + TypeEntry valueType, int size, int offset, boolean isEmbedded, int modifiers) { super(fileEntry, fieldName, ownerType, valueType, modifiers); this.size = size; this.offset = offset; @@ -40,7 +41,7 @@ public FieldEntry(FileEntry fileEntry, String fieldName, StructureTypeEntry owne } public String fieldName() { - return memberName; + return getMemberName(); } public int getSize() { diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/FileEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/FileEntry.java index 3ae8f0a4832e..296f0ea30646 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/FileEntry.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/FileEntry.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -28,26 +28,13 @@ /** * Tracks debug info associated with a Java source file. + * + * @param fileName The name of the associated file excluding path elements. + * @param dirEntry The directory entry associated with this file entry. */ -public class FileEntry { - private final String fileName; - private final DirEntry dirEntry; - - public FileEntry(String fileName, DirEntry dirEntry) { - this.fileName = fileName; - this.dirEntry = dirEntry; - } - - /** - * The name of the associated file excluding path elements. - */ - public String getFileName() { - return fileName; - } +public record FileEntry(String fileName, DirEntry dirEntry) { public String getPathName() { - @SuppressWarnings("hiding") - DirEntry dirEntry = getDirEntry(); if (dirEntry == null) { return ""; } else { @@ -59,22 +46,15 @@ public String getFullName() { if (dirEntry == null) { return fileName; } else { - return dirEntry.getPath().resolve(getFileName()).toString(); + return dirEntry.path().resolve(fileName).toString(); } } - /** - * The directory entry associated with this file entry. - */ - public DirEntry getDirEntry() { - return dirEntry; - } - @Override public String toString() { - if (getDirEntry() == null) { - return getFileName() == null ? "-" : getFileName(); - } else if (getFileName() == null) { + if (dirEntry == null) { + return fileName == null ? "-" : fileName; + } else if (fileName == null) { return "--"; } return String.format("FileEntry(%s)", getFullName()); diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignFloatTypeEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignFloatTypeEntry.java new file mode 100644 index 000000000000..af251b8949b4 --- /dev/null +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignFloatTypeEntry.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.objectfile.debugentry; + +public class ForeignFloatTypeEntry extends ForeignTypeEntry { + + public ForeignFloatTypeEntry(String typeName, int size, long classOffset, long typeSignature, + long layoutTypeSignature, ClassEntry superClass, FileEntry fileEntry, LoaderEntry loader) { + super(typeName, size, classOffset, typeSignature, layoutTypeSignature, superClass, fileEntry, loader); + } +} diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignIntegerTypeEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignIntegerTypeEntry.java new file mode 100644 index 000000000000..129390f7a7d4 --- /dev/null +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignIntegerTypeEntry.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.objectfile.debugentry; + +public class ForeignIntegerTypeEntry extends ForeignTypeEntry { + private final boolean isSigned; + + public ForeignIntegerTypeEntry(String typeName, int size, long classOffset, long typeSignature, + long layoutTypeSignature, ClassEntry superClass, FileEntry fileEntry, LoaderEntry loader, boolean isSigned) { + super(typeName, size, classOffset, typeSignature, layoutTypeSignature, superClass, fileEntry, loader); + this.isSigned = isSigned; + } + + public boolean isSigned() { + return isSigned; + } +} diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignPointerTypeEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignPointerTypeEntry.java new file mode 100644 index 000000000000..2f80d24f87a3 --- /dev/null +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignPointerTypeEntry.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.objectfile.debugentry; + +public class ForeignPointerTypeEntry extends ForeignTypeEntry { + private final TypeEntry pointerTo; + + public ForeignPointerTypeEntry(String typeName, int size, long classOffset, long typeSignature, + long layoutTypeSignature, ClassEntry superClass, FileEntry fileEntry, LoaderEntry loader, TypeEntry pointerTo) { + super(typeName, size, classOffset, typeSignature, layoutTypeSignature, superClass, fileEntry, loader); + this.pointerTo = pointerTo; + } + + public TypeEntry getPointerTo() { + return pointerTo; + } +} diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignStructTypeEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignStructTypeEntry.java new file mode 100644 index 000000000000..afbe344f5ce3 --- /dev/null +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignStructTypeEntry.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.objectfile.debugentry; + +public class ForeignStructTypeEntry extends ForeignTypeEntry { + private final String typedefName; + private final ForeignStructTypeEntry parent; + + public ForeignStructTypeEntry(String typeName, int size, long classOffset, long typeSignature, + long layoutTypeSignature, ClassEntry superClass, FileEntry fileEntry, LoaderEntry loader, + String typedefName, ForeignStructTypeEntry parent) { + super(typeName, size, classOffset, typeSignature, layoutTypeSignature, superClass, fileEntry, loader); + this.typedefName = typedefName; + this.parent = parent; + } + + public String getTypedefName() { + return typedefName; + } + + public ForeignStructTypeEntry getParent() { + return parent; + } +} diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignTypeEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignTypeEntry.java index dc3dae992f16..9426554fbf1c 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignTypeEntry.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignTypeEntry.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2023, 2023, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -26,131 +26,20 @@ package com.oracle.objectfile.debugentry; -import com.oracle.objectfile.debuginfo.DebugInfoProvider; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugForeignTypeInfo; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo; +public abstract class ForeignTypeEntry extends ClassEntry { -import jdk.graal.compiler.debug.DebugContext; -import jdk.vm.ci.meta.ResolvedJavaType; - -public class ForeignTypeEntry extends ClassEntry { - private static final int FLAG_WORD = 1 << 0; - private static final int FLAG_STRUCT = 1 << 1; - private static final int FLAG_POINTER = 1 << 2; - private static final int FLAG_INTEGRAL = 1 << 3; - private static final int FLAG_SIGNED = 1 << 4; - private static final int FLAG_FLOAT = 1 << 5; - private String typedefName; - private ForeignTypeEntry parent; - private TypeEntry pointerTo; - private int flags; - - public ForeignTypeEntry(String className, FileEntry fileEntry, int size) { - super(className, fileEntry, size); - typedefName = null; - parent = null; - pointerTo = null; - flags = 0; - } - - @Override - public DebugInfoProvider.DebugTypeInfo.DebugTypeKind typeKind() { - return DebugInfoProvider.DebugTypeInfo.DebugTypeKind.FOREIGN; - } - - /* - * When we process a foreign pointer type for the .debug_info section we want to reuse its type - * signature. After sizing the .debug_info the layout type signature contains the type signature - * of the type this foreign pointer points to. - */ - public void setLayoutTypeSignature(long typeSignature) { - this.layoutTypeSignature = typeSignature; + public ForeignTypeEntry(String typeName, int size, long classOffset, long typeSignature, + long layoutTypeSignature, ClassEntry superClass, FileEntry fileEntry, LoaderEntry loader) { + super(typeName, size, classOffset, typeSignature, typeSignature, layoutTypeSignature, superClass, fileEntry, loader); } @Override - public void addDebugInfo(DebugInfoBase debugInfoBase, DebugTypeInfo debugTypeInfo, DebugContext debugContext) { - assert debugTypeInfo instanceof DebugForeignTypeInfo; - super.addDebugInfo(debugInfoBase, debugTypeInfo, debugContext); - DebugForeignTypeInfo debugForeignTypeInfo = (DebugForeignTypeInfo) debugTypeInfo; - this.typedefName = debugForeignTypeInfo.typedefName(); - if (debugForeignTypeInfo.isWord()) { - flags = FLAG_WORD; - } else if (debugForeignTypeInfo.isStruct()) { - flags = FLAG_STRUCT; - ResolvedJavaType parentIdType = debugForeignTypeInfo.parent(); - if (parentIdType != null) { - TypeEntry parentTypeEntry = debugInfoBase.lookupClassEntry(parentIdType); - assert parentTypeEntry instanceof ForeignTypeEntry; - parent = (ForeignTypeEntry) parentTypeEntry; - } - } else if (debugForeignTypeInfo.isPointer()) { - flags = FLAG_POINTER; - ResolvedJavaType referent = debugForeignTypeInfo.pointerTo(); - if (referent != null) { - pointerTo = debugInfoBase.lookupTypeEntry(referent); - } - } else if (debugForeignTypeInfo.isIntegral()) { - flags = FLAG_INTEGRAL; - } else if (debugForeignTypeInfo.isFloat()) { - flags = FLAG_FLOAT; - } - if (debugForeignTypeInfo.isSigned()) { - flags |= FLAG_SIGNED; - } - if (debugContext.isLogEnabled()) { - if (isPointer() && pointerTo != null) { - debugContext.log("foreign type %s flags 0x%x referent %s ", typeName, flags, pointerTo.getTypeName()); - } else { - debugContext.log("foreign type %s flags 0x%x", typeName, flags); - } - } - } - - public String getTypedefName() { - return typedefName; - } - - public ForeignTypeEntry getParent() { - return parent; - } - - public TypeEntry getPointerTo() { - return pointerTo; - } - - public boolean isWord() { - return (flags & FLAG_WORD) != 0; - } - - public boolean isStruct() { - return (flags & FLAG_STRUCT) != 0; - } - - public boolean isPointer() { - return (flags & FLAG_POINTER) != 0; - } - - public boolean isIntegral() { - return (flags & FLAG_INTEGRAL) != 0; - } - - public boolean isSigned() { - return (flags & FLAG_SIGNED) != 0; - } - - public boolean isFloat() { - return (flags & FLAG_FLOAT) != 0; + public boolean isForeign() { + return true; } @Override - protected void processInterface(ResolvedJavaType interfaceType, DebugInfoBase debugInfoBase, DebugContext debugContext) { - ClassEntry parentEntry = debugInfoBase.lookupClassEntry(interfaceType); - // don't model the interface relationship when the Java interface actually identifies a - // foreign type - if (parentEntry instanceof InterfaceClassEntry) { - super.processInterface(interfaceType, debugInfoBase, debugContext); - } else { - assert parentEntry instanceof ForeignTypeEntry : "was only expecting an interface or a foreign type"; - } + public boolean isInstance() { + return false; } } diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignWordTypeEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignWordTypeEntry.java new file mode 100644 index 000000000000..f0b31bb58ada --- /dev/null +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignWordTypeEntry.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.objectfile.debugentry; + +public class ForeignWordTypeEntry extends ForeignTypeEntry { + private final boolean isSigned; + + public ForeignWordTypeEntry(String typeName, int size, long classOffset, long typeSignature, + long layoutTypeSignature, ClassEntry superClass, FileEntry fileEntry, LoaderEntry loader, boolean isSigned) { + super(typeName, size, classOffset, typeSignature, layoutTypeSignature, superClass, fileEntry, loader); + this.isSigned = isSigned; + } + + public boolean isSigned() { + return isSigned; + } +} diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/FrameSizeChangeEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/FrameSizeChangeEntry.java new file mode 100644 index 000000000000..b4c6d15e00cd --- /dev/null +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/FrameSizeChangeEntry.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.objectfile.debugentry; + +import com.oracle.objectfile.debuginfo.DebugInfoProvider.FrameSizeChangeType; + +public record FrameSizeChangeEntry(int offset, FrameSizeChangeType type) { + public boolean isExtend() { + return type == FrameSizeChangeType.EXTEND; + } + + public boolean isContract() { + return type == FrameSizeChangeType.CONTRACT; + } +} diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/HeaderTypeEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/HeaderTypeEntry.java index 4ececbccd8bf..c1e9a3908e5b 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/HeaderTypeEntry.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/HeaderTypeEntry.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -26,28 +26,14 @@ package com.oracle.objectfile.debugentry; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugHeaderTypeInfo; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo.DebugTypeKind; - -import jdk.graal.compiler.debug.DebugContext; - public class HeaderTypeEntry extends StructureTypeEntry { - public HeaderTypeEntry(String typeName, int size) { - super(typeName, size); - } - - @Override - public DebugTypeKind typeKind() { - return DebugTypeKind.HEADER; + public HeaderTypeEntry(String typeName, int size, long typeSignature) { + super(typeName, size, -1, typeSignature, typeSignature, typeSignature); } @Override - public void addDebugInfo(DebugInfoBase debugInfoBase, DebugTypeInfo debugTypeInfo, DebugContext debugContext) { - super.addDebugInfo(debugInfoBase, debugTypeInfo, debugContext); - assert debugTypeInfo.typeName().equals(typeName); - DebugHeaderTypeInfo debugHeaderTypeInfo = (DebugHeaderTypeInfo) debugTypeInfo; - debugHeaderTypeInfo.fieldInfoProvider().forEach(debugFieldInfo -> this.processField(debugFieldInfo, debugInfoBase, debugContext)); + public boolean isHeader() { + return true; } } diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/InterfaceClassEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/InterfaceClassEntry.java index 42b2e7ac2d03..0f1851c4dbab 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/InterfaceClassEntry.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/InterfaceClassEntry.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -26,44 +26,36 @@ package com.oracle.objectfile.debugentry; -import java.util.ArrayList; +import java.util.Comparator; import java.util.List; -import java.util.stream.Stream; - -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugInterfaceTypeInfo; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo.DebugTypeKind; - -import jdk.graal.compiler.debug.DebugContext; +import java.util.concurrent.ConcurrentSkipListSet; public class InterfaceClassEntry extends ClassEntry { - private final List implementors; + private final ConcurrentSkipListSet implementors; - public InterfaceClassEntry(String className, FileEntry fileEntry, int size) { - super(className, fileEntry, size); - implementors = new ArrayList<>(); + public InterfaceClassEntry(String typeName, int size, long classOffset, long typeSignature, + long compressedTypeSignature, long layoutTypeSignature, + ClassEntry superClass, FileEntry fileEntry, LoaderEntry loader) { + super(typeName, size, classOffset, typeSignature, compressedTypeSignature, layoutTypeSignature, superClass, fileEntry, loader); + this.implementors = new ConcurrentSkipListSet<>(Comparator.comparingLong(ClassEntry::getTypeSignature)); } @Override - public DebugTypeKind typeKind() { - return DebugTypeKind.INTERFACE; + public boolean isInterface() { + return true; } @Override - public void addDebugInfo(DebugInfoBase debugInfoBase, DebugTypeInfo debugTypeInfo, DebugContext debugContext) { - assert debugTypeInfo instanceof DebugInterfaceTypeInfo; - super.addDebugInfo(debugInfoBase, debugTypeInfo, debugContext); + public boolean isInstance() { + return false; } - public void addImplementor(ClassEntry classEntry, DebugContext debugContext) { + public void addImplementor(ClassEntry classEntry) { implementors.add(classEntry); - if (debugContext.isLogEnabled()) { - debugContext.log("typename %s add implementor %s%n", typeName, classEntry.getTypeName()); - } } - public Stream implementors() { - return implementors.stream(); + public List getImplementors() { + return List.copyOf(implementors); } @Override @@ -75,7 +67,7 @@ public int getSize() { * size of the wrapper class that handles address translation for values embedded in object * fields. */ - int maxSize = super.size; + int maxSize = super.getSize(); for (ClassEntry implementor : implementors) { int nextSize = implementor.getSize(); diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/LoaderEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/LoaderEntry.java index f3390e29e802..ed4fbe480081 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/LoaderEntry.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/LoaderEntry.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -26,19 +26,10 @@ package com.oracle.objectfile.debugentry; /** - * A LoaderEntry is used to record a unique id string for the class loader asscoiated with classes + * A LoaderEntry is used to record a unique id string for the class loader associated with classes * that can be susceptible to repeat definitions. In such cases the loader id can be used when * constructing a unique linker symbol for methods and static fields of the class. That same id may * need to be embedded in debug info that identifies class and method names. */ -public class LoaderEntry { - private final String loaderId; - - public LoaderEntry(String id) { - loaderId = id; - } - - public String getLoaderId() { - return loaderId; - } +public record LoaderEntry(String loaderId) { } diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/LocalEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/LocalEntry.java new file mode 100644 index 000000000000..2c95bf44bd71 --- /dev/null +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/LocalEntry.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.objectfile.debugentry; + +import java.util.concurrent.atomic.AtomicInteger; + +public record LocalEntry(String name, TypeEntry type, int slot, AtomicInteger line) { + + public LocalEntry(String name, TypeEntry type, int slot, int line) { + /* + * Use a AtomicInteger for the line number as it might change if we encounter the same local + * variable in a different frame state with a lower line number + */ + this(name, type, slot, new AtomicInteger(line)); + } + + @Override + public String toString() { + return String.format("Local(%s type=%s slot=%d line=%d)", name, type.getTypeName(), slot, getLine()); + } + + public void setLine(int newLine) { + this.line.set(newLine); + } + + public int getLine() { + return line.get(); + } +} diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/LocalValueEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/LocalValueEntry.java new file mode 100644 index 000000000000..a676ba2018db --- /dev/null +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/LocalValueEntry.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.objectfile.debugentry; + +public interface LocalValueEntry { +} diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/MemberEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/MemberEntry.java index 48a5bf55ffca..df9160f4077e 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/MemberEntry.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/MemberEntry.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -31,12 +31,12 @@ * shared by both field and method entries. */ public abstract class MemberEntry { - protected FileEntry fileEntry; - protected final int line; - protected final String memberName; - protected final StructureTypeEntry ownerType; - protected final TypeEntry valueType; - protected final int modifiers; + private final FileEntry fileEntry; + private final int line; + private final String memberName; + private final StructureTypeEntry ownerType; + private final TypeEntry valueType; + private final int modifiers; public MemberEntry(FileEntry fileEntry, String memberName, StructureTypeEntry ownerType, TypeEntry valueType, int modifiers) { this(fileEntry, 0, memberName, ownerType, valueType, modifiers); @@ -54,7 +54,7 @@ public MemberEntry(FileEntry fileEntry, int line, String memberName, StructureTy public String getFileName() { if (fileEntry != null) { - return fileEntry.getFileName(); + return fileEntry.fileName(); } else { return ""; } @@ -81,6 +81,12 @@ public FileEntry getFileEntry() { return fileEntry; } + /** + * Fetch the file index from its owner class entry with {@link ClassEntry#getFileIdx}. The file + * entry must only be fetched for members whose owner is a {@link ClassEntry}. + * + * @return the file index of this members file in the owner class entry + */ public int getFileIdx() { if (ownerType instanceof ClassEntry) { return ((ClassEntry) ownerType).getFileIdx(fileEntry); @@ -90,11 +96,15 @@ public int getFileIdx() { return 1; } + public String getMemberName() { + return memberName; + } + public int getLine() { return line; } - public StructureTypeEntry ownerType() { + public StructureTypeEntry getOwnerType() { return ownerType; } @@ -109,4 +119,9 @@ public int getModifiers() { public String getModifiersString() { return ownerType.memberModifiers(modifiers); } + + @Override + public String toString() { + return String.format("Member(%s %s %s owner=%s %s:%d)", getModifiersString(), valueType.getTypeName(), memberName, ownerType.getTypeName(), fileEntry.getFullName(), line); + } } diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/MethodEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/MethodEntry.java index d583784898ad..008aa7b5e2b4 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/MethodEntry.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/MethodEntry.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -26,198 +26,183 @@ package com.oracle.objectfile.debugentry; -import java.util.ArrayList; -import java.util.ListIterator; - -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugCodeInfo; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugLocalInfo; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugLocalValueInfo; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugLocationInfo; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugMethodInfo; - -import jdk.vm.ci.meta.JavaKind; -import jdk.vm.ci.meta.ResolvedJavaType; +import java.util.Comparator; +import java.util.List; +import java.util.SortedSet; +import java.util.concurrent.ConcurrentSkipListSet; public class MethodEntry extends MemberEntry { - private final TypeEntry[] paramTypes; - private final DebugLocalInfo thisParam; - private final DebugLocalInfo[] paramInfos; - private final int firstLocalSlot; + private final LocalEntry thisParam; + private final SortedSet paramInfos; + private final int lastParamSlot; // local vars are accumulated as they are referenced sorted by slot, then name, then // type name. we don't currently deal handle references to locals with no slot. - private final ArrayList locals; - static final int DEOPT = 1 << 0; - static final int IN_RANGE = 1 << 1; - static final int INLINED = 1 << 2; - static final int IS_OVERRIDE = 1 << 3; - static final int IS_CONSTRUCTOR = 1 << 4; - private int flags; + private final ConcurrentSkipListSet locals; + private final boolean isDeopt; + private boolean isInRange; + private boolean isInlined; + private final boolean isOverride; + private final boolean isConstructor; private final int vtableOffset; private final String symbolName; - @SuppressWarnings("this-escape") - public MethodEntry(DebugInfoBase debugInfoBase, DebugMethodInfo debugMethodInfo, - FileEntry fileEntry, int line, String methodName, ClassEntry ownerType, - TypeEntry valueType, TypeEntry[] paramTypes, DebugLocalInfo[] paramInfos, DebugLocalInfo thisParam) { - super(fileEntry, line, methodName, ownerType, valueType, debugMethodInfo.modifiers()); - this.paramTypes = paramTypes; + public MethodEntry(FileEntry fileEntry, int line, String methodName, StructureTypeEntry ownerType, + TypeEntry valueType, int modifiers, SortedSet paramInfos, LocalEntry thisParam, + String symbolName, boolean isDeopt, boolean isOverride, boolean isConstructor, int vtableOffset, + int lastParamSlot, List locals) { + super(fileEntry, line, methodName, ownerType, valueType, modifiers); this.paramInfos = paramInfos; this.thisParam = thisParam; - this.symbolName = debugMethodInfo.symbolNameForMethod(); - this.flags = 0; - if (debugMethodInfo.isDeoptTarget()) { - setIsDeopt(); - } - if (debugMethodInfo.isConstructor()) { - setIsConstructor(); - } - if (debugMethodInfo.isOverride()) { - setIsOverride(); - } - vtableOffset = debugMethodInfo.vtableOffset(); - int paramCount = paramInfos.length; - if (paramCount > 0) { - DebugLocalInfo lastParam = paramInfos[paramCount - 1]; - firstLocalSlot = lastParam.slot() + lastParam.slotCount(); - } else { - firstLocalSlot = (thisParam == null ? 0 : thisParam.slotCount()); - } - locals = new ArrayList<>(); - updateRangeInfo(debugInfoBase, debugMethodInfo); + this.symbolName = symbolName; + this.isDeopt = isDeopt; + this.isOverride = isOverride; + this.isConstructor = isConstructor; + this.vtableOffset = vtableOffset; + this.lastParamSlot = lastParamSlot; + + this.locals = new ConcurrentSkipListSet<>(Comparator.comparingInt(LocalEntry::slot).thenComparing(LocalEntry::name).thenComparing(le -> le.type().getTypeName())); + /* + * Sort by line and add all locals, such that the methods locals only contain the lowest + * line number. + */ + locals.stream().sorted(Comparator.comparingInt(LocalEntry::getLine)).forEach(this.locals::add); + + /* + * Flags to identify compiled methods. We set inRange if there is a compilation for this + * method and inlined if it is encountered as a CallNode when traversing the compilation + * result frame tree. + */ + this.isInRange = false; + this.isInlined = false; } - public String methodName() { - return memberName; + public String getMethodName() { + return getMemberName(); } @Override - public ClassEntry ownerType() { + public ClassEntry getOwnerType() { + StructureTypeEntry ownerType = super.getOwnerType(); assert ownerType instanceof ClassEntry; return (ClassEntry) ownerType; } - public int getParamCount() { - return paramInfos.length; - } - - public TypeEntry getParamType(int idx) { - assert idx < paramInfos.length; - return paramTypes[idx]; - } - - public TypeEntry[] getParamTypes() { - return paramTypes; + public List getParamTypes() { + return paramInfos.stream().map(LocalEntry::type).toList(); } - public String getParamTypeName(int idx) { - assert idx < paramTypes.length; - return paramTypes[idx].getTypeName(); + public List getParams() { + return List.copyOf(paramInfos); } - public String getParamName(int idx) { - assert idx < paramInfos.length; - /* N.b. param names may be null. */ - return paramInfos[idx].name(); + public LocalEntry getThisParam() { + return thisParam; } - public int getParamLine(int idx) { - assert idx < paramInfos.length; - /* N.b. param names may be null. */ - return paramInfos[idx].line(); - } + /** + * Returns the local entry for a given name slot and type entry. Decides with the slot number + * whether this is a parameter or local variable. + * + *

+ * Local variable might not be contained in this method entry. Creates a new local entry in + * {@link #locals} with the given parameters. + *

+ * For locals, we also make sure that the line information is the lowest line encountered in + * local variable lookups. If a new lower line number is encountered for an existing local + * entry, we update the line number in the local entry. + * + * @param name the given name + * @param slot the given slot + * @param type the given {@code TypeEntry} + * @param line the given line + * @return the local entry stored in {@link #locals} + */ + public LocalEntry lookupLocalEntry(String name, int slot, TypeEntry type, int line) { + if (slot < 0) { + return null; + } - public DebugLocalInfo getParam(int i) { - assert i >= 0 && i < paramInfos.length : "bad param index"; - return paramInfos[i]; - } + if (slot <= lastParamSlot) { + if (thisParam != null) { + if (thisParam.slot() == slot && thisParam.name().equals(name) && thisParam.type().equals(type)) { + return thisParam; + } + } + for (LocalEntry param : paramInfos) { + if (param.slot() == slot && param.name().equals(name) && param.type().equals(type)) { + return param; + } + } + } else { + /* + * Add a new local entry if it was not already present in the locals list. A local is + * unique if it has different slot, name, and/or type. If the local is already + * contained, we might update the line number to the lowest occurring line number. + */ + LocalEntry local = new LocalEntry(name, type, slot, line); + if (!locals.add(local)) { + /* + * Fetch local from locals list. This iterates over all locals to create the head + * set and then takes the last value. + */ + local = locals.headSet(local, true).last(); + // Update line number if a lower one was encountered. + if (local.getLine() > line) { + local.setLine(line); + } + } + return local; + } - public DebugLocalInfo getThisParam() { - return thisParam; + /* + * The slot is within the range of the params, but none of the params exactly match. This + * might be some local value that is stored in a slot where we expect a param. We just + * ignore such values for now. + * + * This also applies to params that are inferred from frame values, as the types do not + * match most of the time. + */ + return null; } - public int getLocalCount() { - return locals.size(); + public List getLocals() { + return List.copyOf(locals); } - public DebugLocalInfo getLocal(int i) { - assert i >= 0 && i < locals.size() : "bad param index"; - return locals.get(i); + public int getLastParamSlot() { + return lastParamSlot; } - private void setIsDeopt() { - flags |= DEOPT; + public boolean isStatic() { + return thisParam == null; } public boolean isDeopt() { - return (flags & DEOPT) != 0; - } - - private void setIsInRange() { - flags |= IN_RANGE; + return isDeopt; } public boolean isInRange() { - return (flags & IN_RANGE) != 0; + return isInRange; } - private void setIsInlined() { - flags |= INLINED; + public void setInRange() { + isInRange = true; } public boolean isInlined() { - return (flags & INLINED) != 0; + return isInlined; } - private void setIsOverride() { - flags |= IS_OVERRIDE; + public void setInlined() { + isInlined = true; } public boolean isOverride() { - return (flags & IS_OVERRIDE) != 0; - } - - private void setIsConstructor() { - flags |= IS_CONSTRUCTOR; + return isOverride; } public boolean isConstructor() { - return (flags & IS_CONSTRUCTOR) != 0; - } - - /** - * Sets {@code isInRange} and ensures that the {@code fileEntry} is up to date. If the - * MethodEntry was added by traversing the DeclaredMethods of a Class its fileEntry will point - * to the original source file, thus it will be wrong for substituted methods. As a result when - * setting a MethodEntry as isInRange we also make sure that its fileEntry reflects the file - * info associated with the corresponding Range. - * - * @param debugInfoBase - * @param debugMethodInfo - */ - public void updateRangeInfo(DebugInfoBase debugInfoBase, DebugMethodInfo debugMethodInfo) { - if (debugMethodInfo instanceof DebugLocationInfo) { - DebugLocationInfo locationInfo = (DebugLocationInfo) debugMethodInfo; - if (locationInfo.getCaller() != null) { - /* this is a real inlined method */ - setIsInlined(); - } - } else if (debugMethodInfo instanceof DebugCodeInfo) { - /* this method is being notified as a top level compiled method */ - if (isInRange()) { - /* it has already been seen -- just check for consistency */ - assert fileEntry == debugInfoBase.ensureFileEntry(debugMethodInfo); - } else { - /* - * If the MethodEntry was added by traversing the DeclaredMethods of a Class its - * fileEntry may point to the original source file, which will be wrong for - * substituted methods. As a result when setting a MethodEntry as isInRange we also - * make sure that its fileEntry reflects the file info associated with the - * corresponding Range. - */ - setIsInRange(); - fileEntry = debugInfoBase.ensureFileEntry(debugMethodInfo); - } - } + return isConstructor; } public boolean isVirtual() { @@ -231,149 +216,4 @@ public int getVtableOffset() { public String getSymbolName() { return symbolName; } - - /** - * Return a unique local or parameter variable associated with the value, optionally recording - * it as a new local variable or fail, returning null, when the local value does not conform - * with existing recorded parameter or local variables. Values with invalid (negative) slots - * always fail. Values whose slot is associated with a parameter only conform if their name and - * type equal those of the parameter. Values whose slot is in the local range will always - * succeed,. either by matchign the slot and name of an existing local or by being recorded as a - * new local variable. - * - * @param localValueInfo - * @return the unique local variable with which this local value can be legitimately associated - * otherwise null. - */ - public DebugLocalInfo recordLocal(DebugLocalValueInfo localValueInfo) { - int slot = localValueInfo.slot(); - if (slot < 0) { - return null; - } else { - if (slot < firstLocalSlot) { - return matchParam(localValueInfo); - } else { - return matchLocal(localValueInfo); - } - } - } - - private DebugLocalInfo matchParam(DebugLocalValueInfo localValueInfo) { - if (thisParam != null) { - if (checkMatch(thisParam, localValueInfo)) { - return thisParam; - } - } - for (int i = 0; i < paramInfos.length; i++) { - DebugLocalInfo paramInfo = paramInfos[i]; - if (checkMatch(paramInfo, localValueInfo)) { - return paramInfo; - } - } - return null; - } - - /** - * wrapper class for a local value that stands in as a unique identifier for the associated - * local variable while allowing its line to be adjusted when earlier occurrences of the same - * local are identified. - */ - private static class DebugLocalInfoWrapper implements DebugLocalInfo { - DebugLocalValueInfo value; - int line; - - DebugLocalInfoWrapper(DebugLocalValueInfo value) { - this.value = value; - this.line = value.line(); - } - - @Override - public ResolvedJavaType valueType() { - return value.valueType(); - } - - @Override - public String name() { - return value.name(); - } - - @Override - public String typeName() { - return value.typeName(); - } - - @Override - public int slot() { - return value.slot(); - } - - @Override - public int slotCount() { - return value.slotCount(); - } - - @Override - public JavaKind javaKind() { - return value.javaKind(); - } - - @Override - public int line() { - return line; - } - - public void setLine(int line) { - this.line = line; - } - } - - private DebugLocalInfo matchLocal(DebugLocalValueInfo localValueInfo) { - ListIterator listIterator = locals.listIterator(); - while (listIterator.hasNext()) { - DebugLocalInfoWrapper next = (DebugLocalInfoWrapper) listIterator.next(); - if (checkMatch(next, localValueInfo)) { - int currentLine = next.line(); - int newLine = localValueInfo.line(); - if ((currentLine < 0 && newLine >= 0) || - (newLine >= 0 && newLine < currentLine)) { - next.setLine(newLine); - } - return next; - } else if (next.slot() > localValueInfo.slot()) { - // we have iterated just beyond the insertion point - // so wind cursor back one element - listIterator.previous(); - break; - } - } - DebugLocalInfoWrapper newLocal = new DebugLocalInfoWrapper(localValueInfo); - // add at the current cursor position - listIterator.add(newLocal); - return newLocal; - } - - boolean checkMatch(DebugLocalInfo local, DebugLocalValueInfo value) { - boolean isMatch = (local.slot() == value.slot() && - local.name().equals(value.name()) && - local.typeName().equals(value.typeName())); - assert !isMatch || verifyMatch(local, value) : "failed to verify matched var and value"; - return isMatch; - } - - private static boolean verifyMatch(DebugLocalInfo local, DebugLocalValueInfo value) { - // slot counts are normally expected to match - if (local.slotCount() == value.slotCount()) { - return true; - } - // we can have a zero count for the local or value if it is undefined - if (local.slotCount() == 0 || value.slotCount() == 0) { - return true; - } - // pseudo-object locals can appear as longs - if (local.javaKind() == JavaKind.Object && value.javaKind() == JavaKind.Long) { - return true; - } - // something is wrong - return false; - } } diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/PrimitiveTypeEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/PrimitiveTypeEntry.java index a60ed3b1b1fd..56c236e69865 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/PrimitiveTypeEntry.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/PrimitiveTypeEntry.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -26,77 +26,45 @@ package com.oracle.objectfile.debugentry; -import static com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugPrimitiveTypeInfo.FLAG_INTEGRAL; -import static com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugPrimitiveTypeInfo.FLAG_NUMERIC; -import static com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugPrimitiveTypeInfo.FLAG_SIGNED; - -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugPrimitiveTypeInfo; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo.DebugTypeKind; - -import jdk.graal.compiler.debug.DebugContext; +import jdk.vm.ci.meta.JavaKind; public class PrimitiveTypeEntry extends TypeEntry { - private char typeChar; - private int flags; - private int bitCount; - public PrimitiveTypeEntry(String typeName, int size) { - super(typeName, size); - typeChar = '#'; - flags = 0; - bitCount = 0; + private final int bitCount; + private final boolean isNumericInteger; + private final boolean isNumericFloat; + private final boolean isUnsigned; + + public PrimitiveTypeEntry(String typeName, int size, long classOffset, long typeSignature, JavaKind kind) { + this(typeName, size, classOffset, typeSignature, kind == JavaKind.Void ? 0 : kind.getBitCount(), kind.isNumericInteger(), kind.isNumericFloat(), kind.isUnsigned()); } - @Override - public DebugTypeKind typeKind() { - return DebugTypeKind.PRIMITIVE; + public PrimitiveTypeEntry(String typeName, int size, long classOffset, long typeSignature, int bitCount, boolean isNumericInteger, boolean isNumericFloat, boolean isUnsigned) { + super(typeName, size, classOffset, typeSignature, typeSignature); + this.bitCount = bitCount; + this.isNumericInteger = isNumericInteger; + this.isNumericFloat = isNumericFloat; + this.isUnsigned = isUnsigned; } @Override - public void addDebugInfo(DebugInfoBase debugInfoBase, DebugTypeInfo debugTypeInfo, DebugContext debugContext) { - super.addDebugInfo(debugInfoBase, debugTypeInfo, debugContext); - DebugPrimitiveTypeInfo debugPrimitiveTypeInfo = (DebugPrimitiveTypeInfo) debugTypeInfo; - flags = debugPrimitiveTypeInfo.flags(); - typeChar = debugPrimitiveTypeInfo.typeChar(); - bitCount = debugPrimitiveTypeInfo.bitCount(); - if (debugContext.isLogEnabled()) { - debugContext.log("typename %s %s (%d bits)%n", typeName, decodeFlags(), bitCount); - } + public boolean isPrimitive() { + return true; } - private String decodeFlags() { - StringBuilder builder = new StringBuilder(); - if ((flags & FLAG_NUMERIC) != 0) { - if ((flags & FLAG_INTEGRAL) != 0) { - if ((flags & FLAG_SIGNED) != 0) { - builder.append("SIGNED "); - } else { - builder.append("UNSIGNED "); - } - builder.append("INTEGRAL"); - } else { - builder.append("FLOATING"); - } - } else { - if (bitCount > 0) { - builder.append("LOGICAL"); - } else { - builder.append("VOID"); - } - } - return builder.toString(); + public int getBitCount() { + return bitCount; } - public char getTypeChar() { - return typeChar; + public boolean isNumericInteger() { + return isNumericInteger; } - public int getBitCount() { - return bitCount; + public boolean isNumericFloat() { + return isNumericFloat; } - public int getFlags() { - return flags; + public boolean isUnsigned() { + return isUnsigned; } } diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/RegisterValueEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/RegisterValueEntry.java new file mode 100644 index 000000000000..501a0a64cefa --- /dev/null +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/RegisterValueEntry.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.objectfile.debugentry; + +public record RegisterValueEntry(int regIndex) implements LocalValueEntry { + + @Override + public String toString() { + return "REG:" + regIndex; + } +} diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StackValueEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StackValueEntry.java new file mode 100644 index 000000000000..ac79be0943fe --- /dev/null +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StackValueEntry.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.objectfile.debugentry; + +public record StackValueEntry(int stackSlot) implements LocalValueEntry { + + @Override + public String toString() { + return "STACK:" + stackSlot; + } +} diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StringEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StringEntry.java index 01eb6fb9de38..86e64e0069f8 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StringEntry.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StringEntry.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -33,7 +33,6 @@ public class StringEntry { private final String string; private int offset; - private boolean addToStrSection; StringEntry(String string) { this.string = string; @@ -55,20 +54,11 @@ public void setOffset(int offset) { this.offset = offset; } - public boolean isAddToStrSection() { - return addToStrSection; - } - - public void setAddToStrSection() { - this.addToStrSection = true; - } - @Override public boolean equals(Object object) { - if (object == null || !(object instanceof StringEntry)) { + if (!(object instanceof StringEntry other)) { return false; } else { - StringEntry other = (StringEntry) object; return this == other || string.equals(other.string); } } @@ -80,10 +70,10 @@ public int hashCode() { @Override public String toString() { - return string; + return "'" + string + "'@" + Integer.toHexString(offset); } public boolean isEmpty() { - return string.length() == 0; + return string.isEmpty(); } } diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StringTable.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StringTable.java index 1242c79a154e..a2b202363dd2 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StringTable.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StringTable.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -26,8 +26,8 @@ package com.oracle.objectfile.debugentry; -import java.util.HashMap; import java.util.Iterator; +import java.util.concurrent.ConcurrentHashMap; /** * Allows incoming strings to be reduced to unique (up to equals) instances and supports marking of @@ -36,22 +36,10 @@ */ public class StringTable implements Iterable { - private final HashMap table; + private final ConcurrentHashMap table; public StringTable() { - this.table = new HashMap<>(); - } - - /** - * Ensures a unique instance of a string exists in the table, inserting the supplied String if - * no equivalent String is already present. This should only be called before the string section - * has been written. - * - * @param string the string to be included in the table - * @return the unique instance of the String - */ - public String uniqueString(String string) { - return ensureString(string, false); + this.table = new ConcurrentHashMap<>(); } /** @@ -64,19 +52,7 @@ public String uniqueString(String string) { * @return the unique instance of the String */ public String uniqueDebugString(String string) { - return ensureString(string, true); - } - - private String ensureString(String string, boolean addToStrSection) { - StringEntry stringEntry = table.get(string); - if (stringEntry == null) { - stringEntry = new StringEntry(string); - table.put(string, stringEntry); - } - if (addToStrSection && !stringEntry.isAddToStrSection()) { - stringEntry.setAddToStrSection(); - } - return stringEntry.getString(); + return table.computeIfAbsent(string, StringEntry::new).getString(); } /** @@ -90,9 +66,6 @@ private String ensureString(String string, boolean addToStrSection) { public int debugStringIndex(String string) { StringEntry stringEntry = table.get(string); assert stringEntry != null : "\"" + string + "\" not in string table"; - if (stringEntry == null) { - return -1; - } return stringEntry.getOffset(); } diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StructureTypeEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StructureTypeEntry.java index 43473ca1cfb2..ac7080408d57 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StructureTypeEntry.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StructureTypeEntry.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -27,16 +27,9 @@ package com.oracle.objectfile.debugentry; import java.lang.reflect.Modifier; -import java.util.ArrayList; +import java.util.Comparator; import java.util.List; -import java.util.stream.Stream; - -import com.oracle.objectfile.debuginfo.DebugInfoProvider; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugFieldInfo; -import com.oracle.objectfile.elf.dwarf.DwarfDebugInfo; - -import jdk.graal.compiler.debug.DebugContext; -import jdk.vm.ci.meta.ResolvedJavaType; +import java.util.concurrent.ConcurrentSkipListSet; /** * An intermediate type that provides behaviour for managing fields. This unifies code for handling @@ -46,7 +39,7 @@ public abstract class StructureTypeEntry extends TypeEntry { /** * Details of fields located in this instance. */ - protected final List fields; + private final ConcurrentSkipListSet fields; /** * The type signature of this types' layout. The layout of a type contains debug info of fields @@ -55,50 +48,24 @@ public abstract class StructureTypeEntry extends TypeEntry { */ protected long layoutTypeSignature; - public StructureTypeEntry(String typeName, int size) { - super(typeName, size); - this.fields = new ArrayList<>(); - this.layoutTypeSignature = 0; + public StructureTypeEntry(String typeName, int size, long classOffset, long typeSignature, + long compressedTypeSignature, long layoutTypeSignature) { + super(typeName, size, classOffset, typeSignature, compressedTypeSignature); + this.layoutTypeSignature = layoutTypeSignature; + + this.fields = new ConcurrentSkipListSet<>(Comparator.comparingInt(FieldEntry::getOffset)); } public long getLayoutTypeSignature() { return layoutTypeSignature; } - public Stream fields() { - return fields.stream(); - } - - public int fieldCount() { - return fields.size(); + public void addField(FieldEntry field) { + fields.add(field); } - protected void processField(DebugFieldInfo debugFieldInfo, DebugInfoBase debugInfoBase, DebugContext debugContext) { - /* Delegate this so superclasses can override this and inspect the computed FieldEntry. */ - addField(debugFieldInfo, debugInfoBase, debugContext); - } - - protected FieldEntry addField(DebugFieldInfo debugFieldInfo, DebugInfoBase debugInfoBase, DebugContext debugContext) { - String fieldName = debugInfoBase.uniqueDebugString(debugFieldInfo.name()); - ResolvedJavaType valueType = debugFieldInfo.valueType(); - String valueTypeName = valueType.toJavaName(); - int fieldSize = debugFieldInfo.size(); - int fieldoffset = debugFieldInfo.offset(); - boolean fieldIsEmbedded = debugFieldInfo.isEmbedded(); - int fieldModifiers = debugFieldInfo.modifiers(); - if (debugContext.isLogEnabled()) { - debugContext.log("typename %s adding %s field %s type %s%s size %s at offset 0x%x%n", - typeName, memberModifiers(fieldModifiers), fieldName, valueTypeName, (fieldIsEmbedded ? "(embedded)" : ""), fieldSize, fieldoffset); - } - TypeEntry valueTypeEntry = debugInfoBase.lookupTypeEntry(valueType); - /* - * n.b. the field file may differ from the owning class file when the field is a - * substitution - */ - FileEntry fileEntry = debugInfoBase.ensureFileEntry(debugFieldInfo); - FieldEntry fieldEntry = new FieldEntry(fileEntry, fieldName, this, valueTypeEntry, fieldSize, fieldoffset, fieldIsEmbedded, fieldModifiers); - fields.add(fieldEntry); - return fieldEntry; + public List getFields() { + return List.copyOf(fields); } String memberModifiers(int modifiers) { @@ -134,14 +101,7 @@ String memberModifiers(int modifiers) { return builder.toString(); } - @Override - public void addDebugInfo(@SuppressWarnings("unused") DebugInfoBase debugInfoBase, DebugInfoProvider.DebugTypeInfo debugTypeInfo, @SuppressWarnings("unused") DebugContext debugContext) { - super.addDebugInfo(debugInfoBase, debugTypeInfo, debugContext); - // header type does not have a separate layout type - if (this instanceof HeaderTypeEntry) { - this.layoutTypeSignature = typeSignature; - } else { - this.layoutTypeSignature = debugTypeInfo.typeSignature(DwarfDebugInfo.LAYOUT_PREFIX); - } + public String getModifiersString(int modifiers) { + return memberModifiers(modifiers); } } diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/TypeEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/TypeEntry.java index 4bb5584a768b..decd81f326d2 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/TypeEntry.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/TypeEntry.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -26,25 +26,11 @@ package com.oracle.objectfile.debugentry; -import static com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo.DebugTypeKind.ARRAY; -import static com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo.DebugTypeKind.ENUM; -import static com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo.DebugTypeKind.FOREIGN; -import static com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo.DebugTypeKind.HEADER; -import static com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo.DebugTypeKind.INSTANCE; -import static com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo.DebugTypeKind.INTERFACE; -import static com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo.DebugTypeKind.PRIMITIVE; - -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo.DebugTypeKind; -import com.oracle.objectfile.elf.dwarf.DwarfDebugInfo; - -import jdk.graal.compiler.debug.DebugContext; - public abstract class TypeEntry { /** * The name of this type. */ - protected final String typeName; + private final String typeName; /** * The type signature of this type. This is a pointer to the underlying layout of the type. @@ -61,19 +47,20 @@ public abstract class TypeEntry { * The offset of the java.lang.Class instance for this class in the image heap or -1 if no such * object exists. */ - private long classOffset; + private final long classOffset; /** * The size of an occurrence of this type in bytes. */ - protected final int size; + private final int size; - protected TypeEntry(String typeName, int size) { + protected TypeEntry(String typeName, int size, long classOffset, long typeSignature, + long typeSignatureForCompressed) { this.typeName = typeName; this.size = size; - this.classOffset = -1; - this.typeSignature = 0; - this.typeSignatureForCompressed = 0; + this.classOffset = classOffset; + this.typeSignature = typeSignature; + this.typeSignatureForCompressed = typeSignatureForCompressed; } public long getTypeSignature() { @@ -96,34 +83,32 @@ public String getTypeName() { return typeName; } - public abstract DebugTypeKind typeKind(); - public boolean isPrimitive() { - return typeKind() == PRIMITIVE; + return false; } public boolean isHeader() { - return typeKind() == HEADER; + return false; } public boolean isArray() { - return typeKind() == ARRAY; + return false; } public boolean isInstance() { - return typeKind() == INSTANCE; + return false; } public boolean isInterface() { - return typeKind() == INTERFACE; + return false; } public boolean isEnum() { - return typeKind() == ENUM; + return false; } public boolean isForeign() { - return typeKind() == FOREIGN; + return false; } /** @@ -147,15 +132,22 @@ public boolean isStructure() { return isClass() || isHeader(); } - public void addDebugInfo(@SuppressWarnings("unused") DebugInfoBase debugInfoBase, DebugTypeInfo debugTypeInfo, @SuppressWarnings("unused") DebugContext debugContext) { - /* Record the location of the Class instance in the heap if there is one */ - this.classOffset = debugTypeInfo.classOffset(); - this.typeSignature = debugTypeInfo.typeSignature(""); - // primitives, header and foreign types are never stored compressed - if (!debugInfoBase.useHeapBase() || this instanceof PrimitiveTypeEntry || this instanceof HeaderTypeEntry || this instanceof ForeignTypeEntry) { - this.typeSignatureForCompressed = typeSignature; - } else { - this.typeSignatureForCompressed = debugTypeInfo.typeSignature(DwarfDebugInfo.COMPRESSED_PREFIX); - } + @Override + public String toString() { + String kind = switch (this) { + case PrimitiveTypeEntry p -> "Primitive"; + case HeaderTypeEntry h -> "Header"; + case ArrayTypeEntry a -> "Array"; + case InterfaceClassEntry i -> "Interface"; + case EnumClassEntry e -> "Enum"; + case ForeignWordTypeEntry fw -> "ForeignWord"; + case ForeignStructTypeEntry fs -> "ForeignStruct"; + case ForeignPointerTypeEntry fp -> "ForeignPointer"; + case ForeignIntegerTypeEntry fi -> "ForeignInteger"; + case ForeignFloatTypeEntry ff -> "ForeignFloat"; + case ClassEntry c -> "Instance"; + default -> ""; + }; + return String.format("%sType(%s size=%d @%s)", kind, getTypeName(), getSize(), Long.toHexString(classOffset)); } } diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/CallRange.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/CallRange.java index 39e639306c41..02e8b2df1d98 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/CallRange.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/CallRange.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2022, 2022, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -26,48 +26,51 @@ package com.oracle.objectfile.debugentry.range; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; +import java.util.stream.Stream; + +import com.oracle.objectfile.debugentry.LocalEntry; +import com.oracle.objectfile.debugentry.LocalValueEntry; import com.oracle.objectfile.debugentry.MethodEntry; -class CallRange extends SubRange { - /** - * The first direct callee whose range is wholly contained in this range or null if this is a - * leaf range. - */ - protected SubRange firstCallee; +public class CallRange extends Range { + /** - * The last direct callee whose range is wholly contained in this range. + * The direct callees whose ranges are wholly contained in this range. Empty if this is a leaf + * range. */ - protected SubRange lastCallee; + private final Set callees = new TreeSet<>(Comparator.comparing(Range::getLoOffset)); - protected CallRange(MethodEntry methodEntry, int lo, int hi, int line, PrimaryRange primary, Range caller) { - super(methodEntry, lo, hi, line, primary, caller); - this.firstCallee = null; - this.lastCallee = null; + protected CallRange(PrimaryRange primary, MethodEntry methodEntry, Map localInfoList, int lo, int hi, int line, CallRange caller, int depth) { + super(primary, methodEntry, localInfoList, lo, hi, line, caller, depth); } @Override - protected void addCallee(SubRange callee) { - assert this.lo <= callee.lo; - assert this.hi >= callee.hi; - assert callee.caller == this; - assert callee.siblingCallee == null; - if (this.firstCallee == null) { - assert this.lastCallee == null; - this.firstCallee = this.lastCallee = callee; - } else { - this.lastCallee.siblingCallee = callee; - this.lastCallee = callee; - } + public List getCallees() { + return List.copyOf(callees); } @Override - public SubRange getFirstCallee() { - return firstCallee; + public Stream rangeStream() { + return Stream.concat(super.rangeStream(), callees.stream().flatMap(Range::rangeStream)); + } + + protected void addCallee(Range callee) { + assert this.contains(callee); + assert callee.getCaller() == this; + callees.add(callee); } @Override public boolean isLeaf() { - assert firstCallee != null; - return false; + return callees.isEmpty(); + } + + public void removeCallee(Range callee) { + callees.remove(callee); } } diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/LeafRange.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/LeafRange.java index 19d9a706cc16..173a3f0cbac2 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/LeafRange.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/LeafRange.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2022, 2022, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -26,21 +26,15 @@ package com.oracle.objectfile.debugentry.range; -import com.oracle.objectfile.debugentry.MethodEntry; - -class LeafRange extends SubRange { - protected LeafRange(MethodEntry methodEntry, int lo, int hi, int line, PrimaryRange primary, Range caller) { - super(methodEntry, lo, hi, line, primary, caller); - } +import java.util.Map; - @Override - protected void addCallee(SubRange callee) { - assert false : "should never be adding callees to a leaf range!"; - } +import com.oracle.objectfile.debugentry.LocalEntry; +import com.oracle.objectfile.debugentry.LocalValueEntry; +import com.oracle.objectfile.debugentry.MethodEntry; - @Override - public SubRange getFirstCallee() { - return null; +public class LeafRange extends Range { + protected LeafRange(PrimaryRange primary, MethodEntry methodEntry, Map localInfoList, int lo, int hi, int line, CallRange caller, int depth) { + super(primary, methodEntry, localInfoList, lo, hi, line, caller, depth); } @Override diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/PrimaryRange.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/PrimaryRange.java index 4e4c7fc6ae91..b675ca6160e1 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/PrimaryRange.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/PrimaryRange.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2022, 2022, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -26,52 +26,30 @@ package com.oracle.objectfile.debugentry.range; -import com.oracle.objectfile.debugentry.MethodEntry; +import java.util.HashMap; -public class PrimaryRange extends Range { - /** - * The first subrange in the range covered by this primary or null if this primary as no - * subranges. - */ - protected SubRange firstCallee; - /** - * The last subrange in the range covered by this primary. - */ - protected SubRange lastCallee; +import com.oracle.objectfile.debugentry.MethodEntry; - protected PrimaryRange(MethodEntry methodEntry, int lo, int hi, int line) { - super(methodEntry, lo, hi, line, -1); - this.firstCallee = null; - this.lastCallee = null; - } +public class PrimaryRange extends CallRange { + private final long codeOffset; - @Override - public boolean isPrimary() { - return true; + protected PrimaryRange(MethodEntry methodEntry, int lo, int hi, int line, long codeOffset) { + super(null, methodEntry, new HashMap<>(), lo, hi, line, null, -1); + this.codeOffset = codeOffset; } @Override - protected void addCallee(SubRange callee) { - assert this.lo <= callee.lo; - assert this.hi >= callee.hi; - assert callee.caller == this; - assert callee.siblingCallee == null; - if (this.firstCallee == null) { - assert this.lastCallee == null; - this.firstCallee = this.lastCallee = callee; - } else { - this.lastCallee.siblingCallee = callee; - this.lastCallee = callee; - } + public long getCodeOffset() { + return codeOffset; } @Override - public SubRange getFirstCallee() { - return firstCallee; + public boolean isPrimary() { + return true; } @Override - public boolean isLeaf() { - return firstCallee == null; + public PrimaryRange getPrimary() { + return this; } } diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/Range.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/Range.java index e0a0360a6177..67b4839d71a4 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/Range.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/Range.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -26,15 +26,24 @@ package com.oracle.objectfile.debugentry.range; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +import com.oracle.objectfile.debugentry.ConstantValueEntry; import com.oracle.objectfile.debugentry.FileEntry; +import com.oracle.objectfile.debugentry.LocalEntry; +import com.oracle.objectfile.debugentry.LocalValueEntry; import com.oracle.objectfile.debugentry.MethodEntry; +import com.oracle.objectfile.debugentry.StackValueEntry; import com.oracle.objectfile.debugentry.TypeEntry; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugLocalInfo; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugLocalValueInfo; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; +import jdk.vm.ci.meta.JavaConstant; +import jdk.vm.ci.meta.JavaKind; +import jdk.vm.ci.meta.PrimitiveConstant; /** * Details of a specific address range in a compiled method either a primary range identifying a @@ -69,13 +78,29 @@ * parameter values for separate sub-extents of an inline called method whose full extent is * represented by the parent call range at level N. */ -public abstract class Range { - private static final String CLASS_DELIMITER = "."; - protected final MethodEntry methodEntry; - protected final int lo; - protected int hi; - protected final int line; - protected final int depth; +public abstract class Range implements Comparable { + private final MethodEntry methodEntry; + private final int loOffset; + private int hiOffset; + private final int line; + private final int depth; + + /** + * The range for the caller or the primary range when this range is for top level method code. + */ + private final CallRange caller; + + private final PrimaryRange primary; + + /** + * Values for the associated method's local and parameter variables that are available or, + * alternatively, marked as invalid in this range. + */ + private final Map localValueInfos; + /** + * Mapping of local entries to subranges they occur in. + */ + private final Map> varRangeMap = new HashMap<>(); /** * Create a primary range representing the root of the subrange tree for a top level compiled @@ -87,8 +112,8 @@ public abstract class Range { * @param line the line number associated with the range * @return a new primary range to serve as the root of the subrange tree. */ - public static PrimaryRange createPrimary(MethodEntry methodEntry, int lo, int hi, int line) { - return new PrimaryRange(methodEntry, lo, hi, line); + public static PrimaryRange createPrimary(MethodEntry methodEntry, int lo, int hi, int line, long codeOffset) { + return new PrimaryRange(methodEntry, lo, hi, line, codeOffset); } /** @@ -99,57 +124,119 @@ public static PrimaryRange createPrimary(MethodEntry methodEntry, int lo, int hi * @param lo the lowest address included in the range. * @param hi the first address above the highest address in the range. * @param line the line number associated with the range - * @param primary the primary range to which this subrange belongs * @param caller the range for which this is a subrange, either an inlined call range or the * primary range. * @param isLeaf true if this is a leaf range with no subranges * @return a new subrange to be linked into the range tree below the primary range. */ - public static SubRange createSubrange(MethodEntry methodEntry, int lo, int hi, int line, PrimaryRange primary, Range caller, boolean isLeaf) { - assert primary != null; - assert primary.isPrimary(); + public static Range createSubrange(PrimaryRange primary, MethodEntry methodEntry, Map localValueInfos, int lo, int hi, int line, CallRange caller, boolean isLeaf) { + assert caller != null; + if (caller != primary) { + methodEntry.setInlined(); + } + Range callee; if (isLeaf) { - return new LeafRange(methodEntry, lo, hi, line, primary, caller); + callee = new LeafRange(primary, methodEntry, localValueInfos, lo, hi, line, caller, caller.getDepth() + 1); } else { - return new CallRange(methodEntry, lo, hi, line, primary, caller); + callee = new CallRange(primary, methodEntry, localValueInfos, lo, hi, line, caller, caller.getDepth() + 1); } + + caller.addCallee(callee); + return callee; } - protected Range(MethodEntry methodEntry, int lo, int hi, int line, int depth) { + protected Range(PrimaryRange primary, MethodEntry methodEntry, Map localValueInfos, int loOffset, int hiOffset, int line, CallRange caller, int depth) { assert methodEntry != null; + this.primary = primary; this.methodEntry = methodEntry; - this.lo = lo; - this.hi = hi; + this.loOffset = loOffset; + this.hiOffset = hiOffset; this.line = line; + this.caller = caller; this.depth = depth; + this.localValueInfos = localValueInfos; } - protected abstract void addCallee(SubRange callee); + /** + * Splits an initial range at the given stack decrement point. The lower split will stay as it + * with its high offset reduced to the stack decrement point. The higher split starts at the + * stack decrement point and has updated local value entries for the parameters in the then + * extended stack. + * + * @param stackDecrement the offset to split the range at + * @param frameSize the frame size after the split + * @param preExtendFrameSize the frame size before the split + * @return the higher split, that has been split off the original {@code Range} + */ + public Range split(int stackDecrement, int frameSize, int preExtendFrameSize) { + // This should be for an initial range extending beyond the stack decrement. + assert loOffset == 0 && loOffset < stackDecrement && stackDecrement < hiOffset : "invalid split request"; + + Map splitLocalValueInfos = new HashMap<>(); + + for (var localInfo : localValueInfos.entrySet()) { + if (localInfo.getValue() instanceof StackValueEntry stackValue) { + /* + * Need to redefine the value for this param using a stack slot value that allows + * for the stack being extended by framesize. however we also need to remove any + * adjustment that was made to allow for the difference between the caller SP and + * the pre-extend callee SP because of a stacked return address. + */ + int adjustment = frameSize - preExtendFrameSize; + splitLocalValueInfos.put(localInfo.getKey(), new StackValueEntry(stackValue.stackSlot() + adjustment)); + } else { + splitLocalValueInfos.put(localInfo.getKey(), localInfo.getValue()); + } + } + + Range splitRange = Range.createSubrange(primary, methodEntry, splitLocalValueInfos, stackDecrement, hiOffset, line, caller, isLeaf()); + hiOffset = stackDecrement; + + return splitRange; + } public boolean contains(Range other) { - return (lo <= other.lo && hi >= other.hi); + return (loOffset <= other.loOffset && hiOffset >= other.hiOffset); } - public abstract boolean isPrimary(); + public boolean isPrimary() { + return false; + } public String getClassName() { - return methodEntry.ownerType().getTypeName(); + return methodEntry.getOwnerType().getTypeName(); } public String getMethodName() { - return methodEntry.methodName(); + return methodEntry.getMethodName(); } public String getSymbolName() { return methodEntry.getSymbolName(); } - public int getHi() { - return hi; + public int getHiOffset() { + return hiOffset; + } + + public int getLoOffset() { + return loOffset; + } + + public long getCodeOffset() { + return primary.getCodeOffset(); + } + + public long getLo() { + return getCodeOffset() + loOffset; } - public int getLo() { - return lo; + public long getHi() { + return getCodeOffset() + hiOffset; + } + + public PrimaryRange getPrimary() { + return primary; } public int getLine() { @@ -157,31 +244,27 @@ public int getLine() { } public String getFullMethodName() { - return constructClassAndMethodName(); + return getExtendedMethodName(false); } public String getFullMethodNameWithParams() { - return constructClassAndMethodNameWithParams(); + return getExtendedMethodName(true); } public boolean isDeoptTarget() { return methodEntry.isDeopt(); } - private String getExtendedMethodName(boolean includeClass, boolean includeParams, boolean includeReturnType) { + private String getExtendedMethodName(boolean includeParams) { StringBuilder builder = new StringBuilder(); - if (includeReturnType && methodEntry.getValueType().getTypeName().length() > 0) { - builder.append(methodEntry.getValueType().getTypeName()); - builder.append(' '); - } - if (includeClass && getClassName() != null) { + if (getClassName() != null) { builder.append(getClassName()); - builder.append(CLASS_DELIMITER); + builder.append("."); } builder.append(getMethodName()); if (includeParams) { builder.append("("); - TypeEntry[] paramTypes = methodEntry.getParamTypes(); + List paramTypes = methodEntry.getParamTypes(); if (paramTypes != null) { String prefix = ""; for (TypeEntry t : paramTypes) { @@ -192,21 +275,9 @@ private String getExtendedMethodName(boolean includeClass, boolean includeParams } builder.append(')'); } - if (includeReturnType) { - builder.append(" "); - builder.append(methodEntry.getValueType().getTypeName()); - } return builder.toString(); } - private String constructClassAndMethodName() { - return getExtendedMethodName(true, false, false); - } - - private String constructClassAndMethodNameWithParams() { - return getExtendedMethodName(true, true, false); - } - public FileEntry getFileEntry() { return methodEntry.getFileEntry(); } @@ -217,7 +288,15 @@ public int getModifiers() { @Override public String toString() { - return String.format("Range(lo=0x%05x hi=0x%05x %s %s:%d)", lo, hi, constructClassAndMethodNameWithParams(), methodEntry.getFullFileName(), line); + return String.format("Range(lo=0x%05x hi=0x%05x %s %s:%d)", getLo(), getHi(), getFullMethodNameWithParams(), methodEntry.getFullFileName(), line); + } + + @Override + public int compareTo(Range other) { + return Comparator.comparingLong(Range::getLo) + .thenComparingLong(Range::getHi) + .thenComparingInt(Range::getLine) + .compare(this, other); } public String getFileName() { @@ -228,74 +307,100 @@ public MethodEntry getMethodEntry() { return methodEntry; } - public abstract SubRange getFirstCallee(); - public abstract boolean isLeaf(); public boolean includesInlineRanges() { - SubRange child = getFirstCallee(); - while (child != null && child.isLeaf()) { - child = child.getSiblingCallee(); + for (Range callee : getCallees()) { + if (!callee.isLeaf()) { + return true; + } } - return child != null; + return false; + } + + public List getCallees() { + return List.of(); + } + + public Stream rangeStream() { + return Stream.of(this); } public int getDepth() { return depth; } - public HashMap> getVarRangeMap() { - MethodEntry calleeMethod; - if (isPrimary()) { - calleeMethod = getMethodEntry(); - } else { - assert !isLeaf() : "should only be looking up var ranges for inlined calls"; - calleeMethod = getFirstCallee().getMethodEntry(); - } - HashMap> varRangeMap = new HashMap<>(); - if (calleeMethod.getThisParam() != null) { - varRangeMap.put(calleeMethod.getThisParam(), new ArrayList<>()); - } - for (int i = 0; i < calleeMethod.getParamCount(); i++) { - varRangeMap.put(calleeMethod.getParam(i), new ArrayList<>()); - } - for (int i = 0; i < calleeMethod.getLocalCount(); i++) { - varRangeMap.put(calleeMethod.getLocal(i), new ArrayList<>()); + /** + * Returns the subranges grouped by local entries in the subranges. If the map is empty, it + * first tries to populate the map with its callees {@link #localValueInfos}. + * + * @return a mapping from local entries to subranges + */ + public Map> getVarRangeMap() { + if (varRangeMap.isEmpty()) { + for (Range callee : getCallees()) { + for (LocalEntry local : callee.localValueInfos.keySet()) { + varRangeMap.computeIfAbsent(local, l -> new ArrayList<>()).add(callee); + } + } } - return updateVarRangeMap(varRangeMap); - } - public HashMap> updateVarRangeMap(HashMap> varRangeMap) { - // leaf subranges of the current range may provide values for param or local vars - // of this range's method. find them and index the range so that we can identify - // both the local/param and the associated range. - SubRange subRange = this.getFirstCallee(); - while (subRange != null) { - addVarRanges(subRange, varRangeMap); - subRange = subRange.siblingCallee; - } return varRangeMap; } - public void addVarRanges(SubRange subRange, HashMap> varRangeMap) { - int localValueCount = subRange.getLocalValueCount(); - for (int i = 0; i < localValueCount; i++) { - DebugLocalValueInfo localValueInfo = subRange.getLocalValue(i); - DebugLocalInfo local = subRange.getLocal(i); - if (local != null) { - switch (localValueInfo.localKind()) { - case REGISTER: - case STACKSLOT: - case CONSTANT: - List varRanges = varRangeMap.get(local); - assert varRanges != null : "local not present in var to ranges map!"; - varRanges.add(subRange); - break; - case UNDEFINED: - break; + /** + * Returns whether subranges contain a value in {@link #localValueInfos} for a given local + * entry. A value is valid if it exists, and it can be represented as local values in the debug + * info. + * + * @param local the local entry to check for + * @return whether a local entry has a value in any of this range's subranges + */ + public boolean hasLocalValues(LocalEntry local) { + for (Range callee : getVarRangeMap().getOrDefault(local, List.of())) { + LocalValueEntry localValue = callee.lookupValue(local); + if (localValue != null) { + if (localValue instanceof ConstantValueEntry constantValueEntry) { + JavaConstant constant = constantValueEntry.constant(); + // can only handle primitive or null constants just now + return constant instanceof PrimitiveConstant || constant.getJavaKind() == JavaKind.Object; + } else { + // register or stack value + return true; } } } + return false; } + public Range getCaller() { + return caller; + } + + public LocalValueEntry lookupValue(LocalEntry local) { + return localValueInfos.getOrDefault(local, null); + } + + public boolean tryMerge(Range that) { + assert this.caller == that.caller; + assert this.isLeaf() == that.isLeaf(); + assert this.depth == that.depth : "should only compare sibling ranges"; + assert this.hiOffset <= that.loOffset : "later nodes should not overlap earlier ones"; + if (this.hiOffset != that.loOffset) { + return false; + } + if (this.methodEntry != that.methodEntry) { + return false; + } + if (this.line != that.line) { + return false; + } + if (!this.localValueInfos.equals(that.localValueInfos)) { + return false; + } + // merging just requires updating lo and hi range as everything else is equal + this.hiOffset = that.hiOffset; + caller.removeCallee(that); + return true; + } } diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/SubRange.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/SubRange.java deleted file mode 100644 index 6c8f9a1fc669..000000000000 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/SubRange.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2022, 2022, Red Hat Inc. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package com.oracle.objectfile.debugentry.range; - -import com.oracle.objectfile.debugentry.MethodEntry; -import com.oracle.objectfile.debuginfo.DebugInfoProvider; - -public abstract class SubRange extends Range { - private static final DebugInfoProvider.DebugLocalInfo[] EMPTY_LOCAL_INFOS = new DebugInfoProvider.DebugLocalInfo[0]; - /** - * The root of the call tree the subrange belongs to. - */ - private final PrimaryRange primary; - /** - * The range for the caller or the primary range when this range is for top level method code. - */ - protected Range caller; - /** - * A link to a sibling callee, i.e., a range sharing the same caller with this range. - */ - protected SubRange siblingCallee; - /** - * Values for the associated method's local and parameter variables that are available or, - * alternatively, marked as invalid in this range. - */ - private DebugInfoProvider.DebugLocalValueInfo[] localValueInfos; - /** - * The set of local or parameter variables with which each corresponding local value in field - * localValueInfos is associated. Local values which are associated with the same local or - * parameter variable will share the same reference in the corresponding array entries. Local - * values with which no local variable can be associated will have a null reference in the - * corresponding array. The latter case can happen when a local value has an invalid slot or - * when a local value that maps to a parameter slot has a different name or type to the - * parameter. - */ - private DebugInfoProvider.DebugLocalInfo[] localInfos; - - @SuppressWarnings("this-escape") - protected SubRange(MethodEntry methodEntry, int lo, int hi, int line, PrimaryRange primary, Range caller) { - super(methodEntry, lo, hi, line, (caller == null ? 0 : caller.depth + 1)); - this.caller = caller; - if (caller != null) { - caller.addCallee(this); - } - assert primary != null; - this.primary = primary; - } - - public Range getPrimary() { - return primary; - } - - @Override - public boolean isPrimary() { - return false; - } - - public Range getCaller() { - return caller; - } - - @Override - public abstract SubRange getFirstCallee(); - - @Override - public abstract boolean isLeaf(); - - public int getLocalValueCount() { - return localValueInfos.length; - } - - public DebugInfoProvider.DebugLocalValueInfo getLocalValue(int i) { - assert i >= 0 && i < localValueInfos.length : "bad index"; - return localValueInfos[i]; - } - - public DebugInfoProvider.DebugLocalInfo getLocal(int i) { - assert i >= 0 && i < localInfos.length : "bad index"; - return localInfos[i]; - } - - public void setLocalValueInfo(DebugInfoProvider.DebugLocalValueInfo[] localValueInfos) { - int len = localValueInfos.length; - this.localValueInfos = localValueInfos; - this.localInfos = (len > 0 ? new DebugInfoProvider.DebugLocalInfo[len] : EMPTY_LOCAL_INFOS); - // set up mapping from local values to local variables - for (int i = 0; i < len; i++) { - localInfos[i] = methodEntry.recordLocal(localValueInfos[i]); - } - } - - public DebugInfoProvider.DebugLocalValueInfo lookupValue(DebugInfoProvider.DebugLocalInfo local) { - int localValueCount = getLocalValueCount(); - for (int i = 0; i < localValueCount; i++) { - if (getLocal(i) == local) { - return getLocalValue(i); - } - } - return null; - } - - public SubRange getSiblingCallee() { - return siblingCallee; - } -} diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debuginfo/DebugInfoProvider.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debuginfo/DebugInfoProvider.java index e8488c87b4f0..4a063510a4c5 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debuginfo/DebugInfoProvider.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debuginfo/DebugInfoProvider.java @@ -26,24 +26,23 @@ package com.oracle.objectfile.debuginfo; -import java.nio.file.Path; -import java.util.List; -import java.util.function.Consumer; -import java.util.stream.Stream; +import java.util.SortedSet; -import jdk.graal.compiler.debug.DebugContext; -import jdk.vm.ci.meta.JavaConstant; -import jdk.vm.ci.meta.JavaKind; -import jdk.vm.ci.meta.ResolvedJavaMethod; -import jdk.vm.ci.meta.ResolvedJavaType; +import com.oracle.objectfile.debugentry.CompiledMethodEntry; +import com.oracle.objectfile.debugentry.TypeEntry; /** * Interfaces used to allow a native image to communicate details of types, code and data to the * underlying object file so that the latter can insert appropriate debug info. */ public interface DebugInfoProvider { + + void installDebugInfo(); + boolean useHeapBase(); + boolean isRuntimeCompilation(); + /** * Number of bits oops are left shifted by when using compressed oops. */ @@ -69,384 +68,14 @@ public interface DebugInfoProvider { */ int objectAlignment(); - int compiledCodeMax(); - - /** - * An interface implemented by items that can be located in a file. - */ - interface DebugFileInfo { - /** - * @return the name of the file containing a file element excluding any path. - */ - String fileName(); - - /** - * @return a relative path to the file containing a file element derived from its package - * name or {@code null} if the element is in the empty package. - */ - Path filePath(); - } - - interface DebugTypeInfo extends DebugFileInfo { - ResolvedJavaType idType(); - - enum DebugTypeKind { - PRIMITIVE, - ENUM, - INSTANCE, - INTERFACE, - ARRAY, - HEADER, - FOREIGN; - - @Override - public String toString() { - switch (this) { - case PRIMITIVE: - return "primitive"; - case ENUM: - return "enum"; - case INSTANCE: - return "instance"; - case INTERFACE: - return "interface"; - case ARRAY: - return "array"; - case HEADER: - return "header"; - case FOREIGN: - return "foreign"; - default: - return "???"; - } - } - } - - void debugContext(Consumer action); - - /** - * @return the fully qualified name of the debug type. - */ - String typeName(); - - /** - * @return a 64bit type signature to uniquely identify the type - */ - long typeSignature(String prefix); - - DebugTypeKind typeKind(); - - /** - * returns the offset in the heap at which the java.lang.Class instance which models this - * class is located or -1 if no such instance exists for this class. - * - * @return the offset of the java.lang.Class instance which models this class or -1. - */ - long classOffset(); - - int size(); - } - - interface DebugInstanceTypeInfo extends DebugTypeInfo { - String loaderName(); - - Stream fieldInfoProvider(); - - Stream methodInfoProvider(); - - ResolvedJavaType superClass(); - - Stream interfaces(); - } - - interface DebugEnumTypeInfo extends DebugInstanceTypeInfo { - } - - interface DebugInterfaceTypeInfo extends DebugInstanceTypeInfo { - } - - interface DebugForeignTypeInfo extends DebugInstanceTypeInfo { - String typedefName(); - - boolean isWord(); - - boolean isStruct(); - - boolean isPointer(); - - boolean isIntegral(); - - boolean isFloat(); - - boolean isSigned(); - - ResolvedJavaType parent(); - - ResolvedJavaType pointerTo(); - } - - interface DebugArrayTypeInfo extends DebugTypeInfo { - int baseSize(); - - int lengthOffset(); - - ResolvedJavaType elementType(); - - Stream fieldInfoProvider(); - } - - interface DebugPrimitiveTypeInfo extends DebugTypeInfo { - /* - * NUMERIC excludes LOGICAL types boolean and void - */ - int FLAG_NUMERIC = 1 << 0; - /* - * INTEGRAL excludes FLOATING types float and double - */ - int FLAG_INTEGRAL = 1 << 1; - /* - * SIGNED excludes UNSIGNED type char - */ - int FLAG_SIGNED = 1 << 2; - - int bitCount(); - - char typeChar(); - - int flags(); - } - - interface DebugHeaderTypeInfo extends DebugTypeInfo { - - Stream fieldInfoProvider(); - } - - interface DebugMemberInfo extends DebugFileInfo { - - String name(); - - ResolvedJavaType valueType(); - - int modifiers(); - } - - interface DebugFieldInfo extends DebugMemberInfo { - int offset(); - - int size(); - - boolean isEmbedded(); - } - - interface DebugMethodInfo extends DebugMemberInfo { - /** - * @return the line number for the outer or inlined segment. - */ - int line(); - - /** - * @return an array of DebugLocalInfo objects holding details of this method's parameters - */ - DebugLocalInfo[] getParamInfo(); + SortedSet typeEntries(); - /** - * @return a DebugLocalInfo objects holding details of the target instance parameter this if - * the method is an instance method or null if it is a static method. - */ - DebugLocalInfo getThisParamInfo(); + SortedSet compiledMethodEntries(); - /** - * @return the symbolNameForMethod string - */ - String symbolNameForMethod(); + String cachePath(); - /** - * @return true if this method has been compiled in as a deoptimization target - */ - boolean isDeoptTarget(); - - /** - * @return true if this method is a constructor. - */ - boolean isConstructor(); - - /** - * @return true if this is a virtual method. In Graal a virtual method can become - * non-virtual if all other implementations are non-reachable. - */ - boolean isVirtual(); - - /** - * @return the offset into the virtual function table for this method if virtual - */ - int vtableOffset(); - - /** - * @return true if this method is an override of another method. - */ - boolean isOverride(); - - /* - * Return the unique type that owns this method.

- * - * @return the unique type that owns this method - */ - ResolvedJavaType ownerType(); - - /* - * Return the unique identifier for this method. The result can be used to unify details of - * methods presented via interface DebugTypeInfo with related details of compiled methods - * presented via interface DebugRangeInfo and of call frame methods presented via interface - * DebugLocationInfo.

- * - * @return the unique identifier for this method - */ - ResolvedJavaMethod idMethod(); - } - - /** - * Access details of a compiled top level or inline method producing the code in a specific - * {@link com.oracle.objectfile.debugentry.range.Range}. - */ - interface DebugRangeInfo extends DebugMethodInfo { - - /** - * @return the lowest address containing code generated for an outer or inlined code segment - * reported at this line represented as an offset into the code segment. - */ - int addressLo(); - - /** - * @return the first address above the code generated for an outer or inlined code segment - * reported at this line represented as an offset into the code segment. - */ - int addressHi(); - } - - /** - * Access details of a specific compiled method. - */ - interface DebugCodeInfo extends DebugRangeInfo { - void debugContext(Consumer action); - - /** - * @return a stream of records detailing source local var and line locations within the - * compiled method. - */ - Stream locationInfoProvider(); - - /** - * @return the size of the method frame between prologue and epilogue. - */ - int getFrameSize(); - - /** - * @return a list of positions at which the stack is extended to a full frame or torn down - * to an empty frame - */ - List getFrameSizeChanges(); - } - - /** - * Access details of a specific heap object. - */ - interface DebugDataInfo { - void debugContext(Consumer action); - - String getProvenance(); - - String getTypeName(); - - String getPartition(); - - long getOffset(); - - long getSize(); - } - - /** - * Access details of code generated for a specific outer or inlined method at a given line - * number. - */ - interface DebugLocationInfo extends DebugRangeInfo { - /** - * @return the {@link DebugLocationInfo} of the nested inline caller-line - */ - DebugLocationInfo getCaller(); - - /** - * @return a stream of {@link DebugLocalValueInfo} objects identifying local or parameter - * variables present in the frame of the current range. - */ - DebugLocalValueInfo[] getLocalValueInfo(); - - boolean isLeaf(); - } - - /** - * A DebugLocalInfo details a local or parameter variable recording its name and type, the - * (abstract machine) local slot index it resides in and the number of slots it occupies. - */ - interface DebugLocalInfo { - ResolvedJavaType valueType(); - - String name(); - - String typeName(); - - int slot(); - - int slotCount(); - - JavaKind javaKind(); - - int line(); + enum FrameSizeChangeType { + EXTEND, + CONTRACT; } - - /** - * A DebugLocalValueInfo details the value a local or parameter variable present in a specific - * frame. The value may be undefined. If not then the instance records its type and either its - * (constant) value or the register or stack location in which the value resides. - */ - interface DebugLocalValueInfo extends DebugLocalInfo { - enum LocalKind { - UNDEFINED, - REGISTER, - STACKSLOT, - CONSTANT - } - - LocalKind localKind(); - - int regIndex(); - - int stackSlot(); - - long heapOffset(); - - JavaConstant constantValue(); - } - - interface DebugFrameSizeChange { - enum Type { - EXTEND, - CONTRACT - } - - int getOffset(); - - DebugFrameSizeChange.Type getType(); - } - - @SuppressWarnings("unused") - Stream typeInfoProvider(); - - Stream codeInfoProvider(); - - @SuppressWarnings("unused") - Stream dataInfoProvider(); - - Path getCachePath(); - - void recordActivity(); } diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/ELFObjectFile.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/ELFObjectFile.java index e0af88f3fd07..60ab09fd39a5 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/ELFObjectFile.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/ELFObjectFile.java @@ -35,7 +35,6 @@ import java.util.Map; import java.util.Set; -import com.oracle.objectfile.elf.dwarf.DwarfLocSectionImpl; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform; @@ -53,6 +52,8 @@ import com.oracle.objectfile.elf.dwarf.DwarfFrameSectionImpl; import com.oracle.objectfile.elf.dwarf.DwarfInfoSectionImpl; import com.oracle.objectfile.elf.dwarf.DwarfLineSectionImpl; +import com.oracle.objectfile.elf.dwarf.DwarfLineStrSectionImpl; +import com.oracle.objectfile.elf.dwarf.DwarfLocSectionImpl; import com.oracle.objectfile.elf.dwarf.DwarfRangesSectionImpl; import com.oracle.objectfile.elf.dwarf.DwarfStrSectionImpl; import com.oracle.objectfile.io.AssemblyBuffer; @@ -1174,8 +1175,10 @@ protected int getMinimumFileSize() { @Override public void installDebugInfo(DebugInfoProvider debugInfoProvider) { DwarfDebugInfo dwarfSections = new DwarfDebugInfo(getMachine(), getByteOrder()); + /* We need an implementation for each generated DWARF section. */ DwarfStrSectionImpl elfStrSectionImpl = dwarfSections.getStrSectionImpl(); + DwarfLineStrSectionImpl elfLineStrSectionImpl = dwarfSections.getLineStrSectionImpl(); DwarfAbbrevSectionImpl elfAbbrevSectionImpl = dwarfSections.getAbbrevSectionImpl(); DwarfFrameSectionImpl frameSectionImpl = dwarfSections.getFrameSectionImpl(); DwarfLocSectionImpl elfLocSectionImpl = dwarfSections.getLocSectionImpl(); @@ -1185,6 +1188,7 @@ public void installDebugInfo(DebugInfoProvider debugInfoProvider) { DwarfLineSectionImpl elfLineSectionImpl = dwarfSections.getLineSectionImpl(); /* Now we can create the section elements with empty content. */ newDebugSection(elfStrSectionImpl.getSectionName(), elfStrSectionImpl); + newDebugSection(elfLineStrSectionImpl.getSectionName(), elfLineStrSectionImpl); newDebugSection(elfAbbrevSectionImpl.getSectionName(), elfAbbrevSectionImpl); newDebugSection(frameSectionImpl.getSectionName(), frameSectionImpl); newDebugSection(elfLocSectionImpl.getSectionName(), elfLocSectionImpl); @@ -1202,6 +1206,7 @@ public void installDebugInfo(DebugInfoProvider debugInfoProvider) { createDefinedSymbol(elfInfoSectionImpl.getSectionName(), elfInfoSectionImpl.getElement(), 0, 0, false, false); createDefinedSymbol(elfLineSectionImpl.getSectionName(), elfLineSectionImpl.getElement(), 0, 0, false, false); createDefinedSymbol(elfStrSectionImpl.getSectionName(), elfStrSectionImpl.getElement(), 0, 0, false, false); + createDefinedSymbol(elfLineStrSectionImpl.getSectionName(), elfLineStrSectionImpl.getElement(), 0, 0, false, false); createDefinedSymbol(elfRangesSectionImpl.getSectionName(), elfRangesSectionImpl.getElement(), 0, 0, false, false); createDefinedSymbol(elfLocSectionImpl.getSectionName(), elfLocSectionImpl.getElement(), 0, 0, false, false); /* @@ -1214,6 +1219,7 @@ public void installDebugInfo(DebugInfoProvider debugInfoProvider) { * relevant reloc sections here in advance. */ elfStrSectionImpl.getOrCreateRelocationElement(0); + elfLineStrSectionImpl.getOrCreateRelocationElement(0); elfAbbrevSectionImpl.getOrCreateRelocationElement(0); frameSectionImpl.getOrCreateRelocationElement(0); elfInfoSectionImpl.getOrCreateRelocationElement(0); diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfARangesSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfARangesSectionImpl.java index 91ba489360c5..ec3a149e0a20 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfARangesSectionImpl.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfARangesSectionImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -26,18 +26,12 @@ package com.oracle.objectfile.elf.dwarf; -import java.util.Map; - -import com.oracle.objectfile.debugentry.ClassEntry; +import com.oracle.objectfile.debugentry.CompiledMethodEntry; +import com.oracle.objectfile.debugentry.range.Range; import com.oracle.objectfile.elf.dwarf.constants.DwarfSectionName; import com.oracle.objectfile.elf.dwarf.constants.DwarfVersion; -import jdk.graal.compiler.debug.DebugContext; -import com.oracle.objectfile.LayoutDecision; -import com.oracle.objectfile.LayoutDecisionMap; -import com.oracle.objectfile.ObjectFile; -import com.oracle.objectfile.debugentry.CompiledMethodEntry; -import com.oracle.objectfile.debugentry.range.Range; +import jdk.graal.compiler.debug.DebugContext; /** * Section generator for debug_aranges section. @@ -90,11 +84,10 @@ public void createContent() { * Where N is the number of compiled methods. */ assert !contentByteArrayCreated(); - Cursor byteCount = new Cursor(); - instanceClassStream().filter(ClassEntry::hasCompiledEntries).forEachOrdered(classEntry -> { - byteCount.add(entrySize(classEntry.compiledEntryCount())); - }); - byte[] buffer = new byte[byteCount.get()]; + int byteCount = instanceClassWithCompilationStream() + .mapToInt(classEntry -> entrySize(classEntry.compiledMethods().size())) + .sum(); + byte[] buffer = new byte[byteCount]; super.setContent(buffer); } @@ -111,23 +104,6 @@ private static int entrySize(int methodCount) { return size; } - @Override - public byte[] getOrDecideContent(Map alreadyDecided, byte[] contentHint) { - ObjectFile.Element textElement = getElement().getOwner().elementForName(".text"); - LayoutDecisionMap decisionMap = alreadyDecided.get(textElement); - if (decisionMap != null) { - Object valueObj = decisionMap.getDecidedValue(LayoutDecision.Kind.VADDR); - if (valueObj != null && valueObj instanceof Number) { - /* - * This may not be the final vaddr for the text segment but it will be close enough - * to make debug easier i.e. to within a 4k page or two. - */ - debugTextBase = ((Number) valueObj).longValue(); - } - } - return super.getOrDecideContent(alreadyDecided, contentHint); - } - @Override public void writeContent(DebugContext context) { assert contentByteArrayCreated(); @@ -135,14 +111,14 @@ public void writeContent(DebugContext context) { int size = buffer.length; Cursor cursor = new Cursor(); - enableLog(context, cursor.get()); + enableLog(context); log(context, " [0x%08x] DEBUG_ARANGES", cursor.get()); - instanceClassStream().filter(ClassEntry::hasCompiledEntries).forEachOrdered(classEntry -> { + instanceClassWithCompilationStream().forEachOrdered(classEntry -> { int lengthPos = cursor.get(); log(context, " [0x%08x] class %s CU 0x%x", lengthPos, classEntry.getTypeName(), getCUIndex(classEntry)); cursor.set(writeHeader(getCUIndex(classEntry), buffer, cursor.get())); - classEntry.compiledEntries().forEachOrdered(compiledMethodEntry -> { + classEntry.compiledMethods().forEach(compiledMethodEntry -> { cursor.set(writeARange(context, compiledMethodEntry, buffer, cursor.get())); }); // write two terminating zeroes @@ -176,9 +152,9 @@ private int writeHeader(int cuIndex, byte[] buffer, int p) { int writeARange(DebugContext context, CompiledMethodEntry compiledMethod, byte[] buffer, int p) { int pos = p; - Range primary = compiledMethod.getPrimary(); - log(context, " [0x%08x] %016x %016x %s", pos, debugTextBase + primary.getLo(), primary.getHi() - primary.getLo(), primary.getFullMethodNameWithParams()); - pos = writeRelocatableCodeOffset(primary.getLo(), buffer, pos); + Range primary = compiledMethod.primary(); + log(context, " [0x%08x] %016x %016x %s", pos, primary.getLo(), primary.getHi() - primary.getLo(), primary.getFullMethodNameWithParams()); + pos = writeCodeOffset(primary.getLo(), buffer, pos); pos = writeLong(primary.getHi() - primary.getLo(), buffer, pos); return pos; } diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfAbbrevSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfAbbrevSectionImpl.java index ead4598414a3..9a5a477b9169 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfAbbrevSectionImpl.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfAbbrevSectionImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -905,7 +905,7 @@ public void writeContent(DebugContext context) { int size = buffer.length; int pos = 0; - enableLog(context, pos); + enableLog(context); pos = writeAbbrevs(context, buffer, pos); @@ -914,60 +914,62 @@ public void writeContent(DebugContext context) { public int writeAbbrevs(DebugContext context, byte[] buffer, int p) { int pos = p; + // Write Abbrevs that are shared for AOT and Runtime compilation pos = writeCompileUnitAbbrevs(context, buffer, pos); pos = writeTypeUnitAbbrev(context, buffer, pos); - - pos = writePrimitiveTypeAbbrev(context, buffer, pos); - pos = writeVoidTypeAbbrev(context, buffer, pos); - pos = writeObjectHeaderAbbrev(context, buffer, pos); - pos = writeClassConstantAbbrev(context, buffer, pos); - pos = writeNamespaceAbbrev(context, buffer, pos); - pos = writeClassLayoutAbbrevs(context, buffer, pos); + pos = writeDummyClassLayoutAbbrev(context, buffer, pos); pos = writeClassReferenceAbbrevs(context, buffer, pos); pos = writeMethodDeclarationAbbrevs(context, buffer, pos); - pos = writeFieldDeclarationAbbrevs(context, buffer, pos); - - pos = writeArrayLayoutAbbrev(context, buffer, pos); - - pos = writeInterfaceLayoutAbbrev(context, buffer, pos); - - pos = writeForeignTypedefAbbrev(context, buffer, pos); - pos = writeForeignStructAbbrev(context, buffer, pos); - - pos = writeHeaderFieldAbbrev(context, buffer, pos); - pos = writeArrayElementFieldAbbrev(context, buffer, pos); - pos = writeArrayDataTypeAbbrevs(context, buffer, pos); - pos = writeArraySubrangeTypeAbbrev(context, buffer, pos); pos = writeMethodLocationAbbrev(context, buffer, pos); pos = writeAbstractInlineMethodAbbrev(context, buffer, pos); - pos = writeStaticFieldLocationAbbrev(context, buffer, pos); - pos = writeSuperReferenceAbbrev(context, buffer, pos); - pos = writeInterfaceImplementorAbbrev(context, buffer, pos); - - pos = writeInlinedSubroutineAbbrev(buffer, pos, false); - pos = writeInlinedSubroutineAbbrev(buffer, pos, true); - - /* - * if we address rebasing is required then we need to use compressed layout types supplied - * with a suitable data_location attribute and compressed pointer types to ensure that gdb - * converts offsets embedded in static or instance fields to raw pointers. Transformed - * addresses are typed using pointers to the underlying layout. - * - * if address rebasing is not required then a data_location attribute on the layout type - * will ensure that address tag bits are removed. - */ - if (dwarfSections.useHeapBase()) { - pos = writeCompressedLayoutAbbrev(context, buffer, pos); - } + pos = writeInlinedSubroutineAbbrev(buffer, pos); pos = writeParameterDeclarationAbbrevs(context, buffer, pos); pos = writeLocalDeclarationAbbrevs(context, buffer, pos); - pos = writeParameterLocationAbbrevs(context, buffer, pos); pos = writeLocalLocationAbbrevs(context, buffer, pos); + // Write Abbrevs that are only used for AOT debuginfo generation + if (!dwarfSections.isRuntimeCompilation() || true) { + + pos = writePrimitiveTypeAbbrev(context, buffer, pos); + pos = writeVoidTypeAbbrev(context, buffer, pos); + pos = writeObjectHeaderAbbrev(context, buffer, pos); + + pos = writeFieldDeclarationAbbrevs(context, buffer, pos); + pos = writeClassConstantAbbrev(context, buffer, pos); + + pos = writeArrayLayoutAbbrev(context, buffer, pos); + + pos = writeInterfaceLayoutAbbrev(context, buffer, pos); + + pos = writeForeignTypedefAbbrev(context, buffer, pos); + pos = writeForeignStructAbbrev(context, buffer, pos); + + pos = writeHeaderFieldAbbrev(context, buffer, pos); + pos = writeArrayElementFieldAbbrev(context, buffer, pos); + pos = writeArrayDataTypeAbbrevs(context, buffer, pos); + pos = writeArraySubrangeTypeAbbrev(context, buffer, pos); + pos = writeStaticFieldLocationAbbrev(context, buffer, pos); + pos = writeSuperReferenceAbbrev(context, buffer, pos); + pos = writeInterfaceImplementorAbbrev(context, buffer, pos); + + /* + * if we address rebasing is required then we need to use compressed layout types + * supplied with a suitable data_location attribute and compressed pointer types to + * ensure that gdb converts offsets embedded in static or instance fields to raw + * pointers. Transformed addresses are typed using pointers to the underlying layout. + * + * if address rebasing is not required then a data_location attribute on the layout type + * will ensure that address tag bits are removed. + */ + if (dwarfSections.useHeapBase()) { + pos = writeCompressedLayoutAbbrev(context, buffer, pos); + } + } + /* write a null abbrev to terminate the sequence */ pos = writeNullAbbrev(context, buffer, pos); return pos; @@ -987,9 +989,11 @@ private int writeHasChildren(DwarfHasChildren hasChildren, byte[] buffer, int po private int writeCompileUnitAbbrevs(@SuppressWarnings("unused") DebugContext context, byte[] buffer, int p) { int pos = p; - pos = writeCompileUnitAbbrev(context, AbbrevCode.CLASS_CONSTANT_UNIT, buffer, pos); - pos = writeCompileUnitAbbrev(context, AbbrevCode.CLASS_UNIT_1, buffer, pos); - pos = writeCompileUnitAbbrev(context, AbbrevCode.CLASS_UNIT_2, buffer, pos); + if (!dwarfSections.isRuntimeCompilation() || true) { + pos = writeCompileUnitAbbrev(context, AbbrevCode.CLASS_CONSTANT_UNIT, buffer, pos); + pos = writeCompileUnitAbbrev(context, AbbrevCode.CLASS_UNIT_1, buffer, pos); + pos = writeCompileUnitAbbrev(context, AbbrevCode.CLASS_UNIT_2, buffer, pos); + } pos = writeCompileUnitAbbrev(context, AbbrevCode.CLASS_UNIT_3, buffer, pos); return pos; } @@ -1114,11 +1118,14 @@ private int writeNamespaceAbbrev(@SuppressWarnings("unused") DebugContext contex private int writeClassLayoutAbbrevs(@SuppressWarnings("unused") DebugContext context, byte[] buffer, int p) { int pos = p; - pos = writeClassLayoutAbbrev(context, AbbrevCode.CLASS_LAYOUT_1, buffer, pos); - pos = writeClassLayoutAbbrev(context, AbbrevCode.CLASS_LAYOUT_3, buffer, pos); - if (!dwarfSections.useHeapBase()) { - pos = writeClassLayoutAbbrev(context, AbbrevCode.CLASS_LAYOUT_2, buffer, pos); + if (!dwarfSections.isRuntimeCompilation() || true) { + pos = writeClassLayoutAbbrev(context, AbbrevCode.CLASS_LAYOUT_1, buffer, pos); + if (!dwarfSections.useHeapBase()) { + pos = writeClassLayoutAbbrev(context, AbbrevCode.CLASS_LAYOUT_2, buffer, pos); + } } + pos = writeClassLayoutAbbrev(context, AbbrevCode.CLASS_LAYOUT_3, buffer, pos); + pos = writeClassLayoutAbbrev(context, AbbrevCode.CLASS_LAYOUT_4, buffer, pos); return pos; } @@ -1130,21 +1137,23 @@ private int writeClassLayoutAbbrev(@SuppressWarnings("unused") DebugContext cont pos = writeHasChildren(DwarfHasChildren.DW_CHILDREN_yes, buffer, pos); pos = writeAttrType(DwarfAttribute.DW_AT_name, buffer, pos); pos = writeAttrForm(DwarfForm.DW_FORM_strp, buffer, pos); - if (abbrevCode == AbbrevCode.CLASS_LAYOUT_3) { + if (abbrevCode == AbbrevCode.CLASS_LAYOUT_3 || abbrevCode == AbbrevCode.CLASS_LAYOUT_4) { pos = writeAttrType(DwarfAttribute.DW_AT_declaration, buffer, pos); pos = writeAttrForm(DwarfForm.DW_FORM_flag, buffer, pos); pos = writeAttrType(DwarfAttribute.DW_AT_signature, buffer, pos); pos = writeAttrForm(DwarfForm.DW_FORM_ref_sig8, buffer, pos); + if (abbrevCode == AbbrevCode.CLASS_LAYOUT_4) { + pos = writeAttrType(DwarfAttribute.DW_AT_decl_file, buffer, pos); + pos = writeAttrForm(DwarfForm.DW_FORM_data2, buffer, pos); + /* + * At present, we definitely don't have a line number for the class itself. pos = + * writeAttrType(DwarfDebugInfo.DW_AT_decl_line, buffer, pos); pos = + * writeAttrForm(DwarfDebugInfo.DW_FORM_data2, buffer, pos); + */ + } } else { pos = writeAttrType(DwarfAttribute.DW_AT_byte_size, buffer, pos); pos = writeAttrForm(DwarfForm.DW_FORM_data2, buffer, pos); - pos = writeAttrType(DwarfAttribute.DW_AT_decl_file, buffer, pos); - pos = writeAttrForm(DwarfForm.DW_FORM_data2, buffer, pos); - /*- - * At present we definitely don't have a line number for the class itself. - pos = writeAttrType(DwarfDebugInfo.DW_AT_decl_line, buffer, pos); - pos = writeAttrForm(DwarfDebugInfo.DW_FORM_data2, buffer, pos); - */ if (abbrevCode == AbbrevCode.CLASS_LAYOUT_2) { pos = writeAttrType(DwarfAttribute.DW_AT_data_location, buffer, pos); pos = writeAttrForm(DwarfForm.DW_FORM_expr_loc, buffer, pos); @@ -1159,6 +1168,27 @@ private int writeClassLayoutAbbrev(@SuppressWarnings("unused") DebugContext cont return pos; } + private int writeDummyClassLayoutAbbrev(@SuppressWarnings("unused") DebugContext context, byte[] buffer, int p) { + int pos = p; + + pos = writeAbbrevCode(AbbrevCode.CLASS_LAYOUT_DUMMY, buffer, pos); + pos = writeTag(DwarfTag.DW_TAG_structure_type, buffer, pos); + pos = writeHasChildren(DwarfHasChildren.DW_CHILDREN_no, buffer, pos); + pos = writeAttrType(DwarfAttribute.DW_AT_name, buffer, pos); + pos = writeAttrForm(DwarfForm.DW_FORM_strp, buffer, pos); + pos = writeAttrType(DwarfAttribute.DW_AT_declaration, buffer, pos); + pos = writeAttrForm(DwarfForm.DW_FORM_flag, buffer, pos); + pos = writeAttrType(DwarfAttribute.DW_AT_byte_size, buffer, pos); + pos = writeAttrForm(DwarfForm.DW_FORM_data2, buffer, pos); + /* + * Now terminate. + */ + pos = writeAttrType(DwarfAttribute.DW_AT_null, buffer, pos); + pos = writeAttrForm(DwarfForm.DW_FORM_null, buffer, pos); + + return pos; + } + private int writeClassReferenceAbbrevs(@SuppressWarnings("unused") DebugContext context, byte[] buffer, int p) { int pos = p; pos = writeClassReferenceAbbrev(context, AbbrevCode.TYPE_POINTER_SIG, buffer, pos); @@ -1189,8 +1219,12 @@ private int writeClassReferenceAbbrev(@SuppressWarnings("unused") DebugContext c private int writeMethodDeclarationAbbrevs(@SuppressWarnings("unused") DebugContext context, byte[] buffer, int p) { int pos = p; pos = writeMethodDeclarationAbbrev(context, AbbrevCode.METHOD_DECLARATION, buffer, pos); + pos = writeMethodDeclarationAbbrev(context, AbbrevCode.METHOD_DECLARATION_INLINE, buffer, pos); pos = writeMethodDeclarationAbbrev(context, AbbrevCode.METHOD_DECLARATION_STATIC, buffer, pos); - pos = writeMethodDeclarationAbbrev(context, AbbrevCode.METHOD_DECLARATION_SKELETON, buffer, pos); + pos = writeMethodDeclarationAbbrev(context, AbbrevCode.METHOD_DECLARATION_INLINE_STATIC, buffer, pos); + if (!dwarfSections.isRuntimeCompilation() || true) { + pos = writeMethodDeclarationAbbrev(context, AbbrevCode.METHOD_DECLARATION_SKELETON, buffer, pos); + } return pos; } @@ -1199,11 +1233,15 @@ private int writeMethodDeclarationAbbrev(@SuppressWarnings("unused") DebugContex pos = writeAbbrevCode(abbrevCode, buffer, pos); pos = writeTag(DwarfTag.DW_TAG_subprogram, buffer, pos); pos = writeHasChildren(DwarfHasChildren.DW_CHILDREN_yes, buffer, pos); + if (abbrevCode == AbbrevCode.METHOD_DECLARATION_INLINE || abbrevCode == AbbrevCode.METHOD_DECLARATION_INLINE_STATIC) { + pos = writeAttrType(DwarfAttribute.DW_AT_inline, buffer, pos); + pos = writeAttrForm(DwarfForm.DW_FORM_data1, buffer, pos); + } pos = writeAttrType(DwarfAttribute.DW_AT_external, buffer, pos); pos = writeAttrForm(DwarfForm.DW_FORM_flag, buffer, pos); pos = writeAttrType(DwarfAttribute.DW_AT_name, buffer, pos); pos = writeAttrForm(DwarfForm.DW_FORM_strp, buffer, pos); - if (abbrevCode == AbbrevCode.METHOD_DECLARATION || abbrevCode == AbbrevCode.METHOD_DECLARATION_STATIC) { + if (abbrevCode != AbbrevCode.METHOD_DECLARATION_SKELETON) { pos = writeAttrType(DwarfAttribute.DW_AT_decl_file, buffer, pos); pos = writeAttrForm(DwarfForm.DW_FORM_data2, buffer, pos); pos = writeAttrType(DwarfAttribute.DW_AT_decl_line, buffer, pos); @@ -1219,14 +1257,14 @@ private int writeMethodDeclarationAbbrev(@SuppressWarnings("unused") DebugContex pos = writeAttrForm(DwarfForm.DW_FORM_data1, buffer, pos); pos = writeAttrType(DwarfAttribute.DW_AT_declaration, buffer, pos); pos = writeAttrForm(DwarfForm.DW_FORM_flag, buffer, pos); - if (abbrevCode == AbbrevCode.METHOD_DECLARATION || abbrevCode == AbbrevCode.METHOD_DECLARATION_STATIC) { - /* This is not in DWARF2 */ - // pos = writeAttrType(DW_AT_virtuality, buffer, pos); - // pos = writeAttrForm(DW_FORM_data1, buffer, pos); + /* This is not in DWARF2 */ + // pos = writeAttrType(DW_AT_virtuality, buffer, pos); + // pos = writeAttrForm(DW_FORM_data1, buffer, pos); + if (abbrevCode != AbbrevCode.METHOD_DECLARATION_SKELETON) { pos = writeAttrType(DwarfAttribute.DW_AT_containing_type, buffer, pos); pos = writeAttrForm(DwarfForm.DW_FORM_ref_sig8, buffer, pos); } - if (abbrevCode == AbbrevCode.METHOD_DECLARATION) { + if (abbrevCode == AbbrevCode.METHOD_DECLARATION || abbrevCode == AbbrevCode.METHOD_DECLARATION_INLINE) { pos = writeAttrType(DwarfAttribute.DW_AT_object_pointer, buffer, pos); pos = writeAttrForm(DwarfForm.DW_FORM_ref4, buffer, pos); } @@ -1248,6 +1286,8 @@ private int writeFieldDeclarationAbbrevs(DebugContext context, byte[] buffer, in pos = writeFieldDeclarationAbbrev(context, AbbrevCode.FIELD_DECLARATION_3, buffer, pos); /* A static field with line and file. */ pos = writeFieldDeclarationAbbrev(context, AbbrevCode.FIELD_DECLARATION_4, buffer, pos); + + pos = writeFieldDeclarationRelTypeAbbrev(context, buffer, pos); return pos; } @@ -1613,8 +1653,10 @@ private int writeParameterDeclarationAbbrevs(DebugContext context, byte[] buffer pos = writeParameterDeclarationAbbrev(context, AbbrevCode.METHOD_PARAMETER_DECLARATION_1, buffer, pos); pos = writeParameterDeclarationAbbrev(context, AbbrevCode.METHOD_PARAMETER_DECLARATION_2, buffer, pos); pos = writeParameterDeclarationAbbrev(context, AbbrevCode.METHOD_PARAMETER_DECLARATION_3, buffer, pos); - pos = writeSkeletonParameterDeclarationAbbrev(context, AbbrevCode.METHOD_PARAMETER_DECLARATION_4, buffer, pos); - pos = writeSkeletonParameterDeclarationAbbrev(context, AbbrevCode.METHOD_PARAMETER_DECLARATION_5, buffer, pos); + if (!dwarfSections.isRuntimeCompilation() || true) { + pos = writeSkeletonParameterDeclarationAbbrev(context, AbbrevCode.METHOD_PARAMETER_DECLARATION_4, buffer, pos); + pos = writeSkeletonParameterDeclarationAbbrev(context, AbbrevCode.METHOD_PARAMETER_DECLARATION_5, buffer, pos); + } return pos; } @@ -1760,11 +1802,11 @@ private int writeNullAbbrev(@SuppressWarnings("unused") DebugContext context, by return pos; } - private int writeInlinedSubroutineAbbrev(byte[] buffer, int p, boolean withChildren) { + private int writeInlinedSubroutineAbbrev(byte[] buffer, int p) { int pos = p; - pos = writeAbbrevCode(withChildren ? AbbrevCode.INLINED_SUBROUTINE_WITH_CHILDREN : AbbrevCode.INLINED_SUBROUTINE, buffer, pos); + pos = writeAbbrevCode(AbbrevCode.INLINED_SUBROUTINE, buffer, pos); pos = writeTag(DwarfTag.DW_TAG_inlined_subroutine, buffer, pos); - pos = writeHasChildren(withChildren ? DwarfHasChildren.DW_CHILDREN_yes : DwarfHasChildren.DW_CHILDREN_no, buffer, pos); + pos = writeHasChildren(DwarfHasChildren.DW_CHILDREN_yes, buffer, pos); pos = writeAttrType(DwarfAttribute.DW_AT_abstract_origin, buffer, pos); pos = writeAttrForm(DwarfForm.DW_FORM_ref4, buffer, pos); pos = writeAttrType(DwarfAttribute.DW_AT_low_pc, buffer, pos); diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfDebugInfo.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfDebugInfo.java index e3634115c5b3..a360b678e2b2 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfDebugInfo.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfDebugInfo.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -27,16 +27,15 @@ package com.oracle.objectfile.elf.dwarf; import java.nio.ByteOrder; - -import org.graalvm.collections.EconomicMap; +import java.util.HashMap; import com.oracle.objectfile.debugentry.ClassEntry; import com.oracle.objectfile.debugentry.DebugInfoBase; +import com.oracle.objectfile.debugentry.LocalEntry; import com.oracle.objectfile.debugentry.MethodEntry; import com.oracle.objectfile.debugentry.StructureTypeEntry; import com.oracle.objectfile.debugentry.TypeEntry; import com.oracle.objectfile.debugentry.range.Range; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugLocalInfo; import com.oracle.objectfile.elf.ELFMachine; import com.oracle.objectfile.elf.dwarf.constants.DwarfLanguage; @@ -52,7 +51,7 @@ public class DwarfDebugInfo extends DebugInfoBase { /* * Define all the abbrev section codes we need for our DIEs. */ - enum AbbrevCode { + public enum AbbrevCode { /* null marker which must come first as its ordinal has to equal zero */ NULL, /* Level 0 DIEs. */ @@ -70,6 +69,8 @@ enum AbbrevCode { CLASS_LAYOUT_1, CLASS_LAYOUT_2, CLASS_LAYOUT_3, + CLASS_LAYOUT_4, + CLASS_LAYOUT_DUMMY, TYPE_POINTER_SIG, TYPE_POINTER, FOREIGN_TYPEDEF, @@ -81,7 +82,9 @@ enum AbbrevCode { COMPRESSED_LAYOUT, /* Level 2 DIEs. */ METHOD_DECLARATION, + METHOD_DECLARATION_INLINE, METHOD_DECLARATION_STATIC, + METHOD_DECLARATION_INLINE_STATIC, METHOD_DECLARATION_SKELETON, FIELD_DECLARATION_1, FIELD_DECLARATION_2, @@ -125,23 +128,9 @@ enum AbbrevCode { public static final byte rheapbase_x86 = (byte) 14; public static final byte rthread_x86 = (byte) 15; - /* - * A prefix used to label indirect types used to ensure gdb performs oop reference --> raw - * address translation - */ - public static final String COMPRESSED_PREFIX = "_z_."; - /* - * A prefix used for type signature generation to generate unique type signatures for type - * layout type units - */ - public static final String LAYOUT_PREFIX = "_layout_."; - /* - * The name of the type for header field hub which needs special case processing to remove tag - * bits - */ - public static final String HUB_TYPE_NAME = "java.lang.Class"; /* Full byte/word values. */ private final DwarfStrSectionImpl dwarfStrSection; + private final DwarfLineStrSectionImpl dwarfLineStrSection; private final DwarfAbbrevSectionImpl dwarfAbbrevSection; private final DwarfInfoSectionImpl dwarfInfoSection; private final DwarfLocSectionImpl dwarfLocSection; @@ -164,23 +153,24 @@ enum AbbrevCode { * n.b. this collection includes entries for the structure types used to define the object and * array headers which do not have an associated TypeEntry. */ - private final EconomicMap classPropertiesIndex = EconomicMap.create(); + private final HashMap classPropertiesIndex = new HashMap<>(); /** * A collection of method properties associated with each generated method record. */ - private final EconomicMap methodPropertiesIndex = EconomicMap.create(); + private final HashMap methodPropertiesIndex = new HashMap<>(); /** * A collection of local variable properties associated with an inlined subrange. */ - private final EconomicMap rangeLocalPropertiesIndex = EconomicMap.create(); + private final HashMap rangeLocalPropertiesIndex = new HashMap<>(); @SuppressWarnings("this-escape") public DwarfDebugInfo(ELFMachine elfMachine, ByteOrder byteOrder) { super(byteOrder); this.elfMachine = elfMachine; dwarfStrSection = new DwarfStrSectionImpl(this); + dwarfLineStrSection = new DwarfLineStrSectionImpl(this); dwarfAbbrevSection = new DwarfAbbrevSectionImpl(this); dwarfInfoSection = new DwarfInfoSectionImpl(this); dwarfLocSection = new DwarfLocSectionImpl(this); @@ -203,6 +193,10 @@ public DwarfStrSectionImpl getStrSectionImpl() { return dwarfStrSection; } + public DwarfLineStrSectionImpl getLineStrSectionImpl() { + return dwarfLineStrSection; + } + public DwarfAbbrevSectionImpl getAbbrevSectionImpl() { return dwarfAbbrevSection; } @@ -271,7 +265,7 @@ static class DwarfClassProperties { /** * Map from field names to info section index for the field declaration. */ - private EconomicMap fieldDeclarationIndex; + private HashMap fieldDeclarationIndex; public StructureTypeEntry getTypeEntry() { return typeEntry; @@ -301,12 +295,12 @@ static class DwarfMethodProperties { * Per class map that identifies the info declarations for a top level method declaration or * an abstract inline method declaration. */ - private EconomicMap localPropertiesMap; + private HashMap localPropertiesMap; /** * Per class map that identifies the info declaration for an abstract inline method. */ - private EconomicMap abstractInlineMethodIndex; + private HashMap abstractInlineMethodIndex; DwarfMethodProperties() { methodDeclarationIndex = -1; @@ -326,19 +320,14 @@ public void setMethodDeclarationIndex(int pos) { public DwarfLocalProperties getLocalProperties(ClassEntry classEntry) { if (localPropertiesMap == null) { - localPropertiesMap = EconomicMap.create(); - } - DwarfLocalProperties localProperties = localPropertiesMap.get(classEntry); - if (localProperties == null) { - localProperties = new DwarfLocalProperties(); - localPropertiesMap.put(classEntry, localProperties); + localPropertiesMap = new HashMap<>(); } - return localProperties; + return localPropertiesMap.computeIfAbsent(classEntry, k -> new DwarfLocalProperties()); } public void setAbstractInlineMethodIndex(ClassEntry classEntry, int pos) { if (abstractInlineMethodIndex == null) { - abstractInlineMethodIndex = EconomicMap.create(); + abstractInlineMethodIndex = new HashMap<>(); } // replace but check it did not change Integer val = abstractInlineMethodIndex.put(classEntry, pos); @@ -383,7 +372,7 @@ private DwarfMethodProperties lookupMethodProperties(MethodEntry methodEntry) { public void setCUIndex(ClassEntry classEntry, int idx) { assert idx >= 0; DwarfClassProperties classProperties = lookupClassProperties(classEntry); - assert classProperties.getTypeEntry() == classEntry; + assert classProperties.getTypeEntry().equals(classEntry); assert classProperties.cuIndex == -1 || classProperties.cuIndex == idx; classProperties.cuIndex = idx; } @@ -391,7 +380,7 @@ public void setCUIndex(ClassEntry classEntry, int idx) { public int getCUIndex(ClassEntry classEntry) { DwarfClassProperties classProperties; classProperties = lookupClassProperties(classEntry); - assert classProperties.getTypeEntry() == classEntry; + assert classProperties.getTypeEntry().equals(classEntry); assert classProperties.cuIndex >= 0; return classProperties.cuIndex; } @@ -399,7 +388,7 @@ public int getCUIndex(ClassEntry classEntry) { public void setCodeRangesIndex(ClassEntry classEntry, int idx) { assert idx >= 0; DwarfClassProperties classProperties = lookupClassProperties(classEntry); - assert classProperties.getTypeEntry() == classEntry; + assert classProperties.getTypeEntry().equals(classEntry); assert classProperties.codeRangesIndex == -1 || classProperties.codeRangesIndex == idx; classProperties.codeRangesIndex = idx; } @@ -407,7 +396,7 @@ public void setCodeRangesIndex(ClassEntry classEntry, int idx) { public int getCodeRangesIndex(ClassEntry classEntry) { DwarfClassProperties classProperties; classProperties = lookupClassProperties(classEntry); - assert classProperties.getTypeEntry() == classEntry; + assert classProperties.getTypeEntry().equals(classEntry); assert classProperties.codeRangesIndex >= 0; return classProperties.codeRangesIndex; } @@ -415,7 +404,7 @@ public int getCodeRangesIndex(ClassEntry classEntry) { public void setLocationListIndex(ClassEntry classEntry, int idx) { assert idx >= 0; DwarfClassProperties classProperties = lookupClassProperties(classEntry); - assert classProperties.getTypeEntry() == classEntry; + assert classProperties.getTypeEntry().equals(classEntry); assert classProperties.locationListIndex == 0 || classProperties.locationListIndex == idx; classProperties.locationListIndex = idx; } @@ -423,14 +412,14 @@ public void setLocationListIndex(ClassEntry classEntry, int idx) { public int getLocationListIndex(ClassEntry classEntry) { DwarfClassProperties classProperties; classProperties = lookupClassProperties(classEntry); - assert classProperties.getTypeEntry() == classEntry; + assert classProperties.getTypeEntry().equals(classEntry); return classProperties.locationListIndex; } public void setLineIndex(ClassEntry classEntry, int idx) { assert idx >= 0; DwarfClassProperties classProperties = lookupClassProperties(classEntry); - assert classProperties.getTypeEntry() == classEntry; + assert classProperties.getTypeEntry().equals(classEntry); assert classProperties.lineIndex == -1 || classProperties.lineIndex == idx; classProperties.lineIndex = idx; } @@ -438,7 +427,7 @@ public void setLineIndex(ClassEntry classEntry, int idx) { public int getLineIndex(ClassEntry classEntry) { DwarfClassProperties classProperties; classProperties = lookupClassProperties(classEntry); - assert classProperties.getTypeEntry() == classEntry; + assert classProperties.getTypeEntry().equals(classEntry); assert classProperties.lineIndex >= 0; return classProperties.lineIndex; } @@ -446,7 +435,7 @@ public int getLineIndex(ClassEntry classEntry) { public void setLinePrologueSize(ClassEntry classEntry, int size) { assert size >= 0; DwarfClassProperties classProperties = lookupClassProperties(classEntry); - assert classProperties.getTypeEntry() == classEntry; + assert classProperties.getTypeEntry().equals(classEntry); assert classProperties.linePrologueSize == -1 || classProperties.linePrologueSize == size; classProperties.linePrologueSize = size; } @@ -454,7 +443,7 @@ public void setLinePrologueSize(ClassEntry classEntry, int size) { public int getLinePrologueSize(ClassEntry classEntry) { DwarfClassProperties classProperties; classProperties = lookupClassProperties(classEntry); - assert classProperties.getTypeEntry() == classEntry; + assert classProperties.getTypeEntry().equals(classEntry); assert classProperties.linePrologueSize >= 0; return classProperties.linePrologueSize; } @@ -462,10 +451,10 @@ public int getLinePrologueSize(ClassEntry classEntry) { public void setFieldDeclarationIndex(StructureTypeEntry entry, String fieldName, int pos) { DwarfClassProperties classProperties; classProperties = lookupClassProperties(entry); - assert classProperties.getTypeEntry() == entry; - EconomicMap fieldDeclarationIndex = classProperties.fieldDeclarationIndex; + assert classProperties.getTypeEntry().equals(entry); + HashMap fieldDeclarationIndex = classProperties.fieldDeclarationIndex; if (fieldDeclarationIndex == null) { - classProperties.fieldDeclarationIndex = fieldDeclarationIndex = EconomicMap.create(); + classProperties.fieldDeclarationIndex = fieldDeclarationIndex = new HashMap<>(); } if (fieldDeclarationIndex.get(fieldName) != null) { assert fieldDeclarationIndex.get(fieldName) == pos : entry.getTypeName() + fieldName; @@ -477,8 +466,8 @@ public void setFieldDeclarationIndex(StructureTypeEntry entry, String fieldName, public int getFieldDeclarationIndex(StructureTypeEntry entry, String fieldName) { DwarfClassProperties classProperties; classProperties = lookupClassProperties(entry); - assert classProperties.getTypeEntry() == entry; - EconomicMap fieldDeclarationIndex = classProperties.fieldDeclarationIndex; + assert classProperties.getTypeEntry().equals(entry); + HashMap fieldDeclarationIndex = classProperties.fieldDeclarationIndex; assert fieldDeclarationIndex != null : fieldName; assert fieldDeclarationIndex.get(fieldName) != null : entry.getTypeName() + fieldName; return fieldDeclarationIndex.get(fieldName); @@ -500,17 +489,17 @@ public int getMethodDeclarationIndex(MethodEntry methodEntry) { */ static final class DwarfLocalProperties { - private EconomicMap locals; + private final HashMap locals; private DwarfLocalProperties() { - locals = EconomicMap.create(); + locals = new HashMap<>(); } - int getIndex(DebugLocalInfo localInfo) { + int getIndex(LocalEntry localInfo) { return locals.get(localInfo); } - void setIndex(DebugLocalInfo localInfo, int index) { + void setIndex(LocalEntry localInfo, int index) { if (locals.get(localInfo) != null) { assert locals.get(localInfo) == index; } else { @@ -533,16 +522,16 @@ private DwarfLocalProperties addRangeLocalProperties(Range range) { return localProperties; } - public DwarfLocalProperties lookupLocalProperties(ClassEntry classEntry, MethodEntry methodEntry) { + private DwarfLocalProperties lookupLocalProperties(ClassEntry classEntry, MethodEntry methodEntry) { return lookupMethodProperties(methodEntry).getLocalProperties(classEntry); } - public void setMethodLocalIndex(ClassEntry classEntry, MethodEntry methodEntry, DebugLocalInfo localInfo, int index) { + public void setMethodLocalIndex(ClassEntry classEntry, MethodEntry methodEntry, LocalEntry localInfo, int index) { DwarfLocalProperties localProperties = lookupLocalProperties(classEntry, methodEntry); localProperties.setIndex(localInfo, index); } - public int getMethodLocalIndex(ClassEntry classEntry, MethodEntry methodEntry, DebugLocalInfo localInfo) { + public int getMethodLocalIndex(ClassEntry classEntry, MethodEntry methodEntry, LocalEntry localInfo) { DwarfLocalProperties localProperties = lookupLocalProperties(classEntry, methodEntry); assert localProperties != null : "get of non-existent local index"; int index = localProperties.getIndex(localInfo); @@ -550,7 +539,7 @@ public int getMethodLocalIndex(ClassEntry classEntry, MethodEntry methodEntry, D return index; } - public void setRangeLocalIndex(Range range, DebugLocalInfo localInfo, int index) { + public void setRangeLocalIndex(Range range, LocalEntry localInfo, int index) { DwarfLocalProperties rangeProperties = rangeLocalPropertiesIndex.get(range); if (rangeProperties == null) { rangeProperties = addRangeLocalProperties(range); @@ -558,10 +547,10 @@ public void setRangeLocalIndex(Range range, DebugLocalInfo localInfo, int index) rangeProperties.setIndex(localInfo, index); } - public int getRangeLocalIndex(Range range, DebugLocalInfo localinfo) { + public int getRangeLocalIndex(Range range, LocalEntry localInfo) { DwarfLocalProperties rangeProperties = rangeLocalPropertiesIndex.get(range); assert rangeProperties != null : "get of non-existent local index"; - int index = rangeProperties.getIndex(localinfo); + int index = rangeProperties.getIndex(localInfo); assert index >= 0 : "get of local index before it was set"; return index; } diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfFrameSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfFrameSectionImpl.java index ea0c269521f1..345a2906f2f9 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfFrameSectionImpl.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfFrameSectionImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -26,14 +26,15 @@ package com.oracle.objectfile.elf.dwarf; +import java.util.List; + import com.oracle.objectfile.debugentry.CompiledMethodEntry; +import com.oracle.objectfile.debugentry.FrameSizeChangeEntry; import com.oracle.objectfile.debugentry.range.Range; -import com.oracle.objectfile.debuginfo.DebugInfoProvider; import com.oracle.objectfile.elf.dwarf.constants.DwarfFrameValue; import com.oracle.objectfile.elf.dwarf.constants.DwarfSectionName; -import jdk.graal.compiler.debug.DebugContext; -import java.util.List; +import jdk.graal.compiler.debug.DebugContext; /** * Section generic generator for debug_frame section. @@ -45,8 +46,8 @@ public abstract class DwarfFrameSectionImpl extends DwarfSectionImpl { private static final int CFA_CIE_id_default = -1; public DwarfFrameSectionImpl(DwarfDebugInfo dwarfSections) { - // debug_frame section depends on debug_line section - super(dwarfSections, DwarfSectionName.DW_FRAME_SECTION, DwarfSectionName.DW_LINE_SECTION); + // debug_frame section depends on debug_line_str section + super(dwarfSections, DwarfSectionName.DW_FRAME_SECTION, DwarfSectionName.DW_LINE_STR_SECTION); } @Override @@ -74,7 +75,7 @@ public void writeContent(DebugContext context) { int size = buffer.length; int pos = 0; - enableLog(context, pos); + enableLog(context); /* * There are entries for the prologue region where the stack is being built, the method body @@ -143,11 +144,11 @@ private int writeCIEVersion(byte[] buffer, int pos) { private int writeMethodFrame(CompiledMethodEntry compiledEntry, byte[] buffer, int p) { int pos = p; int lengthPos = pos; - Range range = compiledEntry.getPrimary(); + Range range = compiledEntry.primary(); long lo = range.getLo(); long hi = range.getHi(); - pos = writeFDEHeader((int) lo, (int) hi, buffer, pos); - pos = writeFDEs(compiledEntry.getFrameSize(), compiledEntry.getFrameSizeInfos(), buffer, pos); + pos = writeFDEHeader(lo, hi, buffer, pos); + pos = writeFDEs(compiledEntry.frameSize(), compiledEntry.frameSizeInfos(), buffer, pos); pos = writePaddingNops(buffer, pos); patchLength(lengthPos, buffer, pos); return pos; @@ -161,9 +162,9 @@ private int writeMethodFrames(byte[] buffer, int p) { return cursor.get(); } - protected abstract int writeFDEs(int frameSize, List frameSizeInfos, byte[] buffer, int pos); + protected abstract int writeFDEs(int frameSize, List frameSizeInfos, byte[] buffer, int pos); - private int writeFDEHeader(int lo, int hi, byte[] buffer, int p) { + private int writeFDEHeader(long lo, long hi, byte[] buffer, int p) { /* * We only need a vanilla FDE header with default fields the layout is: * @@ -189,7 +190,7 @@ private int writeFDEHeader(int lo, int hi, byte[] buffer, int p) { /* CIE_offset */ pos = writeInt(0, buffer, pos); /* Initial address. */ - pos = writeRelocatableCodeOffset(lo, buffer, pos); + pos = writeCodeOffset(lo, buffer, pos); /* Address range. */ return writeLong(hi - lo, buffer, pos); } @@ -263,8 +264,7 @@ protected int writeOffset(int register, int offset, byte[] buffer, int p) { protected int writeRestore(int register, byte[] buffer, int p) { byte op = restoreOp(register); - int pos = p; - return writeByte(op, buffer, pos); + return writeByte(op, buffer, p); } @SuppressWarnings("unused") diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfFrameSectionImplAArch64.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfFrameSectionImplAArch64.java index b568d396c919..51fc2b6b475a 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfFrameSectionImplAArch64.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfFrameSectionImplAArch64.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -26,10 +26,10 @@ package com.oracle.objectfile.elf.dwarf; -import com.oracle.objectfile.debuginfo.DebugInfoProvider; - import java.util.List; +import com.oracle.objectfile.debugentry.FrameSizeChangeEntry; + /** * AArch64-specific section generator for debug_frame section that knows details of AArch64 * registers and frame layout. @@ -73,14 +73,14 @@ public int writeInitialInstructions(byte[] buffer, int p) { } @Override - protected int writeFDEs(int frameSize, List frameSizeInfos, byte[] buffer, int p) { + protected int writeFDEs(int frameSize, List frameSizeInfos, byte[] buffer, int p) { int pos = p; int currentOffset = 0; - for (DebugInfoProvider.DebugFrameSizeChange debugFrameSizeInfo : frameSizeInfos) { - int advance = debugFrameSizeInfo.getOffset() - currentOffset; + for (FrameSizeChangeEntry frameSizeInfo : frameSizeInfos) { + int advance = frameSizeInfo.offset() - currentOffset; currentOffset += advance; pos = writeAdvanceLoc(advance, buffer, pos); - if (debugFrameSizeInfo.getType() == DebugInfoProvider.DebugFrameSizeChange.Type.EXTEND) { + if (frameSizeInfo.isExtend()) { /* * SP has been extended so rebase CFA using full frame. * diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfFrameSectionImplX86_64.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfFrameSectionImplX86_64.java index 2a2fb2fd78ba..16025f9f0899 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfFrameSectionImplX86_64.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfFrameSectionImplX86_64.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -26,10 +26,10 @@ package com.oracle.objectfile.elf.dwarf; -import com.oracle.objectfile.debuginfo.DebugInfoProvider; - import java.util.List; +import com.oracle.objectfile.debugentry.FrameSizeChangeEntry; + /** * x86_64-specific section generator for debug_frame section that knows details of x86_64 registers * and frame layout. @@ -84,14 +84,14 @@ public int writeInitialInstructions(byte[] buffer, int p) { } @Override - protected int writeFDEs(int frameSize, List frameSizeInfos, byte[] buffer, int p) { + protected int writeFDEs(int frameSize, List frameSizeInfos, byte[] buffer, int p) { int pos = p; int currentOffset = 0; - for (DebugInfoProvider.DebugFrameSizeChange debugFrameSizeInfo : frameSizeInfos) { - int advance = debugFrameSizeInfo.getOffset() - currentOffset; + for (FrameSizeChangeEntry frameSizeInfo : frameSizeInfos) { + int advance = frameSizeInfo.offset() - currentOffset; currentOffset += advance; pos = writeAdvanceLoc(advance, buffer, pos); - if (debugFrameSizeInfo.getType() == DebugInfoProvider.DebugFrameSizeChange.Type.EXTEND) { + if (frameSizeInfo.isExtend()) { /* * SP has been extended so rebase CFA using full frame. * diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfInfoSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfInfoSectionImpl.java index 6d5926c0530e..aef975d21098 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfInfoSectionImpl.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfInfoSectionImpl.java @@ -27,31 +27,28 @@ package com.oracle.objectfile.elf.dwarf; import java.lang.reflect.Modifier; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; +import java.util.HashSet; import java.util.List; -import java.util.stream.Stream; - -import org.graalvm.collections.EconomicSet; import com.oracle.objectfile.debugentry.ArrayTypeEntry; import com.oracle.objectfile.debugentry.ClassEntry; import com.oracle.objectfile.debugentry.CompiledMethodEntry; import com.oracle.objectfile.debugentry.FieldEntry; import com.oracle.objectfile.debugentry.FileEntry; +import com.oracle.objectfile.debugentry.ForeignFloatTypeEntry; +import com.oracle.objectfile.debugentry.ForeignIntegerTypeEntry; +import com.oracle.objectfile.debugentry.ForeignPointerTypeEntry; +import com.oracle.objectfile.debugentry.ForeignStructTypeEntry; import com.oracle.objectfile.debugentry.ForeignTypeEntry; +import com.oracle.objectfile.debugentry.ForeignWordTypeEntry; import com.oracle.objectfile.debugentry.HeaderTypeEntry; import com.oracle.objectfile.debugentry.InterfaceClassEntry; +import com.oracle.objectfile.debugentry.LocalEntry; import com.oracle.objectfile.debugentry.MethodEntry; import com.oracle.objectfile.debugentry.PrimitiveTypeEntry; import com.oracle.objectfile.debugentry.StructureTypeEntry; import com.oracle.objectfile.debugentry.TypeEntry; import com.oracle.objectfile.debugentry.range.Range; -import com.oracle.objectfile.debugentry.range.SubRange; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugLocalInfo; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugLocalValueInfo; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugPrimitiveTypeInfo; import com.oracle.objectfile.elf.dwarf.DwarfDebugInfo.AbbrevCode; import com.oracle.objectfile.elf.dwarf.constants.DwarfAccess; import com.oracle.objectfile.elf.dwarf.constants.DwarfEncoding; @@ -64,9 +61,6 @@ import com.oracle.objectfile.elf.dwarf.constants.DwarfVersion; import jdk.graal.compiler.debug.DebugContext; -import jdk.vm.ci.meta.JavaConstant; -import jdk.vm.ci.meta.JavaKind; -import jdk.vm.ci.meta.PrimitiveConstant; /** * Section generator for debug_info section. @@ -105,7 +99,7 @@ public void writeContent(DebugContext context) { int size = buffer.length; int pos = 0; - enableLog(context, pos); + enableLog(context); log(context, " [0x%08x] DEBUG_INFO", pos); log(context, " [0x%08x] size = 0x%08x", pos, size); @@ -113,48 +107,76 @@ public void writeContent(DebugContext context) { assert pos == size; } - DwarfEncoding computeEncoding(int flags, int bitCount) { + DwarfEncoding computeEncoding(PrimitiveTypeEntry type) { + int bitCount = type.getBitCount(); assert bitCount > 0; - if ((flags & DebugPrimitiveTypeInfo.FLAG_NUMERIC) != 0) { - if (((flags & DebugPrimitiveTypeInfo.FLAG_INTEGRAL) != 0)) { - if ((flags & DebugPrimitiveTypeInfo.FLAG_SIGNED) != 0) { - switch (bitCount) { - case 8: - return DwarfEncoding.DW_ATE_signed_char; - default: - assert bitCount == 16 || bitCount == 32 || bitCount == 64; - return DwarfEncoding.DW_ATE_signed; - } + if (type.isNumericInteger()) { + if (type.isUnsigned()) { + if (bitCount == 1) { + return DwarfEncoding.DW_ATE_boolean; + } else if (bitCount == 8) { + return DwarfEncoding.DW_ATE_unsigned_char; } else { - assert bitCount == 16; + assert bitCount == 16 || bitCount == 32 || bitCount == 64; return DwarfEncoding.DW_ATE_unsigned; } + } else if (bitCount == 8) { + return DwarfEncoding.DW_ATE_signed_char; } else { - assert bitCount == 32 || bitCount == 64; - return DwarfEncoding.DW_ATE_float; + assert bitCount == 16 || bitCount == 32 || bitCount == 64; + return DwarfEncoding.DW_ATE_signed; } } else { - assert bitCount == 1; - return DwarfEncoding.DW_ATE_boolean; + assert type.isNumericFloat(); + assert bitCount == 32 || bitCount == 64; + return DwarfEncoding.DW_ATE_float; } } public int generateContent(DebugContext context, byte[] buffer) { int pos = 0; - /* Write TUs for primitive types and header struct. */ - pos = writeBuiltInTypes(context, buffer, pos); + if (dwarfSections.isRuntimeCompilation()) { + Cursor cursor = new Cursor(pos); + instanceClassWithCompilationStream().forEachOrdered(classEntry -> { + setCUIndex(classEntry, cursor.get()); + cursor.set(writeInstanceClassInfo(context, classEntry, buffer, cursor.get())); + }); + typeStream().forEach(typeEntry -> { + switch (typeEntry) { + case PrimitiveTypeEntry primitiveTypeEntry -> { + if (primitiveTypeEntry.getBitCount() > 0) { + cursor.set(writePrimitiveType(context, primitiveTypeEntry, buffer, cursor.get())); + } else { + cursor.set(writeVoidType(context, primitiveTypeEntry, buffer, cursor.get())); + } + } + case HeaderTypeEntry headerTypeEntry -> { + cursor.set(writeHeaderType(context, headerTypeEntry, buffer, cursor.get())); + } + case StructureTypeEntry structureTypeEntry -> { + cursor.set(writeDummyTypeUnit(context, structureTypeEntry, buffer, cursor.get())); + } + default -> { + } + } + }); + pos = cursor.get(); + } else { + /* Write TUs for primitive types and header struct. */ + pos = writeBuiltInTypes(context, buffer, pos); - /* - * Write TUs and CUs for all instance classes, which includes interfaces and enums. That - * also incorporates interfaces that model foreign types. - */ - pos = writeInstanceClasses(context, buffer, pos); + /* + * Write TUs and CUs for all instance classes, which includes interfaces and enums. That + * also incorporates interfaces that model foreign types. + */ + pos = writeInstanceClasses(context, buffer, pos); - /* Write TUs and CUs for array types. */ - pos = writeArrays(context, buffer, pos); + /* Write TUs and CUs for array types. */ + pos = writeArrays(context, buffer, pos); - /* Write CU for class constant objects. */ - pos = writeClassConstantObjects(context, buffer, pos); + /* Write CU for class constant objects. */ + pos = writeClassConstantObjects(context, buffer, pos); + } return pos; } @@ -162,10 +184,10 @@ public int generateContent(DebugContext context, byte[] buffer) { private int writeSkeletonClassLayout(DebugContext context, ClassEntry classEntry, byte[] buffer, int p) { int pos = p; log(context, " [0x%08x] class layout", pos); - AbbrevCode abbrevCode = AbbrevCode.CLASS_LAYOUT_3; + AbbrevCode abbrevCode = AbbrevCode.CLASS_LAYOUT_4; log(context, " [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal()); pos = writeAbbrevCode(abbrevCode, buffer, pos); - String name = classEntry.getTypeName(); + String name = uniqueDebugString(classEntry.getTypeName()); log(context, " [0x%08x] name 0x%x (%s)", pos, debugStringIndex(name), name); pos = writeStrSectionOffset(name, buffer, pos); log(context, " [0x%08x] declaration true", pos); @@ -173,6 +195,9 @@ private int writeSkeletonClassLayout(DebugContext context, ClassEntry classEntry long typeSignature = classEntry.getLayoutTypeSignature(); log(context, " [0x%08x] type specification 0x%x", pos, typeSignature); pos = writeTypeSignature(typeSignature, buffer, pos); + int fileIdx = classEntry.getFileIdx(); + log(context, " [0x%08x] file 0x%x (%s)", pos, fileIdx, classEntry.getFileName()); + pos = writeAttrData2((short) fileIdx, buffer, pos); pos = writeStaticFieldDeclarations(context, classEntry, buffer, pos); pos = writeMethodDeclarations(context, classEntry, buffer, pos); @@ -218,7 +243,7 @@ private int writeClassConstantObjects(DebugContext context, byte[] buffer, int p String name = uniqueDebugString("JAVA"); log(context, " [0x%08x] name 0x%x (%s)", pos, debugStringIndex(name), name); pos = writeStrSectionOffset(name, buffer, pos); - String compilationDirectory = dwarfSections.getCachePath(); + String compilationDirectory = uniqueDebugString(dwarfSections.getCachePath()); log(context, " [0x%08x] comp_dir 0x%x (%s)", pos, debugStringIndex(compilationDirectory), compilationDirectory); pos = writeStrSectionOffset(compilationDirectory, buffer, pos); @@ -257,10 +282,10 @@ private int writePrimitiveType(DebugContext context, PrimitiveTypeEntry primitiv byte bitCount = (byte) primitiveTypeEntry.getBitCount(); log(context, " [0x%08x] bitCount %d", pos, bitCount); pos = writeAttrData1(bitCount, buffer, pos); - DwarfEncoding encoding = computeEncoding(primitiveTypeEntry.getFlags(), bitCount); + DwarfEncoding encoding = computeEncoding(primitiveTypeEntry); log(context, " [0x%08x] encoding 0x%x", pos, encoding.value()); pos = writeAttrEncoding(encoding, buffer, pos); - String name = primitiveTypeEntry.getTypeName(); + String name = uniqueDebugString(primitiveTypeEntry.getTypeName()); log(context, " [0x%08x] name 0x%x (%s)", pos, debugStringIndex(name), name); pos = writeStrSectionOffset(name, buffer, pos); @@ -284,7 +309,7 @@ private int writeVoidType(DebugContext context, PrimitiveTypeEntry primitiveType AbbrevCode abbrevCode = AbbrevCode.VOID_TYPE; log(context, " [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal()); pos = writeAbbrevCode(abbrevCode, buffer, pos); - String name = primitiveTypeEntry.getTypeName(); + String name = uniqueDebugString(primitiveTypeEntry.getTypeName()); log(context, " [0x%08x] name 0x%x (%s)", pos, debugStringIndex(name), name); pos = writeStrSectionOffset(name, buffer, pos); @@ -303,7 +328,7 @@ private int writeHeaderType(DebugContext context, HeaderTypeEntry headerTypeEntr int lengthPos = pos; pos = writeTUPreamble(context, headerTypeEntry.getTypeSignature(), "", buffer, p); - String name = headerTypeEntry.getTypeName(); + String name = uniqueDebugString(headerTypeEntry.getTypeName()); byte size = (byte) headerTypeEntry.getSize(); log(context, " [0x%08x] header type %s", pos, name); AbbrevCode abbrevCode = AbbrevCode.OBJECT_HEADER; @@ -313,7 +338,7 @@ private int writeHeaderType(DebugContext context, HeaderTypeEntry headerTypeEntr pos = writeStrSectionOffset(name, buffer, pos); log(context, " [0x%08x] byte_size 0x%x", pos, size); pos = writeAttrData1(size, buffer, pos); - pos = writeStructFields(context, headerTypeEntry.fields(), buffer, pos); + pos = writeStructFields(context, headerTypeEntry.getFields(), buffer, pos); /* Write a terminating null attribute. */ pos = writeAttrNull(buffer, pos); @@ -326,7 +351,7 @@ private int writeHeaderType(DebugContext context, HeaderTypeEntry headerTypeEntr return pos; } - private int writeStructFields(DebugContext context, Stream fields, byte[] buffer, int p) { + private int writeStructFields(DebugContext context, List fields, byte[] buffer, int p) { Cursor cursor = new Cursor(p); fields.forEach(fieldEntry -> { cursor.set(writeStructField(context, fieldEntry, buffer, cursor.get())); @@ -336,7 +361,7 @@ private int writeStructFields(DebugContext context, Stream fields, b private int writeStructField(DebugContext context, FieldEntry fieldEntry, byte[] buffer, int p) { int pos = p; - String fieldName = fieldEntry.fieldName(); + String fieldName = uniqueDebugString(fieldEntry.fieldName()); TypeEntry valueType = fieldEntry.getValueType(); long typeSignature = 0; int typeIdx = 0; @@ -355,8 +380,8 @@ private int writeStructField(DebugContext context, FieldEntry fieldEntry, byte[] abbrevCode = AbbrevCode.ARRAY_ELEMENT_FIELD; pos = writeEmbeddedArrayDataType(context, foreignValueType, valueSize, fieldSize / valueSize, buffer, pos); } else { - if (foreignValueType.isPointer()) { - TypeEntry pointerTo = foreignValueType.getPointerTo(); + if (foreignValueType instanceof ForeignPointerTypeEntry pointerTypeEntry) { + TypeEntry pointerTo = pointerTypeEntry.getPointerTo(); assert pointerTo != null : "ADDRESS field pointer type must have a known target type"; // type the array using the referent of the pointer type // @@ -413,15 +438,14 @@ private int writeInstanceClasses(DebugContext context, byte[] buffer, int pos) { private int writeTypeUnits(DebugContext context, StructureTypeEntry typeEntry, byte[] buffer, int p) { int pos = p; - if (typeEntry.isForeign()) { - ForeignTypeEntry foreignTypeEntry = (ForeignTypeEntry) typeEntry; + if (typeEntry instanceof ForeignTypeEntry foreignTypeEntry) { pos = writeForeignLayoutTypeUnit(context, foreignTypeEntry, buffer, pos); pos = writeForeignTypeUnit(context, foreignTypeEntry, buffer, pos); } else { - if (typeEntry.isArray()) { - pos = writeArrayLayoutTypeUnit(context, (ArrayTypeEntry) typeEntry, buffer, pos); - } else if (typeEntry.isInterface()) { - pos = writeInterfaceLayoutTypeUnit(context, (InterfaceClassEntry) typeEntry, buffer, pos); + if (typeEntry instanceof ArrayTypeEntry arrayTypeEntry) { + pos = writeArrayLayoutTypeUnit(context, arrayTypeEntry, buffer, pos); + } else if (typeEntry instanceof InterfaceClassEntry interfaceClassEntry) { + pos = writeInterfaceLayoutTypeUnit(context, interfaceClassEntry, buffer, pos); } else { assert typeEntry instanceof ClassEntry; pos = writeClassLayoutTypeUnit(context, (ClassEntry) typeEntry, buffer, pos); @@ -463,10 +487,10 @@ private int writePointerTypeUnit(DebugContext context, StructureTypeEntry typeEn int pos = p; String loaderId = ""; - if (typeEntry.isArray()) { - loaderId = ((ArrayTypeEntry) typeEntry).getLoaderId(); - } else if (typeEntry.isClass()) { - loaderId = ((ClassEntry) typeEntry).getLoaderId(); + if (typeEntry instanceof ArrayTypeEntry arrayTypeEntry) { + loaderId = arrayTypeEntry.getLoaderId(); + } else if (typeEntry instanceof ClassEntry classEntry) { + loaderId = classEntry.getLoaderId(); } int lengthPos = pos; long typeSignature = typeEntry.getTypeSignature(); @@ -516,10 +540,10 @@ private int writePointerTypeUnitForCompressed(DebugContext context, StructureTyp /* if the class has a loader then embed the children in a namespace */ String loaderId = ""; - if (typeEntry.isArray()) { - loaderId = ((ArrayTypeEntry) typeEntry).getLoaderId(); - } else if (typeEntry.isClass()) { - loaderId = ((ClassEntry) typeEntry).getLoaderId(); + if (typeEntry instanceof ArrayTypeEntry arrayTypeEntry) { + loaderId = arrayTypeEntry.getLoaderId(); + } else if (typeEntry instanceof ClassEntry classEntry) { + loaderId = classEntry.getLoaderId(); } if (!loaderId.isEmpty()) { pos = writeNameSpace(context, loaderId, buffer, pos); @@ -598,7 +622,7 @@ private int writeInterfaceLayoutTypeUnit(DebugContext context, InterfaceClassEnt AbbrevCode abbrevCode = AbbrevCode.INTERFACE_LAYOUT; log(context, " [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal()); pos = writeAbbrevCode(abbrevCode, buffer, pos); - String name = interfaceClassEntry.getTypeName(); + String name = uniqueDebugString(interfaceClassEntry.getTypeName()); log(context, " [0x%08x] name 0x%x (%s)", pos, debugStringIndex(name), name); pos = writeStrSectionOffset(name, buffer, pos); @@ -640,15 +664,12 @@ private int writeClassLayoutTypeUnit(DebugContext context, ClassEntry classEntry } log(context, " [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal()); pos = writeAbbrevCode(abbrevCode, buffer, pos); - String name = classEntry.getTypeName(); + String name = uniqueDebugString(classEntry.getTypeName()); log(context, " [0x%08x] name 0x%x (%s)", pos, debugStringIndex(name), name); pos = writeStrSectionOffset(name, buffer, pos); int size = classEntry.getSize(); log(context, " [0x%08x] byte_size 0x%x", pos, size); pos = writeAttrData2((short) size, buffer, pos); - int fileIdx = classEntry.getFileIdx(); - log(context, " [0x%08x] file 0x%x (%s)", pos, fileIdx, classEntry.getFileName()); - pos = writeAttrData2((short) fileIdx, buffer, pos); if (abbrevCode == AbbrevCode.CLASS_LAYOUT_2) { /* Write a data location expression to mask and/or rebase oop pointers. */ log(context, " [0x%08x] data_location", pos); @@ -710,28 +731,46 @@ private int writeForeignTypeUnit(DebugContext context, ForeignTypeEntry foreignT /* Define a pointer type referring to the base type */ int refTypeIdx = pos; - log(context, " [0x%08x] foreign pointer type", pos); - abbrevCode = AbbrevCode.TYPE_POINTER_SIG; + log(context, " [0x%08x] foreign type wrapper", pos); + abbrevCode = AbbrevCode.FOREIGN_STRUCT; log(context, " [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal()); pos = writeAbbrevCode(abbrevCode, buffer, pos); - int pointerSize = dwarfSections.pointerSize(); - log(context, " [0x%08x] byte_size 0x%x", pos, pointerSize); - pos = writeAttrData1((byte) pointerSize, buffer, pos); - long layoutTypeSignature = foreignTypeEntry.getLayoutTypeSignature(); - log(context, " [0x%08x] type 0x%x", pos, layoutTypeSignature); - pos = writeTypeSignature(layoutTypeSignature, buffer, pos); + String name = uniqueDebugString(foreignTypeEntry.getTypeName()); + log(context, " [0x%08x] name %s", pos, name); + pos = writeStrSectionOffset(name, buffer, pos); + int size = foreignTypeEntry.getSize(); + log(context, " [0x%08x] byte_size 0x%x", pos, size); + pos = writeAttrData1((byte) size, buffer, pos); + + log(context, " [0x%08x] field definition", pos); + abbrevCode = AbbrevCode.FIELD_DECLARATION_1; + log(context, " [0x%08x] <2> Abbrev Number %d", pos, abbrevCode.ordinal()); + pos = writeAbbrevCode(abbrevCode, buffer, pos); + name = uniqueDebugString("rawValue"); + log(context, " [0x%08x] name 0x%x (%s)", pos, debugStringIndex(name), name); + pos = writeStrSectionOffset(name, buffer, pos); + typeSignature = foreignTypeEntry.getLayoutTypeSignature(); + log(context, " [0x%08x] type 0x%x (%s)", pos, typeSignature, foreignTypeEntry.getTypeName()); + pos = writeTypeSignature(typeSignature, buffer, pos); + int memberOffset = 0; + log(context, " [0x%08x] member offset 0x%x", pos, memberOffset); + pos = writeAttrData2((short) memberOffset, buffer, pos); + log(context, " [0x%08x] accessibility public", pos); + pos = writeAttrAccessibility(Modifier.PUBLIC, buffer, pos); + + /* Write a terminating null attribute for the wrapper type. */ + pos = writeAttrNull(buffer, pos); /* Fix up the type offset. */ writeInt(pos - lengthPos, buffer, typeOffsetPos); - /* Define a typedef for the layout type using the Java name. */ - log(context, " [0x%08x] foreign typedef", pos); - abbrevCode = AbbrevCode.FOREIGN_TYPEDEF; + log(context, " [0x%08x] foreign pointer type", pos); + abbrevCode = AbbrevCode.TYPE_POINTER; log(context, " [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal()); pos = writeAbbrevCode(abbrevCode, buffer, pos); - String name = uniqueDebugString(foreignTypeEntry.getTypeName()); - log(context, " [0x%08x] name %s", pos, name); - pos = writeStrSectionOffset(name, buffer, pos); + int pointerSize = dwarfSections.pointerSize(); + log(context, " [0x%08x] byte_size 0x%x", pos, pointerSize); + pos = writeAttrData1((byte) pointerSize, buffer, pos); log(context, " [0x%08x] type 0x%x", pos, refTypeIdx); pos = writeAttrRef4(refTypeIdx, buffer, pos); @@ -748,50 +787,91 @@ private int writeForeignTypeUnit(DebugContext context, ForeignTypeEntry foreignT return pos; } + private int writeDummyTypeUnit(DebugContext context, TypeEntry typeEntry, byte[] buffer, int p) { + int pos = p; + long typeSignature = typeEntry.getTypeSignature(); + + // Write a type unit header + int lengthPos = pos; + pos = writeTUHeader(typeSignature, buffer, pos); + int typeOffsetPos = pos - 4; + assert pos == lengthPos + TU_DIE_HEADER_SIZE; + AbbrevCode abbrevCode = AbbrevCode.TYPE_UNIT; + log(context, " [0x%08x] <0> Abbrev Number %d", pos, abbrevCode.ordinal()); + pos = writeAbbrevCode(abbrevCode, buffer, pos); + log(context, " [0x%08x] language %s", pos, "DW_LANG_Java"); + pos = writeAttrLanguage(DwarfDebugInfo.LANG_ENCODING, buffer, pos); + log(context, " [0x%08x] use_UTF8", pos); + pos = writeFlag(DwarfFlag.DW_FLAG_true, buffer, pos); + + int refTypeIdx = pos; + log(context, " [0x%08x] class layout", pos); + abbrevCode = AbbrevCode.CLASS_LAYOUT_DUMMY; + log(context, " [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal()); + pos = writeAbbrevCode(abbrevCode, buffer, pos); + String name = uniqueDebugString(typeEntry.getTypeName()); + log(context, " [0x%08x] name 0x%x (%s)", pos, debugStringIndex(name), name); + pos = writeStrSectionOffset(name, buffer, pos); + log(context, " [0x%08x] declaration true", pos); + pos = writeFlag(DwarfFlag.DW_FLAG_true, buffer, pos); + log(context, " [0x%08x] byte_size 0x%x", pos, 0); + pos = writeAttrData2((short) 0, buffer, pos); + + /* Fix up the type offset. */ + writeInt(pos - lengthPos, buffer, typeOffsetPos); + + /* Define a pointer type referring to the underlying layout. */ + log(context, " [0x%08x] %s dummy pointer type", pos, typeEntry.isInterface() ? "interface" : "class"); + abbrevCode = AbbrevCode.TYPE_POINTER; + log(context, " [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal()); + pos = writeAbbrevCode(abbrevCode, buffer, pos); + int pointerSize = dwarfSections.referenceSize(); + log(context, " [0x%08x] byte_size 0x%x", pos, pointerSize); + pos = writeAttrData1((byte) pointerSize, buffer, pos); + log(context, " [0x%08x] type 0x%x", pos, refTypeIdx); + pos = writeAttrRef4(refTypeIdx, buffer, pos); + + /* Write a terminating null attribute for the top level TU DIE. */ + pos = writeAttrNull(buffer, pos); + + /* Fix up the TU length. */ + patchLength(lengthPos, buffer, pos); + return pos; + } + private int writeForeignLayoutTypeUnit(DebugContext context, ForeignTypeEntry foreignTypeEntry, byte[] buffer, int p) { int pos = p; String loaderId = foreignTypeEntry.getLoaderId(); int lengthPos = pos; - /* Only write a TU preamble if we will write a new layout type. */ - if (foreignTypeEntry.isWord() || foreignTypeEntry.isIntegral() || foreignTypeEntry.isFloat() || foreignTypeEntry.isStruct()) { - pos = writeTUPreamble(context, foreignTypeEntry.getLayoutTypeSignature(), loaderId, buffer, pos); + if (foreignTypeEntry instanceof ForeignPointerTypeEntry pointerTypeEntry) { + TypeEntry pointerTo = pointerTypeEntry.getPointerTo(); + log(context, " [0x%08x] foreign pointer type %s referent 0x%x (%s)", pos, foreignTypeEntry.getTypeName(), pointerTo.getTypeSignature(), pointerTo.getTypeName()); + // As we do not write anything, we can just return the initial position. + return p; } + /* Only write a TU preamble if we will write a new layout type. */ + pos = writeTUPreamble(context, foreignTypeEntry.getLayoutTypeSignature(), loaderId, buffer, pos); + int size = foreignTypeEntry.getSize(); - if (foreignTypeEntry.isWord()) { - // define the type as a typedef for a signed or unsigned word i.e. we don't have a - // layout type - pos = writeForeignWordLayout(context, foreignTypeEntry, size, foreignTypeEntry.isSigned(), buffer, pos); - } else if (foreignTypeEntry.isIntegral()) { - // use a suitably sized signed or unsigned integral type as the layout type - pos = writeForeignIntegerLayout(context, foreignTypeEntry, size, foreignTypeEntry.isSigned(), buffer, pos); - } else if (foreignTypeEntry.isFloat()) { - // use a suitably sized float type as the layout type - pos = writeForeignFloatLayout(context, foreignTypeEntry, size, buffer, pos); - } else if (foreignTypeEntry.isStruct()) { - // define this type using a structure layout - pos = writeForeignStructLayout(context, foreignTypeEntry, size, buffer, pos); - } else { - // this must be a pointer. if the target type is known use it to declare the pointer - // type, otherwise default to 'void *' - TypeEntry targetType = voidType(); - if (foreignTypeEntry.isPointer()) { - TypeEntry pointerTo = foreignTypeEntry.getPointerTo(); - if (pointerTo != null) { - targetType = pointerTo; - } + switch (foreignTypeEntry) { + case ForeignWordTypeEntry wordTypeEntry -> + // define the type as a typedef for a signed or unsigned word i.e. we don't have a + // layout type + pos = writeForeignWordLayout(context, wordTypeEntry, size, buffer, pos); + case ForeignIntegerTypeEntry integerTypeEntry -> + // use a suitably sized signed or unsigned integer type as the layout type + pos = writeForeignIntegerLayout(context, integerTypeEntry, size, buffer, pos); + case ForeignFloatTypeEntry floatTypeEntry -> + // use a suitably sized float type as the layout type + pos = writeForeignFloatLayout(context, floatTypeEntry, size, buffer, pos); + case ForeignStructTypeEntry structTypeEntry -> + // define this type using a structure layout + pos = writeForeignStructLayout(context, structTypeEntry, size, buffer, pos); + default -> { } - log(context, " [0x%08x] foreign pointer type %s referent 0x%x (%s)", pos, foreignTypeEntry.getTypeName(), targetType.getTypeSignature(), targetType.getTypeName()); - /* - * Setting the layout type to the type we point to reuses an available type unit, so we - * do not have to write are separate type unit. - * - * As we do not write anything, we can just return the initial position. - */ - foreignTypeEntry.setLayoutTypeSignature(targetType.getTypeSignature()); - return p; } /* @@ -823,7 +903,7 @@ private int writeInstanceClassInfo(DebugContext context, ClassEntry classEntry, pos = writeCUHeader(buffer, pos); assert pos == lengthPos + CU_DIE_HEADER_SIZE; AbbrevCode abbrevCode; - if (classEntry.hasCompiledEntries()) { + if (classEntry.hasCompiledMethods()) { if (getLocationListIndex(classEntry) == 0) { abbrevCode = AbbrevCode.CLASS_UNIT_2; } else { @@ -844,15 +924,15 @@ private int writeInstanceClassInfo(DebugContext context, ClassEntry classEntry, } log(context, " [0x%08x] name 0x%x (%s)", pos, debugStringIndex(name), name); pos = writeStrSectionOffset(uniqueDebugString(name), buffer, pos); - String compilationDirectory = dwarfSections.getCachePath(); + String compilationDirectory = uniqueDebugString(dwarfSections.getCachePath()); log(context, " [0x%08x] comp_dir 0x%x (%s)", pos, debugStringIndex(compilationDirectory), compilationDirectory); pos = writeStrSectionOffset(compilationDirectory, buffer, pos); if (abbrevCode == AbbrevCode.CLASS_UNIT_2 || abbrevCode == AbbrevCode.CLASS_UNIT_3) { int codeRangesIndex = getCodeRangesIndex(classEntry); log(context, " [0x%08x] ranges 0x%x", pos, codeRangesIndex); pos = writeRangeListsSectionOffset(codeRangesIndex, buffer, pos); - // write low_pc as well as ranges so that location lists can default the base address - int lo = classEntry.lowpc(); + // write low_pc as well as ranges so that lcoation lists can default the base address + long lo = classEntry.lowpc(); log(context, " [0x%08x] low_pc 0x%x", pos, codeRangesIndex); pos = writeAttrAddress(lo, buffer, pos); int lineIndex = getLineIndex(classEntry); @@ -971,7 +1051,7 @@ private int writeSuperReference(DebugContext context, long typeSignature, String } private int writeFields(DebugContext context, ClassEntry classEntry, byte[] buffer, int p) { - return classEntry.fields().filter(DwarfInfoSectionImpl::isManifestedField).reduce(p, + return classEntry.getFields().stream().filter(DwarfInfoSectionImpl::isManifestedField).reduce(p, (pos, fieldEntry) -> writeField(context, classEntry, fieldEntry, buffer, pos), (oldPos, newPos) -> newPos); } @@ -1003,7 +1083,7 @@ private int writeField(DebugContext context, StructureTypeEntry entry, FieldEntr log(context, " [0x%08x] <2> Abbrev Number %d", pos, abbrevCode.ordinal()); pos = writeAbbrevCode(abbrevCode, buffer, pos); - String name = fieldEntry.fieldName(); + String name = uniqueDebugString(fieldEntry.fieldName()); log(context, " [0x%08x] name 0x%x (%s)", pos, debugStringIndex(name), name); pos = writeStrSectionOffset(name, buffer, pos); /* We may not have a file and line for a field. */ @@ -1053,13 +1133,13 @@ private int writeSkeletonMethodDeclarations(DebugContext context, ClassEntry cla private int writeSkeletonMethodDeclaration(DebugContext context, ClassEntry classEntry, MethodEntry method, byte[] buffer, int p) { int pos = p; - log(context, " [0x%08x] method declaration %s::%s", pos, classEntry.getTypeName(), method.methodName()); + log(context, " [0x%08x] method declaration %s::%s", pos, classEntry.getTypeName(), method.getMethodName()); AbbrevCode abbrevCode = AbbrevCode.METHOD_DECLARATION_SKELETON; log(context, " [0x%08x] <2> Abbrev Number %d", pos, abbrevCode.ordinal()); pos = writeAbbrevCode(abbrevCode, buffer, pos); log(context, " [0x%08x] external true", pos); pos = writeFlag(DwarfFlag.DW_FLAG_true, buffer, pos); - String name = uniqueDebugString(method.methodName()); + String name = uniqueDebugString(method.getMethodName()); log(context, " [0x%08x] name 0x%x (%s)", pos, debugStringIndex(name), name); pos = writeStrSectionOffset(name, buffer, pos); String linkageName = uniqueDebugString(method.getSymbolName()); @@ -1087,22 +1167,21 @@ private int writeSkeletonMethodDeclaration(DebugContext context, ClassEntry clas private int writeSkeletonMethodParameterDeclarations(DebugContext context, MethodEntry method, byte[] buffer, int p) { int pos = p; if (!Modifier.isStatic(method.getModifiers())) { - DebugLocalInfo paramInfo = method.getThisParam(); + LocalEntry paramInfo = method.getThisParam(); pos = writeSkeletonMethodParameterDeclaration(context, paramInfo, true, buffer, pos); } - for (int i = 0; i < method.getParamCount(); i++) { - DebugLocalInfo paramInfo = method.getParam(i); + for (LocalEntry paramInfo : method.getParams()) { pos = writeSkeletonMethodParameterDeclaration(context, paramInfo, false, buffer, pos); } return pos; } - private int writeSkeletonMethodParameterDeclaration(DebugContext context, DebugLocalInfo paramInfo, boolean artificial, byte[] buffer, + private int writeSkeletonMethodParameterDeclaration(DebugContext context, LocalEntry paramInfo, boolean artificial, byte[] buffer, int p) { int pos = p; log(context, " [0x%08x] method parameter declaration", pos); AbbrevCode abbrevCode; - TypeEntry paramType = lookupType(paramInfo.valueType()); + TypeEntry paramType = paramInfo.type(); if (artificial) { abbrevCode = AbbrevCode.METHOD_PARAMETER_DECLARATION_4; } else { @@ -1125,29 +1204,38 @@ private int writeMethodDeclarations(DebugContext context, ClassEntry classEntry, for (MethodEntry method : classEntry.getMethods()) { if (method.isInRange() || method.isInlined()) { /* - * Declare all methods whether or not they have been compiled or inlined. + * Declare all methods whether they have been compiled or inlined. */ - pos = writeMethodDeclaration(context, classEntry, method, buffer, pos); + pos = writeMethodDeclaration(context, classEntry, method, false, buffer, pos); } } return pos; } - private int writeMethodDeclaration(DebugContext context, ClassEntry classEntry, MethodEntry method, byte[] buffer, int p) { + private int writeMethodDeclaration(DebugContext context, ClassEntry classEntry, MethodEntry method, boolean isInlined, byte[] buffer, int p) { int pos = p; String methodKey = method.getSymbolName(); String linkageName = uniqueDebugString(methodKey); setMethodDeclarationIndex(method, pos); int modifiers = method.getModifiers(); boolean isStatic = Modifier.isStatic(modifiers); - log(context, " [0x%08x] method declaration %s::%s", pos, classEntry.getTypeName(), method.methodName()); - AbbrevCode abbrevCode = (isStatic ? AbbrevCode.METHOD_DECLARATION_STATIC : AbbrevCode.METHOD_DECLARATION); + log(context, " [0x%08x] method declaration %s::%s", pos, classEntry.getTypeName(), method.getMethodName()); + AbbrevCode abbrevCode; + if (isInlined) { + abbrevCode = (isStatic ? AbbrevCode.METHOD_DECLARATION_INLINE_STATIC : AbbrevCode.METHOD_DECLARATION_INLINE); + } else { + abbrevCode = (isStatic ? AbbrevCode.METHOD_DECLARATION_STATIC : AbbrevCode.METHOD_DECLARATION); + } log(context, " [0x%08x] <2> Abbrev Number %d", pos, abbrevCode.ordinal()); pos = writeAbbrevCode(abbrevCode, buffer, pos); + if (isInlined) { + log(context, " [0x%08x] inline 0x%x", pos, DwarfInline.DW_INL_inlined.value()); + pos = writeAttrInline(DwarfInline.DW_INL_inlined, buffer, pos); + } log(context, " [0x%08x] external true", pos); pos = writeFlag(DwarfFlag.DW_FLAG_true, buffer, pos); - String name = uniqueDebugString(method.methodName()); + String name = uniqueDebugString(method.getMethodName()); log(context, " [0x%08x] name 0x%x (%s)", pos, debugStringIndex(name), name); pos = writeStrSectionOffset(name, buffer, pos); FileEntry fileEntry = method.getFileEntry(); @@ -1176,7 +1264,7 @@ private int writeMethodDeclaration(DebugContext context, ClassEntry classEntry, long typeSignature = classEntry.getLayoutTypeSignature(); log(context, " [0x%08x] containing_type 0x%x (%s)", pos, typeSignature, classEntry.getTypeName()); pos = writeTypeSignature(typeSignature, buffer, pos); - if (abbrevCode == AbbrevCode.METHOD_DECLARATION) { + if (abbrevCode == AbbrevCode.METHOD_DECLARATION | abbrevCode == AbbrevCode.METHOD_DECLARATION_INLINE) { /* Record the current position so we can back patch the object pointer. */ int objectPointerIndex = pos; /* @@ -1201,30 +1289,26 @@ private int writeMethodDeclaration(DebugContext context, ClassEntry classEntry, private int writeMethodParameterDeclarations(DebugContext context, ClassEntry classEntry, MethodEntry method, int fileIdx, int level, byte[] buffer, int p) { int pos = p; - int refAddr; if (!Modifier.isStatic(method.getModifiers())) { - refAddr = pos; - DebugLocalInfo paramInfo = method.getThisParam(); - setMethodLocalIndex(classEntry, method, paramInfo, refAddr); + LocalEntry paramInfo = method.getThisParam(); + setMethodLocalIndex(classEntry, method, paramInfo, pos); pos = writeMethodParameterDeclaration(context, paramInfo, fileIdx, true, level, buffer, pos); } - for (int i = 0; i < method.getParamCount(); i++) { - refAddr = pos; - DebugLocalInfo paramInfo = method.getParam(i); - setMethodLocalIndex(classEntry, method, paramInfo, refAddr); + for (LocalEntry paramInfo : method.getParams()) { + setMethodLocalIndex(classEntry, method, paramInfo, pos); pos = writeMethodParameterDeclaration(context, paramInfo, fileIdx, false, level, buffer, pos); } return pos; } - private int writeMethodParameterDeclaration(DebugContext context, DebugLocalInfo paramInfo, int fileIdx, boolean artificial, int level, byte[] buffer, + private int writeMethodParameterDeclaration(DebugContext context, LocalEntry paramInfo, int fileIdx, boolean artificial, int level, byte[] buffer, int p) { int pos = p; log(context, " [0x%08x] method parameter declaration", pos); AbbrevCode abbrevCode; String paramName = paramInfo.name(); - TypeEntry paramType = lookupType(paramInfo.valueType()); - int line = paramInfo.line(); + TypeEntry paramType = paramInfo.type(); + int line = paramInfo.getLine(); if (artificial) { abbrevCode = AbbrevCode.METHOD_PARAMETER_DECLARATION_1; } else if (line >= 0) { @@ -1256,24 +1340,21 @@ private int writeMethodParameterDeclaration(DebugContext context, DebugLocalInfo private int writeMethodLocalDeclarations(DebugContext context, ClassEntry classEntry, MethodEntry method, int fileIdx, int level, byte[] buffer, int p) { int pos = p; - int refAddr; - for (int i = 0; i < method.getLocalCount(); i++) { - refAddr = pos; - DebugLocalInfo localInfo = method.getLocal(i); - setMethodLocalIndex(classEntry, method, localInfo, refAddr); - pos = writeMethodLocalDeclaration(context, localInfo, fileIdx, level, buffer, pos); + for (LocalEntry local : method.getLocals()) { + setMethodLocalIndex(classEntry, method, local, pos); + pos = writeMethodLocalDeclaration(context, local, fileIdx, level, buffer, pos); } return pos; } - private int writeMethodLocalDeclaration(DebugContext context, DebugLocalInfo paramInfo, int fileIdx, int level, byte[] buffer, + private int writeMethodLocalDeclaration(DebugContext context, LocalEntry paramInfo, int fileIdx, int level, byte[] buffer, int p) { int pos = p; log(context, " [0x%08x] method local declaration", pos); AbbrevCode abbrevCode; - String paramName = paramInfo.name(); - TypeEntry paramType = lookupType(paramInfo.valueType()); - int line = paramInfo.line(); + String paramName = uniqueDebugString(paramInfo.name()); + TypeEntry paramType = paramInfo.type(); + int line = paramInfo.getLine(); if (line >= 0) { abbrevCode = AbbrevCode.METHOD_LOCAL_DECLARATION_1; } else { @@ -1282,7 +1363,7 @@ private int writeMethodLocalDeclaration(DebugContext context, DebugLocalInfo par log(context, " [0x%08x] <%d> Abbrev Number %d", pos, level, abbrevCode.ordinal()); pos = writeAbbrevCode(abbrevCode, buffer, pos); log(context, " [0x%08x] name %s", pos, paramName); - pos = writeStrSectionOffset(uniqueDebugString(paramName), buffer, pos); + pos = writeStrSectionOffset(paramName, buffer, pos); if (abbrevCode == AbbrevCode.METHOD_LOCAL_DECLARATION_1) { log(context, " [0x%08x] file 0x%x", pos, fileIdx); pos = writeAttrData2((short) fileIdx, buffer, pos); @@ -1298,7 +1379,7 @@ private int writeMethodLocalDeclaration(DebugContext context, DebugLocalInfo par } private int writeInterfaceImplementors(DebugContext context, InterfaceClassEntry interfaceClassEntry, byte[] buffer, int p) { - return interfaceClassEntry.implementors().reduce(p, + return interfaceClassEntry.getImplementors().stream().reduce(p, (pos, classEntry) -> writeInterfaceImplementor(context, classEntry, buffer, pos), (oldPos, newPos) -> newPos); } @@ -1321,15 +1402,15 @@ private int writeInterfaceImplementor(DebugContext context, ClassEntry classEntr return pos; } - private int writeForeignStructLayout(DebugContext context, ForeignTypeEntry foreignTypeEntry, int size, byte[] buffer, int p) { + private int writeForeignStructLayout(DebugContext context, ForeignStructTypeEntry foreignStructTypeEntry, int size, byte[] buffer, int p) { int pos = p; - log(context, " [0x%08x] foreign struct type for %s", pos, foreignTypeEntry.getTypeName()); + log(context, " [0x%08x] foreign struct type for %s", pos, foreignStructTypeEntry.getTypeName()); AbbrevCode abbrevCode = AbbrevCode.FOREIGN_STRUCT; log(context, " [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal()); pos = writeAbbrevCode(abbrevCode, buffer, pos); - String typedefName = foreignTypeEntry.getTypedefName(); + String typedefName = foreignStructTypeEntry.getTypedefName(); if (typedefName == null) { - typedefName = "_" + foreignTypeEntry.getTypeName(); + typedefName = "_" + foreignStructTypeEntry.getTypeName(); verboseLog(context, " [0x%08x] using synthetic typedef name %s", pos, typedefName); } if (typedefName.startsWith("struct ")) { @@ -1343,21 +1424,21 @@ private int writeForeignStructLayout(DebugContext context, ForeignTypeEntry fore log(context, " [0x%08x] byte_size 0x%x", pos, size); pos = writeAttrData1((byte) size, buffer, pos); // if we have a parent write a super attribute - ForeignTypeEntry parent = foreignTypeEntry.getParent(); + ForeignStructTypeEntry parent = foreignStructTypeEntry.getParent(); if (parent != null) { long typeSignature = parent.getLayoutTypeSignature(); pos = writeSuperReference(context, typeSignature, parent.getTypedefName(), buffer, pos); } - pos = writeStructFields(context, foreignTypeEntry.fields(), buffer, pos); + pos = writeStructFields(context, foreignStructTypeEntry.getFields(), buffer, pos); /* * Write a terminating null attribute. */ return writeAttrNull(buffer, pos); } - private int writeForeignWordLayout(DebugContext context, ForeignTypeEntry foreignTypeEntry, int size, boolean isSigned, byte[] buffer, int p) { + private int writeForeignWordLayout(DebugContext context, ForeignWordTypeEntry foreignWordTypeEntry, int size, byte[] buffer, int p) { int pos = p; - log(context, " [0x%08x] foreign primitive word type for %s", pos, foreignTypeEntry.getTypeName()); + log(context, " [0x%08x] foreign primitive word type for %s", pos, foreignWordTypeEntry.getTypeName()); /* Record the location of this type entry. */ AbbrevCode abbrevCode = AbbrevCode.PRIMITIVE_TYPE; log(context, " [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal()); @@ -1370,17 +1451,18 @@ private int writeForeignWordLayout(DebugContext context, ForeignTypeEntry foreig log(context, " [0x%08x] bitCount %d", pos, bitCount); pos = writeAttrData1(bitCount, buffer, pos); // treat the layout as a signed or unsigned word of the relevant size - DwarfEncoding encoding = (isSigned ? DwarfEncoding.DW_ATE_signed : DwarfEncoding.DW_ATE_unsigned); + DwarfEncoding encoding = (foreignWordTypeEntry.isSigned() ? DwarfEncoding.DW_ATE_signed : DwarfEncoding.DW_ATE_unsigned); log(context, " [0x%08x] encoding 0x%x", pos, encoding.value()); pos = writeAttrEncoding(encoding, buffer, pos); - String name = uniqueDebugString(integralTypeName(byteSize, isSigned)); + String name = uniqueDebugString(integralTypeName(byteSize, foreignWordTypeEntry.isSigned())); log(context, " [0x%08x] name 0x%x (%s)", pos, debugStringIndex(name), name); return writeStrSectionOffset(name, buffer, pos); } - private int writeForeignIntegerLayout(DebugContext context, ForeignTypeEntry foreignTypeEntry, int size, boolean isSigned, byte[] buffer, int p) { + private int writeForeignIntegerLayout(DebugContext context, ForeignIntegerTypeEntry foreignIntegerTypeEntry, int size, byte[] buffer, int p) { + assert false; int pos = p; - log(context, " [0x%08x] foreign primitive integral type for %s", pos, foreignTypeEntry.getTypeName()); + log(context, " [0x%08x] foreign primitive integral type for %s", pos, foreignIntegerTypeEntry.getTypeName()); /* Record the location of this type entry. */ AbbrevCode abbrevCode = AbbrevCode.PRIMITIVE_TYPE; log(context, " [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal()); @@ -1393,17 +1475,18 @@ private int writeForeignIntegerLayout(DebugContext context, ForeignTypeEntry for log(context, " [0x%08x] bitCount %d", pos, bitCount); pos = writeAttrData1(bitCount, buffer, pos); // treat the layout as a signed or unsigned word of the relevant size - DwarfEncoding encoding = (isSigned ? DwarfEncoding.DW_ATE_signed : DwarfEncoding.DW_ATE_unsigned); + DwarfEncoding encoding = (foreignIntegerTypeEntry.isSigned() ? DwarfEncoding.DW_ATE_signed : DwarfEncoding.DW_ATE_unsigned); log(context, " [0x%08x] encoding 0x%x", pos, encoding.value()); pos = writeAttrEncoding(encoding, buffer, pos); - String name = uniqueDebugString(integralTypeName(byteSize, isSigned)); + String name = uniqueDebugString(integralTypeName(byteSize, foreignIntegerTypeEntry.isSigned())); log(context, " [0x%08x] name 0x%x (%s)", pos, debugStringIndex(name), name); return writeStrSectionOffset(name, buffer, pos); } - private int writeForeignFloatLayout(DebugContext context, ForeignTypeEntry foreignTypeEntry, int size, byte[] buffer, int p) { + private int writeForeignFloatLayout(DebugContext context, ForeignFloatTypeEntry foreignFloatTypeEntry, int size, byte[] buffer, int p) { + assert false; int pos = p; - log(context, " [0x%08x] foreign primitive float type for %s", pos, foreignTypeEntry.getTypeName()); + log(context, " [0x%08x] foreign primitive float type for %s", pos, foreignFloatTypeEntry.getTypeName()); /* Record the location of this type entry. */ AbbrevCode abbrevCode = AbbrevCode.PRIMITIVE_TYPE; log(context, " [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal()); @@ -1442,7 +1525,7 @@ private int writeStaticFieldLocations(DebugContext context, ClassEntry classEntr * offset indicates that the field has been folded into code as an unmaterialized constant. */ Cursor cursor = new Cursor(p); - classEntry.fields().filter(DwarfInfoSectionImpl::isManifestedStaticField) + classEntry.getFields().stream().filter(DwarfInfoSectionImpl::isManifestedStaticField) .forEach(fieldEntry -> { cursor.set(writeClassStaticFieldLocation(context, classEntry, fieldEntry, buffer, cursor.get())); }); @@ -1455,7 +1538,7 @@ private int writeStaticFieldDeclarations(DebugContext context, ClassEntry classE * offset indicates that the field has been folded into code as an unmaterialized constant. */ Cursor cursor = new Cursor(p); - classEntry.fields().filter(DwarfInfoSectionImpl::isManifestedStaticField) + classEntry.getFields().stream().filter(DwarfInfoSectionImpl::isManifestedStaticField) .forEach(fieldEntry -> { cursor.set(writeClassStaticFieldDeclaration(context, classEntry, fieldEntry, buffer, cursor.get())); }); @@ -1483,7 +1566,7 @@ private int writeClassStaticFieldDeclaration(DebugContext context, ClassEntry cl log(context, " [0x%08x] <2> Abbrev Number %d", pos, abbrevCode.ordinal()); pos = writeAbbrevCode(abbrevCode, buffer, pos); - String name = fieldEntry.fieldName(); + String name = uniqueDebugString(fieldEntry.fieldName()); log(context, " [0x%08x] name 0x%x (%s)", pos, debugStringIndex(name), name); pos = writeStrSectionOffset(name, buffer, pos); /* We may not have a file and line for a field. */ @@ -1551,7 +1634,7 @@ private int writeArrayLayoutTypeUnit(DebugContext context, ArrayTypeEntry arrayT AbbrevCode abbrevCode = AbbrevCode.ARRAY_LAYOUT; log(context, " [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal()); pos = writeAbbrevCode(abbrevCode, buffer, pos); - String name = arrayTypeEntry.getTypeName(); + String name = uniqueDebugString(arrayTypeEntry.getTypeName()); log(context, " [0x%08x] name 0x%x (%s)", pos, debugStringIndex(name), name); pos = writeStrSectionOffset(name, buffer, pos); log(context, " [0x%08x] byte_size 0x%x", pos, size); @@ -1606,7 +1689,7 @@ private int writeArray(DebugContext context, ArrayTypeEntry arrayTypeEntry, byte String name = uniqueDebugString("JAVA"); log(context, " [0x%08x] name 0x%x (%s)", pos, debugStringIndex(name), name); pos = writeStrSectionOffset(name, buffer, pos); - String compilationDirectory = dwarfSections.getCachePath(); + String compilationDirectory = uniqueDebugString(dwarfSections.getCachePath()); log(context, " [0x%08x] comp_dir 0x%x (%s)", pos, debugStringIndex(compilationDirectory), compilationDirectory); pos = writeStrSectionOffset(compilationDirectory, buffer, pos); @@ -1641,7 +1724,7 @@ private int writeSkeletonArrayLayout(DebugContext context, ArrayTypeEntry arrayT AbbrevCode abbrevCode = AbbrevCode.CLASS_LAYOUT_3; log(context, " [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal()); pos = writeAbbrevCode(abbrevCode, buffer, pos); - String name = arrayTypeEntry.getTypeName(); + String name = uniqueDebugString(arrayTypeEntry.getTypeName()); log(context, " [0x%08x] name 0x%x (%s)", pos, debugStringIndex(name), name); pos = writeStrSectionOffset(name, buffer, pos); log(context, " [0x%08x] declaration true", pos); @@ -1657,7 +1740,7 @@ private int writeSkeletonArrayLayout(DebugContext context, ArrayTypeEntry arrayT private int writeFields(DebugContext context, ArrayTypeEntry arrayTypeEntry, byte[] buffer, int p) { Cursor cursor = new Cursor(p); - arrayTypeEntry.fields().filter(DwarfInfoSectionImpl::isManifestedField) + arrayTypeEntry.getFields().stream().filter(DwarfInfoSectionImpl::isManifestedField) .forEach(fieldEntry -> { cursor.set(writeField(context, arrayTypeEntry, fieldEntry, buffer, cursor.get())); }); @@ -1691,8 +1774,8 @@ private int writeEmbeddedArrayDataType(DebugContext context, ForeignTypeEntry fo pos = writeAttrData4(size, buffer, pos); String elementTypeName = foreignValueType.getTypeName(); long elementTypeSignature; - if (foreignValueType.isPointer()) { - TypeEntry pointerTo = foreignValueType.getPointerTo(); + if (foreignValueType instanceof ForeignPointerTypeEntry pointerTypeEntry) { + TypeEntry pointerTo = pointerTypeEntry.getPointerTo(); assert pointerTo != null : "ADDRESS field pointer type must have a known target type"; // type the array using the referent of the pointer type // @@ -1744,7 +1827,7 @@ private int writeArrayElementField(DebugContext context, int offset, int arrayDa private int writeMethodLocations(DebugContext context, ClassEntry classEntry, byte[] buffer, int p) { Cursor cursor = new Cursor(p); - classEntry.compiledEntries().forEach(compiledMethodEntry -> { + classEntry.compiledMethods().forEach(compiledMethodEntry -> { cursor.set(writeMethodLocation(context, classEntry, compiledMethodEntry, buffer, cursor.get())); }); return cursor.get(); @@ -1752,7 +1835,7 @@ private int writeMethodLocations(DebugContext context, ClassEntry classEntry, by private int writeMethodLocation(DebugContext context, ClassEntry classEntry, CompiledMethodEntry compiledEntry, byte[] buffer, int p) { int pos = p; - Range primary = compiledEntry.getPrimary(); + Range primary = compiledEntry.primary(); log(context, " [0x%08x] method location", pos); AbbrevCode abbrevCode = AbbrevCode.METHOD_LOCATION; log(context, " [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal()); @@ -1770,9 +1853,8 @@ private int writeMethodLocation(DebugContext context, ClassEntry classEntry, Com int methodSpecOffset = getMethodDeclarationIndex(primary.getMethodEntry()); log(context, " [0x%08x] specification 0x%x (%s)", pos, methodSpecOffset, methodKey); pos = writeInfoSectionOffset(methodSpecOffset, buffer, pos); - HashMap> varRangeMap = primary.getVarRangeMap(); - pos = writeMethodParameterLocations(context, classEntry, varRangeMap, primary, 2, buffer, pos); - pos = writeMethodLocalLocations(context, classEntry, varRangeMap, primary, 2, buffer, pos); + pos = writeMethodParameterLocations(context, classEntry, primary, 2, buffer, pos); + pos = writeMethodLocalLocations(context, classEntry, primary, 2, buffer, pos); if (primary.includesInlineRanges()) { /* * the method has inlined ranges so write concrete inlined method entries as its @@ -1786,80 +1868,54 @@ private int writeMethodLocation(DebugContext context, ClassEntry classEntry, Com return writeAttrNull(buffer, pos); } - private int writeMethodParameterLocations(DebugContext context, ClassEntry classEntry, HashMap> varRangeMap, Range range, int depth, byte[] buffer, int p) { + private int writeMethodParameterLocations(DebugContext context, ClassEntry classEntry, Range range, int depth, byte[] buffer, int p) { int pos = p; MethodEntry methodEntry; if (range.isPrimary()) { methodEntry = range.getMethodEntry(); } else { assert !range.isLeaf() : "should only be looking up var ranges for inlined calls"; - methodEntry = range.getFirstCallee().getMethodEntry(); + methodEntry = range.getCallees().getFirst().getMethodEntry(); } if (!Modifier.isStatic(methodEntry.getModifiers())) { - DebugLocalInfo thisParamInfo = methodEntry.getThisParam(); + LocalEntry thisParamInfo = methodEntry.getThisParam(); int refAddr = getMethodLocalIndex(classEntry, methodEntry, thisParamInfo); - List ranges = varRangeMap.get(thisParamInfo); - pos = writeMethodLocalLocation(context, range, thisParamInfo, refAddr, ranges, depth, true, buffer, pos); + pos = writeMethodLocalLocation(context, range, thisParamInfo, refAddr, depth, true, buffer, pos); } - for (int i = 0; i < methodEntry.getParamCount(); i++) { - DebugLocalInfo paramInfo = methodEntry.getParam(i); + for (LocalEntry paramInfo : methodEntry.getParams()) { int refAddr = getMethodLocalIndex(classEntry, methodEntry, paramInfo); - List ranges = varRangeMap.get(paramInfo); - pos = writeMethodLocalLocation(context, range, paramInfo, refAddr, ranges, depth, true, buffer, pos); + pos = writeMethodLocalLocation(context, range, paramInfo, refAddr, depth, true, buffer, pos); } return pos; } - private int writeMethodLocalLocations(DebugContext context, ClassEntry classEntry, HashMap> varRangeMap, Range range, int depth, byte[] buffer, int p) { + private int writeMethodLocalLocations(DebugContext context, ClassEntry classEntry, Range range, int depth, byte[] buffer, int p) { int pos = p; MethodEntry methodEntry; if (range.isPrimary()) { methodEntry = range.getMethodEntry(); } else { assert !range.isLeaf() : "should only be looking up var ranges for inlined calls"; - methodEntry = range.getFirstCallee().getMethodEntry(); + methodEntry = range.getCallees().getFirst().getMethodEntry(); } - int count = methodEntry.getLocalCount(); - for (int i = 0; i < count; i++) { - DebugLocalInfo localInfo = methodEntry.getLocal(i); - int refAddr = getMethodLocalIndex(classEntry, methodEntry, localInfo); - List ranges = varRangeMap.get(localInfo); - pos = writeMethodLocalLocation(context, range, localInfo, refAddr, ranges, depth, false, buffer, pos); + + for (LocalEntry local : methodEntry.getLocals()) { + int refAddr = getMethodLocalIndex(classEntry, methodEntry, local); + pos = writeMethodLocalLocation(context, range, local, refAddr, depth, false, buffer, pos); } return pos; } - private int writeMethodLocalLocation(DebugContext context, Range range, DebugLocalInfo localInfo, int refAddr, List ranges, int depth, boolean isParam, byte[] buffer, + private int writeMethodLocalLocation(DebugContext context, Range range, LocalEntry localInfo, int refAddr, int depth, boolean isParam, byte[] buffer, int p) { int pos = p; - log(context, " [0x%08x] method %s location %s:%s", pos, (isParam ? "parameter" : "local"), localInfo.name(), localInfo.typeName()); - List localValues = new ArrayList<>(); - for (SubRange subrange : ranges) { - DebugLocalValueInfo value = subrange.lookupValue(localInfo); - if (value != null) { - log(context, " [0x%08x] local %s:%s [0x%x, 0x%x] = %s", pos, value.name(), value.typeName(), subrange.getLo(), subrange.getHi(), formatValue(value)); - switch (value.localKind()) { - case REGISTER: - case STACKSLOT: - localValues.add(value); - break; - case CONSTANT: - JavaConstant constant = value.constantValue(); - // can only handle primitive or null constants just now - if (constant instanceof PrimitiveConstant || constant.getJavaKind() == JavaKind.Object) { - localValues.add(value); - } - break; - default: - break; - } - } - } + log(context, " [0x%08x] method %s location %s:%s", pos, (isParam ? "parameter" : "local"), localInfo.name(), localInfo.type().getTypeName()); + AbbrevCode abbrevCode; - if (localValues.isEmpty()) { - abbrevCode = (isParam ? AbbrevCode.METHOD_PARAMETER_LOCATION_1 : AbbrevCode.METHOD_LOCAL_LOCATION_1); - } else { + if (range.hasLocalValues(localInfo)) { abbrevCode = (isParam ? AbbrevCode.METHOD_PARAMETER_LOCATION_2 : AbbrevCode.METHOD_LOCAL_LOCATION_2); + } else { + abbrevCode = (isParam ? AbbrevCode.METHOD_PARAMETER_LOCATION_1 : AbbrevCode.METHOD_LOCAL_LOCATION_1); } log(context, " [0x%08x] <%d> Abbrev Number %d", pos, depth, abbrevCode.ordinal()); pos = writeAbbrevCode(abbrevCode, buffer, pos); @@ -1878,20 +1934,14 @@ private int writeMethodLocalLocation(DebugContext context, Range range, DebugLoc * Go through the subranges and generate concrete debug entries for inlined methods. */ private int generateConcreteInlinedMethods(DebugContext context, ClassEntry classEntry, CompiledMethodEntry compiledEntry, byte[] buffer, int p) { - Range primary = compiledEntry.getPrimary(); + Range primary = compiledEntry.primary(); if (primary.isLeaf()) { return p; } int pos = p; log(context, " [0x%08x] concrete entries [0x%x,0x%x] %s", pos, primary.getLo(), primary.getHi(), primary.getFullMethodName()); int depth = 0; - Iterator iterator = compiledEntry.topDownRangeIterator(); - while (iterator.hasNext()) { - SubRange subrange = iterator.next(); - if (subrange.isLeaf()) { - // we only generate concrete methods for non-leaf entries - continue; - } + for (Range subrange : compiledEntry.callRangeStream().toList()) { // if we just stepped out of a child range write nulls for each step up while (depth > subrange.getDepth()) { pos = writeAttrNull(buffer, pos); @@ -1899,11 +1949,10 @@ private int generateConcreteInlinedMethods(DebugContext context, ClassEntry clas } depth = subrange.getDepth(); pos = writeInlineSubroutine(context, classEntry, subrange, depth + 2, buffer, pos); - HashMap> varRangeMap = subrange.getVarRangeMap(); // increment depth to account for parameter and method locations depth++; - pos = writeMethodParameterLocations(context, classEntry, varRangeMap, subrange, depth + 2, buffer, pos); - pos = writeMethodLocalLocations(context, classEntry, varRangeMap, subrange, depth + 2, buffer, pos); + pos = writeMethodParameterLocations(context, classEntry, subrange, depth + 2, buffer, pos); + pos = writeMethodLocalLocations(context, classEntry, subrange, depth + 2, buffer, pos); } // if we just stepped out of a child range write nulls for each step up while (depth > 0) { @@ -1913,17 +1962,22 @@ private int generateConcreteInlinedMethods(DebugContext context, ClassEntry clas return pos; } - private int writeInlineSubroutine(DebugContext context, ClassEntry classEntry, SubRange caller, int depth, byte[] buffer, int p) { + private int writeInlineSubroutine(DebugContext context, ClassEntry classEntry, Range caller, int depth, byte[] buffer, int p) { assert !caller.isLeaf(); // the supplied range covers an inline call and references the caller method entry. its // child ranges all reference the same inlined called method. leaf children cover code for // that inlined method. non-leaf children cover code for recursively inlined methods. // identify the inlined method by looking at the first callee - Range callee = caller.getFirstCallee(); + Range callee = caller.getCallees().getFirst(); MethodEntry methodEntry = callee.getMethodEntry(); String methodKey = methodEntry.getSymbolName(); /* the abstract index was written in the method's class entry */ - int abstractOriginIndex = (classEntry == methodEntry.ownerType() ? getMethodDeclarationIndex(methodEntry) : getAbstractInlineMethodIndex(classEntry, methodEntry)); + int abstractOriginIndex; + if (classEntry == methodEntry.getOwnerType() && !dwarfSections.isRuntimeCompilation()) { + abstractOriginIndex = getMethodDeclarationIndex(methodEntry); + } else { + abstractOriginIndex = getAbstractInlineMethodIndex(classEntry, methodEntry); + } int pos = p; log(context, " [0x%08x] concrete inline subroutine [0x%x, 0x%x] %s", pos, caller.getLo(), caller.getHi(), methodKey); @@ -1945,7 +1999,7 @@ private int writeInlineSubroutine(DebugContext context, ClassEntry classEntry, S fileIndex = classEntry.getFileIdx(); } } - final AbbrevCode abbrevCode = AbbrevCode.INLINED_SUBROUTINE_WITH_CHILDREN; + final AbbrevCode abbrevCode = AbbrevCode.INLINED_SUBROUTINE; log(context, " [0x%08x] <%d> Abbrev Number %d", pos, depth, abbrevCode.ordinal()); pos = writeAbbrevCode(abbrevCode, buffer, pos); log(context, " [0x%08x] abstract_origin 0x%x", pos, abstractOriginIndex); @@ -1962,41 +2016,39 @@ private int writeInlineSubroutine(DebugContext context, ClassEntry classEntry, S } private int writeAbstractInlineMethods(DebugContext context, ClassEntry classEntry, byte[] buffer, int p) { - EconomicSet inlinedMethods = collectInlinedMethods(context, classEntry, p); + HashSet inlinedMethods = collectInlinedMethods(context, classEntry, p); int pos = p; for (MethodEntry methodEntry : inlinedMethods) { // n.b. class entry used to index the method belongs to the inlining method // not the inlined method setAbstractInlineMethodIndex(classEntry, methodEntry, pos); - pos = writeAbstractInlineMethod(context, classEntry, methodEntry, buffer, pos); + if (dwarfSections.isRuntimeCompilation() && classEntry != methodEntry.getOwnerType()) { + pos = writeMethodDeclaration(context, classEntry, methodEntry, true, buffer, pos); + } else { + pos = writeAbstractInlineMethod(context, classEntry, methodEntry, buffer, pos); + } } return pos; } - private EconomicSet collectInlinedMethods(DebugContext context, ClassEntry classEntry, int p) { - final EconomicSet methods = EconomicSet.create(); - classEntry.compiledEntries().forEach(compiledEntry -> addInlinedMethods(context, compiledEntry, compiledEntry.getPrimary(), methods, p)); + private HashSet collectInlinedMethods(DebugContext context, ClassEntry classEntry, int p) { + final HashSet methods = new HashSet<>(); + classEntry.compiledMethods().forEach(compiledMethod -> addInlinedMethods(context, compiledMethod, compiledMethod.primary(), methods, p)); return methods; } - private void addInlinedMethods(DebugContext context, CompiledMethodEntry compiledEntry, Range primary, EconomicSet hashSet, int p) { + private void addInlinedMethods(DebugContext context, CompiledMethodEntry compiledEntry, Range primary, HashSet hashSet, int p) { if (primary.isLeaf()) { return; } verboseLog(context, " [0x%08x] collect abstract inlined methods %s", p, primary.getFullMethodName()); - Iterator iterator = compiledEntry.topDownRangeIterator(); - while (iterator.hasNext()) { - SubRange subrange = iterator.next(); - if (subrange.isLeaf()) { - // we only generate abstract inline methods for non-leaf entries - continue; - } + for (Range subrange : compiledEntry.callRangeStream().toList()) { // the subrange covers an inline call and references the caller method entry. its // child ranges all reference the same inlined called method. leaf children cover code // for // that inlined method. non-leaf children cover code for recursively inlined methods. // identify the inlined method by looking at the first callee - Range callee = subrange.getFirstCallee(); + Range callee = subrange.getCallees().getFirst(); MethodEntry methodEntry = callee.getMethodEntry(); if (hashSet.add(methodEntry)) { verboseLog(context, " [0x%08x] add abstract inlined method %s", p, methodEntry.getSymbolName()); @@ -2006,7 +2058,7 @@ private void addInlinedMethods(DebugContext context, CompiledMethodEntry compile private int writeAbstractInlineMethod(DebugContext context, ClassEntry classEntry, MethodEntry method, byte[] buffer, int p) { int pos = p; - log(context, " [0x%08x] abstract inline method %s::%s", pos, classEntry.getTypeName(), method.methodName()); + log(context, " [0x%08x] abstract inline method %s::%s", pos, classEntry.getTypeName(), method.getMethodName()); AbbrevCode abbrevCode = AbbrevCode.ABSTRACT_INLINE_METHOD; log(context, " [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal()); pos = writeAbbrevCode(abbrevCode, buffer, pos); @@ -2024,7 +2076,7 @@ private int writeAbstractInlineMethod(DebugContext context, ClassEntry classEntr * If the inline method exists in a different CU then write locals and params otherwise we * can just reuse the locals and params in the declaration */ - if (classEntry != method.ownerType()) { + if (classEntry != method.getOwnerType()) { FileEntry fileEntry = method.getFileEntry(); if (fileEntry == null) { fileEntry = classEntry.getFileEntry(); diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfLineSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfLineSectionImpl.java index ae1071c2e700..569743c625e0 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfLineSectionImpl.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfLineSectionImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -26,22 +26,17 @@ package com.oracle.objectfile.elf.dwarf; -import java.util.Iterator; -import java.util.Map; - import com.oracle.objectfile.debugentry.ClassEntry; +import com.oracle.objectfile.debugentry.CompiledMethodEntry; +import com.oracle.objectfile.debugentry.FileEntry; +import com.oracle.objectfile.debugentry.range.Range; +import com.oracle.objectfile.elf.dwarf.constants.DwarfForm; +import com.oracle.objectfile.elf.dwarf.constants.DwarfLineNumberHeaderEntry; import com.oracle.objectfile.elf.dwarf.constants.DwarfLineOpcode; import com.oracle.objectfile.elf.dwarf.constants.DwarfSectionName; import com.oracle.objectfile.elf.dwarf.constants.DwarfVersion; -import jdk.graal.compiler.debug.DebugContext; -import com.oracle.objectfile.LayoutDecision; -import com.oracle.objectfile.LayoutDecisionMap; -import com.oracle.objectfile.ObjectFile; -import com.oracle.objectfile.debugentry.CompiledMethodEntry; -import com.oracle.objectfile.debugentry.FileEntry; -import com.oracle.objectfile.debugentry.range.Range; -import com.oracle.objectfile.debugentry.range.SubRange; +import jdk.graal.compiler.debug.DebugContext; /** * Section generator for debug_line section. @@ -55,7 +50,7 @@ public class DwarfLineSectionImpl extends DwarfSectionImpl { /** * Line header section always contains fixed number of bytes. */ - private static final int LN_HEADER_SIZE = 28; + private static final int LN_HEADER_SIZE = 30; /** * Current generator follows C++ with line base -5. */ @@ -84,11 +79,11 @@ public void createContent() { */ Cursor byteCount = new Cursor(); - instanceClassStream().filter(ClassEntry::hasCompiledEntries).forEachOrdered(classEntry -> { + instanceClassWithCompilationStream().forEachOrdered(classEntry -> { setLineIndex(classEntry, byteCount.get()); int headerSize = headerSize(); - int dirTableSize = computeDirTableSize(classEntry); - int fileTableSize = computeFileTableSize(classEntry); + int dirTableSize = writeDirTable(null, classEntry, null, 0); + int fileTableSize = writeFileTable(null, classEntry, null, 0); int prologueSize = headerSize + dirTableSize + fileTableSize; setLinePrologueSize(classEntry, prologueSize); // mark the start of the line table for this entry @@ -110,6 +105,10 @@ private static int headerSize() { * *

  • uint16 version * + *
  • uint8 address_size + * + *
  • uint8 segment_selector_size + * *
  • uint32 header_length * *
  • uint8 min_insn_length @@ -132,53 +131,6 @@ private static int headerSize() { return LN_HEADER_SIZE; } - private int computeDirTableSize(ClassEntry classEntry) { - /* - * Table contains a sequence of 'nul'-terminated UTF8 dir name bytes followed by an extra - * 'nul'. - */ - Cursor cursor = new Cursor(); - classEntry.dirStream().forEachOrdered(dirEntry -> { - int length = countUTF8Bytes(dirEntry.getPathString()); - // We should never have a null or zero length entry in local dirs - assert length > 0; - cursor.add(length + 1); - }); - /* - * Allow for terminator nul. - */ - cursor.add(1); - return cursor.get(); - } - - private int computeFileTableSize(ClassEntry classEntry) { - /* - * Table contains a sequence of file entries followed by an extra 'nul' - * - * each file entry consists of a 'nul'-terminated UTF8 file name, a dir entry idx and two 0 - * time stamps - */ - Cursor cursor = new Cursor(); - classEntry.fileStream().forEachOrdered(fileEntry -> { - // We want the file base name excluding path. - String baseName = fileEntry.getFileName(); - int length = countUTF8Bytes(baseName); - // We should never have a null or zero length entry in local files. - assert length > 0; - cursor.add(length + 1); - // The dir index gets written as a ULEB - int dirIdx = classEntry.getDirIdx(fileEntry); - cursor.add(writeULEB(dirIdx, scratch, 0)); - // The two zero timestamps require 1 byte each - cursor.add(2); - }); - /* - * Allow for terminator nul. - */ - cursor.add(1); - return cursor.get(); - } - private int computeLineNumberTableSize(ClassEntry classEntry) { /* * Sigh -- we have to do this by generating the content even though we cannot write it into @@ -187,23 +139,6 @@ private int computeLineNumberTableSize(ClassEntry classEntry) { return writeLineNumberTable(null, classEntry, null, 0); } - @Override - public byte[] getOrDecideContent(Map alreadyDecided, byte[] contentHint) { - ObjectFile.Element textElement = getElement().getOwner().elementForName(".text"); - LayoutDecisionMap decisionMap = alreadyDecided.get(textElement); - if (decisionMap != null) { - Object valueObj = decisionMap.getDecidedValue(LayoutDecision.Kind.VADDR); - if (valueObj != null && valueObj instanceof Number) { - /* - * This may not be the final vaddr for the text segment but it will be close enough - * to make debug easier i.e. to within a 4k page or two. - */ - debugTextBase = ((Number) valueObj).longValue(); - } - } - return super.getOrDecideContent(alreadyDecided, contentHint); - } - @Override public void writeContent(DebugContext context) { assert contentByteArrayCreated(); @@ -211,9 +146,9 @@ public void writeContent(DebugContext context) { byte[] buffer = getContent(); Cursor cursor = new Cursor(); - enableLog(context, cursor.get()); + enableLog(context); log(context, " [0x%08x] DEBUG_LINE", cursor.get()); - instanceClassStream().filter(ClassEntry::hasCompiledEntries).forEachOrdered(classEntry -> { + instanceClassWithCompilationStream().forEachOrdered(classEntry -> { int pos = cursor.get(); setLineIndex(classEntry, pos); int lengthPos = pos; @@ -242,14 +177,25 @@ private int writeHeader(ClassEntry classEntry, byte[] buffer, int p) { */ pos = writeInt(0, buffer, pos); /* - * 2 ubyte version is always 2. + * 2 ubyte version is always 5. + */ + pos = writeDwarfVersion(DwarfVersion.DW_VERSION_5, buffer, pos); + /* + * 1 ubyte address size field. */ - pos = writeDwarfVersion(DwarfVersion.DW_VERSION_4, buffer, pos); + pos = writeByte((byte) 8, buffer, pos); + /* + * 1 ubyte segment selector size field. + */ + pos = writeByte((byte) 0, buffer, pos); + /* - * 4 ubyte prologue length includes rest of header and dir + file table section. + * TODO: fix this 4 ubyte prologue length includes rest of header and dir + file table + * section. */ - int prologueSize = getLinePrologueSize(classEntry) - (4 + 2 + 4); + int prologueSize = getLinePrologueSize(classEntry) - (4 + 2 + 1 + 1 + 4); pos = writeInt(prologueSize, buffer, pos); + /* * 1 ubyte min instruction length is always 1. */ @@ -305,52 +251,93 @@ private int writeHeader(ClassEntry classEntry, byte[] buffer, int p) { } private int writeDirTable(DebugContext context, ClassEntry classEntry, byte[] buffer, int p) { + int pos = p; verboseLog(context, " [0x%08x] Dir Name", p); + + /* + * 1 ubyte directory entry format count field. + */ + pos = writeByte((byte) 1, buffer, pos); + /* + * 1 ULEB128 pair for the directory entry format. + */ + pos = writeULEB(DwarfLineNumberHeaderEntry.DW_LNCT_path.value(), buffer, pos); + // DW_FORM_strp is not supported by GDB but DW_FORM_line_strp is + pos = writeULEB(DwarfForm.DW_FORM_line_strp.value(), buffer, pos); + + /* + * 1 ULEB128 for directory count. + */ + pos = writeULEB(classEntry.getDirs().size() + 1, buffer, pos); + + /* + * Write explicit 0 entry for current directory. (compilation directory) + */ + String compilationDirectory = uniqueDebugLineString(dwarfSections.getCachePath()); + pos = writeLineStrSectionOffset(compilationDirectory, buffer, pos); + /* * Write out the list of dirs */ - Cursor cursor = new Cursor(p); + Cursor cursor = new Cursor(pos); Cursor idx = new Cursor(1); - classEntry.dirStream().forEach(dirEntry -> { + classEntry.getDirs().forEach(dirEntry -> { int dirIdx = idx.get(); assert (classEntry.getDirIdx(dirEntry) == dirIdx); - String dirPath = dirEntry.getPathString(); + String dirPath = uniqueDebugLineString(dirEntry.getPathString()); verboseLog(context, " [0x%08x] %-4d %s", cursor.get(), dirIdx, dirPath); - cursor.set(writeUTF8StringBytes(dirPath, buffer, cursor.get())); + cursor.set(writeLineStrSectionOffset(dirPath, buffer, cursor.get())); idx.add(1); }); - /* - * Separate dirs from files with a nul. - */ - cursor.set(writeByte((byte) 0, buffer, cursor.get())); + return cursor.get(); } private int writeFileTable(DebugContext context, ClassEntry classEntry, byte[] buffer, int p) { + int pos = p; verboseLog(context, " [0x%08x] Entry Dir Name", p); + + /* + * 1 ubyte file name entry format count field. + */ + pos = writeByte((byte) 2, buffer, pos); + /* + * 2 ULEB128 pairs for the directory entry format. + */ + pos = writeULEB(DwarfLineNumberHeaderEntry.DW_LNCT_path.value(), buffer, pos); + // DW_FORM_strp is not supported by GDB but DW_FORM_line_strp is + pos = writeULEB(DwarfForm.DW_FORM_line_strp.value(), buffer, pos); + pos = writeULEB(DwarfLineNumberHeaderEntry.DW_LNCT_directory_index.value(), buffer, pos); + pos = writeULEB(DwarfForm.DW_FORM_udata.value(), buffer, pos); + + /* + * 1 ULEB128 for directory count. + */ + pos = writeULEB(classEntry.getFiles().size() + 1, buffer, pos); + + /* + * Write explicit 0 dummy entry. + */ + String fileName = uniqueDebugLineString(classEntry.getFileName()); + pos = writeLineStrSectionOffset(fileName, buffer, pos); + pos = writeULEB(classEntry.getDirIdx(), buffer, pos); + /* * Write out the list of files */ - Cursor cursor = new Cursor(p); + Cursor cursor = new Cursor(pos); Cursor idx = new Cursor(1); - classEntry.fileStream().forEach(fileEntry -> { - int pos = cursor.get(); + classEntry.getFiles().forEach(fileEntry -> { int fileIdx = idx.get(); assert classEntry.getFileIdx(fileEntry) == fileIdx; int dirIdx = classEntry.getDirIdx(fileEntry); - String baseName = fileEntry.getFileName(); - verboseLog(context, " [0x%08x] %-5d %-5d %s", pos, fileIdx, dirIdx, baseName); - pos = writeUTF8StringBytes(baseName, buffer, pos); - pos = writeULEB(dirIdx, buffer, pos); - pos = writeULEB(0, buffer, pos); - pos = writeULEB(0, buffer, pos); - cursor.set(pos); + String baseName = uniqueDebugLineString(fileEntry.fileName()); + verboseLog(context, " [0x%08x] %-5d %-5d %s", cursor.get(), fileIdx, dirIdx, baseName); + cursor.set(writeLineStrSectionOffset(baseName, buffer, cursor.get())); + cursor.set(writeULEB(dirIdx, buffer, cursor.get())); idx.add(1); }); - /* - * Terminate files with a nul. - */ - cursor.set(writeByte((byte) 0, buffer, cursor.get())); + return cursor.get(); } @@ -359,15 +346,15 @@ private int writeFileTable(DebugContext context, ClassEntry classEntry, byte[] b private int writeCompiledMethodLineInfo(DebugContext context, ClassEntry classEntry, CompiledMethodEntry compiledEntry, byte[] buffer, int p) { int pos = p; - Range primaryRange = compiledEntry.getPrimary(); + Range primaryRange = compiledEntry.primary(); // the compiled method might be a substitution and not in the file of the class entry FileEntry fileEntry = primaryRange.getFileEntry(); if (fileEntry == null) { - log(context, " [0x%08x] primary range [0x%08x, 0x%08x] skipped (no file) %s", pos, debugTextBase + primaryRange.getLo(), debugTextBase + primaryRange.getHi(), + log(context, " [0x%08x] primary range [0x%08x, 0x%08x] skipped (no file) %s", pos, primaryRange.getLo(), primaryRange.getHi(), primaryRange.getFullMethodNameWithParams()); return pos; } - String file = fileEntry.getFileName(); + String file = fileEntry.fileName(); int fileIdx = classEntry.getFileIdx(fileEntry); /* * Each primary represents a method i.e. a contiguous sequence of subranges. For normal @@ -396,7 +383,7 @@ private int writeCompiledMethodLineInfo(DebugContext context, ClassEntry classEn /* * Set state for primary. */ - log(context, " [0x%08x] primary range [0x%08x, 0x%08x] %s %s:%d", pos, debugTextBase + primaryRange.getLo(), debugTextBase + primaryRange.getHi(), + log(context, " [0x%08x] primary range [0x%08x, 0x%08x] %s %s:%d", pos, primaryRange.getLo(), primaryRange.getHi(), primaryRange.getFullMethodNameWithParams(), file, primaryRange.getLine()); @@ -420,27 +407,24 @@ private int writeCompiledMethodLineInfo(DebugContext context, ClassEntry classEn /* * Now write a row for each subrange lo and hi. */ - Iterator iterator = compiledEntry.leafRangeIterator(); - if (prologueRange != null) { - // skip already processed range - SubRange first = iterator.next(); - assert first == prologueRange; - } - while (iterator.hasNext()) { - SubRange subrange = iterator.next(); + + assert prologueRange == null || compiledEntry.leafRangeStream().findFirst().filter(first -> first == prologueRange).isPresent(); + + // skip already processed range + for (Range subrange : compiledEntry.leafRangeStream().skip(prologueRange != null ? 1 : 0).toList()) { assert subrange.getLo() >= primaryRange.getLo(); assert subrange.getHi() <= primaryRange.getHi(); FileEntry subFileEntry = subrange.getFileEntry(); if (subFileEntry == null) { continue; } - String subfile = subFileEntry.getFileName(); + String subfile = subFileEntry.fileName(); int subFileIdx = classEntry.getFileIdx(subFileEntry); assert subFileIdx > 0; long subLine = subrange.getLine(); long subAddressLo = subrange.getLo(); long subAddressHi = subrange.getHi(); - log(context, " [0x%08x] sub range [0x%08x, 0x%08x] %s %s:%d", pos, debugTextBase + subAddressLo, debugTextBase + subAddressHi, subrange.getFullMethodNameWithParams(), subfile, + log(context, " [0x%08x] sub range [0x%08x, 0x%08x] %s %s:%d", pos, subAddressLo, subAddressHi, subrange.getFullMethodNameWithParams(), subfile, subLine); if (subLine < 0) { /* @@ -538,6 +522,7 @@ private int writeCompiledMethodLineInfo(DebugContext context, ClassEntry classEn line += lineDelta; address += addressDelta; } + /* * Append a final end sequence just below the next primary range. */ @@ -555,10 +540,10 @@ private int writeCompiledMethodLineInfo(DebugContext context, ClassEntry classEn private int writeLineNumberTable(DebugContext context, ClassEntry classEntry, byte[] buffer, int p) { Cursor cursor = new Cursor(p); - classEntry.compiledEntries().forEachOrdered(compiledMethod -> { + classEntry.compiledMethods().forEach(compiledMethod -> { int pos = cursor.get(); - String methodName = compiledMethod.getPrimary().getFullMethodNameWithParams(); - String fileName = compiledMethod.getClassEntry().getFullFileName(); + String methodName = compiledMethod.primary().getFullMethodNameWithParams(); + String fileName = compiledMethod.classEntry().getFullFileName(); log(context, " [0x%08x] %s %s", pos, methodName, fileName); pos = writeCompiledMethodLineInfo(context, classEntry, compiledMethod, buffer, pos); cursor.set(pos); @@ -566,23 +551,18 @@ private int writeLineNumberTable(DebugContext context, ClassEntry classEntry, by return cursor.get(); } - private static SubRange prologueLeafRange(CompiledMethodEntry compiledEntry) { - Iterator iterator = compiledEntry.leafRangeIterator(); - if (iterator.hasNext()) { - SubRange range = iterator.next(); - if (range.getLo() == compiledEntry.getPrimary().getLo()) { - return range; - } - } - return null; + private static Range prologueLeafRange(CompiledMethodEntry compiledEntry) { + return compiledEntry.leafRangeStream() + .findFirst() + .filter(r -> r.getLo() == compiledEntry.primary().getLo()) + .orElse(null); } private int writeCopyOp(DebugContext context, byte[] buffer, int p) { DwarfLineOpcode opcode = DwarfLineOpcode.DW_LNS_copy; - int pos = p; debugCopyCount++; - verboseLog(context, " [0x%08x] Copy %d", pos, debugCopyCount); - return writeLineOpcode(opcode, buffer, pos); + verboseLog(context, " [0x%08x] Copy %d", p, debugCopyCount); + return writeLineOpcode(opcode, buffer, p); } private int writeAdvancePCOp(DebugContext context, long uleb, byte[] buffer, int p) { @@ -622,24 +602,21 @@ private int writeSetColumnOp(DebugContext context, long uleb, byte[] buffer, int @SuppressWarnings("unused") private int writeNegateStmtOp(DebugContext context, byte[] buffer, int p) { DwarfLineOpcode opcode = DwarfLineOpcode.DW_LNS_negate_stmt; - int pos = p; - return writeLineOpcode(opcode, buffer, pos); + return writeLineOpcode(opcode, buffer, p); } private int writeSetBasicBlockOp(DebugContext context, byte[] buffer, int p) { DwarfLineOpcode opcode = DwarfLineOpcode.DW_LNS_set_basic_block; - int pos = p; - verboseLog(context, " [0x%08x] Set basic block", pos); - return writeLineOpcode(opcode, buffer, pos); + verboseLog(context, " [0x%08x] Set basic block", p); + return writeLineOpcode(opcode, buffer, p); } private int writeConstAddPCOp(DebugContext context, byte[] buffer, int p) { DwarfLineOpcode opcode = DwarfLineOpcode.DW_LNS_const_add_pc; - int pos = p; int advance = opcodeAddress((byte) 255); debugAddress += advance; - verboseLog(context, " [0x%08x] Advance PC by constant %d to 0x%08x", pos, advance, debugAddress); - return writeLineOpcode(opcode, buffer, pos); + verboseLog(context, " [0x%08x] Advance PC by constant %d to 0x%08x", p, advance, debugAddress); + return writeLineOpcode(opcode, buffer, p); } private int writeFixedAdvancePCOp(DebugContext context, short arg, byte[] buffer, int p) { @@ -655,7 +632,7 @@ private int writeEndSequenceOp(DebugContext context, byte[] buffer, int p) { DwarfLineOpcode opcode = DwarfLineOpcode.DW_LNE_end_sequence; int pos = p; verboseLog(context, " [0x%08x] Extended opcode 1: End sequence", pos); - debugAddress = debugTextBase; + debugAddress = 0; debugLine = 1; debugCopyCount = 0; pos = writePrefixOpcode(buffer, pos); @@ -669,15 +646,14 @@ private int writeEndSequenceOp(DebugContext context, byte[] buffer, int p) { private int writeSetAddressOp(DebugContext context, long arg, byte[] buffer, int p) { DwarfLineOpcode opcode = DwarfLineOpcode.DW_LNE_set_address; int pos = p; - debugAddress = debugTextBase + (int) arg; - verboseLog(context, " [0x%08x] Extended opcode 2: Set Address to 0x%08x", pos, debugAddress); + verboseLog(context, " [0x%08x] Extended opcode 2: Set Address to 0x%08x", pos, arg); pos = writePrefixOpcode(buffer, pos); /* * Insert extended insn byte count as ULEB. */ pos = writeULEB(9, buffer, pos); pos = writeLineOpcode(opcode, buffer, pos); - return writeRelocatableCodeOffset(arg, buffer, pos); + return writeCodeOffset(arg, buffer, pos); } @SuppressWarnings("unused") @@ -733,15 +709,14 @@ private static int opcodeLine(byte opcode) { } private int writeSpecialOpcode(DebugContext context, byte opcode, byte[] buffer, int p) { - int pos = p; if (debug && opcode == 0) { verboseLog(context, " [0x%08x] ERROR Special Opcode %d: Address 0x%08x Line %d", debugAddress, debugLine); } debugAddress += opcodeAddress(opcode); debugLine += opcodeLine(opcode); verboseLog(context, " [0x%08x] Special Opcode %d: advance Address by %d to 0x%08x and Line by %d to %d", - pos, opcodeId(opcode), opcodeAddress(opcode), debugAddress, opcodeLine(opcode), debugLine); - return writeByte(opcode, buffer, pos); + p, opcodeId(opcode), opcodeAddress(opcode), debugAddress, opcodeLine(opcode), debugLine); + return writeByte(opcode, buffer, p); } private static final int MAX_ADDRESS_ONLY_DELTA = (0xff - LN_OPCODE_BASE) / LN_LINE_RANGE; diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfLineStrSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfLineStrSectionImpl.java new file mode 100644 index 000000000000..0984c5e3a5fa --- /dev/null +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfLineStrSectionImpl.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.objectfile.elf.dwarf; + +import com.oracle.objectfile.debugentry.StringEntry; +import com.oracle.objectfile.elf.dwarf.constants.DwarfSectionName; + +import jdk.graal.compiler.debug.DebugContext; + +/** + * Generator for debug_line_str section. + */ +public class DwarfLineStrSectionImpl extends DwarfSectionImpl { + public DwarfLineStrSectionImpl(DwarfDebugInfo dwarfSections) { + // debug_line_str section depends on line section + super(dwarfSections, DwarfSectionName.DW_LINE_STR_SECTION, DwarfSectionName.DW_LINE_SECTION); + } + + @Override + public void createContent() { + assert !contentByteArrayCreated(); + + int pos = 0; + for (StringEntry stringEntry : dwarfSections.getLineStringTable()) { + stringEntry.setOffset(pos); + String string = stringEntry.getString(); + pos = writeUTF8StringBytes(string, null, pos); + } + byte[] buffer = new byte[pos]; + super.setContent(buffer); + } + + @Override + public void writeContent(DebugContext context) { + assert contentByteArrayCreated(); + + byte[] buffer = getContent(); + int size = buffer.length; + int pos = 0; + + enableLog(context); + + verboseLog(context, " [0x%08x] DEBUG_STR", pos); + for (StringEntry stringEntry : dwarfSections.getLineStringTable()) { + assert stringEntry.getOffset() == pos; + String string = stringEntry.getString(); + pos = writeUTF8StringBytes(string, buffer, pos); + verboseLog(context, " [0x%08x] string = %s", pos, string); + } + assert pos == size; + } +} diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfLocSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfLocSectionImpl.java index bdc82370dbc8..cfa45ab326d3 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfLocSectionImpl.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfLocSectionImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2022, 2022, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -28,7 +28,6 @@ import java.util.ArrayList; import java.util.Arrays; -import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; @@ -38,10 +37,12 @@ import com.oracle.objectfile.LayoutDecisionMap; import com.oracle.objectfile.ObjectFile; import com.oracle.objectfile.debugentry.ClassEntry; +import com.oracle.objectfile.debugentry.ConstantValueEntry; +import com.oracle.objectfile.debugentry.LocalEntry; +import com.oracle.objectfile.debugentry.LocalValueEntry; +import com.oracle.objectfile.debugentry.RegisterValueEntry; +import com.oracle.objectfile.debugentry.StackValueEntry; import com.oracle.objectfile.debugentry.range.Range; -import com.oracle.objectfile.debugentry.range.SubRange; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugLocalInfo; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugLocalValueInfo; import com.oracle.objectfile.elf.ELFMachine; import com.oracle.objectfile.elf.ELFObjectFile; import com.oracle.objectfile.elf.dwarf.constants.DwarfExpressionOpcode; @@ -71,15 +72,9 @@ public class DwarfLocSectionImpl extends DwarfSectionImpl { */ private int dwarfStackRegister; - private static final LayoutDecision.Kind[] targetLayoutKinds = { - LayoutDecision.Kind.CONTENT, - LayoutDecision.Kind.SIZE, - /* Add this so we can use the text section base address for debug. */ - LayoutDecision.Kind.VADDR}; - public DwarfLocSectionImpl(DwarfDebugInfo dwarfSections) { // debug_loc section depends on text section - super(dwarfSections, DwarfSectionName.DW_LOCLISTS_SECTION, DwarfSectionName.TEXT_SECTION, targetLayoutKinds); + super(dwarfSections, DwarfSectionName.DW_LOCLISTS_SECTION, DwarfSectionName.TEXT_SECTION); initDwarfRegMap(); } @@ -116,7 +111,7 @@ public void writeContent(DebugContext context) { int size = buffer.length; int pos = 0; - enableLog(context, pos); + enableLog(context); log(context, " [0x%08x] DEBUG_LOC", pos); log(context, " [0x%08x] size = 0x%08x", pos, size); @@ -131,7 +126,7 @@ private int generateContent(DebugContext context, byte[] buffer) { // reason for doing it in class entry order is to because it mirrors the // order in which entries appear in the info section. That stops objdump // posting spurious messages about overlaps and holes in the var ranges. - instanceClassStream().filter(ClassEntry::hasCompiledEntries).forEachOrdered(classEntry -> { + instanceClassStream().filter(ClassEntry::hasCompiledMethods).forEachOrdered(classEntry -> { List locationListEntries = getLocationListEntries(classEntry); if (locationListEntries.isEmpty()) { // no need to emit empty location list @@ -177,14 +172,14 @@ private int writeLocationListsHeader(int offsetEntries, byte[] buffer, int p) { return writeInt(offsetEntries, buffer, pos); } - private record LocationListEntry(Range range, int base, DebugLocalInfo local, List rangeList) { + private record LocationListEntry(Range range, long base, LocalEntry local, List rangeList) { } private static List getLocationListEntries(ClassEntry classEntry) { List locationListEntries = new ArrayList<>(); - classEntry.compiledEntries().forEachOrdered(compiledEntry -> { - Range primary = compiledEntry.getPrimary(); + classEntry.compiledMethods().forEach(compiledEntry -> { + Range primary = compiledEntry.primary(); /* * Note that offsets are written relative to the primary range base. This requires * writing a base address entry before each of the location list ranges. It is possible @@ -194,37 +189,25 @@ private static List getLocationListEntries(ClassEntry classEn * code addresses e.g. to set a breakpoint, leading to a very slow response for the * user. */ - int base = primary.getLo(); + long base = primary.getLo(); // location list entries for primary range locationListEntries.addAll(getRangeLocationListEntries(primary, base)); // location list entries for inlined calls if (!primary.isLeaf()) { - Iterator iterator = compiledEntry.topDownRangeIterator(); - while (iterator.hasNext()) { - SubRange subrange = iterator.next(); - if (subrange.isLeaf()) { - continue; - } - locationListEntries.addAll(getRangeLocationListEntries(subrange, base)); - } + compiledEntry.callRangeStream().forEach(subrange -> locationListEntries.addAll(getRangeLocationListEntries(subrange, base))); } }); return locationListEntries; } - private static List getRangeLocationListEntries(Range range, int base) { - List locationListEntries = new ArrayList<>(); - - for (Map.Entry> entry : range.getVarRangeMap().entrySet()) { - if (!entry.getValue().isEmpty()) { - locationListEntries.add(new LocationListEntry(range, base, entry.getKey(), entry.getValue())); - } - } - - return locationListEntries; + private static List getRangeLocationListEntries(Range range, long base) { + return range.getVarRangeMap().entrySet().stream() + .filter(entry -> !entry.getValue().isEmpty()) + .map(entry -> new LocationListEntry(range, base, entry.getKey(), entry.getValue())) + .toList(); } - private int writeVarLocations(DebugContext context, DebugLocalInfo local, int base, List rangeList, byte[] buffer, int p) { + private int writeVarLocations(DebugContext context, LocalEntry local, long base, List rangeList, byte[] buffer, int p) { assert !rangeList.isEmpty(); int pos = p; // collect ranges and values, merging adjacent ranges that have equal value @@ -236,27 +219,27 @@ private int writeVarLocations(DebugContext context, DebugLocalInfo local, int ba pos = writeAttrAddress(base, buffer, pos); // write ranges as offsets from base for (LocalValueExtent extent : extents) { - DebugLocalValueInfo value = extent.value; + LocalValueEntry value = extent.value; assert (value != null); - log(context, " [0x%08x] local %s:%s [0x%x, 0x%x] = %s", pos, value.name(), value.typeName(), extent.getLo(), extent.getHi(), formatValue(value)); + log(context, " [0x%08x] local %s:%s [0x%x, 0x%x] = %s", pos, local.name(), local.type().getTypeName(), extent.getLo(), extent.getHi(), value); pos = writeLocationListEntry(DwarfLocationListEntry.DW_LLE_offset_pair, buffer, pos); pos = writeULEB(extent.getLo() - base, buffer, pos); pos = writeULEB(extent.getHi() - base, buffer, pos); - switch (value.localKind()) { - case REGISTER: - pos = writeRegisterLocation(context, value.regIndex(), buffer, pos); + switch (value) { + case RegisterValueEntry registerValueEntry: + pos = writeRegisterLocation(context, registerValueEntry.regIndex(), buffer, pos); break; - case STACKSLOT: - pos = writeStackLocation(context, value.stackSlot(), buffer, pos); + case StackValueEntry stackValueEntry: + pos = writeStackLocation(context, stackValueEntry.stackSlot(), buffer, pos); break; - case CONSTANT: - JavaConstant constant = value.constantValue(); + case ConstantValueEntry constantValueEntry: + JavaConstant constant = constantValueEntry.constant(); if (constant instanceof PrimitiveConstant) { - pos = writePrimitiveConstantLocation(context, value.constantValue(), buffer, pos); + pos = writePrimitiveConstantLocation(context, constant, buffer, pos); } else if (constant.isNull()) { - pos = writeNullConstantLocation(context, value.constantValue(), buffer, pos); + pos = writeNullConstantLocation(context, constant, buffer, pos); } else { - pos = writeObjectConstantLocation(context, value.constantValue(), value.heapOffset(), buffer, pos); + pos = writeObjectConstantLocation(context, constant, constantValueEntry.heapOffset(), buffer, pos); } break; default: @@ -378,16 +361,16 @@ private int writeObjectConstantLocation(DebugContext context, JavaConstant const static class LocalValueExtent { long lo; long hi; - DebugLocalValueInfo value; + LocalValueEntry value; - LocalValueExtent(long lo, long hi, DebugLocalValueInfo value) { + LocalValueExtent(long lo, long hi, LocalValueEntry value) { this.lo = lo; this.hi = hi; this.value = value; } @SuppressWarnings("unused") - boolean shouldMerge(int otherLo, int otherHi, DebugLocalValueInfo otherValue) { + boolean shouldMerge(long otherLo, long otherHi, LocalValueEntry otherValue) { // ranges need to be contiguous to merge if (hi != otherLo) { return false; @@ -395,7 +378,7 @@ boolean shouldMerge(int otherLo, int otherHi, DebugLocalValueInfo otherValue) { return value.equals(otherValue); } - private LocalValueExtent maybeMerge(int otherLo, int otherHi, DebugLocalValueInfo otherValue) { + private LocalValueExtent maybeMerge(long otherLo, long otherHi, LocalValueEntry otherValue) { if (shouldMerge(otherLo, otherHi, otherValue)) { // We can extend the current extent to cover the next one. this.hi = otherHi; @@ -414,14 +397,14 @@ public long getHi() { return hi; } - public DebugLocalValueInfo getValue() { + public LocalValueEntry getValue() { return value; } - public static List coalesce(DebugLocalInfo local, List rangeList) { + public static List coalesce(LocalEntry local, List rangeList) { List extents = new ArrayList<>(); LocalValueExtent current = null; - for (SubRange range : rangeList) { + for (Range range : rangeList) { if (current == null) { current = new LocalValueExtent(range.getLo(), range.getHi(), range.lookupValue(local)); extents.add(current); @@ -632,10 +615,6 @@ public enum DwarfRegEncodingAMD64 implements DwarfRegEncoding { this.graalEncoding = graalEncoding; } - public static int graalOrder(DwarfRegEncodingAMD64 e1, DwarfRegEncodingAMD64 e2) { - return Integer.compare(e1.graalEncoding, e2.graalEncoding); - } - @Override public int getDwarfEncoding() { return dwarfEncoding; diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfRangesSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfRangesSectionImpl.java index 1d289c6307c8..2709733098de 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfRangesSectionImpl.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfRangesSectionImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2023, 2023, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -26,11 +26,6 @@ package com.oracle.objectfile.elf.dwarf; -import java.util.Map; - -import com.oracle.objectfile.LayoutDecision; -import com.oracle.objectfile.LayoutDecisionMap; -import com.oracle.objectfile.ObjectFile; import com.oracle.objectfile.debugentry.ClassEntry; import com.oracle.objectfile.elf.dwarf.constants.DwarfRangeListEntry; import com.oracle.objectfile.elf.dwarf.constants.DwarfSectionName; @@ -55,23 +50,6 @@ public void createContent() { super.setContent(buffer); } - @Override - public byte[] getOrDecideContent(Map alreadyDecided, byte[] contentHint) { - ObjectFile.Element textElement = getElement().getOwner().elementForName(".text"); - LayoutDecisionMap decisionMap = alreadyDecided.get(textElement); - if (decisionMap != null) { - Object valueObj = decisionMap.getDecidedValue(LayoutDecision.Kind.VADDR); - if (valueObj != null && valueObj instanceof Number) { - /* - * This may not be the final vaddr for the text segment but it will be close enough - * to make debug easier i.e. to within a 4k page or two. - */ - debugTextBase = ((Number) valueObj).longValue(); - } - } - return super.getOrDecideContent(alreadyDecided, contentHint); - } - @Override public void writeContent(DebugContext context) { assert contentByteArrayCreated(); @@ -80,7 +58,7 @@ public void writeContent(DebugContext context) { int size = buffer.length; int pos = 0; - enableLog(context, pos); + enableLog(context); log(context, " [0x%08x] DEBUG_RANGES", pos); log(context, " [0x%08x] size = 0x%08x", pos, size); @@ -119,7 +97,7 @@ private int writeRangeListsHeader(byte[] buffer, int p) { private int writeRangeLists(DebugContext context, byte[] buffer, int p) { Cursor entryCursor = new Cursor(p); - instanceClassStream().filter(ClassEntry::hasCompiledEntries).forEachOrdered(classEntry -> { + instanceClassWithCompilationStream().forEachOrdered(classEntry -> { int pos = entryCursor.get(); setCodeRangesIndex(classEntry, pos); /* Write range list for a class */ @@ -131,18 +109,18 @@ private int writeRangeLists(DebugContext context, byte[] buffer, int p) { private int writeRangeList(DebugContext context, ClassEntry classEntry, byte[] buffer, int p) { int pos = p; log(context, " [0x%08x] ranges start for class %s", pos, classEntry.getTypeName()); - int base = classEntry.compiledEntriesBase(); + long base = classEntry.lowpc(); log(context, " [0x%08x] base 0x%x", pos, base); pos = writeRangeListEntry(DwarfRangeListEntry.DW_RLE_base_address, buffer, pos); - pos = writeRelocatableCodeOffset(base, buffer, pos); + pos = writeCodeOffset(base, buffer, pos); Cursor cursor = new Cursor(pos); - classEntry.compiledEntries().forEach(compiledMethodEntry -> { + classEntry.compiledMethods().forEach(compiledMethodEntry -> { cursor.set(writeRangeListEntry(DwarfRangeListEntry.DW_RLE_offset_pair, buffer, cursor.get())); - int loOffset = compiledMethodEntry.getPrimary().getLo() - base; - int hiOffset = compiledMethodEntry.getPrimary().getHi() - base; - log(context, " [0x%08x] lo 0x%x (%s)", cursor.get(), loOffset, compiledMethodEntry.getPrimary().getFullMethodNameWithParams()); + long loOffset = compiledMethodEntry.primary().getLo() - base; + long hiOffset = compiledMethodEntry.primary().getHi() - base; + log(context, " [0x%08x] lo 0x%x (%s)", cursor.get(), loOffset, compiledMethodEntry.primary().getFullMethodNameWithParams()); cursor.set(writeULEB(loOffset, buffer, cursor.get())); log(context, " [0x%08x] hi 0x%x", cursor.get(), hiOffset); cursor.set(writeULEB(hiOffset, buffer, cursor.get())); diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfSectionImpl.java index 04b550b7e9a8..ea9f3e65b98e 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfSectionImpl.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfSectionImpl.java @@ -40,16 +40,13 @@ import com.oracle.objectfile.debugentry.ArrayTypeEntry; import com.oracle.objectfile.debugentry.ClassEntry; import com.oracle.objectfile.debugentry.CompiledMethodEntry; -import com.oracle.objectfile.debugentry.DirEntry; -import com.oracle.objectfile.debugentry.FileEntry; import com.oracle.objectfile.debugentry.HeaderTypeEntry; +import com.oracle.objectfile.debugentry.LocalEntry; import com.oracle.objectfile.debugentry.MethodEntry; import com.oracle.objectfile.debugentry.PrimitiveTypeEntry; import com.oracle.objectfile.debugentry.StructureTypeEntry; import com.oracle.objectfile.debugentry.TypeEntry; import com.oracle.objectfile.debugentry.range.Range; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugLocalInfo; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugLocalValueInfo; import com.oracle.objectfile.elf.ELFMachine; import com.oracle.objectfile.elf.ELFObjectFile; import com.oracle.objectfile.elf.dwarf.DwarfDebugInfo.AbbrevCode; @@ -63,14 +60,13 @@ import com.oracle.objectfile.elf.dwarf.constants.DwarfVersion; import jdk.graal.compiler.debug.DebugContext; -import jdk.vm.ci.meta.ResolvedJavaType; /** * A class from which all DWARF debug sections inherit providing common behaviours. */ public abstract class DwarfSectionImpl extends BasicProgbitsSectionImpl { // auxiliary class used to track byte array positions - protected class Cursor { + protected static class Cursor { private int pos; public Cursor() { @@ -100,9 +96,7 @@ public int get() { protected final DwarfDebugInfo dwarfSections; protected boolean debug = false; - protected long debugTextBase = 0; protected long debugAddress = 0; - protected int debugBase = 0; /** * The name of this section. @@ -188,7 +182,7 @@ private String debugSectionLogName() { return "dwarf" + getSectionName(); } - protected void enableLog(DebugContext context, int pos) { + protected void enableLog(DebugContext context) { /* * Debug output is disabled during the first pass where we size the buffer. this is called * to enable it during the second pass where the buffer gets written, but only if the scope @@ -196,10 +190,8 @@ protected void enableLog(DebugContext context, int pos) { */ assert contentByteArrayCreated(); - if (context.areScopesEnabled()) { + if (context.areScopesEnabled() && context.isLogEnabled()) { debug = true; - debugBase = pos; - debugAddress = debugTextBase; } } @@ -281,33 +273,45 @@ protected int putLong(long l, byte[] buffer, int p) { return pos; } - protected int putRelocatableCodeOffset(long l, byte[] buffer, int p) { + protected int putCodeOffset(long l, byte[] buffer, int p) { int pos = p; - /* - * Mark address so it is relocated relative to the start of the text segment. - */ - markRelocationSite(pos, ObjectFile.RelocationKind.DIRECT_8, DwarfSectionName.TEXT_SECTION.value(), l); - pos = writeLong(0, buffer, pos); + if (dwarfSections.isRuntimeCompilation()) { + pos = writeLong(l, buffer, p); + } else { + /* + * Mark address so it is relocated relative to the start of the text segment. + */ + markRelocationSite(pos, ObjectFile.RelocationKind.DIRECT_8, DwarfSectionName.TEXT_SECTION.value(), l); + pos = writeLong(0, buffer, pos); + } return pos; } - protected int putRelocatableHeapOffset(long l, byte[] buffer, int p) { + protected int putHeapOffset(long l, byte[] buffer, int p) { int pos = p; - /* - * Mark address so it is relocated relative to the start of the heap. - */ - markRelocationSite(pos, ObjectFile.RelocationKind.DIRECT_8, DwarfDebugInfo.HEAP_BEGIN_NAME, l); - pos = writeLong(0, buffer, pos); + if (dwarfSections.isRuntimeCompilation()) { + pos = writeLong(l, buffer, pos); + } else { + /* + * Mark address so it is relocated relative to the start of the heap. + */ + markRelocationSite(pos, ObjectFile.RelocationKind.DIRECT_8, DwarfDebugInfo.HEAP_BEGIN_NAME, l); + pos = writeLong(0, buffer, pos); + } return pos; } - protected int putRelocatableDwarfSectionOffset(int offset, byte[] buffer, String referencedSectionName, int p) { + protected int putDwarfSectionOffset(int offset, byte[] buffer, String referencedSectionName, int p) { int pos = p; - /* - * Mark address so it is relocated relative to the start of the desired section. - */ - markRelocationSite(pos, ObjectFile.RelocationKind.DIRECT_4, referencedSectionName, offset); - pos = writeInt(0, buffer, pos); + if (dwarfSections.isRuntimeCompilation()) { + pos = writeInt(offset, buffer, pos); + } else { + /* + * Mark address so it is relocated relative to the start of the desired section. + */ + markRelocationSite(pos, ObjectFile.RelocationKind.DIRECT_4, referencedSectionName, offset); + pos = writeInt(0, buffer, pos); + } return pos; } @@ -402,17 +406,17 @@ protected int writeLong(long l, byte[] buffer, int p) { } } - protected int writeRelocatableCodeOffset(long l, byte[] buffer, int p) { + protected int writeCodeOffset(long l, byte[] buffer, int p) { if (buffer != null) { - return putRelocatableCodeOffset(l, buffer, p); + return putCodeOffset(l, buffer, p); } else { return p + 8; } } - protected int writeRelocatableHeapOffset(long l, byte[] buffer, int p) { + protected int writeHeapOffset(long l, byte[] buffer, int p) { if (buffer != null) { - return putRelocatableHeapOffset(l, buffer, p); + return putHeapOffset(l, buffer, p); } else { return p + 8; } @@ -446,7 +450,8 @@ protected int writeUTF8StringBytes(String s, int startChar, byte[] buffer, int p if (buffer != null) { return putUTF8StringBytes(s, startChar, buffer, p); } else { - return s.substring(startChar).getBytes(StandardCharsets.UTF_8).length; + // +1 for null termination + return p + s.substring(startChar).getBytes(StandardCharsets.UTF_8).length + 1; } } @@ -522,7 +527,7 @@ protected int writeFlag(DwarfFlag flag, byte[] buffer, int pos) { } protected int writeAttrAddress(long address, byte[] buffer, int pos) { - return writeRelocatableCodeOffset(address, buffer, pos); + return writeCodeOffset(address, buffer, pos); } @SuppressWarnings("unused") @@ -559,15 +564,23 @@ protected int writeAbbrevSectionOffset(int offset, byte[] buffer, int pos) { } protected int writeStrSectionOffset(String value, byte[] buffer, int p) { - int pos = p; int idx = debugStringIndex(value); - return writeStrSectionOffset(idx, buffer, pos); + return writeStrSectionOffset(idx, buffer, p); } private int writeStrSectionOffset(int offset, byte[] buffer, int pos) { return writeDwarfSectionOffset(offset, buffer, DwarfSectionName.DW_STR_SECTION, pos); } + protected int writeLineStrSectionOffset(String value, byte[] buffer, int p) { + int idx = debugLineStringIndex(value); + return writeLineStrSectionOffset(idx, buffer, p); + } + + private int writeLineStrSectionOffset(int offset, byte[] buffer, int pos) { + return writeDwarfSectionOffset(offset, buffer, DwarfSectionName.DW_LINE_STR_SECTION, pos); + } + protected int writeLocSectionOffset(int offset, byte[] buffer, int pos) { return writeDwarfSectionOffset(offset, buffer, DwarfSectionName.DW_LOCLISTS_SECTION, pos); } @@ -576,7 +589,7 @@ protected int writeDwarfSectionOffset(int offset, byte[] buffer, DwarfSectionNam // offsets to abbrev section DIEs need a relocation // the linker uses this to update the offset when info sections are merged if (buffer != null) { - return putRelocatableDwarfSectionOffset(offset, buffer, referencedSectionName.value(), pos); + return putDwarfSectionOffset(offset, buffer, referencedSectionName.value(), pos); } else { return pos + 4; } @@ -639,7 +652,7 @@ protected int writeHeapLocation(long offset, byte[] buffer, int p) { if (dwarfSections.useHeapBase()) { return writeHeapLocationBaseRelative(offset, buffer, p); } else { - return writeHeapLocationRelocatable(offset, buffer, p); + return writeHeapLocationOffset(offset, buffer, p); } } @@ -650,25 +663,11 @@ private int writeHeapLocationBaseRelative(long offset, byte[] buffer, int p) { return writeSLEB(offset, buffer, pos); } - private int writeHeapLocationRelocatable(long offset, byte[] buffer, int p) { + private int writeHeapLocationOffset(long offset, byte[] buffer, int p) { int pos = p; /* Write a relocatable address relative to the heap section start. */ pos = writeExprOpcode(DwarfExpressionOpcode.DW_OP_addr, buffer, pos); - return writeRelocatableHeapOffset(offset, buffer, pos); - } - - protected static String formatValue(DebugLocalValueInfo value) { - switch (value.localKind()) { - case REGISTER: - return "REG:" + value.regIndex(); - case STACKSLOT: - return "STACK:" + value.stackSlot(); - case CONSTANT: - return "CONST:" + value.constantValue() + "[" + Long.toHexString(value.heapOffset()) + "]"; - case UNDEFINED: - default: - return "-"; - } + return writeHeapOffset(offset, buffer, pos); } /** @@ -763,7 +762,7 @@ protected Stream typeStream() { * @return a stream of all primitive types notified via the DebugTypeInfo API. */ protected Stream primitiveTypeStream() { - return typeStream().filter(TypeEntry::isPrimitive).map(entry -> ((PrimitiveTypeEntry) entry)); + return dwarfSections.getPrimitiveTypes().stream(); } /** @@ -772,7 +771,7 @@ protected Stream primitiveTypeStream() { * @return a stream of all array types notified via the DebugTypeInfo API. */ protected Stream arrayTypeStream() { - return typeStream().filter(TypeEntry::isArray).map(entry -> ((ArrayTypeEntry) entry)); + return dwarfSections.getArrayTypes().stream(); } /** @@ -803,6 +802,10 @@ protected Stream instanceClassStream() { return dwarfSections.getInstanceClasses().stream(); } + protected Stream instanceClassWithCompilationStream() { + return dwarfSections.getInstanceClassesWithCompilation().stream(); + } + /** * Retrieve a stream of all compiled methods notified via the DebugTypeInfo API. * @@ -812,26 +815,6 @@ protected Stream compiledMethodsStream() { return dwarfSections.getCompiledMethods().stream(); } - protected int compiledMethodsCount() { - return dwarfSections.getCompiledMethods().size(); - } - - protected Stream fileStream() { - return dwarfSections.getFiles().stream(); - } - - protected int fileCount() { - return dwarfSections.getFiles().size(); - } - - protected Stream dirStream() { - return dwarfSections.getDirs().stream(); - } - - protected int dirCount() { - return dwarfSections.getDirs().size(); - } - /** * Retrieve an iterable for all instance classes, including interfaces and enums, notified via * the DebugTypeInfo API. @@ -849,12 +832,19 @@ protected int debugStringIndex(String str) { return dwarfSections.debugStringIndex(str); } + protected int debugLineStringIndex(String str) { + if (!contentByteArrayCreated()) { + return 0; + } + return dwarfSections.debugLineStringIndex(str); + } + protected String uniqueDebugString(String str) { return dwarfSections.uniqueDebugString(str); } - protected TypeEntry lookupType(ResolvedJavaType type) { - return dwarfSections.lookupTypeEntry(type); + protected String uniqueDebugLineString(String str) { + return dwarfSections.uniqueDebugLineString(str); } protected ClassEntry lookupObjectClass() { @@ -955,7 +945,7 @@ protected int getAbstractInlineMethodIndex(ClassEntry classEntry, MethodEntry me * @param localInfo the local or param whose index is to be recorded. * @param index the info section offset to be recorded. */ - protected void setMethodLocalIndex(ClassEntry classEntry, MethodEntry methodEntry, DebugLocalInfo localInfo, int index) { + protected void setMethodLocalIndex(ClassEntry classEntry, MethodEntry methodEntry, LocalEntry localInfo, int index) { dwarfSections.setMethodLocalIndex(classEntry, methodEntry, localInfo, index); } @@ -968,7 +958,7 @@ protected void setMethodLocalIndex(ClassEntry classEntry, MethodEntry methodEntr * @param localInfo the local or param whose index is to be retrieved. * @return the associated info section offset. */ - protected int getMethodLocalIndex(ClassEntry classEntry, MethodEntry methodEntry, DebugLocalInfo localInfo) { + protected int getMethodLocalIndex(ClassEntry classEntry, MethodEntry methodEntry, LocalEntry localInfo) { if (!contentByteArrayCreated()) { return 0; } @@ -984,7 +974,7 @@ protected int getMethodLocalIndex(ClassEntry classEntry, MethodEntry methodEntry * @param localInfo the local or param whose index is to be recorded. * @param index the info section offset index to be recorded. */ - protected void setRangeLocalIndex(Range range, DebugLocalInfo localInfo, int index) { + protected void setRangeLocalIndex(Range range, LocalEntry localInfo, int index) { dwarfSections.setRangeLocalIndex(range, localInfo, index); } @@ -997,7 +987,7 @@ protected void setRangeLocalIndex(Range range, DebugLocalInfo localInfo, int ind * @param localInfo the local or param whose index is to be retrieved. * @return the associated info section offset. */ - protected int getRangeLocalIndex(Range range, DebugLocalInfo localInfo) { + protected int getRangeLocalIndex(Range range, LocalEntry localInfo) { return dwarfSections.getRangeLocalIndex(range, localInfo); } } diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfStrSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfStrSectionImpl.java index 34cddd22e18f..303e82a2af1e 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfStrSectionImpl.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfStrSectionImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -28,6 +28,7 @@ import com.oracle.objectfile.debugentry.StringEntry; import com.oracle.objectfile.elf.dwarf.constants.DwarfSectionName; + import jdk.graal.compiler.debug.DebugContext; /** @@ -45,11 +46,9 @@ public void createContent() { int pos = 0; for (StringEntry stringEntry : dwarfSections.getStringTable()) { - if (stringEntry.isAddToStrSection()) { - stringEntry.setOffset(pos); - String string = stringEntry.getString(); - pos += countUTF8Bytes(string) + 1; - } + stringEntry.setOffset(pos); + String string = stringEntry.getString(); + pos = writeUTF8StringBytes(string, null, pos); } byte[] buffer = new byte[pos]; super.setContent(buffer); @@ -63,16 +62,14 @@ public void writeContent(DebugContext context) { int size = buffer.length; int pos = 0; - enableLog(context, pos); + enableLog(context); verboseLog(context, " [0x%08x] DEBUG_STR", pos); for (StringEntry stringEntry : dwarfSections.getStringTable()) { - if (stringEntry.isAddToStrSection()) { - assert stringEntry.getOffset() == pos; - String string = stringEntry.getString(); - pos = writeUTF8StringBytes(string, buffer, pos); - verboseLog(context, " [0x%08x] string = %s", pos, string); - } + assert stringEntry.getOffset() == pos; + String string = stringEntry.getString(); + pos = writeUTF8StringBytes(string, buffer, pos); + verboseLog(context, " [0x%08x] string = %s", pos, string); } assert pos == size; } diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfAttribute.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfAttribute.java index 050b9e9c726d..b6fec32e5ffe 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfAttribute.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfAttribute.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2023, 2023, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -47,16 +47,16 @@ public enum DwarfAttribute { DW_AT_artificial(0x34), DW_AT_count(0x37), DW_AT_data_member_location(0x38), - @SuppressWarnings("unused") + @SuppressWarnings("unused")// DW_AT_decl_column(0x39), DW_AT_decl_file(0x3a), DW_AT_decl_line(0x3b), DW_AT_declaration(0x3c), DW_AT_encoding(0x3e), DW_AT_external(0x3f), - @SuppressWarnings("unused") + @SuppressWarnings("unused")// DW_AT_return_addr(0x2a), - @SuppressWarnings("unused") + @SuppressWarnings("unused")// DW_AT_frame_base(0x40), DW_AT_specification(0x47), DW_AT_type(0x49), diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfEncoding.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfEncoding.java index c5cb704e0022..3adbebe9716b 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfEncoding.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfEncoding.java @@ -30,14 +30,14 @@ * DW_AT_encoding attribute values. */ public enum DwarfEncoding { - @SuppressWarnings("unused") + @SuppressWarnings("unused")// DW_ATE_address((byte) 0x1), DW_ATE_boolean((byte) 0x2), DW_ATE_float((byte) 0x4), DW_ATE_signed((byte) 0x5), DW_ATE_signed_char((byte) 0x6), DW_ATE_unsigned((byte) 0x7), - @SuppressWarnings("unused") + @SuppressWarnings("unused")// DW_ATE_unsigned_char((byte) 0x8); private final byte value; diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfExpressionOpcode.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfExpressionOpcode.java index c55014c908cb..3da675084ff3 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfExpressionOpcode.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfExpressionOpcode.java @@ -31,7 +31,7 @@ */ public enum DwarfExpressionOpcode { DW_OP_addr((byte) 0x03), - @SuppressWarnings("unused") + @SuppressWarnings("unused")// DW_OP_deref((byte) 0x06), DW_OP_dup((byte) 0x12), DW_OP_and((byte) 0x1a), diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfFlag.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfFlag.java index f83fe93b8bfd..3ae00282ff4b 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfFlag.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfFlag.java @@ -30,7 +30,7 @@ * DW_FORM_flag only has two possible attribute values. */ public enum DwarfFlag { - @SuppressWarnings("unused") + @SuppressWarnings("unused")// DW_FLAG_false((byte) 0), DW_FLAG_true((byte) 1); diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfForm.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfForm.java index 1855bb16850e..0a50b51b506c 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfForm.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfForm.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2023, 2023, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -32,27 +32,29 @@ public enum DwarfForm { DW_FORM_null(0x0), DW_FORM_addr(0x1), - DW_FORM_data2(0x05), + DW_FORM_data2(0x5), DW_FORM_data4(0x6), - @SuppressWarnings("unused") + @SuppressWarnings("unused")// DW_FORM_data8(0x7), - @SuppressWarnings("unused") + @SuppressWarnings("unused")// DW_FORM_string(0x8), - @SuppressWarnings("unused") - DW_FORM_block1(0x0a), + @SuppressWarnings("unused")// + DW_FORM_block1(0xa), + DW_FORM_data1(0xb), + DW_FORM_flag(0xc), + DW_FORM_strp(0xe), + DW_FORM_udata(0xf), DW_FORM_ref_addr(0x10), - @SuppressWarnings("unused") + @SuppressWarnings("unused")// DW_FORM_ref1(0x11), - @SuppressWarnings("unused") + @SuppressWarnings("unused")// DW_FORM_ref2(0x12), DW_FORM_ref4(0x13), - @SuppressWarnings("unused") + @SuppressWarnings("unused")// DW_FORM_ref8(0x14), DW_FORM_sec_offset(0x17), - DW_FORM_data1(0x0b), - DW_FORM_flag(0xc), - DW_FORM_strp(0xe), DW_FORM_expr_loc(0x18), + DW_FORM_line_strp(0x1f), DW_FORM_ref_sig8(0x20), DW_FORM_loclistx(0x22); diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfFrameValue.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfFrameValue.java index 77ba64cecf37..992c2e942700 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfFrameValue.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfFrameValue.java @@ -37,22 +37,22 @@ public enum DwarfFrameValue { DW_CFA_restore((byte) 0x3), /* Values encoded in low 6 bits. */ DW_CFA_nop((byte) 0x0), - @SuppressWarnings("unused") + @SuppressWarnings("unused")// DW_CFA_set_loc1((byte) 0x1), DW_CFA_advance_loc1((byte) 0x2), DW_CFA_advance_loc2((byte) 0x3), DW_CFA_advance_loc4((byte) 0x4), - @SuppressWarnings("unused") + @SuppressWarnings("unused")// DW_CFA_offset_extended((byte) 0x5), - @SuppressWarnings("unused") + @SuppressWarnings("unused")// DW_CFA_restore_extended((byte) 0x6), - @SuppressWarnings("unused") + @SuppressWarnings("unused")// DW_CFA_undefined((byte) 0x7), - @SuppressWarnings("unused") + @SuppressWarnings("unused")// DW_CFA_same_value((byte) 0x8), DW_CFA_register((byte) 0x9), DW_CFA_def_cfa((byte) 0xc), - @SuppressWarnings("unused") + @SuppressWarnings("unused")// DW_CFA_def_cfa_register((byte) 0xd), DW_CFA_def_cfa_offset((byte) 0xe); diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfInline.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfInline.java index 91498c4a1ed0..76f81bde00d3 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfInline.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfInline.java @@ -30,12 +30,12 @@ * Values for DW_AT_inline attribute. */ public enum DwarfInline { - @SuppressWarnings("unused") + @SuppressWarnings("unused")// DW_INL_not_inlined((byte) 0), DW_INL_inlined((byte) 1), - @SuppressWarnings("unused") + @SuppressWarnings("unused")// DW_INL_declared_not_inlined((byte) 2), - @SuppressWarnings("unused") + @SuppressWarnings("unused")// DW_INL_declared_inlined((byte) 3); private final byte value; diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfLineNumberHeaderEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfLineNumberHeaderEntry.java new file mode 100644 index 000000000000..0c651d62392f --- /dev/null +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfLineNumberHeaderEntry.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.objectfile.elf.dwarf.constants; + +/** + * All the Dwarf attribute forms needed to type attribute values generated by GraalVM. + */ +public enum DwarfLineNumberHeaderEntry { + DW_LNCT_path(0x1), + DW_LNCT_directory_index(0x2), + DW_LNCT_timestamp(0x3), + DW_LNCT_size(0x4), + DW_LNCT_MD5(0x5); + + private final int value; + + DwarfLineNumberHeaderEntry(int i) { + value = i; + } + + public int value() { + return value; + } +} diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfLineOpcode.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfLineOpcode.java index 2bb3269ee327..406af4ce539f 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfLineOpcode.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfLineOpcode.java @@ -73,12 +73,12 @@ public enum DwarfLineOpcode { /* * Increment address 1 ushort arg. */ - @SuppressWarnings("unused") + @SuppressWarnings("unused")// DW_LNS_set_prologue_end((byte) 10), /* * Increment address 1 ushort arg. */ - @SuppressWarnings("unused") + @SuppressWarnings("unused")// DW_LNS_set_epilogue_begin((byte) 11), /* * Extended line section opcodes defined by DWARF 2. @@ -86,7 +86,7 @@ public enum DwarfLineOpcode { /* * There is no extended opcode 0. */ - @SuppressWarnings("unused") + @SuppressWarnings("unused")// DW_LNE_undefined((byte) 0), /* * End sequence of addresses. diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfSectionName.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfSectionName.java index 9d91ebf24437..09d0366fa9c6 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfSectionName.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfSectionName.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2023, 2023, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -33,6 +33,7 @@ public enum DwarfSectionName { TEXT_SECTION(".text"), DW_STR_SECTION(".debug_str"), + DW_LINE_STR_SECTION(".debug_line_str"), DW_LINE_SECTION(".debug_line"), DW_FRAME_SECTION(".debug_frame"), DW_ABBREV_SECTION(".debug_abbrev"), diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfTag.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfTag.java index 77527e493900..50ce270354b4 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfTag.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfTag.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2023, 2023, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfVersion.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfVersion.java index de98c7e83fb2..970c748638ff 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfVersion.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfVersion.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2023, 2023, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVDebugInfo.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVDebugInfo.java index 27e0efc5cf04..66b9df7c5e22 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVDebugInfo.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVDebugInfo.java @@ -26,13 +26,14 @@ package com.oracle.objectfile.pecoff.cv; +import java.nio.ByteOrder; + import com.oracle.objectfile.debugentry.DebugInfoBase; import com.oracle.objectfile.pecoff.PECoffMachine; + import jdk.graal.compiler.debug.DebugContext; import jdk.graal.compiler.debug.GraalError; -import java.nio.ByteOrder; - /** * CVDebugInfo is a container class for all the CodeView sections to be emitted in the object file. * Currently, those are.debug$S (CVSymbolSectionImpl) and .debug$T (CVTypeSectionImpl). diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVLineRecordBuilder.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVLineRecordBuilder.java index dd7f777b3bbe..e80c0730a478 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVLineRecordBuilder.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVLineRecordBuilder.java @@ -26,12 +26,11 @@ package com.oracle.objectfile.pecoff.cv; -import com.oracle.objectfile.debugentry.FileEntry; +import java.util.Iterator; + import com.oracle.objectfile.debugentry.CompiledMethodEntry; +import com.oracle.objectfile.debugentry.FileEntry; import com.oracle.objectfile.debugentry.range.Range; -import com.oracle.objectfile.debugentry.range.SubRange; - -import java.util.Iterator; /* * In CV4, the line table consists of a series of file headers followed by line number entries. @@ -60,16 +59,16 @@ public void debug(String format, Object... args) { */ CVLineRecord build(CompiledMethodEntry entry) { this.compiledEntry = entry; - Range primaryRange = compiledEntry.getPrimary(); + Range primaryRange = compiledEntry.primary(); debug("DEBUG_S_LINES linerecord for 0x%05x file: %s:%d", primaryRange.getLo(), primaryRange.getFileName(), primaryRange.getLine()); this.lineRecord = new CVLineRecord(cvDebugInfo, primaryRange.getSymbolName()); debug("CVLineRecord.computeContents: processing primary range %s", primaryRange); processRange(primaryRange); - Iterator iterator = compiledEntry.leafRangeIterator(); + Iterator iterator = compiledEntry.leafRangeStream().iterator(); while (iterator.hasNext()) { - SubRange subRange = iterator.next(); + Range subRange = iterator.next(); debug("CVLineRecord.computeContents: processing range %s", subRange); processRange(subRange); } @@ -102,9 +101,9 @@ private void processRange(Range range) { } /* Add line record. */ - int lineLoAddr = range.getLo() - compiledEntry.getPrimary().getLo(); + int lineLoAddr = (int) (range.getLo() - compiledEntry.primary().getLo()); int line = Math.max(range.getLine(), 1); - debug(" processRange: addNewLine: 0x%05x-0x%05x %s", lineLoAddr, range.getHi() - compiledEntry.getPrimary().getLo(), line); + debug(" processRange: addNewLine: 0x%05x-0x%05x %s", lineLoAddr, range.getHi() - compiledEntry.primary().getLo(), line); lineRecord.addNewLine(lineLoAddr, line); } } diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVNames.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVNames.java index a1666dc08ce3..1c71b03e5d02 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVNames.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVNames.java @@ -41,10 +41,10 @@ static String typeNameToCodeViewName(TypeEntry typeEntry) { } static String methodNameToCodeViewName(MethodEntry memberEntry) { - return typeNameToCodeViewName(memberEntry.ownerType()) + "::" + memberEntry.methodName(); + return typeNameToCodeViewName(memberEntry.getOwnerType()) + "::" + memberEntry.getMethodName(); } static String fieldNameToCodeViewName(FieldEntry memberEntry) { - return typeNameToCodeViewName(memberEntry.ownerType()) + "::" + memberEntry.fieldName(); + return typeNameToCodeViewName(memberEntry.getOwnerType()) + "::" + memberEntry.fieldName(); } } diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVSymbolSubrecord.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVSymbolSubrecord.java index 29d819c743f1..a1df75b6763f 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVSymbolSubrecord.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVSymbolSubrecord.java @@ -90,7 +90,7 @@ private static String findObjectName(CVDebugInfo cvDebugInfo) { String fn = null; for (ClassEntry classEntry : cvDebugInfo.getInstanceClasses()) { if (classEntry.getFileName() != null) { - fn = classEntry.getFileEntry().getFileName(); + fn = classEntry.getFileEntry().fileName(); if (fn.endsWith(".java")) { fn = fn.substring(0, fn.lastIndexOf(".java")) + ".obj"; } @@ -216,7 +216,7 @@ private static String findFirstFile(CVDebugInfo cvDebugInfo) { String fn = null; for (ClassEntry classEntry : cvDebugInfo.getInstanceClasses()) { if (classEntry.getFileName() != null) { - fn = classEntry.getFileEntry().getFileName(); + fn = classEntry.getFileEntry().fileName(); break; } } diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVSymbolSubsectionBuilder.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVSymbolSubsectionBuilder.java index 3d4722d384ac..9c8881d4c492 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVSymbolSubsectionBuilder.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVSymbolSubsectionBuilder.java @@ -26,6 +26,11 @@ package com.oracle.objectfile.pecoff.cv; +import static com.oracle.objectfile.pecoff.cv.CVConstants.CV_AMD64_R8; +import static com.oracle.objectfile.pecoff.cv.CVTypeConstants.MAX_PRIMITIVE; + +import java.lang.reflect.Modifier; + import com.oracle.objectfile.SectionName; import com.oracle.objectfile.debugentry.ClassEntry; import com.oracle.objectfile.debugentry.CompiledMethodEntry; @@ -33,11 +38,6 @@ import com.oracle.objectfile.debugentry.TypeEntry; import com.oracle.objectfile.debugentry.range.Range; -import java.lang.reflect.Modifier; - -import static com.oracle.objectfile.pecoff.cv.CVConstants.CV_AMD64_R8; -import static com.oracle.objectfile.pecoff.cv.CVTypeConstants.MAX_PRIMITIVE; - final class CVSymbolSubsectionBuilder { private final CVDebugInfo cvDebugInfo; @@ -85,13 +85,13 @@ void build() { private void buildClass(ClassEntry classEntry) { /* Define all the functions in this class all functions defined in this class. */ - classEntry.compiledEntries().forEach(this::buildFunction); + classEntry.compiledMethods().forEach(this::buildFunction); /* Define the class itself. */ addTypeRecords(classEntry); /* Add manifested static fields as S_GDATA32 records. */ - classEntry.fields().filter(CVSymbolSubsectionBuilder::isManifestedStaticField).forEach(f -> { + classEntry.getFields().stream().filter(CVSymbolSubsectionBuilder::isManifestedStaticField).forEach(f -> { int typeIndex = cvDebugInfo.getCVTypeSection().getIndexForPointer(f.getValueType()); String displayName = CVNames.fieldNameToCodeViewName(f); if (cvDebugInfo.useHeapBase()) { @@ -118,7 +118,7 @@ private static boolean isManifestedStaticField(FieldEntry fieldEntry) { * @param compiledEntry compiled method for this function */ private void buildFunction(CompiledMethodEntry compiledEntry) { - final Range primaryRange = compiledEntry.getPrimary(); + final Range primaryRange = compiledEntry.primary(); /* The name as it will appear in the debugger. */ final String debuggerName = CVNames.methodNameToCodeViewName(primaryRange.getMethodEntry()); @@ -129,8 +129,8 @@ private void buildFunction(CompiledMethodEntry compiledEntry) { /* S_PROC32 add function definition. */ int functionTypeIndex = addTypeRecords(compiledEntry); byte funcFlags = 0; - CVSymbolSubrecord.CVSymbolGProc32Record proc32 = new CVSymbolSubrecord.CVSymbolGProc32Record(cvDebugInfo, externalName, debuggerName, 0, 0, 0, primaryRange.getHi() - primaryRange.getLo(), 0, - 0, functionTypeIndex, (short) 0, funcFlags); + CVSymbolSubrecord.CVSymbolGProc32Record proc32 = new CVSymbolSubrecord.CVSymbolGProc32Record(cvDebugInfo, externalName, debuggerName, 0, 0, 0, + primaryRange.getHiOffset() - primaryRange.getLoOffset(), 0, 0, functionTypeIndex, (short) 0, funcFlags); addSymbolRecord(proc32); /* S_FRAMEPROC add frame definitions. */ @@ -139,7 +139,7 @@ private void buildFunction(CompiledMethodEntry compiledEntry) { int localBP = 1 << 14; /* Local base pointer = SP (0=none, 1=sp, 2=bp 3=r13). */ int paramBP = 1 << 16; /* Param base pointer = SP. */ int frameFlags = asynceh + localBP + paramBP; /* NB: LLVM uses 0x14000. */ - addSymbolRecord(new CVSymbolSubrecord.CVSymbolFrameProcRecord(cvDebugInfo, compiledEntry.getFrameSize(), frameFlags)); + addSymbolRecord(new CVSymbolSubrecord.CVSymbolFrameProcRecord(cvDebugInfo, compiledEntry.frameSize(), frameFlags)); /* TODO: add parameter definitions (types have been added already). */ /* TODO: add local variables, and their types. */ diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVTypeSectionBuilder.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVTypeSectionBuilder.java index 6ebd0088c30b..c3dd8bcd07af 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVTypeSectionBuilder.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVTypeSectionBuilder.java @@ -25,28 +25,6 @@ */ package com.oracle.objectfile.pecoff.cv; -import com.oracle.objectfile.debugentry.ArrayTypeEntry; -import com.oracle.objectfile.debugentry.ClassEntry; -import com.oracle.objectfile.debugentry.CompiledMethodEntry; -import com.oracle.objectfile.debugentry.FieldEntry; -import com.oracle.objectfile.debugentry.HeaderTypeEntry; -import com.oracle.objectfile.debugentry.MemberEntry; -import com.oracle.objectfile.debugentry.MethodEntry; -import com.oracle.objectfile.debugentry.StructureTypeEntry; -import com.oracle.objectfile.debugentry.TypeEntry; - -import jdk.graal.compiler.debug.GraalError; - -import java.lang.reflect.Modifier; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Deque; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; - import static com.oracle.objectfile.pecoff.cv.CVTypeConstants.ADDRESS_BITS; import static com.oracle.objectfile.pecoff.cv.CVTypeConstants.CV_CALL_NEAR_C; import static com.oracle.objectfile.pecoff.cv.CVTypeConstants.FUNC_IS_CONSTRUCTOR; @@ -82,8 +60,30 @@ import static com.oracle.objectfile.pecoff.cv.CVTypeConstants.T_UINT4; import static com.oracle.objectfile.pecoff.cv.CVTypeConstants.T_VOID; import static com.oracle.objectfile.pecoff.cv.CVTypeConstants.T_WCHAR; -import static com.oracle.objectfile.pecoff.cv.CVTypeRecord.CVClassRecord.ATTR_FORWARD_REF; import static com.oracle.objectfile.pecoff.cv.CVTypeRecord.CV_TYPE_RECORD_MAX_SIZE; +import static com.oracle.objectfile.pecoff.cv.CVTypeRecord.CVClassRecord.ATTR_FORWARD_REF; + +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Deque; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import com.oracle.objectfile.debugentry.ArrayTypeEntry; +import com.oracle.objectfile.debugentry.ClassEntry; +import com.oracle.objectfile.debugentry.CompiledMethodEntry; +import com.oracle.objectfile.debugentry.FieldEntry; +import com.oracle.objectfile.debugentry.HeaderTypeEntry; +import com.oracle.objectfile.debugentry.MemberEntry; +import com.oracle.objectfile.debugentry.MethodEntry; +import com.oracle.objectfile.debugentry.StructureTypeEntry; +import com.oracle.objectfile.debugentry.TypeEntry; + +import jdk.graal.compiler.debug.GraalError; class CVTypeSectionBuilder { @@ -112,31 +112,20 @@ CVTypeRecord buildType(TypeEntry typeEntry) { * If we've never seen the class or only defined it as a forward reference, define it now. */ if (typeRecord != null && typeRecord.type == LF_CLASS && !((CVTypeRecord.CVClassRecord) typeRecord).isForwardRef()) { - log("buildType() type %s(%s) is known %s", typeEntry.getTypeName(), typeEntry.typeKind().name(), typeRecord); + log("buildType() type %s(%s) is known %s", typeEntry.getTypeName(), "typeEntry.typeKind().name()", typeRecord); } else { - log("buildType() %s %s size=%d - begin", typeEntry.typeKind().name(), typeEntry.getTypeName(), typeEntry.getSize()); - switch (typeEntry.typeKind()) { - case PRIMITIVE: { - typeRecord = types.getExistingType(typeEntry); - break; - } - case ARRAY: - case ENUM: - case INSTANCE: - case INTERFACE: - // TODO continue treat foreign types as interfaces/classes but fix this later - case FOREIGN: { - typeRecord = buildStructureTypeEntry((StructureTypeEntry) typeEntry); - break; - } - case HEADER: { - /* - * The bits at the beginning of an Object: contains pointer to DynamicHub. - */ - assert typeEntry.getTypeName().equals(OBJ_HEADER_NAME); - typeRecord = buildStructureTypeEntry((HeaderTypeEntry) typeEntry); - break; - } + log("buildType() %s %s size=%d - begin", "typeEntry.typeKind().name()", typeEntry.getTypeName(), typeEntry.getSize()); + if (typeEntry.isPrimitive()) { + typeRecord = types.getExistingType(typeEntry); + } else if (typeEntry.isHeader()) { + /* + * The bits at the beginning of an Object: contains pointer to DynamicHub. + */ + assert typeEntry.getTypeName().equals(OBJ_HEADER_NAME); + typeRecord = buildStructureTypeEntry((HeaderTypeEntry) typeEntry); + } else if (typeEntry.isClass() || typeEntry.isArray()) { + // TODO continue treat foreign types as interfaces/classes but fix this later + typeRecord = buildStructureTypeEntry((StructureTypeEntry) typeEntry); } } assert typeRecord != null; @@ -151,7 +140,7 @@ CVTypeRecord buildType(TypeEntry typeEntry) { * @return type record for this function (may return existing matching record) */ CVTypeRecord buildFunction(CompiledMethodEntry entry) { - return buildMemberFunction(entry.getClassEntry(), entry.getPrimary().getMethodEntry()); + return buildMemberFunction(entry.classEntry(), entry.primary().getMethodEntry()); } static class FieldListBuilder { @@ -217,7 +206,7 @@ CVTypeRecord.CVFieldListRecord buildFieldListRecords(CVTypeSectionBuilder builde private CVTypeRecord buildStructureTypeEntry(final StructureTypeEntry typeEntry) { - log("buildStructureTypeEntry size=%d kind=%s %s", typeEntry.getSize(), typeEntry.typeKind().name(), typeEntry.getTypeName()); + log("buildStructureTypeEntry size=%d kind=%s %s", typeEntry.getSize(), "typeEntry.typeKind().name()", typeEntry.getTypeName()); ClassEntry superType = typeEntry.isClass() ? ((ClassEntry) typeEntry).getSuperClass() : null; int superTypeIndex = superType != null ? types.getIndexForForwardRef(superType) : 0; @@ -247,7 +236,7 @@ private CVTypeRecord buildStructureTypeEntry(final StructureTypeEntry typeEntry) } /* Only define manifested fields. */ - typeEntry.fields().filter(CVTypeSectionBuilder::isManifestedField).forEach(f -> { + typeEntry.getFields().stream().filter(CVTypeSectionBuilder::isManifestedField).forEach(f -> { log("field %s attr=(%s) offset=%d size=%d valuetype=%s", f.fieldName(), f.getModifiersString(), f.getOffset(), f.getSize(), f.getValueType().getTypeName()); CVTypeRecord.FieldRecord fieldRecord = buildField(f); log("field %s", fieldRecord); @@ -286,10 +275,10 @@ private CVTypeRecord buildStructureTypeEntry(final StructureTypeEntry typeEntry) HashSet overloaded = new HashSet<>(methods.size()); HashSet allFunctions = new HashSet<>(methods.size()); methods.forEach(m -> { - if (allFunctions.contains(m.methodName())) { - overloaded.add(m.methodName()); + if (allFunctions.contains(m.getMethodName())) { + overloaded.add(m.getMethodName()); } else { - allFunctions.add(m.methodName()); + allFunctions.add(m.getMethodName()); } }); @@ -300,12 +289,12 @@ private CVTypeRecord buildStructureTypeEntry(final StructureTypeEntry typeEntry) CVTypeRecord.CVTypeMethodListRecord mlist = new CVTypeRecord.CVTypeMethodListRecord(); /* LF_MFUNCTION records */ - methods.stream().filter(methodEntry -> methodEntry.methodName().equals(mname)).forEach(m -> { - log("overloaded method %s attr=(%s) valuetype=%s", m.methodName(), m.getModifiersString(), m.getValueType().getTypeName()); + methods.stream().filter(methodEntry -> methodEntry.getMethodName().equals(mname)).forEach(m -> { + log("overloaded method %s attr=(%s) valuetype=%s", m.getMethodName(), m.getModifiersString(), m.getValueType().getTypeName()); CVTypeRecord.CVTypeMFunctionRecord mFunctionRecord = buildMemberFunction((ClassEntry) typeEntry, m); short attr = modifiersToAttr(m); log(" overloaded method %s", mFunctionRecord); - mlist.add(attr, mFunctionRecord.getSequenceNumber(), m.getVtableOffset(), m.methodName()); + mlist.add(attr, mFunctionRecord.getSequenceNumber(), m.getVtableOffset(), m.getMethodName()); }); CVTypeRecord.CVTypeMethodListRecord nmlist = addTypeRecord(mlist); @@ -315,8 +304,8 @@ private CVTypeRecord buildStructureTypeEntry(final StructureTypeEntry typeEntry) fieldListBuilder.addField(methodRecord); }); - methods.stream().filter(methodEntry -> !overloaded.contains(methodEntry.methodName())).forEach(m -> { - log("`unique method %s %s(...)", m.methodName(), m.getModifiersString(), m.getValueType().getTypeName(), m.methodName()); + methods.stream().filter(methodEntry -> !overloaded.contains(methodEntry.getMethodName())).forEach(m -> { + log("`unique method %s %s(...)", m.getMethodName(), m.getModifiersString(), m.getValueType().getTypeName(), m.getMethodName()); CVTypeRecord.CVOneMethodRecord method = buildMethod((ClassEntry) typeEntry, m); log(" unique method %s", method); fieldListBuilder.addField(method); @@ -395,7 +384,7 @@ private static short modifiersToAttr(FieldEntry member) { private CVTypeRecord.CVOneMethodRecord buildMethod(ClassEntry classEntry, MethodEntry methodEntry) { CVTypeRecord.CVTypeMFunctionRecord funcRecord = buildMemberFunction(classEntry, methodEntry); short attr = modifiersToAttr(methodEntry); - return new CVTypeRecord.CVOneMethodRecord(attr, funcRecord.getSequenceNumber(), methodEntry.getVtableOffset(), methodEntry.methodName()); + return new CVTypeRecord.CVOneMethodRecord(attr, funcRecord.getSequenceNumber(), methodEntry.getVtableOffset(), methodEntry.getMethodName()); } private static short accessToAttr(MemberEntry member) { @@ -423,8 +412,8 @@ CVTypeRecord.CVTypeMFunctionRecord buildMemberFunction(ClassEntry classEntry, Me mFunctionRecord.setFuncAttr(attr); mFunctionRecord.setReturnType(types.getIndexForPointerOrPrimitive(methodEntry.getValueType())); CVTypeRecord.CVTypeArglistRecord argListType = new CVTypeRecord.CVTypeArglistRecord(); - for (int i = 0; i < methodEntry.getParamCount(); i++) { - argListType.add(types.getIndexForPointerOrPrimitive(methodEntry.getParamType(i))); + for (TypeEntry paramType : methodEntry.getParamTypes()) { + argListType.add(types.getIndexForPointerOrPrimitive(paramType)); } argListType = addTypeRecord(argListType); mFunctionRecord.setArgList(argListType); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java index 1eab692d7cfe..d7800f1315c9 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java @@ -475,6 +475,9 @@ public static void setImageLayerCreateEnabledHandler(OptionEnabledHandler IncludeNodeSourcePositions = new HostedOptionKey<>(false); + @Option(help = "Provide debuginfo for runtime-compiled code.")// + public static final HostedOptionKey RuntimeDebugInfo = new HostedOptionKey<>(false); + @Option(help = "Search path for C libraries passed to the linker (list of comma-separated directories)", stability = OptionStability.STABLE)// @BundleMember(role = BundleMember.Role.Input)// public static final HostedOptionKey CLibraryPath = new HostedOptionKey<>(AccumulatingLocatableMultiOptionValue.Paths.buildWithCommaDelimiter()); @@ -1003,6 +1006,16 @@ public static boolean useDebugInfoGeneration() { return useLIRBackend() && GenerateDebugInfo.getValue() > 0; } + @Option(help = "Number of threads used to generate debug info.") // + public static final HostedOptionKey DebugInfoGenerationThreadCount = new HostedOptionKey<>(0, SubstrateOptions::validateDebugInfoGenerationThreadCount); + + private static void validateDebugInfoGenerationThreadCount(HostedOptionKey optionKey) { + int value = optionKey.getValue(); + if (value <= 0) { + throw UserError.invalidOptionValue(optionKey, value, "The value must be bigger than 0"); + } + } + @Option(help = "Directory under which to create source file cache for Application or GraalVM classes")// static final HostedOptionKey DebugInfoSourceCacheRoot = new HostedOptionKey<>("sources"); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoDecoder.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoDecoder.java index f82497f7e3cc..fd16011867dc 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoDecoder.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoDecoder.java @@ -347,7 +347,7 @@ private static long loadReferenceMapIndex(CodeInfo info, long entryOffset, int e static final int FRAME_SIZE_ENTRY_POINT = 0b010; static final int FRAME_SIZE_HAS_CALLEE_SAVED_REGISTERS = 0b100; - static final int FRAME_SIZE_STATUS_MASK = FRAME_SIZE_METHOD_START | FRAME_SIZE_ENTRY_POINT | FRAME_SIZE_HAS_CALLEE_SAVED_REGISTERS; + public static final int FRAME_SIZE_STATUS_MASK = FRAME_SIZE_METHOD_START | FRAME_SIZE_ENTRY_POINT | FRAME_SIZE_HAS_CALLEE_SAVED_REGISTERS; @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static boolean decodeIsEntryPoint(long sizeEncoding) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CompilationResultFrameTree.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CompilationResultFrameTree.java index c31ee7208676..4c7b3af55c48 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CompilationResultFrameTree.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CompilationResultFrameTree.java @@ -496,16 +496,12 @@ public CallNode build(CompilationResult compilationResult) { SourceMappingWrapper wrapper = SourceMappingWrapper.create(sourceMapping, maxDepth); if (wrapper != null) { if (wrapper.getStartOffset() > targetCodeSize - 1) { - if (debug.isLogEnabled(DebugContext.DETAILED_LEVEL)) { - debug.log(" Discard SourceMapping outside code-range %s", SourceMappingWrapper.getSourceMappingString(sourceMapping)); - } + debug.log(DebugContext.DETAILED_LEVEL, " Discard SourceMapping outside code-range %s", SourceMappingWrapper.getSourceMappingString(sourceMapping)); continue; } sourcePosData.add(wrapper); } else { - if (debug.isLogEnabled(DebugContext.DETAILED_LEVEL)) { - debug.log(" Discard SourceMapping %s", SourceMappingWrapper.getSourceMappingString(sourceMapping)); - } + debug.log(DebugContext.DETAILED_LEVEL, " Discard SourceMapping %s", SourceMappingWrapper.getSourceMappingString(sourceMapping)); } } } @@ -697,7 +693,7 @@ private CallNode visitFrame(SourcePositionSupplier sourcePos, BytecodePosition f } boolean hasEqualCaller = FrameNode.hasEqualCaller(root.frame, frame); - if (debug.isLogEnabled() && !hasEqualCaller) { + if (!hasEqualCaller) { debug.log("Bottom frame mismatch for %s", sourcePos); } if (callee == null && hasEqualCaller) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/InstalledCodeObserverSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/InstalledCodeObserverSupport.java index 8a3b92ec2b51..cb68d519897b 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/InstalledCodeObserverSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/InstalledCodeObserverSupport.java @@ -27,7 +27,6 @@ import java.util.ArrayList; import java.util.List; -import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.word.Pointer; @@ -42,6 +41,7 @@ import jdk.graal.compiler.code.CompilationResult; import jdk.graal.compiler.debug.DebugContext; +import jdk.graal.compiler.word.Word; @AutomaticallyRegisteredImageSingleton public final class InstalledCodeObserverSupport { @@ -103,6 +103,7 @@ public static void attachToCurrentIsolate(NonmovableArray observerHandles) { forEach(observerHandles, ACTION_RELEASE); + clearObserverHandles(observerHandles); } private interface InstalledCodeObserverHandleAction { @@ -121,6 +122,18 @@ private static void forEach(NonmovableArray array, } } + private static void clearObserverHandles(NonmovableArray array) { + if (array.isNonNull()) { + int length = NonmovableArrays.lengthOf(array); + for (int i = 0; i < length; i++) { + InstalledCodeObserverHandle handle = NonmovableArrays.getWord(array, i); + if (handle.isNonNull()) { + NonmovableArrays.setWord(array, i, Word.nullPointer()); + } + } + } + } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static void removeObserversOnTearDown(NonmovableArray observerHandles) { if (observerHandles.isNonNull()) { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageBFDNameProvider.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/BFDNameProvider.java similarity index 95% rename from substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageBFDNameProvider.java rename to substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/BFDNameProvider.java index e90a74dd6e52..9b0a2e95568a 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageBFDNameProvider.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/BFDNameProvider.java @@ -22,33 +22,33 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ -package com.oracle.svm.hosted.image; +package com.oracle.svm.core.debug; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Executable; +import java.lang.reflect.Member; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.util.List; + +import org.graalvm.collections.EconomicMap; -import com.oracle.graal.pointsto.meta.AnalysisType; import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.UniqueShortNameProvider; +import com.oracle.svm.core.meta.SharedType; import com.oracle.svm.core.util.VMError; -import com.oracle.svm.hosted.c.NativeLibraries; -import com.oracle.svm.hosted.meta.HostedType; + import jdk.vm.ci.meta.JavaKind; import jdk.vm.ci.meta.JavaType; import jdk.vm.ci.meta.ResolvedJavaType; import jdk.vm.ci.meta.Signature; import jdk.vm.ci.meta.UnresolvedJavaType; -import org.graalvm.collections.EconomicMap; - -import java.lang.reflect.Constructor; -import java.lang.reflect.Executable; -import java.lang.reflect.Member; -import java.lang.reflect.Method; -import java.lang.reflect.Parameter; -import java.util.List; /** * A unique name provider employed when debug info generation is enabled on Linux. Names are * generated in the C++ mangled format that is understood by the Linux binutils BFD library. An ELF * symbol defined using a BFD mangled name is linked to a corresponding DWARF method or field info - * declaration (DIE) by inserting it as the value of the the linkage_name attribute. In + * declaration (DIE) by inserting it as the value of the linkage_name attribute. In * order for this linkage to be correctly recognised by the debugger and Linux tools the * name attributes of the associated class, method, field, parameter types and return * type must be the same as the corresponding names encoded in the mangled name. @@ -56,15 +56,11 @@ * Note that when the class component of a mangled name needs to be qualified with a class loader id * the corresponding DWARF class record must embed the class in a namespace whose name matches the * classloader id otherwise the mangled name will not be recognised and demangled successfully. - * TODO: Namespace embedding is not yet implemented. */ -class NativeImageBFDNameProvider implements UniqueShortNameProvider { +public class BFDNameProvider implements UniqueShortNameProvider { - private NativeLibraries nativeLibs; - - NativeImageBFDNameProvider(List ignore) { + public BFDNameProvider(List ignore) { this.ignoredLoaders = ignore; - this.nativeLibs = null; } @Override @@ -113,9 +109,8 @@ private static ClassLoader getClassLoader(ResolvedJavaType type) { if (type.isArray()) { return getClassLoader(type.getElementalType()); } - if (type instanceof HostedType) { - HostedType hostedType = (HostedType) type; - return hostedType.getJavaClass().getClassLoader(); + if (type instanceof SharedType sharedType && sharedType.getHub().isLoaded()) { + return sharedType.getHub().getClassLoader(); } return null; } @@ -410,17 +405,6 @@ public String bfdMangle(Member m) { return new BFDMangler(this).mangle(m); } - /** - * Make the provider aware of the current native libraries. This is needed because the provider - * is created in a feature after registration but the native libraries are only available before - * analysis. - * - * @param nativeLibs the current native libraries singleton. - */ - public void setNativeLibs(NativeLibraries nativeLibs) { - this.nativeLibs = nativeLibs; - } - private static class BFDMangler { /* @@ -464,7 +448,7 @@ private static class BFDMangler { * arise. A name can always be correctly encoded without repeats. In the above example that * would be _ZN5Hello9compareToEJiPS_. */ - final NativeImageBFDNameProvider nameProvider; + final BFDNameProvider nameProvider; final StringBuilder sb; // A list of lookup names identifying substituted elements. A prospective name for an @@ -524,7 +508,7 @@ public String toString() { } } - BFDMangler(NativeImageBFDNameProvider provider) { + BFDMangler(BFDNameProvider provider) { nameProvider = provider; sb = new StringBuilder("_Z"); bindings = EconomicMap.create(); @@ -748,7 +732,7 @@ private void mangleType(ResolvedJavaType type) { } else { String loaderName = nameProvider.classLoaderNameAndId(type); String className = type.toJavaName(); - if (nameProvider.needsPointerPrefix(type)) { + if (needsPointerPrefix(type)) { mangleClassPointer(loaderName, className); } else { mangleClassName(loaderName, className); @@ -907,17 +891,11 @@ private void recordName(LookupName name) { * @param type The type to be checked. * @return true if the type needs to be encoded using pointer prefix P otherwise false. */ - private boolean needsPointerPrefix(ResolvedJavaType type) { - ResolvedJavaType target = type; - if (type instanceof HostedType) { - // unwrap to analysis type as that is what native libs checks test against - target = ((HostedType) target).getWrapped(); + private static boolean needsPointerPrefix(ResolvedJavaType type) { + if (type instanceof SharedType sharedType) { + /* Word types have the kind Object, but a primitive storageKind. */ + return sharedType.getJavaKind() == JavaKind.Object && sharedType.getStorageKind() == sharedType.getJavaKind(); } - if (target instanceof AnalysisType) { - assert nativeLibs != null : "No native libs during or after analysis!"; - return !nativeLibs.isWordBase(target); - } - return true; + return false; } - } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/GdbJitInterface.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/GdbJitInterface.java new file mode 100644 index 000000000000..bcb0c7e539ae --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/GdbJitInterface.java @@ -0,0 +1,201 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.core.debug; + +import java.util.Collections; +import java.util.List; +import java.util.function.BooleanSupplier; + +import org.graalvm.nativeimage.CurrentIsolate; +import org.graalvm.nativeimage.IsolateThread; +import org.graalvm.nativeimage.c.CContext; +import org.graalvm.nativeimage.c.constant.CEnum; +import org.graalvm.nativeimage.c.constant.CEnumValue; +import org.graalvm.nativeimage.c.function.CEntryPoint; +import org.graalvm.nativeimage.c.struct.CField; +import org.graalvm.nativeimage.c.struct.CStruct; +import org.graalvm.nativeimage.c.type.CCharPointer; +import org.graalvm.nativeimage.c.type.CUnsigned; +import org.graalvm.word.PointerBase; + +import com.oracle.svm.core.NeverInline; +import com.oracle.svm.core.SubstrateOptions; +import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.c.CGlobalData; +import com.oracle.svm.core.c.CGlobalDataFactory; +import com.oracle.svm.core.c.ProjectHeaderFile; + +import jdk.graal.compiler.word.Word; + +@CContext(GdbJitInterface.GdbJitInterfaceDirectives.class) +public class GdbJitInterface { + + public static class GdbJitInterfaceDirectives implements CContext.Directives { + @Override + public boolean isInConfiguration() { + return SubstrateOptions.RuntimeDebugInfo.getValue(); + } + + @Override + public List getHeaderFiles() { + return Collections.singletonList(ProjectHeaderFile.resolve("", "include/gdb_jit_compilation_interface.h")); + } + } + + private static final class IncludeForRuntimeDebugOnly implements BooleanSupplier { + @Override + public boolean getAsBoolean() { + return SubstrateOptions.RuntimeDebugInfo.getValue(); + } + } + + @CEnum(value = "jit_actions_t") + public enum JITActions { + JIT_NOACTION, + JIT_REGISTER, + JIT_UNREGISTER; + + @CEnumValue + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public native int getCValue(); + } + + @CStruct(value = "jit_code_entry", addStructKeyword = true) + public interface JITCodeEntry extends PointerBase { + // struct jit_code_entry *next_entry; + @CField("next_entry") + JITCodeEntry getNextEntry(); + + @CField("next_entry") + void setNextEntry(JITCodeEntry jitCodeEntry); + + // struct jit_code_entry *prev_entry; + @CField("prev_entry") + JITCodeEntry getPrevEntry(); + + @CField("prev_entry") + void setPrevEntry(JITCodeEntry jitCodeEntry); + + // const char *symfile_addr; + @CField("symfile_addr") + CCharPointer getSymfileAddr(); + + @CField("symfile_addr") + void setSymfileAddr(CCharPointer symfileAddr); + + // uint64_t symfile_size; + @CField("symfile_size") + @CUnsigned + long getSymfileSize(); + + @CField("symfile_size") + void setSymfileSize(@CUnsigned long symfileSize); + } + + @CStruct(value = "jit_descriptor", addStructKeyword = true) + public interface JITDescriptor extends PointerBase { + // uint32_t version; + @CField("version") + @CUnsigned + int getVersion(); + + @CField("version") + void setVersion(@CUnsigned int version); + + // uint32_t action_flag; + @CField("action_flag") + @CUnsigned + int getActionFlag(); + + @CField("action_flag") + void setActionFlag(@CUnsigned int actionFlag); + + // struct jit_code_entry *relevant_entry; + @CField("relevant_entry") + JITCodeEntry getRelevantEntry(); + + @CField("relevant_entry") + void setRelevantEntry(JITCodeEntry jitCodeEntry); + + // struct jit_code_entry *first_entry; + @CField("first_entry") + JITCodeEntry getFirstEntry(); + + @CField("first_entry") + void setFirstEntry(JITCodeEntry jitCodeEntry); + } + + @NeverInline("Register JIT code stub for GDB.") + @CEntryPoint(name = "__jit_debug_register_code", include = IncludeForRuntimeDebugOnly.class, publishAs = CEntryPoint.Publish.SymbolOnly) + private static void jitDebugRegisterCode(@SuppressWarnings("unused") IsolateThread thread) { + } + + private static final CGlobalData jitDebugDescriptor = CGlobalDataFactory.forSymbol("__jit_debug_descriptor"); + + public static void registerJITCode(CCharPointer addr, @CUnsigned long size, JITCodeEntry entry) { + /* Create new jit_code_entry */ + entry.setSymfileAddr(addr); + entry.setSymfileSize(size); + + /* Insert entry at head of the list. */ + JITCodeEntry nextEntry = jitDebugDescriptor.get().getFirstEntry(); + entry.setPrevEntry(Word.nullPointer()); + entry.setNextEntry(nextEntry); + + if (nextEntry.isNonNull()) { + nextEntry.setPrevEntry(entry); + } + + /* Notify GDB. */ + jitDebugDescriptor.get().setActionFlag(JITActions.JIT_REGISTER.getCValue()); + jitDebugDescriptor.get().setFirstEntry(entry); + jitDebugDescriptor.get().setRelevantEntry(entry); + jitDebugRegisterCode(CurrentIsolate.getCurrentThread()); + } + + @Uninterruptible(reason = "Called with raw object pointer.", calleeMustBe = false) + public static void unregisterJITCode(JITCodeEntry entry) { + JITCodeEntry prevEntry = entry.getPrevEntry(); + JITCodeEntry nextEntry = entry.getNextEntry(); + + /* Fix prev and next in list */ + if (nextEntry.isNonNull()) { + nextEntry.setPrevEntry(prevEntry); + } + + if (prevEntry.isNonNull()) { + prevEntry.setNextEntry(nextEntry); + } else { + assert (jitDebugDescriptor.get().getFirstEntry().equal(entry)); + jitDebugDescriptor.get().setFirstEntry(nextEntry); + } + + /* Notify GDB. */ + jitDebugDescriptor.get().setActionFlag(JITActions.JIT_UNREGISTER.getCValue()); + jitDebugDescriptor.get().setRelevantEntry(entry); + jitDebugRegisterCode(CurrentIsolate.getCurrentThread()); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SharedDebugInfoProvider.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SharedDebugInfoProvider.java new file mode 100644 index 000000000000..7c71a121e8b0 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SharedDebugInfoProvider.java @@ -0,0 +1,1699 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.core.debug; + +import java.lang.reflect.Modifier; +import java.nio.file.FileSystems; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.SortedSet; +import java.util.TreeSet; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Stream; + +import org.graalvm.collections.Pair; +import org.graalvm.word.WordBase; + +import com.oracle.objectfile.ObjectFile; +import com.oracle.objectfile.debugentry.ArrayTypeEntry; +import com.oracle.objectfile.debugentry.ClassEntry; +import com.oracle.objectfile.debugentry.CompiledMethodEntry; +import com.oracle.objectfile.debugentry.ConstantValueEntry; +import com.oracle.objectfile.debugentry.DirEntry; +import com.oracle.objectfile.debugentry.EnumClassEntry; +import com.oracle.objectfile.debugentry.FieldEntry; +import com.oracle.objectfile.debugentry.FileEntry; +import com.oracle.objectfile.debugentry.ForeignTypeEntry; +import com.oracle.objectfile.debugentry.FrameSizeChangeEntry; +import com.oracle.objectfile.debugentry.HeaderTypeEntry; +import com.oracle.objectfile.debugentry.InterfaceClassEntry; +import com.oracle.objectfile.debugentry.LoaderEntry; +import com.oracle.objectfile.debugentry.LocalEntry; +import com.oracle.objectfile.debugentry.LocalValueEntry; +import com.oracle.objectfile.debugentry.MethodEntry; +import com.oracle.objectfile.debugentry.PrimitiveTypeEntry; +import com.oracle.objectfile.debugentry.RegisterValueEntry; +import com.oracle.objectfile.debugentry.StackValueEntry; +import com.oracle.objectfile.debugentry.StructureTypeEntry; +import com.oracle.objectfile.debugentry.TypeEntry; +import com.oracle.objectfile.debugentry.range.CallRange; +import com.oracle.objectfile.debugentry.range.LeafRange; +import com.oracle.objectfile.debugentry.range.PrimaryRange; +import com.oracle.objectfile.debugentry.range.Range; +import com.oracle.objectfile.debuginfo.DebugInfoProvider; +import com.oracle.svm.core.SubstrateOptions; +import com.oracle.svm.core.UniqueShortNameProvider; +import com.oracle.svm.core.code.CompilationResultFrameTree; +import com.oracle.svm.core.config.ConfigurationValues; +import com.oracle.svm.core.config.ObjectLayout; +import com.oracle.svm.core.graal.code.SubstrateBackend; +import com.oracle.svm.core.graal.code.SubstrateCallingConvention; +import com.oracle.svm.core.graal.code.SubstrateCallingConventionKind; +import com.oracle.svm.core.graal.code.SubstrateCallingConventionType; +import com.oracle.svm.core.graal.meta.RuntimeConfiguration; +import com.oracle.svm.core.graal.meta.SubstrateRegisterConfig; +import com.oracle.svm.core.heap.Heap; +import com.oracle.svm.core.heap.ReferenceAccess; +import com.oracle.svm.core.meta.SharedMethod; +import com.oracle.svm.core.meta.SharedType; + +import jdk.graal.compiler.api.replacements.Fold; +import jdk.graal.compiler.code.CompilationResult; +import jdk.graal.compiler.core.common.CompilationIdentifier; +import jdk.graal.compiler.core.target.Backend; +import jdk.graal.compiler.debug.DebugContext; +import jdk.graal.compiler.graph.NodeSourcePosition; +import jdk.graal.compiler.util.Digest; +import jdk.vm.ci.code.BytecodeFrame; +import jdk.vm.ci.code.BytecodePosition; +import jdk.vm.ci.code.CallingConvention; +import jdk.vm.ci.code.RegisterConfig; +import jdk.vm.ci.code.RegisterValue; +import jdk.vm.ci.code.StackSlot; +import jdk.vm.ci.code.ValueUtil; +import jdk.vm.ci.meta.AllocatableValue; +import jdk.vm.ci.meta.JavaConstant; +import jdk.vm.ci.meta.JavaKind; +import jdk.vm.ci.meta.JavaType; +import jdk.vm.ci.meta.JavaValue; +import jdk.vm.ci.meta.LineNumberTable; +import jdk.vm.ci.meta.Local; +import jdk.vm.ci.meta.LocalVariableTable; +import jdk.vm.ci.meta.MetaAccessProvider; +import jdk.vm.ci.meta.PrimitiveConstant; +import jdk.vm.ci.meta.ResolvedJavaField; +import jdk.vm.ci.meta.ResolvedJavaMethod; +import jdk.vm.ci.meta.ResolvedJavaType; +import jdk.vm.ci.meta.Signature; + +/** + * A shared base for providing debug info that can be processed by any debug info format specified + * in {@link ObjectFile}. Implements most of the {@link DebugInfoProvider} interface. + * + *

    + * Debug Info is provided as debug entries, with each entry containing debug info for a logical + * unit, i.e. Types, ClassLoaders, Methods, Fields, Compilations as well as the underlying file + * system. Debug entries model the hosted universe to provide a normalized view on debug info data + * for all debug info implementations. + *

    + * + *
      + * The Debug Info primarily consists of the following debug entries: + *
    • {@link DirEntry}: Represents a parent directory for one or more file entries, based on the + * debug sources directory. One root directory entry contains the path to the debug sources.
    • + *
    • {@link FileEntry}: Represents a single source file, which may be used by the debugger to + * provide source code. During native-image build, the + * {@code com.oracle.svm.hosted.image.sources.SourceManager} safes all the processed source files + * and provides the debug sources directory.
    • + *
    • {@link LoaderEntry}: Represents a {@link ClassLoader}. Built-in class loaders and image + * classloaders are not stored as loader entries, because they are implicitly inferred for types + * with no {@code LoaderEntry}.
    • + *
    • {@link TypeEntry}: Represents a {@link SharedType}. For native image build time debug info + * there exists one {@code TypeEntry} per type in the hosted universe. Type entries are divided into + * following categories: + *
        + *
      • {@link PrimitiveTypeEntry}: Represents a primitive java type.
      • + *
      • {@link HeaderTypeEntry}: A special {@code TypeEntry} that represents the object header + * information in the native image heap, as sort of a super type to {@link Object}.
      • + *
      • {@link ArrayTypeEntry}: Represents an array type.
      • + *
      • {@link ForeignTypeEntry}: Represents a type that is not a java class, e.g. + * {@link org.graalvm.nativeimage.c.struct.CStruct CStruct}, + * {@link org.graalvm.nativeimage.c.struct.CPointerTo CPointerTo}, ... .
      • + *
      • {@link EnumClassEntry}: Represents an {@link Enum} class.
      • + *
      • {@link InterfaceClassEntry}: Represents an interface, and stores references to all + * implementors.
      • + *
      • {@link ClassEntry}: Represents any other java class that is not already covered by other type + * entries (Instance classes).
      • + *
      + *
    • + *
    • {@link MethodEntry}: Represents the method declaration of a {@link SharedMethod} and holds a + * list of all parameters and locals that are used within the method.
    • + *
    • {@link CompiledMethodEntry}: Represents a {@link CompilationResult}. Is composed of ranges, + * i.e. frame states and location information of params and locals (where variables are stored). A + * {@code CompiledMethodEntry} always has a {@link PrimaryRange} that spans the whole compilation, + * which is further composed of: + *
        + *
      • {@link LeafRange}: A leaf in the {@link CompilationResultFrameTree}.
      • + *
      • {@link CallRange}: A {@code CallNode} in the {@link CompilationResultFrameTree}. Represents + * inlined calls and is therefore itself composed of ranges.
      • + *
      + *
    • + *
    + */ +public abstract class SharedDebugInfoProvider implements DebugInfoProvider { + + protected final RuntimeConfiguration runtimeConfiguration; + + protected final MetaAccessProvider metaAccess; + + protected final DebugContext debug; + + protected final boolean useHeapBase; + protected final int compressionShift; + protected final int referenceSize; + protected final int pointerSize; + protected final int objectAlignment; + protected final int reservedBitsMask; + + /** + * The {@code SharedType} for {@link Class}. This is the type that represents a dynamic hub in + * the native image. + */ + protected final SharedType hubType; + + /** + * The {@code SharedType} for {@link WordBase}. This is used to check for foreign types. + */ + protected final SharedType wordBaseType; + + /** + * The {@code SharedType} for {@code void}. This is used as fallback for foreign pointer types, + * if there is no type it points to. + */ + protected final SharedType voidType; + + /** + * An index map that holds all unique dir entries used for file entries in the + * {@link #fileIndex}. + */ + private final ConcurrentHashMap dirIndex = new ConcurrentHashMap<>(); + + /** + * An index map that holds all unique file entries used for type entries and method entries in + * the {@link #typeIndex} and {@link #methodIndex}. + */ + private final ConcurrentHashMap fileIndex = new ConcurrentHashMap<>(); + + /** + * An index map that holds all unique loader entries used for type entries in the + * {@link #typeIndex}. + */ + private final ConcurrentHashMap loaderIndex = new ConcurrentHashMap<>(); + + /** + * An index map that holds all unique type entries except the {@link #headerTypeEntry}. + */ + private final ConcurrentHashMap typeIndex = new ConcurrentHashMap<>(); + + /** + * An index map that holds all unique method entries used for type entries compiled method + * entries in the {@link #typeIndex} and {@link #compiledMethodIndex}. + */ + private final ConcurrentHashMap methodIndex = new ConcurrentHashMap<>(); + + /** + * An index map that holds all unique compiled method entries. + */ + private final ConcurrentHashMap compiledMethodIndex = new ConcurrentHashMap<>(); + + /** + * The header type entry which is used as a super class of {@link Object} in the debug info. It + * describes the object header of an object in the native image. + */ + private HeaderTypeEntry headerTypeEntry; + + /** + * A prefix used to label indirect types used to ensure gdb performs oop reference to raw + * address translation. + */ + public static final String INDIRECT_PREFIX = "_z_."; + + /** + * A prefix used for type signature generation with {@link #getTypeSignature} to generate unique + * type signatures for layout type units. + */ + public static final String LAYOUT_PREFIX = "_layout_."; + + /** + * A prefix used for type signature generation with {@link #getTypeSignature} to generate unique + * type signatures for foreign primitive type units. + */ + public static final String FOREIGN_PREFIX = "_foreign_."; + + static final Path EMPTY_PATH = Paths.get(""); + + public SharedDebugInfoProvider(DebugContext debug, RuntimeConfiguration runtimeConfiguration, MetaAccessProvider metaAccess) { + this.runtimeConfiguration = runtimeConfiguration; + this.metaAccess = metaAccess; + + /* + * Use a disabled DebugContext if log is disabled here. We need to make sure the log stays + * disabled, as we use parallel streams if it is disabled + */ + this.debug = debug.isLogEnabledForMethod() ? debug : DebugContext.disabled(null); + + // Fetch special types that have special use cases. + // hubType: type of the 'hub' field in the object header. + // wordBaseType: for checking for foreign types. + // voidType: fallback type to point to for foreign pointer types + this.hubType = (SharedType) metaAccess.lookupJavaType(Class.class); + this.wordBaseType = (SharedType) metaAccess.lookupJavaType(WordBase.class); + this.voidType = (SharedType) metaAccess.lookupJavaType(void.class); + + // Get some information on heap layout and object/object header layout + this.useHeapBase = ReferenceAccess.singleton().haveCompressedReferences() && ReferenceAccess.singleton().getCompressEncoding().hasBase(); + this.compressionShift = ReferenceAccess.singleton().getCompressionShift(); + this.pointerSize = ConfigurationValues.getTarget().wordSize; + this.referenceSize = getObjectLayout().getReferenceSize(); + this.objectAlignment = getObjectLayout().getAlignment(); + this.reservedBitsMask = Heap.getHeap().getObjectHeader().getReservedBitsMask(); + } + + /** + * Produces a stream of shared types that are processed in {@link #installDebugInfo}. + * + * @return A stream of all {@code SharedType} objects to process + */ + protected abstract Stream typeInfo(); + + /** + * Produces a stream of shared method and compilation pairs that are processed in + * {@link #installDebugInfo}. + * + * @return A stream of all compilations to process. + */ + protected abstract Stream> codeInfo(); + + /** + * Produces a stream of data objects that are processed in {@link #installDebugInfo}. + * + * @return A stream of all data objects to process. + */ + protected abstract Stream dataInfo(); + + protected abstract long getCodeOffset(SharedMethod method); + + public abstract long objectOffset(JavaConstant constant); + + @Override + public boolean useHeapBase() { + return useHeapBase; + } + + @Override + public boolean isRuntimeCompilation() { + return false; + } + + @Override + public int reservedBitsMask() { + return reservedBitsMask; + } + + @Override + public int compressionShift() { + return compressionShift; + } + + @Override + public int referenceSize() { + return referenceSize; + } + + @Override + public int pointerSize() { + return pointerSize; + } + + @Override + public int objectAlignment() { + return objectAlignment; + } + + /** + * Collect all type entries in {@link #typeIndex} plus the {@link #headerTypeEntry} sorted by + * type signature. + * + *

    + * This ensures that type entries are ordered when processed for the debug info object file. + * + * @return A {@code SortedSet} of all type entries found and registered in + * {@link #installDebugInfo}. + */ + @Override + public SortedSet typeEntries() { + SortedSet typeEntries = new TreeSet<>(Comparator.comparingLong(TypeEntry::getTypeSignature)); + /* + * The header type entry does not have an underlying HostedType, so we cant put it into the + * type index and have to add it manually. + */ + typeEntries.add(headerTypeEntry); + typeEntries.addAll(typeIndex.values()); + + return typeEntries; + } + + /** + * Collect all compiled method entries in {@link #compiledMethodIndex} sorted by address of + * their primary range and the owner class' type signature. + * + *

    + * This ensures that compiled method entries are ordered when processed for the debug info + * object file. + * + * @return A {@code SortedSet} of all compiled method entries found and registered in + * {@link #installDebugInfo}. + */ + @Override + public SortedSet compiledMethodEntries() { + SortedSet compiledMethodEntries = new TreeSet<>( + Comparator.comparing(CompiledMethodEntry::primary).thenComparingLong(compiledMethodEntry -> compiledMethodEntry.classEntry().getTypeSignature())); + + compiledMethodEntries.addAll(compiledMethodIndex.values()); + + return compiledMethodEntries; + } + + /** + * This installs debug info into the index maps for all entries in {@link #typeInfo}, + * {@link #codeInfo}, and {@link #dataInfo}. + * + *

    + * If logging with a {@link DebugContext} is enabled, this is done sequential, otherwise in + * parallel. + */ + @Override + @SuppressWarnings("try") + public void installDebugInfo() { + // we can only meaningfully provide logging if debug info is produced sequentially + Stream typeStream = debug.isLogEnabledForMethod() ? typeInfo() : typeInfo().parallel(); + Stream> codeStream = debug.isLogEnabledForMethod() ? codeInfo() : codeInfo().parallel(); + Stream dataStream = debug.isLogEnabledForMethod() ? dataInfo() : dataInfo().parallel(); + + try (DebugContext.Scope s = debug.scope("DebugInfoProvider")) { + // Create and index an empty dir with index 0 for null paths. + lookupDirEntry(EMPTY_PATH); + + /* + * Handle types, compilations and data. Code info needs to be handled first as it + * contains source file infos of compilations which are collected in the class entry. + */ + codeStream.forEach(pair -> handleCodeInfo(pair.getLeft(), pair.getRight())); + typeStream.forEach(this::installTypeInfo); + dataStream.forEach(this::installDataInfo); + + // Create the header type. + installTypeInfo(null); + } catch (Throwable e) { + throw debug.handle(e); + } + } + + /** + * Installs debug info for any given data info. For AOT debug info generation this is used to + * log information for objects in the native image heap. + * + * @param data The data info to process. + */ + protected void installDataInfo(@SuppressWarnings("unused") Object data) { + // by default, we do not use data info for installing debug info + } + + /** + * Installs debug info for a given type info. A type info must be a {@code SharedType}. + * + *

    + * This ensures that the type info is processed and a {@link TypeEntry} is put in the type + * index. + * + * @param type The {@code SharedType} to process. + */ + private void installTypeInfo(SharedType type) { + /* + * Looking up a type will either return the existing type in the index map or create and + * process a new type entry. + */ + lookupTypeEntry(type); + } + + /** + * Installs debug info for a {@code CompilationResult} and the underlying {@code SharedMethod}. + * + *

    + * This ensures that the compilation is processed and a {@link MethodEntry} and a + * {@link CompiledMethodEntry} are put into the method index and compiled method index + * respectively. + * + *

    + * A compilation is processed for its frame states from infopoints/sourcemappings. For + * performance reasons we mostly only use infopoints for processing compilations + * + * @param method The {@code SharedMethod} to process. + * @param compilation The {@code CompilationResult} to process + */ + private void handleCodeInfo(SharedMethod method, CompilationResult compilation) { + // First make sure the underlying MethodEntry exists. + MethodEntry methodEntry = lookupMethodEntry(method); + // Then process the compilation. + lookupCompiledMethodEntry(methodEntry, method, compilation); + } + + @Fold + static boolean omitInline() { + return SubstrateOptions.OmitInlinedMethodDebugLineInfo.getValue(); + } + + @Fold + static int debugCodeInfoMaxDepth() { + return SubstrateOptions.DebugCodeInfoMaxDepth.getValue(); + } + + @Fold + static boolean debugCodeInfoUseSourceMappings() { + return SubstrateOptions.DebugCodeInfoUseSourceMappings.getValue(); + } + + /** + * Creates a {@code CompiledMethodEntry} that holds a {@link PrimaryRange} and still needs to be + * processed in {@link #processCompilationInfo}. + * + * @param methodEntry the {@code MethodEntry} of the method + * @param method the {@code SharedMethod} of the given compilation + * @param compilation the given {@code CompilationResult}. + * @return an unprocessed {@code CompiledMethodEntry}. + */ + protected CompiledMethodEntry createCompilationInfo(MethodEntry methodEntry, SharedMethod method, CompilationResult compilation) { + int primaryLine = methodEntry.getLine(); + int frameSize = compilation.getTotalFrameSize(); + List frameSizeChanges = getFrameSizeChanges(compilation); + ClassEntry ownerType = methodEntry.getOwnerType(); + + // Create a primary range that spans over the compilation. + // The primary range entry holds the code offset information for all its sub ranges. + PrimaryRange primaryRange = Range.createPrimary(methodEntry, 0, compilation.getTargetCodeSize(), primaryLine, getCodeOffset(method)); + debug.log(DebugContext.INFO_LEVEL, "PrimaryRange %s.%s %s %s:%d [0x%x, 0x%x]", ownerType.getTypeName(), methodEntry.getMethodName(), primaryRange.getFileEntry().getPathName(), + primaryRange.getFileName(), primaryLine, primaryRange.getLo(), primaryRange.getHi()); + + return new CompiledMethodEntry(primaryRange, frameSizeChanges, frameSize, ownerType); + } + + /** + * Processes a {@code CompiledMethodEntry} created in {@link #createCompilationInfo}. + * + * @param methodEntry the {@code MethodEntry} of the method + * @param method the {@code SharedMethod} of the given compilation + * @param compilation the given {@code CompilationResult} + * @param compiledMethodEntry the {@code CompiledMethodEntry} to process + */ + protected void processCompilationInfo(MethodEntry methodEntry, SharedMethod method, CompilationResult compilation, CompiledMethodEntry compiledMethodEntry) { + // Mark the method entry for the compilation. + methodEntry.setInRange(); + + // If the compiled method entry was added, we still need to check the frame states + // for subranges. + + // Can we still provide locals if we have no file name? + if (methodEntry.getFileName().isEmpty()) { + return; + } + + // Restrict the frame state traversal based on options. + boolean omitInline = omitInline(); + int maxDepth = debugCodeInfoMaxDepth(); + boolean useSourceMappings = debugCodeInfoUseSourceMappings(); + if (omitInline) { + /* TopLevelVisitor will not go deeper than level 2 */ + maxDepth = 2; + } + + // The root node is represented by the primary range. + // A call nodes in the frame tree will be stored as call ranges and leaf nodes as + // leaf ranges + final CompilationResultFrameTree.CallNode root = new CompilationResultFrameTree.Builder(debug, compilation.getTargetCodeSize(), maxDepth, useSourceMappings, + true) + .build(compilation); + if (root == null) { + return; + } + PrimaryRange primaryRange = compiledMethodEntry.primary(); + int frameSize = compiledMethodEntry.frameSize(); + final List subRanges = new ArrayList<>(); + // The top level visitor will only traverse the direct children of the primary + // range. All sub call ranges will be treated as leaf ranges. + final CompilationResultFrameTree.Visitor visitor = omitInline ? new TopLevelVisitor(subRanges, frameSize, primaryRange) : new MultiLevelVisitor(subRanges, frameSize, primaryRange); + // arguments passed by visitor to apply are + // NativeImageDebugLocationInfo caller location info + // CallNode nodeToEmbed parent call node to convert to entry code leaf + // NativeImageDebugLocationInfo leaf into which current leaf may be merged + root.visitChildren(visitor, primaryRange, null, null); + // try to add a location record for offset zero + updateInitialLocation(primaryRange, subRanges, compilation, method, methodEntry); + + methodEntry.getOwnerType().addCompiledMethod(compiledMethodEntry); + } + + /** + * Installs a compilation info that was not found in {@link #lookupCompiledMethodEntry}. + * + * @param methodEntry the {@code MethodEntry} of the method + * @param method the {@code SharedMethod} of the given compilation + * @param compilation the given {@code CompilationResult} + * @return the fully processed {@code CompiledMethodEntry} for the compilation. + */ + @SuppressWarnings("try") + private CompiledMethodEntry installCompilationInfo(MethodEntry methodEntry, SharedMethod method, CompilationResult compilation) { + try (DebugContext.Scope s = debug.scope("DebugInfoCompilation")) { + debug.log(DebugContext.INFO_LEVEL, "Register compilation %s ", compilation.getName()); + + CompiledMethodEntry compiledMethodEntry = createCompilationInfo(methodEntry, method, compilation); + if (compiledMethodIndex.putIfAbsent(compilation.getCompilationId(), compiledMethodEntry) == null) { + // CompiledMethodEntry was added to the index, now we need to process the + // compilation. + debug.log(DebugContext.INFO_LEVEL, "Process compilation %s ", compilation.getName()); + processCompilationInfo(methodEntry, method, compilation, compiledMethodEntry); + return compiledMethodEntry; + } else { + // The compilation entry was created in the meantime, so we return the one unique + // type. + return compiledMethodIndex.get(compilation.getCompilationId()); + } + } catch (Throwable e) { + throw debug.handle(e); + } + } + + /** + * Collect all frame size changes as list of {@code FrameSizeChangeEntry} for a compilation. + * + * @param compilation the given {@code CompilationResult} + * @return a list of relevant frame size changes. + */ + public List getFrameSizeChanges(CompilationResult compilation) { + List frameSizeChanges = new ArrayList<>(); + for (CompilationResult.CodeMark mark : compilation.getMarks()) { + /* We only need to observe stack increment or decrement points. */ + if (mark.id.equals(SubstrateBackend.SubstrateMarkId.PROLOGUE_DECD_RSP)) { + FrameSizeChangeEntry sizeChange = new FrameSizeChangeEntry(mark.pcOffset, FrameSizeChangeType.EXTEND); + frameSizeChanges.add(sizeChange); + // } else if (mark.id.equals("PROLOGUE_END")) { + // can ignore these + // } else if (mark.id.equals("EPILOGUE_START")) { + // can ignore these + } else if (mark.id.equals(SubstrateBackend.SubstrateMarkId.EPILOGUE_INCD_RSP)) { + FrameSizeChangeEntry sizeChange = new FrameSizeChangeEntry(mark.pcOffset, FrameSizeChangeType.CONTRACT); + frameSizeChanges.add(sizeChange); + } else if (mark.id.equals(SubstrateBackend.SubstrateMarkId.EPILOGUE_END) && mark.pcOffset < compilation.getTargetCodeSize()) { + /* There is code after this return point so notify a stack extend again. */ + FrameSizeChangeEntry sizeChange = new FrameSizeChangeEntry(mark.pcOffset, FrameSizeChangeType.EXTEND); + frameSizeChanges.add(sizeChange); + } + } + return frameSizeChanges; + } + + /** + * Installs a method info that was not found in {@link #lookupMethodEntry}. + * + * @param method the {@code SharedMethod} to process + * @return the corresponding {@code MethodEntry} + */ + @SuppressWarnings("try") + private MethodEntry installMethodEntry(SharedMethod method) { + try (DebugContext.Scope s = debug.scope("DebugInfoMethod")) { + FileEntry fileEntry = lookupFileEntry(method); + + LineNumberTable lineNumberTable = method.getLineNumberTable(); + int line = lineNumberTable == null ? 0 : lineNumberTable.getLineNumber(0); + + String methodName = getMethodName(method); + StructureTypeEntry ownerType = (StructureTypeEntry) lookupTypeEntry((SharedType) method.getDeclaringClass()); + assert ownerType instanceof ClassEntry; + TypeEntry valueType = lookupTypeEntry((SharedType) method.getSignature().getReturnType(null)); + int modifiers = method.getModifiers(); + + /* + * Check the local variable table for parameters. If the params are not in the table, we + * create synthetic ones from the method signature. + */ + SortedSet paramInfos = getParamEntries(method, line); + int lastParamSlot = paramInfos.isEmpty() ? -1 : paramInfos.getLast().slot(); + LocalEntry thisParam = Modifier.isStatic(modifiers) ? null : paramInfos.removeFirst(); + + // look for locals in the methods local variable table + List locals = getLocalEntries(method, lastParamSlot); + + String symbolName = getSymbolName(method); + int vTableOffset = getVTableOffset(method); + + boolean isOverride = isOverride(method); + boolean isDeopt = method.isDeoptTarget(); + boolean isConstructor = method.isConstructor(); + + return methodIndex.computeIfAbsent(method, m -> new MethodEntry(fileEntry, line, methodName, ownerType, + valueType, modifiers, paramInfos, thisParam, symbolName, isDeopt, isOverride, isConstructor, + vTableOffset, lastParamSlot, locals)); + } catch (Throwable e) { + throw debug.handle(e); + } + } + + /** + * Installs a type info that was not found in {@link #lookupTypeEntry}. + * + * @param type the {@code SharedType} to process + * @return a fully processed {@code TypeEntry} + */ + @SuppressWarnings("try") + private TypeEntry installTypeEntry(SharedType type) { + try (DebugContext.Scope s = debug.scope("DebugInfoType")) { + debug.log(DebugContext.INFO_LEVEL, "Register type %s ", type.getName()); + + TypeEntry typeEntry = createTypeEntry(type); + if (typeIndex.putIfAbsent(type, typeEntry) == null) { + // TypeEntry was added to the type index, now we need to process the type. + debug.log(DebugContext.INFO_LEVEL, "Process type %s ", type.getName()); + processTypeEntry(type, typeEntry); + return typeEntry; + } else { + // The type entry was created in the meantime, so we return the one unique type. + return typeIndex.get(type); + } + } catch (Throwable e) { + throw debug.handle(e); + } + } + + /** + * Creates a {@code TypeEntry} that needs to be processed with {@link #processTypeEntry}. + * + * @param type the {@code SharedType} to process + * @return an unprocessed {@code TypeEntry} + */ + protected abstract TypeEntry createTypeEntry(SharedType type); + + /** + * Processes a {@code TypeEntry} created in {@link #createTypeEntry}. + * + * @param type the {@code SharedType} of the type entry + * @param typeEntry the {@code TypeEntry} to process + */ + protected abstract void processTypeEntry(SharedType type, TypeEntry typeEntry); + + /** + * Installs the header type info. This is a made up {@link TypeEntry} that has no underlying + * {@link SharedType} and represents the {@link ObjectLayout}. + */ + private void installHeaderTypeEntry() { + String typeName = "_objhdr"; + long typeSignature = getTypeSignature(typeName); + + // create the header type entry similar to a class entry without a super type + ObjectLayout ol = getObjectLayout(); + headerTypeEntry = new HeaderTypeEntry(typeName, ol.getFirstFieldOffset(), typeSignature); + + // add synthetic fields that hold the location of the hub and the idHash if it is in the + // object header + headerTypeEntry.addField(createSyntheticFieldEntry("hub", headerTypeEntry, hubType, ol.getHubOffset(), referenceSize)); + if (ol.isIdentityHashFieldInObjectHeader()) { + int idHashSize = ol.sizeInBytes(JavaKind.Int); + headerTypeEntry.addField(createSyntheticFieldEntry("idHash", headerTypeEntry, (SharedType) metaAccess.lookupJavaType(JavaKind.Int.toJavaClass()), ol.getObjectHeaderIdentityHashOffset(), + idHashSize)); + } + } + + /** + * Create a synthetic field for the debug info to add additional information as fields in the + * debug info. + * + * @param name name of the field + * @param ownerType {@code StructureTypeEntry} that owns the field + * @param type {@code TypeEntry} of the fields type + * @param offset offset of the field + * @param size size of the field + * @return a {@code FieldEntry} that represents the synthetic field + */ + protected FieldEntry createSyntheticFieldEntry(String name, StructureTypeEntry ownerType, SharedType type, int offset, int size) { + TypeEntry typeEntry = lookupTypeEntry(type); + debug.log("typename %s adding synthetic (public) field %s type %s size %d at offset 0x%x%n", + ownerType.getTypeName(), name, typeEntry.getTypeName(), size, offset); + return new FieldEntry(null, name, ownerType, typeEntry, size, offset, false, Modifier.PUBLIC); + } + + /** + * Create a {@code FieldEntry} for a field of a structured type. + * + * @param fileEntry {@code FileEntry} of the source file, where the field is declared + * @param name name of the field + * @param ownerType {@code StructureTypeEntry} that owns the field + * @param type {@code TypeEntry} of the fields type + * @param offset offset of the field + * @param size size of the field + * @return a {@code FieldEntry} that represents the synthetic field + */ + protected FieldEntry createFieldEntry(FileEntry fileEntry, String name, StructureTypeEntry ownerType, SharedType type, int offset, int size, boolean isEmbedded, int modifier) { + TypeEntry typeEntry = lookupTypeEntry(type); + debug.log("typename %s adding %s field %s type %s%s size %d at offset 0x%x%n", + ownerType.getTypeName(), ownerType.getModifiersString(modifier), name, typeEntry.getTypeName(), (isEmbedded ? "(embedded)" : ""), size, offset); + return new FieldEntry(fileEntry, name, ownerType, typeEntry, size, offset, isEmbedded, modifier); + } + + /** + * Produces a signature for a type name. + * + * @param typeName name of a type + * @return the signature for the type name + */ + protected long getTypeSignature(String typeName) { + return Digest.digestAsUUID(typeName).getLeastSignificantBits(); + } + + /** + * Produce a method name of a {@code SharedMethod} for the debug info. + * + * @param method method to produce a name for + * @return method name for the debug info + */ + protected String getMethodName(SharedMethod method) { + String name = method.getName(); + // replace (method name of a constructor) with the class name + if (method.isConstructor()) { + name = method.format("%h"); + if (name.indexOf('$') >= 0) { + name = name.substring(name.lastIndexOf('$') + 1); + } + } + return name; + } + + /** + * Fetches all locals that are no parameters from a methods {@link LocalVariableTable} and + * processes them into {@code LocalEntry} objects. + * + * @param method method to fetch locals from + * @param lastParamSlot the highest slot number of the methods params + * @return a list of locals in the methods local variable table + */ + private List getLocalEntries(SharedMethod method, int lastParamSlot) { + List localEntries = new ArrayList<>(); + + LineNumberTable lnt = method.getLineNumberTable(); + LocalVariableTable lvt = method.getLocalVariableTable(); + + // we do not have any information on local variables + if (lvt == null) { + return localEntries; + } + + SharedType ownerType = (SharedType) method.getDeclaringClass(); + for (Local local : lvt.getLocals()) { + // check if this is a local (slot is after last param slot) + if (local != null && local.getSlot() > lastParamSlot) { + // we have a local with a known name, type and slot + String name = local.getName(); + SharedType type = (SharedType) local.getType().resolve(ownerType); + int slot = local.getSlot(); + int bciStart = local.getStartBCI(); + int line = lnt == null ? 0 : lnt.getLineNumber(bciStart); + TypeEntry typeEntry = lookupTypeEntry(type); + localEntries.add(new LocalEntry(name, typeEntry, slot, line)); + } + } + + return localEntries; + } + + /** + * Fetches all parameters from a methods {@link Signature} and processes them into + * {@link LocalEntry} objects. The parameters are sorted by slot number. + * + *

    + * If the parameter also exists in the methods {@link LocalVariableTable} we fetch the + * parameters name from there, otherwise a name is generated. + * + * @param method method to fetch parameters from + * @param line first line of the method in its source file + * @return a {@code SortedSet} of parameters in the methods signature + */ + private SortedSet getParamEntries(SharedMethod method, int line) { + Signature signature = method.getSignature(); + int parameterCount = signature.getParameterCount(false); + SortedSet paramInfos = new TreeSet<>(Comparator.comparingInt(LocalEntry::slot)); + LocalVariableTable lvt = method.getLocalVariableTable(); + int slot = 0; + SharedType ownerType = (SharedType) method.getDeclaringClass(); + if (!method.isStatic()) { + JavaKind kind = ownerType.getJavaKind(); + assert kind == JavaKind.Object : "must be an object"; + paramInfos.add(new LocalEntry("this", lookupTypeEntry(ownerType), slot, line)); + slot += kind.getSlotCount(); + } + for (int i = 0; i < parameterCount; i++) { + Local local = null; + if (lvt != null) { + try { + local = lvt.getLocal(slot, 0); + } catch (IllegalStateException e) { + debug.log("Found invalid local variable table from method %s during debug info generation.", method.getName()); + } + } + SharedType paramType = (SharedType) signature.getParameterType(i, null); + JavaKind kind = paramType.getJavaKind(); + JavaKind storageKind = paramType.getStorageKind(); + String name = local != null ? local.getName() : "__" + storageKind.getJavaName() + i; + paramInfos.add(new LocalEntry(name, lookupTypeEntry(paramType), slot, line)); + slot += kind.getSlotCount(); + } + return paramInfos; + } + + /** + * Fetch a methods symbol name from the {@link UniqueShortNameProvider}. + * + * @param method method to get the symbol name for + * @return symbol name of the method + */ + public String getSymbolName(SharedMethod method) { + return UniqueShortNameProvider.singleton().uniqueShortName(null, method.getDeclaringClass(), method.getName(), method.getSignature(), method.isConstructor()); + } + + public boolean isOverride(@SuppressWarnings("unused") SharedMethod method) { + return false; + } + + public boolean isVirtual(@SuppressWarnings("unused") SharedMethod method) { + return false; + } + + public int getVTableOffset(SharedMethod method) { + return isVirtual(method) ? method.getVTableIndex() : -1; + } + + /* Lookup functions for indexed debug info entries. */ + + /** + * Lookup the header type entry and installs it if it does not exist yet. + * + * @return the header type entry + */ + protected HeaderTypeEntry lookupHeaderTypeEntry() { + if (headerTypeEntry == null) { + installHeaderTypeEntry(); + } + return headerTypeEntry; + } + + /** + * Lookup a {@code MethodEntry} for a {@code SharedMethod}. If it does not exist yet, this + * installs the {@code MethodEntry} and updates the method list of the owner type. + * + * @param method the given {@code SharedMethod} + * @return the corresponding {@code MethodEntry} + */ + protected MethodEntry lookupMethodEntry(SharedMethod method) { + if (method == null) { + return null; + } + MethodEntry methodEntry = methodIndex.get(method); + if (methodEntry == null) { + methodEntry = installMethodEntry(method); + methodEntry.getOwnerType().addMethod(methodEntry); + } + return methodEntry; + + } + + /** + * Lookup a {@code CompiledMethodEntry} for a {@code CompilationResult}. If the + * {@code CompiledMethodEntry} does not exist yet, it is installed. + * + * @param methodEntry the {@code MethodEntry} of the method param + * @param method the {@code SharedMethod} of this compilation + * @param compilation the given {@code CompilationResult} + * @return the corresponding {@code CompiledMethodEntry} + */ + protected CompiledMethodEntry lookupCompiledMethodEntry(MethodEntry methodEntry, SharedMethod method, CompilationResult compilation) { + if (method == null) { + return null; + } + CompiledMethodEntry compiledMethodEntry = compiledMethodIndex.get(compilation.getCompilationId()); + if (compiledMethodEntry == null) { + compiledMethodEntry = installCompilationInfo(methodEntry, method, compilation); + } + return compiledMethodEntry; + } + + /** + * Lookup a {@code TypeEntry} for a {@code SharedType}. If the {@code TypeEntry} does not exist + * yet, it is installed. + * + * @param type the given {@code SharedType} + * @return the corresponding {@code TypeEntry} + */ + protected TypeEntry lookupTypeEntry(SharedType type) { + if (type == null) { + // this must be the header type entry, as it is the only one with no underlying + return lookupHeaderTypeEntry(); + } + TypeEntry typeEntry = typeIndex.get(type); + if (typeEntry == null) { + typeEntry = installTypeEntry(type); + } + return typeEntry; + } + + /** + * Lookup a {@code LoaderEntry} for a {@code SharedType}. Extracts the loader name from the + * {@code SharedType} and forwards to {@link #lookupLoaderEntry(String)}. + * + * @param type the given {@code SharedType} + * @return the corresponding {@code LoaderEntry} + */ + protected LoaderEntry lookupLoaderEntry(SharedType type) { + SharedType targetType = type; + if (type.isArray()) { + targetType = (SharedType) type.getElementalType(); + } + return targetType.getHub().isLoaded() ? lookupLoaderEntry(UniqueShortNameProvider.singleton().uniqueShortLoaderName(targetType.getHub().getClassLoader())) : null; + } + + /** + * Lookup a {@code LoaderEntry} for a string. If the {@code LoaderEntry} does not exist yet, it + * is added to the {@link #loaderIndex}. + * + * @param loaderName the given loader name string + * @return the corresponding {@code LoaderEntry} + */ + protected LoaderEntry lookupLoaderEntry(String loaderName) { + if (loaderName == null || loaderName.isEmpty()) { + return null; + } + return loaderIndex.computeIfAbsent(loaderName, LoaderEntry::new); + } + + /** + * Lookup a {@code FileEntry} for a {@code ResolvedJavaType}. + * + * @param type the given {@code ResolvedJavaType} + * @return the corresponding {@code FileEntry} + */ + protected FileEntry lookupFileEntry(ResolvedJavaType type) { + /* + * Conjure up an appropriate, unique file name to keep tools happy even though we cannot + * find a corresponding source. + */ + return lookupFileEntry(fullFilePathFromClassName(type)); + } + + /** + * Lookup a {@code FileEntry} for the declaring class of a {@code ResolvedJavaMethod}. + * + * @param method the given {@code ResolvedJavaMethod} + * @return the corresponding {@code FileEntry} + */ + protected FileEntry lookupFileEntry(ResolvedJavaMethod method) { + return lookupFileEntry(method.getDeclaringClass()); + } + + /** + * Lookup a {@code FileEntry} for the declaring class of a {@code ResolvedJavaField}. + * + * @param field the given {@code ResolvedJavaField} + * @return the corresponding {@code FileEntry} + */ + protected FileEntry lookupFileEntry(ResolvedJavaField field) { + return lookupFileEntry(field.getDeclaringClass()); + } + + /** + * Lookup a {@code FileEntry} for a file path. First extracts the files directory and the + * corresponding {@link DirEntry} and adds a new {@code FileEntry} to {@link #fileIndex} if it + * does not exist yet. + * + * @param fullFilePath the given file path + * @return the corresponding {@code FileEntry} + */ + protected FileEntry lookupFileEntry(Path fullFilePath) { + if (fullFilePath == null) { + return null; + } + Path fileName = fullFilePath.getFileName(); + if (fileName == null) { + return null; + } + + Path dirPath = fullFilePath.getParent(); + DirEntry dirEntry = lookupDirEntry(dirPath); + + /* Reuse any existing entry if available. */ + FileEntry fileEntry = fileIndex.computeIfAbsent(fullFilePath, path -> new FileEntry(fileName.toString(), dirEntry)); + assert dirPath == null || fileEntry.dirEntry() != null && fileEntry.dirEntry().path().equals(dirPath); + return fileEntry; + } + + /** + * Lookup a {@code DirEntry} for a directory path. Adds a new {@code DirEntry} to + * {@link #dirIndex} if it does not exist yet. A {@code null} path is represented by + * {@link #EMPTY_PATH}. + * + * @param dirPath the given directory path + * @return the corresponding {@code FileEntry} + */ + protected DirEntry lookupDirEntry(Path dirPath) { + return dirIndex.computeIfAbsent(dirPath == null ? EMPTY_PATH : dirPath, DirEntry::new); + } + + /* Other helper functions. */ + protected static ObjectLayout getObjectLayout() { + return ConfigurationValues.getObjectLayout(); + } + + protected static Path fullFilePathFromClassName(ResolvedJavaType type) { + String[] elements = type.toJavaName().split("\\."); + int count = elements.length; + String name = elements[count - 1]; + while (name.startsWith("$")) { + name = name.substring(1); + } + if (name.contains("$")) { + name = name.substring(0, name.indexOf('$')); + } + if (name.isEmpty()) { + name = "_nofile_"; + } + elements[count - 1] = name + ".java"; + return FileSystems.getDefault().getPath("", elements); + } + + /** + * Identify a Java type which is being used to model a foreign memory word or pointer type. + * + * @param type the type to be tested + * @param accessingType another type relative to which the first type may need to be resolved + * @return true if the type models a foreign memory word or pointer type + */ + protected boolean isForeignWordType(JavaType type, SharedType accessingType) { + SharedType resolvedJavaType = (SharedType) type.resolve(accessingType); + return isForeignWordType(resolvedJavaType); + } + + /** + * Identify a hosted type which is being used to model a foreign memory word or pointer type. + * + * @param type the type to be tested + * @return true if the type models a foreign memory word or pointer type + */ + protected boolean isForeignWordType(SharedType type) { + return wordBaseType.isAssignableFrom(type); + } + + private static int findMarkOffset(SubstrateBackend.SubstrateMarkId markId, CompilationResult compilation) { + for (CompilationResult.CodeMark mark : compilation.getMarks()) { + if (mark.id.equals(markId)) { + return mark.pcOffset; + } + } + return -1; + } + + /** + * If there are any location info records then the first one will be for a nop which follows the + * stack decrement, stack range check and pushes of arguments into the stack frame. + * + *

    + * We can construct synthetic location info covering the first instruction based on the method + * arguments and the calling convention and that will normally be valid right up to the nop. In + * exceptional cases a call might pass arguments on the stack, in which case the stack decrement + * will invalidate the original stack locations. Providing location info for that case requires + * adding two locations, one for initial instruction that does the stack decrement and another + * for the range up to the nop. They will be essentially the same but the stack locations will + * be adjusted to account for the different value of the stack pointer. + * + * @param primary the {@code PrimaryRange} of the compilation + * @param locationInfos the location infos produced from the compilations frame states + * @param compilation the {@code CompilationResult} of a method + * @param method the given {@code SharedMethod} + * @param methodEntry the methods {@code MethodEntry} + */ + private void updateInitialLocation(PrimaryRange primary, List locationInfos, CompilationResult compilation, SharedMethod method, MethodEntry methodEntry) { + int prologueEnd = findMarkOffset(SubstrateBackend.SubstrateMarkId.PROLOGUE_END, compilation); + if (prologueEnd < 0) { + // this is not a normal compiled method so give up + return; + } + int stackDecrement = findMarkOffset(SubstrateBackend.SubstrateMarkId.PROLOGUE_DECD_RSP, compilation); + if (stackDecrement < 0) { + // this is not a normal compiled method so give up + return; + } + if (locationInfos.isEmpty()) { + // this is not a normal compiled method so give up + return; + } + + Range firstLocation = locationInfos.getFirst(); + int firstLocationOffset = firstLocation.getLoOffset(); + + if (firstLocationOffset == 0) { + // this is not a normal compiled method so give up + return; + } + if (firstLocationOffset < prologueEnd) { + // this is not a normal compiled method so give up + return; + } + + // create a synthetic location record including details of passed arguments + ParamLocationProducer locProducer = new ParamLocationProducer(method); + debug.log(DebugContext.DETAILED_LEVEL, "Add synthetic Location Info : %s (0, %d)", methodEntry.getMethodName(), firstLocationOffset - 1); + + Map localInfoList = initSyntheticInfoList(locProducer, methodEntry); + Range locationInfo = Range.createSubrange(primary, methodEntry, localInfoList, 0, firstLocationOffset, methodEntry.getLine(), primary, true); + + /* + * If the prologue extends beyond the stack extend and uses the stack then the info needs + * splitting at the extent point with the stack offsets adjusted in the new info. + */ + if (locProducer.usesStack() && firstLocationOffset > stackDecrement) { + Range splitLocationInfo = locationInfo.split(stackDecrement, compilation.getTotalFrameSize(), PRE_EXTEND_FRAME_SIZE); + debug.log(DebugContext.DETAILED_LEVEL, "Split synthetic Location Info : %s (0, %d) (%d, %d)", methodEntry.getMethodName(), + locationInfo.getLoOffset() - 1, locationInfo.getLoOffset(), locationInfo.getHiOffset() - 1); + locationInfos.addFirst(splitLocationInfo); + } + locationInfos.addFirst(locationInfo); + } + + /** + * Creates synthetic location infos for a methods parameters that spans to the first location + * info from the compilations frame states. + * + * @param locProducer the location info producer for the methods parameters + * @param methodEntry the given {@code MethodEntry} + * @return a mapping of {@code LocalEntry} to synthetic location info + */ + private Map initSyntheticInfoList(ParamLocationProducer locProducer, MethodEntry methodEntry) { + HashMap localValueInfos = new HashMap<>(); + // Create synthetic this param info + if (methodEntry.getThisParam() != null) { + JavaValue value = locProducer.thisLocation(); + LocalEntry thisParam = methodEntry.getThisParam(); + debug.log(DebugContext.DETAILED_LEVEL, "local[0] %s type %s slot %d", thisParam.name(), thisParam.type().getTypeName(), thisParam.slot()); + debug.log(DebugContext.DETAILED_LEVEL, " => %s", value); + LocalValueEntry localValueEntry = createLocalValueEntry(value, PRE_EXTEND_FRAME_SIZE); + if (localValueEntry != null) { + localValueInfos.put(thisParam, localValueEntry); + } + } + // Iterate over all params and create synthetic param info for each + int paramIdx = 0; + for (LocalEntry param : methodEntry.getParams()) { + JavaValue value = locProducer.paramLocation(paramIdx); + debug.log(DebugContext.DETAILED_LEVEL, "local[%d] %s type %s slot %d", paramIdx + 1, param.name(), param.type().getTypeName(), param.slot()); + debug.log(DebugContext.DETAILED_LEVEL, " => %s", value); + LocalValueEntry localValueEntry = createLocalValueEntry(value, PRE_EXTEND_FRAME_SIZE); + if (localValueEntry != null) { + localValueInfos.put(param, localValueEntry); + } + paramIdx++; + } + return localValueInfos; + } + + /** + * Size in bytes of the frame at call entry before any stack extend. Essentially this accounts + * for any automatically pushed return address whose presence depends upon the architecture. + */ + static final int PRE_EXTEND_FRAME_SIZE = ConfigurationValues.getTarget().arch.getReturnAddressSize(); + + /** + * Retrieve details of the native calling convention for a top level compiled method, including + * details of which registers or stack slots are used to pass parameters. + * + * @param method The method whose calling convention is required. + * @return The calling convention for the method. + */ + protected SubstrateCallingConvention getCallingConvention(SharedMethod method) { + SubstrateCallingConventionKind callingConventionKind = method.getCallingConventionKind(); + ResolvedJavaType declaringClass = method.getDeclaringClass(); + ResolvedJavaType receiverType = method.isStatic() ? null : declaringClass; + Signature signature = method.getSignature(); + final SubstrateCallingConventionType type; + if (callingConventionKind.isCustom()) { + type = method.getCustomCallingConventionType(); + } else { + type = callingConventionKind.toType(false); + } + Backend backend = runtimeConfiguration.lookupBackend(method); + RegisterConfig registerConfig = backend.getCodeCache().getRegisterConfig(); + assert registerConfig instanceof SubstrateRegisterConfig; + return (SubstrateCallingConvention) registerConfig.getCallingConvention(type, signature.getReturnType(null), signature.toParameterTypes(receiverType), backend); + } + + class ParamLocationProducer { + private final SharedMethod method; + private final CallingConvention callingConvention; + private boolean usesStack; + + ParamLocationProducer(SharedMethod method) { + this.method = method; + this.callingConvention = getCallingConvention(method); + // assume no stack slots until we find out otherwise + this.usesStack = false; + } + + JavaValue thisLocation() { + assert !method.isStatic(); + return unpack(callingConvention.getArgument(0)); + } + + JavaValue paramLocation(int paramIdx) { + assert paramIdx < method.getSignature().getParameterCount(false); + int idx = paramIdx; + if (!method.isStatic()) { + idx++; + } + return unpack(callingConvention.getArgument(idx)); + } + + private JavaValue unpack(AllocatableValue value) { + if (value instanceof RegisterValue registerValue) { + return registerValue; + } else { + // call argument must be a stack slot if it is not a register + StackSlot stackSlot = (StackSlot) value; + this.usesStack = true; + // the calling convention provides offsets from the SP relative to the current + // frame size. At the point of call the frame may or may not include a return + // address depending on the architecture. + return stackSlot; + } + } + + public boolean usesStack() { + return usesStack; + } + } + + // indices for arguments passed to SingleLevelVisitor::apply + protected static final int CALLER_INFO = 0; + protected static final int PARENT_NODE_TO_EMBED = 1; + protected static final int LAST_LEAF_INFO = 2; + + private abstract class SingleLevelVisitor implements CompilationResultFrameTree.Visitor { + + protected final List locationInfos; + protected final int frameSize; + + protected final PrimaryRange primary; + + SingleLevelVisitor(List locationInfos, int frameSize, PrimaryRange primary) { + this.locationInfos = locationInfos; + this.frameSize = frameSize; + this.primary = primary; + } + + @Override + public void apply(CompilationResultFrameTree.FrameNode node, Object... args) { + // Visits all nodes at this level and handle call nodes depth first (by default do + // nothing, just add the call nodes range info). + if (node instanceof CompilationResultFrameTree.CallNode && skipPos(node.frame)) { + node.visitChildren(this, args); + } else { + CallRange callerInfo = (CallRange) args[CALLER_INFO]; + CompilationResultFrameTree.CallNode nodeToEmbed = (CompilationResultFrameTree.CallNode) args[PARENT_NODE_TO_EMBED]; + handleNodeToEmbed(nodeToEmbed, node, callerInfo, args); + Range locationInfo = process(node, callerInfo); + if (node instanceof CompilationResultFrameTree.CallNode callNode) { + assert locationInfo instanceof CallRange; + locationInfos.add(locationInfo); + // erase last leaf (if present) since there is an intervening call range + args[LAST_LEAF_INFO] = null; + // handle inlined methods in implementors + handleCallNode(callNode, (CallRange) locationInfo); + } else { + // last leaf node added at this level is 3rd element of arg vector + Range lastLeaf = (Range) args[LAST_LEAF_INFO]; + if (lastLeaf == null || !lastLeaf.tryMerge(locationInfo)) { + // update last leaf and add new leaf to local info list + args[LAST_LEAF_INFO] = locationInfo; + locationInfos.add(locationInfo); + } else { + debug.log(DebugContext.DETAILED_LEVEL, "Merge leaf Location Info : %s depth %d (%d, %d) into (%d, %d)", lastLeaf.getMethodName(), lastLeaf.getDepth(), lastLeaf.getLoOffset(), + lastLeaf.getHiOffset() - 1, locationInfo.getLoOffset(), locationInfo.getHiOffset() - 1); + } + } + } + } + + @SuppressWarnings("unused") + protected void handleCallNode(CompilationResultFrameTree.CallNode callNode, CallRange locationInfo) { + // do nothing by default + } + + @SuppressWarnings("unused") + protected void handleNodeToEmbed(CompilationResultFrameTree.CallNode nodeToEmbed, CompilationResultFrameTree.FrameNode node, + CallRange callerInfo, Object... args) { + // do nothing by default + } + + public Range process(CompilationResultFrameTree.FrameNode node, CallRange callerInfo) { + BytecodePosition pos; + boolean isLeaf = true; + if (node instanceof CompilationResultFrameTree.CallNode callNode) { + // this node represents an inline call range so + // add a location info to cover the range of the call + pos = callNode.frame.getCaller(); + while (skipPos(pos)) { + pos = pos.getCaller(); + } + isLeaf = false; + } else if (isBadLeaf(node, callerInfo)) { + pos = node.frame.getCaller(); + assert pos != null : "bad leaf must have a caller"; + assert pos.getCaller() == null : "bad leaf caller must be root method"; + } else { + pos = node.frame; + } + + SharedMethod method = (SharedMethod) pos.getMethod(); + MethodEntry methodEntry = lookupMethodEntry(method); + + LineNumberTable lineNumberTable = method.getLineNumberTable(); + int line = lineNumberTable == null ? -1 : lineNumberTable.getLineNumber(pos.getBCI()); + + Map localValueInfos = initLocalInfoList(pos, methodEntry, frameSize); + Range locationInfo = Range.createSubrange(primary, methodEntry, localValueInfos, node.getStartPos(), node.getEndPos() + 1, line, callerInfo, isLeaf); + + debug.log(DebugContext.DETAILED_LEVEL, "Create %s Location Info : %s depth %d (%d, %d)", isLeaf ? "leaf" : "call", method.getName(), locationInfo.getDepth(), locationInfo.getLoOffset(), + locationInfo.getHiOffset() - 1); + + return locationInfo; + } + } + + /** + * Generate local info list for a location info from the local values of the current frame. + * Names and types of local variables are fetched from the methods local variable table. If we + * cant find the local in the local variable table, we use the frame information. + * + * @param pos the bytecode position of the location info + * @param methodEntry the {@code MethodEntry} corresponding to the bytecode position + * @param frameSize the current frame size + * @return a mapping from {@code LocalEntry} to {@code LocalValueEntry} + */ + protected Map initLocalInfoList(BytecodePosition pos, MethodEntry methodEntry, int frameSize) { + Map localInfos = new HashMap<>(); + + if (pos instanceof BytecodeFrame frame && frame.numLocals > 0) { + /* + * For each MethodEntry, initially local variables are loaded from the local variable + * table The local variable table is used here to get some additional information about + * locals and double-check the expected value kind + * + * A local variable that is not yet known to the method, will be added to the method + * with a synthesized name, the type according to the JavaKind (Object for all + * classes/array types) and the line of the current bytecode position + */ + SharedMethod method = (SharedMethod) pos.getMethod(); + LocalVariableTable lvt = method.getLocalVariableTable(); + LineNumberTable lnt = method.getLineNumberTable(); + int line = lnt == null ? 0 : lnt.getLineNumber(pos.getBCI()); + + // the owner type to resolve the local types against + SharedType ownerType = (SharedType) method.getDeclaringClass(); + + for (int slot = 0; slot < frame.numLocals; slot++) { + // Read locals from frame by slot - this might be an Illegal value + JavaValue value = frame.getLocalValue(slot); + JavaKind storageKind = frame.getLocalValueKind(slot); + + if (ValueUtil.isIllegalJavaValue(value)) { + /* + * If we have an illegal value, also the storage kind must be Illegal. We don't + * have any value, so we have to continue with the next slot. + */ + assert storageKind == JavaKind.Illegal; + continue; + } + + /* + * We might not have a local variable table at all, which means we can only use the + * frame local value. Even if there is a local variable table, there might not be a + * local at this slot in the local variable table. We also need to check if the + * local variable table is malformed. + */ + Local local = null; + if (lvt != null) { + try { + local = lvt.getLocal(slot, pos.getBCI()); + } catch (IllegalStateException e) { + debug.log("Found invalid local variable table from method %s during debug info generation.", method.getName()); + } + } + + String name; + SharedType type; + if (local == null) { + if (methodEntry.getLastParamSlot() >= slot) { + /* + * If we e.g. get an int from the frame values can we really be sure that + * this is a param and not just any other local value that happens to be an + * int? + * + * Better just skip inferring params if we have no local in the local + * variable table. + */ + continue; + } + + /* + * We don't have a corresponding local in the local variable table. Collect some + * usable information for this local from the frame local kind. + */ + name = "__" + storageKind.getJavaName() + (methodEntry.isStatic() ? slot : slot - 1); + Class clazz = storageKind.isObject() ? Object.class : storageKind.toJavaClass(); + type = (SharedType) metaAccess.lookupJavaType(clazz); + } else { + /* + * Use the information from the local variable table. This allows us to match + * the local variables with the ones we already read for the method entry. In + * this case the information from the frame local kind is just used to + * double-check the type kind from the local variable table. + */ + name = local.getName(); + type = (SharedType) local.getType().resolve(ownerType); + } + + TypeEntry typeEntry = lookupTypeEntry(type); + JavaKind kind = type.getJavaKind(); + + debug.log(DebugContext.DETAILED_LEVEL, "local %s type %s slot %d", name, typeEntry.getTypeName(), slot); + debug.log(DebugContext.DETAILED_LEVEL, " => %s", value); + + // Double-check the kind from the frame local value with the kind from the local + // variable table. + if (storageKind == kind || isIntegralKindPromotion(storageKind, kind) || (isForeignWordType(type, ownerType) && kind == JavaKind.Object && storageKind == JavaKind.Long)) { + /* + * Lookup a LocalEntry from the MethodEntry. If the LocalEntry was already read + * upfront from the local variable table, the LocalEntry already exists. + */ + LocalEntry localEntry = methodEntry.lookupLocalEntry(name, slot, typeEntry, line); + LocalValueEntry localValueEntry = createLocalValueEntry(value, frameSize); + if (localEntry != null && localValueEntry != null) { + localInfos.put(localEntry, localValueEntry); + } + } else { + debug.log(DebugContext.DETAILED_LEVEL, " value kind incompatible with var kind %s!", kind); + } + } + } + + return localInfos; + } + + /** + * Creates a {@code LocalValueEntry} for a given {@code JavaValue}. This processes register + * values, stack values, primitive constants and constant in the heap. + * + * @param value the given {@code JavaValue} + * @param frameSize the frame size for stack values + * @return the {@code LocalValueEntry} or {@code null} if the value can't be processed + */ + private LocalValueEntry createLocalValueEntry(JavaValue value, int frameSize) { + switch (value) { + case RegisterValue registerValue -> { + return new RegisterValueEntry(registerValue.getRegister().number); + } + case StackSlot stackValue -> { + int stackSlot = frameSize == 0 ? stackValue.getRawOffset() : stackValue.getOffset(frameSize); + return new StackValueEntry(stackSlot); + } + case JavaConstant constantValue -> { + if (constantValue instanceof PrimitiveConstant || constantValue.isNull()) { + return new ConstantValueEntry(-1, constantValue); + } else { + long heapOffset = objectOffset(constantValue); + if (heapOffset >= 0) { + return new ConstantValueEntry(heapOffset, constantValue); + } + } + return null; + } + default -> { + return null; + } + } + } + + private static boolean isIntegralKindPromotion(JavaKind promoted, JavaKind original) { + return (promoted == JavaKind.Int && + (original == JavaKind.Boolean || original == JavaKind.Byte || original == JavaKind.Short || original == JavaKind.Char)); + } + + // Top level visitor is just a single level visitor that starts at the primary range/root node + private class TopLevelVisitor extends SingleLevelVisitor { + TopLevelVisitor(List locationInfos, int frameSize, PrimaryRange primary) { + super(locationInfos, frameSize, primary); + } + } + + // Multi level visitor starts at the primary range and defines behavior for stepping into call + // nodes + public class MultiLevelVisitor extends SingleLevelVisitor { + MultiLevelVisitor(List locationInfos, int frameSize, PrimaryRange primary) { + super(locationInfos, frameSize, primary); + } + + @Override + protected void handleCallNode(CompilationResultFrameTree.CallNode callNode, CallRange locationInfo) { + if (hasChildren(callNode)) { + /* + * A call node may include an initial leaf range for the call that must be embedded + * under the newly created location info so pass it as an argument + */ + callNode.visitChildren(this, locationInfo, callNode, null); + } else { + // We need to embed a leaf node for the whole call range + locationInfos.add(createEmbeddedParentLocationInfo(primary, callNode, null, locationInfo, frameSize)); + } + } + + @Override + protected void handleNodeToEmbed(CompilationResultFrameTree.CallNode nodeToEmbed, CompilationResultFrameTree.FrameNode node, CallRange callerInfo, Object... args) { + if (nodeToEmbed != null) { + /* + * We only need to insert a range for the caller if it fills a gap at the start of + * the caller range before the first child. + */ + if (nodeToEmbed.getStartPos() < node.getStartPos()) { + /* + * Embed a leaf range for the method start that was included in the parent + * CallNode Its end range is determined by the start of the first node at this + * level. + */ + Range embeddedLocationInfo = createEmbeddedParentLocationInfo(primary, nodeToEmbed, node, callerInfo, frameSize); + locationInfos.add(embeddedLocationInfo); + // Since this is a leaf node we can merge later leafs into it. + args[LAST_LEAF_INFO] = embeddedLocationInfo; + } + // Reset args so we only embed the parent node before the first node at this level. + args[PARENT_NODE_TO_EMBED] = null; + } + } + } + + /** + * Report whether a call node has any children. + * + * @param callNode the node to check + * @return true if it has any children otherwise false. + */ + private static boolean hasChildren(CompilationResultFrameTree.CallNode callNode) { + Object[] result = new Object[]{false}; + callNode.visitChildren((node, args) -> args[0] = true, result); + return (boolean) result[0]; + } + + /** + * Create a location info record for the initial range associated with a parent call node whose + * position and start are defined by that call node and whose end is determined by the first + * child of the call node. + * + * @param parentToEmbed a parent call node which has already been processed to create the caller + * location info + * @param firstChild the first child of the call node + * @param callerLocation the location info created to represent the range for the call + * @return a location info to be embedded as the first child range of the caller location. + */ + private Range createEmbeddedParentLocationInfo(PrimaryRange primary, CompilationResultFrameTree.CallNode parentToEmbed, CompilationResultFrameTree.FrameNode firstChild, CallRange callerLocation, + int frameSize) { + BytecodePosition pos = parentToEmbed.frame; + int startPos = parentToEmbed.getStartPos(); + int endPos = (firstChild != null ? firstChild.getStartPos() : parentToEmbed.getEndPos() + 1); + + SharedMethod method = (SharedMethod) pos.getMethod(); + MethodEntry methodEntry = lookupMethodEntry(method); + + LineNumberTable lineNumberTable = method.getLineNumberTable(); + int line = lineNumberTable == null ? -1 : lineNumberTable.getLineNumber(pos.getBCI()); + + Map localValueInfos = initLocalInfoList(pos, methodEntry, frameSize); + Range locationInfo = Range.createSubrange(primary, methodEntry, localValueInfos, startPos, endPos, line, callerLocation, true); + + debug.log(DebugContext.DETAILED_LEVEL, "Embed leaf Location Info : %s depth %d (%d, %d)", locationInfo.getMethodName(), locationInfo.getDepth(), locationInfo.getLoOffset(), + locationInfo.getHiOffset() - 1); + + return locationInfo; + } + + /** + * Test whether a node is a bad leaf. + * + *

    + * Sometimes we see a leaf node marked as belonging to an inlined method that sits directly + * under the root method rather than under a call node. It needs replacing with a location info + * for the root method that covers the relevant code range. + * + * @param node the node to check + * @param callerLocation the caller location info + * @return true if the node is a bad leaf otherwise false + */ + private static boolean isBadLeaf(CompilationResultFrameTree.FrameNode node, CallRange callerLocation) { + if (callerLocation.isPrimary()) { + BytecodePosition pos = node.frame; + BytecodePosition callerPos = pos.getCaller(); + if (callerPos != null && !callerPos.getMethod().equals(pos.getMethod())) { + return callerPos.getCaller() == null; + } + } + return false; + } + + /** + * Test whether a bytecode position represents a bogus frame added by the compiler when a + * substitution or snippet call is injected. + * + * @param pos the position to be tested + * @return true if the frame is bogus otherwise false + */ + private static boolean skipPos(BytecodePosition pos) { + return pos.getBCI() == -1 && pos instanceof NodeSourcePosition sourcePos && sourcePos.isSubstitution(); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoFeature.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoFeature.java new file mode 100644 index 000000000000..a0ae63c381a3 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoFeature.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.core.debug; + +import java.util.List; + +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.hosted.Feature; + +import com.oracle.svm.core.SubstrateOptions; +import com.oracle.svm.core.code.InstalledCodeObserverSupport; +import com.oracle.svm.core.code.InstalledCodeObserverSupportFeature; +import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; +import com.oracle.svm.core.feature.InternalFeature; +import com.oracle.svm.core.graal.meta.RuntimeConfiguration; + +@AutomaticallyRegisteredFeature +public class SubstrateDebugInfoFeature implements InternalFeature { + @Override + public boolean isInConfiguration(IsInConfigurationAccess access) { + return Platform.includedIn(Platform.LINUX.class) && SubstrateOptions.useDebugInfoGeneration() && SubstrateOptions.RuntimeDebugInfo.getValue(); + } + + @Override + public List> getRequiredFeatures() { + return List.of(InstalledCodeObserverSupportFeature.class); + } + + @Override + public void registerCodeObserver(RuntimeConfiguration runtimeConfig) { + // This is called at image build-time -> the factory then creates a RuntimeDebugInfoProvider + // at runtime + ImageSingletons.lookup(InstalledCodeObserverSupport.class).addObserverFactory(new SubstrateDebugInfoInstaller.Factory(runtimeConfig.getProviders().getMetaAccess(), runtimeConfig)); + ImageSingletons.add(SubstrateDebugInfoInstaller.GdbJitAccessor.class, new SubstrateDebugInfoInstaller.GdbJitAccessor()); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoInstaller.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoInstaller.java new file mode 100644 index 000000000000..74fd34379fbe --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoInstaller.java @@ -0,0 +1,231 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.core.debug; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.ArrayList; + +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.c.struct.RawField; +import org.graalvm.nativeimage.c.struct.RawStructure; +import org.graalvm.nativeimage.c.struct.SizeOf; +import org.graalvm.nativeimage.c.type.CCharPointer; +import org.graalvm.word.Pointer; + +import com.oracle.objectfile.BasicNobitsSectionImpl; +import com.oracle.objectfile.ObjectFile; +import com.oracle.objectfile.SectionName; +import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.c.NonmovableArray; +import com.oracle.svm.core.c.NonmovableArrays; +import com.oracle.svm.core.code.InstalledCodeObserver; +import com.oracle.svm.core.graal.meta.RuntimeConfiguration; +import com.oracle.svm.core.memory.NativeMemory; +import com.oracle.svm.core.meta.SharedMethod; +import com.oracle.svm.core.nmt.NmtCategory; +import com.oracle.svm.core.os.VirtualMemoryProvider; +import com.oracle.svm.core.thread.VMOperation; +import com.oracle.svm.core.util.VMError; + +import jdk.graal.compiler.code.CompilationResult; +import jdk.graal.compiler.core.common.NumUtil; +import jdk.graal.compiler.debug.DebugContext; +import jdk.vm.ci.meta.MetaAccessProvider; + +public final class SubstrateDebugInfoInstaller implements InstalledCodeObserver { + + private final DebugContext debug; + private final SubstrateDebugInfoProvider substrateDebugInfoProvider; + private final ObjectFile objectFile; + private final ArrayList sortedObjectFileElements; + private final int debugInfoSize; + + static final class Factory implements InstalledCodeObserver.Factory { + + private final MetaAccessProvider metaAccess; + private final RuntimeConfiguration runtimeConfiguration; + + Factory(MetaAccessProvider metaAccess, RuntimeConfiguration runtimeConfiguration) { + this.metaAccess = metaAccess; + this.runtimeConfiguration = runtimeConfiguration; + } + + @Override + public InstalledCodeObserver create(DebugContext debugContext, SharedMethod method, CompilationResult compilation, Pointer code, int codeSize) { + try { + return new SubstrateDebugInfoInstaller(debugContext, method, compilation, metaAccess, runtimeConfiguration, code, codeSize); + } catch (Throwable t) { + throw VMError.shouldNotReachHere(t); + } + } + } + + private SubstrateDebugInfoInstaller(DebugContext debugContext, SharedMethod method, CompilationResult compilation, MetaAccessProvider metaAccess, RuntimeConfiguration runtimeConfiguration, + Pointer code, int codeSize) { + debug = debugContext; + substrateDebugInfoProvider = new SubstrateDebugInfoProvider(debugContext, method, compilation, runtimeConfiguration, metaAccess, code.rawValue()); + + int pageSize = NumUtil.safeToInt(ImageSingletons.lookup(VirtualMemoryProvider.class).getGranularity().rawValue()); + objectFile = ObjectFile.createRuntimeDebugInfo(pageSize); + objectFile.newNobitsSection(SectionName.TEXT.getFormatDependentName(objectFile.getFormat()), new BasicNobitsSectionImpl(codeSize)); + objectFile.installDebugInfo(substrateDebugInfoProvider); + sortedObjectFileElements = new ArrayList<>(); + debugInfoSize = objectFile.bake(sortedObjectFileElements); + + if (debugContext.isLogEnabled()) { + dumpObjectFile(); + } + } + + private void dumpObjectFile() { + StringBuilder sb = new StringBuilder(substrateDebugInfoProvider.getCompilationName()).append(".debug"); + try (FileChannel dumpFile = FileChannel.open(Paths.get(sb.toString()), + StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING, + StandardOpenOption.CREATE)) { + ByteBuffer buffer = dumpFile.map(FileChannel.MapMode.READ_WRITE, 0, debugInfoSize); + objectFile.writeBuffer(sortedObjectFileElements, buffer); + } catch (IOException e) { + debug.log("Failed to dump %s", sb); + } + } + + @RawStructure + private interface Handle extends InstalledCodeObserverHandle { + int INITIALIZED = 0; + int ACTIVATED = 1; + int RELEASED = 2; + + @RawField + GdbJitInterface.JITCodeEntry getRawHandle(); + + @RawField + void setRawHandle(GdbJitInterface.JITCodeEntry value); + + @RawField + NonmovableArray getDebugInfoData(); + + @RawField + void setDebugInfoData(NonmovableArray data); + + @RawField + int getState(); + + @RawField + void setState(int value); + } + + static final class GdbJitAccessor implements InstalledCodeObserverHandleAccessor { + + static Handle createHandle(NonmovableArray debugInfoData) { + Handle handle = NativeMemory.malloc(SizeOf.get(Handle.class), NmtCategory.Code); + GdbJitInterface.JITCodeEntry entry = NativeMemory.calloc(SizeOf.get(GdbJitInterface.JITCodeEntry.class), NmtCategory.Code); + handle.setAccessor(ImageSingletons.lookup(GdbJitAccessor.class)); + handle.setRawHandle(entry); + handle.setDebugInfoData(debugInfoData); + handle.setState(Handle.INITIALIZED); + return handle; + } + + @Override + public void activate(InstalledCodeObserverHandle installedCodeObserverHandle) { + Handle handle = (Handle) installedCodeObserverHandle; + VMOperation.guaranteeInProgressAtSafepoint("SubstrateDebugInfoInstaller.Accessor.activate must run in a VMOperation"); + VMError.guarantee(handle.getState() == Handle.INITIALIZED); + + NonmovableArray debugInfoData = handle.getDebugInfoData(); + CCharPointer address = NonmovableArrays.addressOf(debugInfoData, 0); + int size = NonmovableArrays.lengthOf(debugInfoData); + GdbJitInterface.registerJITCode(address, size, handle.getRawHandle()); + + handle.setState(Handle.ACTIVATED); + } + + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public void release(InstalledCodeObserverHandle installedCodeObserverHandle) { + Handle handle = (Handle) installedCodeObserverHandle; + GdbJitInterface.JITCodeEntry entry = handle.getRawHandle(); + // Handle may still be just initialized here, so it never got registered in GDB. + if (handle.getState() == Handle.ACTIVATED) { + GdbJitInterface.unregisterJITCode(entry); + handle.setState(Handle.RELEASED); + } + NativeMemory.free(entry); + NonmovableArrays.releaseUnmanagedArray(handle.getDebugInfoData()); + NativeMemory.free(handle); + } + + @Override + public void detachFromCurrentIsolate(InstalledCodeObserverHandle installedCodeObserverHandle) { + Handle handle = (Handle) installedCodeObserverHandle; + NonmovableArrays.untrackUnmanagedArray(handle.getDebugInfoData()); + } + + @Override + public void attachToCurrentIsolate(InstalledCodeObserverHandle installedCodeObserverHandle) { + Handle handle = (Handle) installedCodeObserverHandle; + NonmovableArrays.trackUnmanagedArray(handle.getDebugInfoData()); + } + + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public void releaseOnTearDown(InstalledCodeObserverHandle installedCodeObserverHandle) { + release(installedCodeObserverHandle); + } + } + + @Override + @SuppressWarnings("try") + public InstalledCodeObserverHandle install() { + NonmovableArray debugInfoData = writeDebugInfoData(); + Handle handle = GdbJitAccessor.createHandle(debugInfoData); + try (DebugContext.Scope s = debug.scope("RuntimeCompilation")) { + debug.log(toString(handle)); + } + return handle; + } + + private NonmovableArray writeDebugInfoData() { + NonmovableArray array = NonmovableArrays.createByteArray(debugInfoSize, NmtCategory.Code); + objectFile.writeBuffer(sortedObjectFileElements, NonmovableArrays.asByteBuffer(array)); + return array; + } + + private static String toString(Handle handle) { + return "DebugInfoHandle(handle = 0x" + Long.toHexString(handle.getRawHandle().rawValue()) + + ", address = 0x" + + Long.toHexString(NonmovableArrays.addressOf(handle.getDebugInfoData(), 0).rawValue()) + + ", size = " + + NonmovableArrays.lengthOf(handle.getDebugInfoData()) + + ", handleState = " + + handle.getState() + + ")"; + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoProvider.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoProvider.java new file mode 100644 index 000000000000..e3ed93831bfa --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoProvider.java @@ -0,0 +1,272 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.core.debug; + +import java.nio.file.FileSystems; +import java.nio.file.Path; +import java.util.stream.Stream; + +import org.graalvm.collections.Pair; +import org.graalvm.nativeimage.ProcessProperties; + +import com.oracle.objectfile.debugentry.ArrayTypeEntry; +import com.oracle.objectfile.debugentry.ClassEntry; +import com.oracle.objectfile.debugentry.FileEntry; +import com.oracle.objectfile.debugentry.LoaderEntry; +import com.oracle.objectfile.debugentry.PrimitiveTypeEntry; +import com.oracle.objectfile.debugentry.TypeEntry; +import com.oracle.svm.core.SubstrateUtil; +import com.oracle.svm.core.graal.meta.RuntimeConfiguration; +import com.oracle.svm.core.meta.SharedMethod; +import com.oracle.svm.core.meta.SharedType; +import com.oracle.svm.core.option.RuntimeOptionKey; + +import jdk.graal.compiler.code.CompilationResult; +import jdk.graal.compiler.debug.DebugContext; +import jdk.graal.compiler.options.Option; +import jdk.vm.ci.meta.JavaConstant; +import jdk.vm.ci.meta.JavaKind; +import jdk.vm.ci.meta.MetaAccessProvider; +import jdk.vm.ci.meta.ResolvedJavaType; + +/** + * Implements the {@link com.oracle.objectfile.debuginfo.DebugInfoProvider DebugInfoProvider} + * interface based on the {@code SharedDebugInfoProvider} to handle runt-time compiled methods. + * + *

    + * For each run-time compilation, one {@code SubstrateDebugInfoProvider} is created and the debug + * info for the compiled method is installed. As type information is already available in the native + * image's debug info, the {@code SubstrateDebugInfoProvider} just produces as little information as + * needed and reuses debug info from the native image. Therefore, for type entries the + * {@code SubstrateDebugInfoProvider} just creates stubs that contain the type signature, which can + * then be resolved by the debugger. + */ +public class SubstrateDebugInfoProvider extends SharedDebugInfoProvider { + + public static class Options { + @Option(help = "Directory where Java source-files will be placed for the debugger")// + public static final RuntimeOptionKey RuntimeSourceDestDir = new RuntimeOptionKey<>(null, RuntimeOptionKey.RuntimeOptionKeyFlag.RelevantForCompilationIsolates); + + public static Path getRuntimeSourceDestDir() { + String sourceDestDir = RuntimeSourceDestDir.getValue(); + if (sourceDestDir != null) { + return Path.of(sourceDestDir); + } + Path result = Path.of("sources"); + Path exeDir = Path.of(ProcessProperties.getExecutableName()).getParent(); + if (exeDir != null) { + result = exeDir.resolve(result); + } + return result; + } + } + + private final SharedMethod sharedMethod; + private final CompilationResult compilation; + private final long codeAddress; + + public SubstrateDebugInfoProvider(DebugContext debug, SharedMethod sharedMethod, CompilationResult compilation, RuntimeConfiguration runtimeConfiguration, MetaAccessProvider metaAccess, + long codeAddress) { + super(debug, runtimeConfiguration, metaAccess); + this.sharedMethod = sharedMethod; + this.compilation = compilation; + this.codeAddress = codeAddress; + } + + /** + * Create a compilation unit name from the {@link CompilationResult} or the {@link SharedMethod} + * the debug info is produced for. + * + * @return the name of the compilation unit in the debug info + */ + public String getCompilationName() { + String name = null; + if (compilation != null) { + name = compilation.getName(); + } + if ((name == null || name.isEmpty()) && sharedMethod != null) { + name = sharedMethod.format("%h.%n"); + } + if (name == null || name.isEmpty()) { + name = "UnnamedCompilation"; + } + return name + "@0x" + Long.toHexString(codeAddress); + } + + @Override + public String cachePath() { + return Options.getRuntimeSourceDestDir().toString(); + } + + @Override + public boolean isRuntimeCompilation() { + return true; + } + + /** + * Returns an empty stream, because there are no types to stream here. All types needed for the + * run-time debug info are installed when needed for providing debug info of the compilation. + * + * @return an empty stream + */ + @Override + protected Stream typeInfo() { + // create type infos on demand for compilation + return Stream.empty(); + } + + /** + * Provides the single compilation with its corresponding method as code info for this object + * file. All the debug info for the object file is produced based on this compilation and + * method. + * + * @return a stream containing the run-time compilation result and method + */ + @Override + protected Stream> codeInfo() { + return Stream.of(Pair.create(sharedMethod, compilation)); + } + + /** + * Returns an empty stream, no any additional data is handled for run-time compilations. + * + * @return an empty stream + */ + @Override + protected Stream dataInfo() { + // no data info needed for run-time compilations + return Stream.empty(); + } + + @Override + protected long getCodeOffset(@SuppressWarnings("unused") SharedMethod method) { + // use the code offset from the compilation + return codeAddress; + } + + /** + * Fetches the package name from the types hub and the types source file name and produces a + * file name with that. There is no guarantee that the source file is at the location of the + * file entry, but it is the best guess we can make at run-time. + * + * @param type the given {@code ResolvedJavaType} + * @return the {@code FileEntry} of the type + */ + @Override + public FileEntry lookupFileEntry(ResolvedJavaType type) { + if (type instanceof SharedType sharedType) { + String[] packageElements = SubstrateUtil.split(sharedType.getHub().getPackageName(), "."); + String fileName = sharedType.getSourceFileName(); + if (fileName != null && !fileName.isEmpty()) { + Path filePath = FileSystems.getDefault().getPath("", packageElements).resolve(fileName); + return lookupFileEntry(filePath); + } + } + return super.lookupFileEntry(type); + } + + private static int getTypeSize(SharedType type) { + if (type.isPrimitive()) { + JavaKind javaKind = type.getStorageKind(); + return (javaKind == JavaKind.Void ? 0 : javaKind.getByteCount()); + } else if (type.isArray()) { + SharedType componentType = (SharedType) type.getComponentType(); + return getObjectLayout().getArrayBaseOffset(componentType.getStorageKind()); + } else if (type.isInterface() || type.isInstanceClass()) { + return getObjectLayout().getFirstFieldOffset(); + } else { + return 0; + } + } + + /** + * Creates a {@code TypeEntry} for use in object files produced for run-time compilations. + * + *

    + * To avoid duplicating debug info, this mainly produces the {@link #getTypeSignature type + * signatures} to link the types to type entries produced at native image build time. Connecting + * the run-time compiled type entry with the native image's type entry is left for the debugger. + * This allows the reuse of type information from the native image, where we have more + * information available to produce debug info. + * + * @param type the {@code SharedType} to process + * @return a {@code TypeEntry} for the type + */ + @Override + protected TypeEntry createTypeEntry(SharedType type) { + String typeName = type.toJavaName(); + LoaderEntry loaderEntry = lookupLoaderEntry(type); + int size = getTypeSize(type); + long classOffset = -1; + String loaderName = loaderEntry == null ? "" : loaderEntry.loaderId(); + long typeSignature = getTypeSignature(typeName + loaderName); + long compressedTypeSignature = useHeapBase ? getTypeSignature(INDIRECT_PREFIX + typeName + loaderName) : typeSignature; + + if (type.isPrimitive()) { + JavaKind kind = type.getStorageKind(); + return new PrimitiveTypeEntry(typeName, size, classOffset, typeSignature, kind); + } else { + // otherwise we have a structured type + long layoutTypeSignature = getTypeSignature(LAYOUT_PREFIX + typeName + loaderName); + if (type.isArray()) { + TypeEntry elementTypeEntry = lookupTypeEntry((SharedType) type.getComponentType()); + return new ArrayTypeEntry(typeName, size, classOffset, typeSignature, compressedTypeSignature, + layoutTypeSignature, elementTypeEntry, loaderEntry); + } else { + // otherwise this is a class entry + ClassEntry superClass = type.getSuperclass() == null ? null : (ClassEntry) lookupTypeEntry((SharedType) type.getSuperclass()); + FileEntry fileEntry = lookupFileEntry(type); + if (isForeignWordType(type)) { + // we just need the correct type signatures here + return new ClassEntry(typeName, size, classOffset, typeSignature, typeSignature, layoutTypeSignature, + superClass, fileEntry, loaderEntry); + } else { + return new ClassEntry(typeName, size, classOffset, typeSignature, compressedTypeSignature, + layoutTypeSignature, superClass, fileEntry, loaderEntry); + } + } + } + } + + /** + * The run-time debug info relies on type entries in the native image. In + * {@link #createTypeEntry} we just produce dummy types that hold just enough information to + * connect them to the types in the native image. Therefore, there is nothing to do for + * processing types at run-time. + * + * @param type the {@code SharedType} of the type entry + * @param typeEntry the {@code TypeEntry} to process + */ + @Override + protected void processTypeEntry(@SuppressWarnings("unused") SharedType type, @SuppressWarnings("unused") TypeEntry typeEntry) { + // nothing to do here + } + + @Override + public long objectOffset(@SuppressWarnings("unused") JavaConstant constant) { + return -1; + } +} diff --git a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/SubstrateGraalUtils.java b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/SubstrateGraalUtils.java index d5112144f8a9..783da21e849a 100644 --- a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/SubstrateGraalUtils.java +++ b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/SubstrateGraalUtils.java @@ -39,15 +39,21 @@ import com.oracle.graal.pointsto.util.GraalAccess; import com.oracle.svm.common.option.CommonOptionParser; import com.oracle.svm.core.CPUFeatureAccess; +import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.graal.code.SubstrateCompilationIdentifier; import com.oracle.svm.core.graal.code.SubstrateCompilationResult; import com.oracle.svm.core.graal.meta.RuntimeConfiguration; +import com.oracle.svm.core.log.Log; import com.oracle.svm.core.meta.SharedMethod; import com.oracle.svm.core.meta.SubstrateObjectConstant; import com.oracle.svm.core.option.RuntimeOptionKey; import com.oracle.svm.core.option.RuntimeOptionParser; +import com.oracle.svm.core.option.RuntimeOptionValues; import com.oracle.svm.core.util.VMError; +import com.oracle.svm.graal.isolated.IsolatedGraalUtils; +import com.oracle.svm.graal.meta.RuntimeCodeInstaller; +import com.oracle.svm.graal.meta.SubstrateInstalledCodeImpl; import com.oracle.svm.graal.meta.SubstrateMethod; import jdk.graal.compiler.code.CompilationResult; @@ -70,7 +76,9 @@ import jdk.graal.compiler.phases.OptimisticOptimizations; import jdk.graal.compiler.phases.tiers.Suites; import jdk.graal.compiler.phases.util.Providers; +import jdk.graal.compiler.printer.GraalDebugHandlersFactory; import jdk.vm.ci.code.Architecture; +import jdk.vm.ci.code.InstalledCode; import jdk.vm.ci.meta.ConstantReflectionProvider; import jdk.vm.ci.meta.JavaConstant; @@ -81,6 +89,19 @@ public static CompilationResult compile(DebugContext debug, final SubstrateMetho return doCompile(debug, TruffleRuntimeCompilationSupport.getRuntimeConfig(), TruffleRuntimeCompilationSupport.getLIRSuites(), method); } + public static InstalledCode compileAndInstall(SubstrateMethod method) { + if (SubstrateOptions.shouldCompileInIsolates()) { + return IsolatedGraalUtils.compileInNewIsolateAndInstall(method); + } + RuntimeConfiguration runtimeConfiguration = TruffleRuntimeCompilationSupport.getRuntimeConfig(); + DebugContext debug = new DebugContext.Builder(RuntimeOptionValues.singleton(), new GraalDebugHandlersFactory(runtimeConfiguration.getProviders().getSnippetReflection())).build(); + SubstrateInstalledCodeImpl installedCode = new SubstrateInstalledCodeImpl(method); + CompilationResult compilationResult = doCompile(debug, TruffleRuntimeCompilationSupport.getRuntimeConfig(), TruffleRuntimeCompilationSupport.getLIRSuites(), method); + RuntimeCodeInstaller.install(method, compilationResult, installedCode); + Log.log().string("Code for " + method.format("%H.%n(%p)") + ": " + compilationResult.getTargetCodeSize() + " bytes").newline(); + return installedCode; + } + private static final Map compilationProblemsPerAction = new EnumMap<>(ExceptionAction.class); private static final CompilationWatchDog.EventHandler COMPILATION_WATCH_DOG_EVENT_HANDLER = new CompilationWatchDog.EventHandler() { diff --git a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/runtimecompilation/GraalGraphObjectReplacer.java b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/runtimecompilation/GraalGraphObjectReplacer.java index af1702a2dbb3..8036300ac58c 100644 --- a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/runtimecompilation/GraalGraphObjectReplacer.java +++ b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/runtimecompilation/GraalGraphObjectReplacer.java @@ -43,6 +43,7 @@ import com.oracle.graal.pointsto.meta.AnalysisType; import com.oracle.graal.pointsto.meta.AnalysisUniverse; import com.oracle.svm.common.meta.MultiMethod; +import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.code.CodeInfoTable; import com.oracle.svm.core.code.ImageCodeInfo; import com.oracle.svm.core.graal.meta.SharedRuntimeMethod; @@ -70,6 +71,7 @@ import com.oracle.svm.hosted.meta.HostedMethod; import com.oracle.svm.hosted.meta.HostedType; import com.oracle.svm.hosted.meta.HostedUniverse; +import com.oracle.svm.util.LogUtils; import com.oracle.svm.util.ReflectionUtil; import jdk.graal.compiler.api.replacements.SnippetReflectionProvider; @@ -89,6 +91,8 @@ import jdk.vm.ci.hotspot.HotSpotJVMCIRuntime; import jdk.vm.ci.meta.JavaConstant; import jdk.vm.ci.meta.JavaType; +import jdk.vm.ci.meta.Local; +import jdk.vm.ci.meta.LocalVariableTable; import jdk.vm.ci.meta.MetaAccessProvider; import jdk.vm.ci.meta.ResolvedJavaField; import jdk.vm.ci.meta.ResolvedJavaMethod; @@ -279,7 +283,18 @@ public synchronized SubstrateMethod createMethod(ResolvedJavaMethod original) { * The links to other meta objects must be set after adding to the methods to avoid * infinite recursion. */ - sMethod.setLinks(createSignature(aMethod.getSignature()), createType(aMethod.getDeclaringClass())); + LocalVariableTable localVariableTable; + if (SubstrateOptions.RuntimeDebugInfo.getValue()) { + try { + localVariableTable = createLocalVariableTable(aMethod.getLocalVariableTable()); + } catch (IllegalStateException e) { + LogUtils.warning("Omit invalid local variable table from method %s", sMethod.getName()); + localVariableTable = null; + } + } else { + localVariableTable = null; + } + sMethod.setLinks(createSignature(aMethod.getSignature()), createType(aMethod.getDeclaringClass()), localVariableTable); } } return sMethod; @@ -408,6 +423,31 @@ private synchronized SubstrateSignature createSignature(ResolvedSignature ignored = List.of(systemLoader, imageLoaderParent, appLoader, imageLoader); - bfdNameProvider = new NativeImageBFDNameProvider(ignored); + + BFDNameProvider bfdNameProvider = new BFDNameProvider(ignored); ImageSingletons.add(UniqueShortNameProvider.class, bfdNameProvider); } } @@ -105,29 +118,58 @@ public void afterRegistration(AfterRegistrationAccess access) { @Override public void beforeAnalysis(BeforeAnalysisAccess access) { /* - * Make the name provider aware of the native libs + * Ensure ClassLoader.nameAndId is available at runtime for type lookup from GDB. */ - if (bfdNameProvider != null) { - var accessImpl = (FeatureImpl.BeforeAnalysisAccessImpl) access; - bfdNameProvider.setNativeLibs(accessImpl.getNativeLibraries()); - } + access.registerAsAccessed(ReflectionUtil.lookupField(ClassLoader.class, "nameAndId")); /* - * Ensure ClassLoader.nameAndId is available at runtime for type lookup from gdb + * Provide some global symbol for the gdb-debughelpers script. */ - access.registerAsAccessed(ReflectionUtil.lookupField(ClassLoader.class, "nameAndId")); - CompressEncoding compressEncoding = ImageSingletons.lookup(CompressEncoding.class); CGlobalData compressionShift = CGlobalDataFactory.createWord(Word.signed(compressEncoding.getShift()), "__svm_compression_shift"); CGlobalData useHeapBase = CGlobalDataFactory.createWord(Word.unsigned(compressEncoding.hasBase() ? 1 : 0), "__svm_use_heap_base"); CGlobalData reservedBitsMask = CGlobalDataFactory.createWord(Word.unsigned(Heap.getHeap().getObjectHeader().getReservedBitsMask()), "__svm_reserved_bits_mask"); CGlobalData objectAlignment = CGlobalDataFactory.createWord(Word.unsigned(ConfigurationValues.getObjectLayout().getAlignment()), "__svm_object_alignment"); + CGlobalData frameSizeStatusMask = CGlobalDataFactory.createWord(Word.unsigned(CodeInfoDecoder.FRAME_SIZE_STATUS_MASK), "__svm_frame_size_status_mask"); CGlobalData heapBaseRegnum = CGlobalDataFactory.createWord(Word.unsigned(ReservedRegisters.singleton().getHeapBaseRegister().number), "__svm_heap_base_regnum"); CGlobalDataFeature.singleton().registerWithGlobalHiddenSymbol(compressionShift); CGlobalDataFeature.singleton().registerWithGlobalHiddenSymbol(useHeapBase); CGlobalDataFeature.singleton().registerWithGlobalHiddenSymbol(reservedBitsMask); CGlobalDataFeature.singleton().registerWithGlobalHiddenSymbol(objectAlignment); + CGlobalDataFeature.singleton().registerWithGlobalHiddenSymbol(frameSizeStatusMask); CGlobalDataFeature.singleton().registerWithGlobalHiddenSymbol(heapBaseRegnum); + + /* + * Create a global symbol for the jit debug descriptor with proper initial values for the + * GDB JIT compilation interface. + */ + if (SubstrateOptions.RuntimeDebugInfo.getValue()) { + Architecture arch = ConfigurationValues.getTarget().arch; + ByteBuffer buffer = ByteBuffer.allocate(SizeOf.get(GdbJitInterface.JITDescriptor.class)).order(arch.getByteOrder()); + + /* + * Set version to 1. Must be 1 otherwise GDB does not register breakpoints for the GDB + * JIT Compilation interface. + */ + buffer.putInt(1); + + /* Set action flag to JIT_NOACTION (0). */ + buffer.putInt(GdbJitInterface.JITActions.JIT_NOACTION.ordinal()); + + /* + * Set relevant entry to nullptr. This is the pointer to the debug info entry that is + * affected by the GDB JIT interface action. + */ + buffer.putLong(0); + + /* + * Set first entry to nullptr. This is the pointer to the last debug info entry notified + * to the GDB JIT interface We will prepend new entries here. + */ + buffer.putLong(0); + + CGlobalDataFeature.singleton().registerWithGlobalSymbol(CGlobalDataFactory.createBytes(buffer::array, "__jit_debug_descriptor")); + } } @Override @@ -143,7 +185,15 @@ public void beforeImageWrite(BeforeImageWriteAccess access) { DebugInfoProvider provider = new NativeImageDebugInfoProvider(debugContext, image.getCodeCache(), image.getHeap(), image.getNativeLibs(), accessImpl.getHostedMetaAccess(), runtimeConfiguration); var objectFile = image.getObjectFile(); - objectFile.installDebugInfo(provider); + + int debugInfoGenerationThreadCount = SubstrateOptions.DebugInfoGenerationThreadCount.getValue(); + if (debugInfoGenerationThreadCount > 0) { + try (ForkJoinPool threadPool = new ForkJoinPool(debugInfoGenerationThreadCount)) { + threadPool.submit(() -> objectFile.installDebugInfo(provider)).join(); + } + } else { + objectFile.installDebugInfo(provider); + } if (Platform.includedIn(Platform.LINUX.class) && SubstrateOptions.UseImagebuildDebugSections.getValue()) { /*- @@ -165,6 +215,10 @@ public boolean isLoadable() { }; }; + /* + * Create a section that triggers GDB to read debugging assistance information from + * gdb-debughelpers.py in the current working directory. + */ Supplier makeGDBSectionImpl = () -> { var content = AssemblyBuffer.createOutputAssembler(objectFile.getByteOrder()); // 1 -> python file @@ -184,7 +238,7 @@ public boolean isLoadable() { objectFile.newUserDefinedSection(".debug.svm.imagebuild.arguments", makeSectionImpl.apply(DiagnosticUtils.getBuilderArguments(imageClassLoader))); objectFile.newUserDefinedSection(".debug.svm.imagebuild.java.properties", makeSectionImpl.apply(DiagnosticUtils.getBuilderProperties())); - Path svmDebugHelper = Path.of(System.getProperty("java.home"), "lib/svm/debug/gdb-debughelpers.py"); + Path svmDebugHelper = Path.of(System.getProperty("java.home"), "lib", "svm", "debug", "gdb-debughelpers.py"); if (Files.exists(svmDebugHelper)) { objectFile.newUserDefinedSection(".debug_gdb_scripts", makeGDBSectionImpl.get()); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoProvider.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoProvider.java index d2e22544f084..d7b9c4525927 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoProvider.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -25,24 +25,23 @@ */ package com.oracle.svm.hosted.image; -import static com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugFrameSizeChange.Type.CONTRACT; -import static com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugFrameSizeChange.Type.EXTEND; +import static com.oracle.svm.hosted.c.info.AccessorInfo.AccessorKind.ADDRESS; +import static com.oracle.svm.hosted.c.info.AccessorInfo.AccessorKind.GETTER; +import static com.oracle.svm.hosted.c.info.AccessorInfo.AccessorKind.SETTER; import java.lang.reflect.Modifier; import java.nio.file.Path; -import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; -import java.util.LinkedList; +import java.util.Comparator; import java.util.List; -import java.util.Objects; import java.util.Set; -import java.util.function.Consumer; +import java.util.SortedSet; +import java.util.concurrent.ConcurrentHashMap; import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; -import org.graalvm.collections.EconomicMap; +import org.graalvm.collections.Pair; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.c.struct.CPointerTo; import org.graalvm.nativeimage.c.struct.RawPointerTo; @@ -50,20 +49,31 @@ import com.oracle.graal.pointsto.infrastructure.OriginalClassProvider; import com.oracle.graal.pointsto.infrastructure.WrappedJavaMethod; import com.oracle.graal.pointsto.infrastructure.WrappedJavaType; -import com.oracle.graal.pointsto.meta.AnalysisMethod; -import com.oracle.objectfile.debuginfo.DebugInfoProvider; +import com.oracle.objectfile.debugentry.ArrayTypeEntry; +import com.oracle.objectfile.debugentry.ClassEntry; +import com.oracle.objectfile.debugentry.EnumClassEntry; +import com.oracle.objectfile.debugentry.FieldEntry; +import com.oracle.objectfile.debugentry.FileEntry; +import com.oracle.objectfile.debugentry.ForeignPointerTypeEntry; +import com.oracle.objectfile.debugentry.ForeignStructTypeEntry; +import com.oracle.objectfile.debugentry.ForeignTypeEntry; +import com.oracle.objectfile.debugentry.ForeignWordTypeEntry; +import com.oracle.objectfile.debugentry.InterfaceClassEntry; +import com.oracle.objectfile.debugentry.LoaderEntry; +import com.oracle.objectfile.debugentry.LocalEntry; +import com.oracle.objectfile.debugentry.MethodEntry; +import com.oracle.objectfile.debugentry.PrimitiveTypeEntry; +import com.oracle.objectfile.debugentry.StructureTypeEntry; +import com.oracle.objectfile.debugentry.TypeEntry; +import com.oracle.svm.core.StaticFieldsSupport; import com.oracle.svm.core.SubstrateOptions; -import com.oracle.svm.core.UniqueShortNameProvider; -import com.oracle.svm.core.code.CompilationResultFrameTree.Builder; -import com.oracle.svm.core.code.CompilationResultFrameTree.CallNode; -import com.oracle.svm.core.code.CompilationResultFrameTree.FrameNode; -import com.oracle.svm.core.code.CompilationResultFrameTree.Visitor; -import com.oracle.svm.core.config.ConfigurationValues; -import com.oracle.svm.core.config.ObjectLayout; -import com.oracle.svm.core.graal.code.SubstrateBackend.SubstrateMarkId; +import com.oracle.svm.core.debug.BFDNameProvider; +import com.oracle.svm.core.debug.SharedDebugInfoProvider; import com.oracle.svm.core.graal.meta.RuntimeConfiguration; import com.oracle.svm.core.image.ImageHeapPartition; -import com.oracle.svm.hosted.DeadlockWatchdog; +import com.oracle.svm.core.meta.SharedMethod; +import com.oracle.svm.core.meta.SharedType; +import com.oracle.svm.core.util.VMError; import com.oracle.svm.hosted.c.NativeLibraries; import com.oracle.svm.hosted.c.info.AccessorInfo; import com.oracle.svm.hosted.c.info.ElementInfo; @@ -71,13 +81,10 @@ import com.oracle.svm.hosted.c.info.PropertyInfo; import com.oracle.svm.hosted.c.info.RawStructureInfo; import com.oracle.svm.hosted.c.info.SizableInfo; -import com.oracle.svm.hosted.c.info.SizableInfo.ElementKind; import com.oracle.svm.hosted.c.info.StructFieldInfo; import com.oracle.svm.hosted.c.info.StructInfo; -import com.oracle.svm.hosted.image.NativeImageHeap.ObjectInfo; import com.oracle.svm.hosted.image.sources.SourceManager; import com.oracle.svm.hosted.meta.HostedArrayClass; -import com.oracle.svm.hosted.meta.HostedClass; import com.oracle.svm.hosted.meta.HostedField; import com.oracle.svm.hosted.meta.HostedInstanceClass; import com.oracle.svm.hosted.meta.HostedInterface; @@ -85,49 +92,51 @@ import com.oracle.svm.hosted.meta.HostedMethod; import com.oracle.svm.hosted.meta.HostedPrimitiveType; import com.oracle.svm.hosted.meta.HostedType; -import com.oracle.svm.hosted.substitute.SubstitutionField; +import com.oracle.svm.hosted.substitute.InjectedFieldsType; import com.oracle.svm.hosted.substitute.SubstitutionMethod; +import com.oracle.svm.hosted.substitute.SubstitutionType; import com.oracle.svm.util.ClassUtil; import jdk.graal.compiler.code.CompilationResult; import jdk.graal.compiler.debug.DebugContext; -import jdk.graal.compiler.graph.NodeSourcePosition; -import jdk.graal.compiler.java.StableMethodNameFormatter; -import jdk.graal.compiler.util.Digest; -import jdk.vm.ci.aarch64.AArch64; -import jdk.vm.ci.amd64.AMD64; -import jdk.vm.ci.code.BytecodeFrame; -import jdk.vm.ci.code.BytecodePosition; -import jdk.vm.ci.code.CallingConvention; -import jdk.vm.ci.code.Register; -import jdk.vm.ci.code.RegisterValue; -import jdk.vm.ci.code.StackSlot; -import jdk.vm.ci.meta.AllocatableValue; import jdk.vm.ci.meta.JavaConstant; import jdk.vm.ci.meta.JavaKind; -import jdk.vm.ci.meta.JavaValue; -import jdk.vm.ci.meta.LineNumberTable; -import jdk.vm.ci.meta.Local; -import jdk.vm.ci.meta.LocalVariableTable; -import jdk.vm.ci.meta.PrimitiveConstant; import jdk.vm.ci.meta.ResolvedJavaField; import jdk.vm.ci.meta.ResolvedJavaMethod; import jdk.vm.ci.meta.ResolvedJavaType; -import jdk.vm.ci.meta.Signature; -import jdk.vm.ci.meta.Value; /** * Implementation of the DebugInfoProvider API interface that allows type, code and heap data info - * to be passed to an ObjectFile when generation of debug info is enabled. + * to be passed to an ObjectFile when generation of debug info is enabled at native image build + * time. */ -class NativeImageDebugInfoProvider extends NativeImageDebugInfoProviderBase implements DebugInfoProvider { - private final DebugContext debugContext; +class NativeImageDebugInfoProvider extends SharedDebugInfoProvider { + protected final NativeImageHeap heap; + protected final NativeImageCodeCache codeCache; + protected final NativeLibraries nativeLibs; + + protected final int primitiveStartOffset; + protected final int referenceStartOffset; private final Set allOverrides; - NativeImageDebugInfoProvider(DebugContext debugContext, NativeImageCodeCache codeCache, NativeImageHeap heap, NativeLibraries nativeLibs, HostedMetaAccess metaAccess, + /** + * An index map that holds all unique native primitive type entries. + */ + private final ConcurrentHashMap nativePrimitiveTypeIndex = new ConcurrentHashMap<>(); + + NativeImageDebugInfoProvider(DebugContext debug, NativeImageCodeCache codeCache, NativeImageHeap heap, NativeLibraries nativeLibs, HostedMetaAccess metaAccess, RuntimeConfiguration runtimeConfiguration) { - super(codeCache, heap, nativeLibs, metaAccess, runtimeConfiguration); - this.debugContext = debugContext; + super(debug, runtimeConfiguration, metaAccess); + this.heap = heap; + this.codeCache = codeCache; + this.nativeLibs = nativeLibs; + + /* Offsets need to be adjusted relative to the heap base plus partition-specific offset. */ + NativeImageHeap.ObjectInfo primitiveFields = heap.getObjectInfo(StaticFieldsSupport.getStaticPrimitiveFields()); + NativeImageHeap.ObjectInfo objectFields = heap.getObjectInfo(StaticFieldsSupport.getStaticObjectFields()); + primitiveStartOffset = (int) primitiveFields.getOffset(); + referenceStartOffset = (int) objectFields.getOffset(); + /* Calculate the set of all HostedMethods that are overrides. */ allOverrides = heap.hUniverse.getMethods().stream() .filter(HostedMethod::hasVTableIndex) @@ -137,914 +146,744 @@ class NativeImageDebugInfoProvider extends NativeImageDebugInfoProviderBase impl } @Override - public Stream typeInfoProvider() { - Stream headerTypeInfo = computeHeaderTypeInfo(); - Stream heapTypeInfo = heap.hUniverse.getTypes().stream().map(this::createDebugTypeInfo); - return Stream.concat(headerTypeInfo, heapTypeInfo); - } - - @Override - public Stream codeInfoProvider() { - return codeCache.getOrderedCompilations().stream().map(pair -> new NativeImageDebugCodeInfo(pair.getLeft(), pair.getRight())); - } - - @Override - public Stream dataInfoProvider() { - return heap.getObjects().stream().filter(this::acceptObjectInfo).map(this::createDebugDataInfo); + public SortedSet typeEntries() { + SortedSet typeEntries = super.typeEntries(); + typeEntries.addAll(nativePrimitiveTypeIndex.values()); + return typeEntries; } - private abstract class NativeImageDebugFileInfo implements DebugFileInfo { - private final Path fullFilePath; - - @SuppressWarnings("try") - NativeImageDebugFileInfo(HostedType hostedType) { - ResolvedJavaType javaType = getDeclaringClass(hostedType, false); - Class clazz = hostedType.getJavaClass(); - SourceManager sourceManager = ImageSingletons.lookup(SourceManager.class); - try (DebugContext.Scope s = debugContext.scope("DebugFileInfo", hostedType)) { - Path filePath = sourceManager.findAndCacheSource(javaType, clazz, debugContext); - if (filePath == null && (hostedType instanceof HostedInstanceClass || hostedType instanceof HostedInterface)) { - // conjure up an appropriate, unique file name to keep tools happy - // even though we cannot find a corresponding source - filePath = fullFilePathFromClassName(hostedType); - } - fullFilePath = filePath; - } catch (Throwable e) { - throw debugContext.handle(e); - } + @SuppressWarnings("unused") + private static ResolvedJavaType getOriginal(ResolvedJavaType type) { + /* + * Unwrap then traverse through substitutions to the original. We don't want to get the + * original type of LambdaSubstitutionType to keep the stable name. + */ + ResolvedJavaType targetType = type; + while (targetType instanceof WrappedJavaType wrappedJavaType) { + targetType = wrappedJavaType.getWrapped(); } - @SuppressWarnings("try") - NativeImageDebugFileInfo(ResolvedJavaMethod method) { - /* - * Note that this constructor allows for any ResolvedJavaMethod, not just a - * HostedMethod, because it needs to provide common behaviour for DebugMethodInfo, - * DebugCodeInfo and DebugLocationInfo records. The former two are derived from a - * HostedMethod while the latter may be derived from an arbitrary ResolvedJavaMethod. - */ - ResolvedJavaType javaType; - if (method instanceof HostedMethod) { - javaType = getDeclaringClass((HostedMethod) method, false); - } else { - javaType = method.getDeclaringClass(); - } - Class clazz = OriginalClassProvider.getJavaClass(javaType); - SourceManager sourceManager = ImageSingletons.lookup(SourceManager.class); - try (DebugContext.Scope s = debugContext.scope("DebugFileInfo", javaType)) { - fullFilePath = sourceManager.findAndCacheSource(javaType, clazz, debugContext); - } catch (Throwable e) { - throw debugContext.handle(e); - } + if (targetType instanceof SubstitutionType substitutionType) { + targetType = substitutionType.getOriginal(); + } else if (targetType instanceof InjectedFieldsType injectedFieldsType) { + targetType = injectedFieldsType.getOriginal(); } - @SuppressWarnings("try") - NativeImageDebugFileInfo(HostedField hostedField) { - ResolvedJavaType javaType = getDeclaringClass(hostedField, false); - HostedType hostedType = hostedField.getDeclaringClass(); - Class clazz = hostedType.getJavaClass(); - SourceManager sourceManager = ImageSingletons.lookup(SourceManager.class); - try (DebugContext.Scope s = debugContext.scope("DebugFileInfo", hostedType)) { - fullFilePath = sourceManager.findAndCacheSource(javaType, clazz, debugContext); - } catch (Throwable e) { - throw debugContext.handle(e); - } - } + return targetType; + } - @Override - public String fileName() { - if (fullFilePath != null) { - Path filename = fullFilePath.getFileName(); - if (filename != null) { - return filename.toString(); - } - } - return ""; + @SuppressWarnings("unused") + private static ResolvedJavaMethod getAnnotatedOrOriginal(ResolvedJavaMethod method) { + ResolvedJavaMethod targetMethod = method; + while (targetMethod instanceof WrappedJavaMethod wrappedJavaMethod) { + targetMethod = wrappedJavaMethod.getWrapped(); } + /* + * This method is only used when identifying the modifiers or the declaring class of a + * HostedMethod. Normally the method unwraps to the underlying JVMCI method which is the one + * that provides bytecode to the compiler as well as, line numbers and local info. If we + * unwrap to a SubstitutionMethod then we use the annotated method, not the JVMCI method + * that the annotation refers to since that will be the one providing the bytecode etc used + * by the compiler. If we unwrap to any other, custom substitution method we simply use it + * rather than dereferencing to the original. The difference is that the annotated method's + * bytecode will be used to replace the original and the debugger needs to use it to + * identify the file and access permissions. A custom substitution may exist alongside the + * original, as is the case with some uses for reflection. So, we don't want to conflate the + * custom substituted method and the original. In this latter case the method code will be + * synthesized without reference to the bytecode of the original. Hence, there is no + * associated file and the permissions need to be determined from the custom substitution + * method itself. + */ - @Override - public Path filePath() { - if (fullFilePath != null) { - return fullFilePath.getParent(); - } - return null; + if (targetMethod instanceof SubstitutionMethod substitutionMethod) { + targetMethod = substitutionMethod.getAnnotated(); } - } - private abstract class NativeImageDebugTypeInfo extends NativeImageDebugFileInfo implements DebugTypeInfo { - protected final HostedType hostedType; + return targetMethod; + } - @SuppressWarnings("try") - protected NativeImageDebugTypeInfo(HostedType hostedType) { - super(hostedType); - this.hostedType = hostedType; - } + @Override + public String cachePath() { + return SubstrateOptions.getDebugInfoSourceCacheRoot().toString(); + } - @SuppressWarnings("try") - @Override - public void debugContext(Consumer action) { - try (DebugContext.Scope s = debugContext.scope("DebugTypeInfo", typeName())) { - action.accept(debugContext); + /** + * Logs information of {@link NativeImageHeap.ObjectInfo ObjectInfo}. + * + * @param data the data info to process + */ + @Override + @SuppressWarnings("try") + protected void installDataInfo(Object data) { + // log ObjectInfo data + if (debug.isLogEnabled(DebugContext.INFO_LEVEL) && data instanceof NativeImageHeap.ObjectInfo objectInfo) { + try (DebugContext.Scope s = debug.scope("DebugDataInfo")) { + long offset = objectInfo.getOffset(); + long size = objectInfo.getSize(); + String typeName = objectInfo.getClazz().toJavaName(); + ImageHeapPartition partition = objectInfo.getPartition(); + + debug.log(DebugContext.INFO_LEVEL, "Data: offset 0x%x size 0x%x type %s partition %s{%d}@%d provenance %s ", offset, size, typeName, partition.getName(), partition.getSize(), + partition.getStartOffset(), objectInfo); } catch (Throwable e) { - throw debugContext.handle(e); + throw debug.handle(e); } + } else { + super.installDataInfo(data); } + } - @Override - public long typeSignature(String prefix) { - return Digest.digestAsUUID(prefix + typeName()).getLeastSignificantBits(); - } - - public String toJavaName(@SuppressWarnings("hiding") HostedType hostedType) { - return getDeclaringClass(hostedType, true).toJavaName(); + private static int elementSize(ElementInfo elementInfo) { + if (!(elementInfo instanceof SizableInfo) || elementInfo instanceof StructInfo structInfo && structInfo.isIncomplete()) { + return 0; } + return ((SizableInfo) elementInfo).getSizeInBytes(); + } - @Override - public ResolvedJavaType idType() { - // always use the original type for establishing identity - return getOriginal(hostedType); + private static String elementName(ElementInfo elementInfo) { + if (elementInfo == null) { + return ""; + } else { + return elementInfo.getName(); } + } - @Override - public String typeName() { - return toJavaName(hostedType); - } + private static SizableInfo.ElementKind elementKind(SizableInfo sizableInfo) { + return sizableInfo.getKind(); + } - @Override - public long classOffset() { - /* - * Only query the heap for reachable types. These are guaranteed to have been seen by - * the analysis and to exist in the shadow heap. - */ - if (hostedType.getWrapped().isReachable()) { - ObjectInfo objectInfo = heap.getObjectInfo(hostedType.getHub()); - if (objectInfo != null) { - return objectInfo.getOffset(); - } + /** + * Fetch the typedef name of an element info for {@link StructInfo structs} or + * {@link PointerToInfo pointer types}. Otherwise, we use the name of the element info. + * + * @param elementInfo the given element info + * @return the typedef name + */ + private static String typedefName(ElementInfo elementInfo) { + String name = null; + if (elementInfo != null) { + if (elementInfo instanceof PointerToInfo) { + name = ((PointerToInfo) elementInfo).getTypedefName(); + } else if (elementInfo instanceof StructInfo) { + name = ((StructInfo) elementInfo).getTypedefName(); } - return -1; - } - - @Override - public int size() { - if (hostedType instanceof HostedInstanceClass) { - /* We know the actual instance size in bytes. */ - return ((HostedInstanceClass) hostedType).getInstanceSize(); - } else if (hostedType instanceof HostedArrayClass) { - /* Use the size of header common to all arrays of this type. */ - return getObjectLayout().getArrayBaseOffset(hostedType.getComponentType().getStorageKind()); - } else if (hostedType instanceof HostedInterface) { - /* Use the size of the header common to all implementors. */ - return getObjectLayout().getFirstFieldOffset(); - } else { - /* Use the number of bytes needed needed to store the value. */ - assert hostedType instanceof HostedPrimitiveType; - JavaKind javaKind = hostedType.getStorageKind(); - return (javaKind == JavaKind.Void ? 0 : javaKind.getByteCount()); + if (name == null) { + name = elementInfo.getName(); } } + return name; } - private class NativeImageHeaderTypeInfo implements DebugHeaderTypeInfo { - String typeName; - int size; - List fieldInfos; - - NativeImageHeaderTypeInfo(String typeName, int size) { - this.typeName = typeName; - this.size = size; - this.fieldInfos = new LinkedList<>(); - } - - void addField(String name, ResolvedJavaType valueType, int offset, @SuppressWarnings("hiding") int size) { - NativeImageDebugHeaderFieldInfo fieldinfo = new NativeImageDebugHeaderFieldInfo(name, valueType, offset, size); - fieldInfos.add(fieldinfo); - } + /** + * Checks if a foreign type is a pointer type with {@link NativeLibraries}. + * + * @param type the given foreign type + * @return true if the type is a foreign pointer type, otherwise false + */ + private boolean isForeignPointerType(HostedType type) { + // unwrap because native libs operates on the analysis type universe + return nativeLibs.isPointerBase(type.getWrapped()); + } - @Override - public ResolvedJavaType idType() { - // The header type is unique in that it does not have an associated ResolvedJavaType - return null; - } + /* + * Foreign pointer types have associated element info which describes the target type. The + * following helpers support querying of and access to this element info. + */ - @SuppressWarnings("try") - @Override - public void debugContext(Consumer action) { - try (DebugContext.Scope s = debugContext.scope("DebugTypeInfo", typeName())) { - action.accept(debugContext); - } catch (Throwable e) { - throw debugContext.handle(e); + protected static boolean isTypedField(ElementInfo elementInfo) { + if (elementInfo instanceof StructFieldInfo) { + for (ElementInfo child : elementInfo.getChildren()) { + if (child instanceof AccessorInfo) { + switch (((AccessorInfo) child).getAccessorKind()) { + case GETTER: + case SETTER: + case ADDRESS: + return true; + } + } } } + return false; + } - @Override - public String typeName() { - return typeName; + protected HostedType getFieldType(StructFieldInfo field) { + // we should always have some sort of accessor, preferably a GETTER or a SETTER + // but possibly an ADDRESS accessor + for (ElementInfo elt : field.getChildren()) { + if (elt instanceof AccessorInfo accessorInfo) { + if (accessorInfo.getAccessorKind() == GETTER) { + return heap.hUniverse.lookup(accessorInfo.getReturnType()); + } + } } - - @Override - public long typeSignature(String prefix) { - return Digest.digestAsUUID(typeName).getLeastSignificantBits(); + for (ElementInfo elt : field.getChildren()) { + if (elt instanceof AccessorInfo accessorInfo) { + if (accessorInfo.getAccessorKind() == SETTER) { + return heap.hUniverse.lookup(accessorInfo.getParameterType(0)); + } + } } - - @Override - public DebugTypeKind typeKind() { - return DebugTypeKind.HEADER; + for (ElementInfo elt : field.getChildren()) { + if (elt instanceof AccessorInfo accessorInfo) { + if (accessorInfo.getAccessorKind() == ADDRESS) { + return heap.hUniverse.lookup(accessorInfo.getReturnType()); + } + } } + assert false : "Field %s must have a GETTER, SETTER, ADDRESS or OFFSET accessor".formatted(field); + // treat it as a word? + // n.b. we want a hosted type not an analysis type + return heap.hUniverse.lookup(wordBaseType); + } - @Override - public String fileName() { - return ""; + protected static boolean fieldTypeIsEmbedded(StructFieldInfo field) { + // we should always have some sort of accessor, preferably a GETTER or a SETTER + // but possibly an ADDRESS + for (ElementInfo elt : field.getChildren()) { + if (elt instanceof AccessorInfo accessorInfo) { + if (accessorInfo.getAccessorKind() == GETTER) { + return false; + } + } } - - @Override - public Path filePath() { - return null; + for (ElementInfo elt : field.getChildren()) { + if (elt instanceof AccessorInfo accessorInfo) { + if (accessorInfo.getAccessorKind() == SETTER) { + return false; + } + } } - - @Override - public long classOffset() { - return -1; + for (ElementInfo elt : field.getChildren()) { + if (elt instanceof AccessorInfo accessorInfo) { + if (accessorInfo.getAccessorKind() == ADDRESS) { + return true; + } + } } + throw VMError.shouldNotReachHere("Field %s must have a GETTER, SETTER, ADDRESS or OFFSET accessor".formatted(field)); + } - @Override - public int size() { - return size; - } + /** + * Creates a stream all types from the hosted universe. + * + * @return a stream of type in the hosted universe + */ + @Override + protected Stream typeInfo() { + // null represents the header type + return heap.hUniverse.getTypes().stream().map(type -> type); + } - @Override - public Stream fieldInfoProvider() { - return fieldInfos.stream(); - } + /** + * Creates a stream of all compilations with the corresponding hosted methods from the native + * image code cache. + * + * @return a stream of compilations + */ + @Override + protected Stream> codeInfo() { + return codeCache.getOrderedCompilations().stream().map(pair -> Pair.create(pair.getLeft(), pair.getRight())); } - private class NativeImageDebugHeaderFieldInfo implements DebugFieldInfo { - private final String name; - private final ResolvedJavaType valueType; - private final int offset; - private final int size; - private final int modifiers; - - NativeImageDebugHeaderFieldInfo(String name, ResolvedJavaType valueType, int offset, int size) { - this.name = name; - this.valueType = valueType; - this.offset = offset; - this.size = size; - this.modifiers = Modifier.PUBLIC; - } + /** + * Creates a stream of all {@link NativeImageHeap.ObjectInfo objects} in the native image heap. + * + * @return a stream of native image heap objects. + */ + @Override + protected Stream dataInfo() { + return heap.getObjects().stream().filter(obj -> obj.getPartition().getStartOffset() > 0).map(obj -> obj); + } - @Override - public String name() { - return name; - } + @Override + protected long getCodeOffset(SharedMethod method) { + assert method instanceof HostedMethod; + return ((HostedMethod) method).getCodeAddressOffset(); + } - @Override - public ResolvedJavaType valueType() { - if (valueType instanceof HostedType) { - return getOriginal((HostedType) valueType); + /** + * Processes type entries for {@link HostedType hosted types}. + * + *

    + * We need to process fields of {@link StructureTypeEntry structured types} after creating it to + * make sure that it is available for the field types. Otherwise, this would create a cycle for + * the type lookup. + * + *

    + * For a {@link ClassEntry} we also need to process interfaces and methods for the same reason + * with the fields for structured types. + * + * @param type the {@code SharedType} of the type entry + * @param typeEntry the {@code TypeEntry} to process + */ + @Override + protected void processTypeEntry(SharedType type, TypeEntry typeEntry) { + assert type instanceof HostedType; + HostedType hostedType = (HostedType) type; + + if (typeEntry instanceof StructureTypeEntry structureTypeEntry) { + if (typeEntry instanceof ArrayTypeEntry arrayTypeEntry) { + processArrayFields(hostedType, arrayTypeEntry); + } else if (typeEntry instanceof ForeignTypeEntry foreignTypeEntry) { + processForeignTypeFields(hostedType, foreignTypeEntry); + } else { + processFieldEntries(hostedType, structureTypeEntry); } - return valueType; - } - @Override - public int offset() { - return offset; + if (typeEntry instanceof ClassEntry classEntry) { + processInterfaces(hostedType, classEntry); + processMethods(hostedType, classEntry); + } } + } - @Override - public int size() { - return size; + /** + * For processing methods of a type, we iterate over all its declared methods and lookup the + * corresponding {@link MethodEntry} objects. This ensures that all declared methods of a type + * are installed. + * + * @param type the given type + * @param classEntry the type's {@code ClassEntry} + */ + private void processMethods(HostedType type, ClassEntry classEntry) { + for (HostedMethod method : type.getAllDeclaredMethods()) { + MethodEntry methodEntry = lookupMethodEntry(method); + debug.log("typename %s adding %s method %s %s(%s)%n", + classEntry.getTypeName(), methodEntry.getModifiersString(), methodEntry.getValueType().getTypeName(), methodEntry.getMethodName(), + formatParams(methodEntry.getThisParam(), methodEntry.getParams())); } + } - @Override - public boolean isEmbedded() { - return false; + private static String formatParams(LocalEntry thisParam, List paramInfo) { + if (paramInfo.isEmpty()) { + return ""; } - - @Override - public int modifiers() { - return modifiers; + StringBuilder builder = new StringBuilder(); + if (thisParam != null) { + builder.append(thisParam.type().getTypeName()); + builder.append(' '); + builder.append(thisParam.name()); } - - @Override - public String fileName() { - return ""; + for (LocalEntry param : paramInfo) { + if (!builder.isEmpty()) { + builder.append(", "); + } + builder.append(param.type().getTypeName()); + builder.append(' '); + builder.append(param.name()); } - @Override - public Path filePath() { - return null; - } + return builder.toString(); } - private Stream computeHeaderTypeInfo() { - ObjectLayout ol = getObjectLayout(); - - List infos = new LinkedList<>(); - int hubOffset = ol.getHubOffset(); - - NativeImageHeaderTypeInfo objHeader = new NativeImageHeaderTypeInfo("_objhdr", ol.getFirstFieldOffset()); - objHeader.addField("hub", hubType, hubOffset, referenceSize); - if (ol.isIdentityHashFieldInObjectHeader()) { - int idHashSize = ol.sizeInBytes(JavaKind.Int); - objHeader.addField("idHash", javaKindToHostedType.get(JavaKind.Int), ol.getObjectHeaderIdentityHashOffset(), idHashSize); + /** + * Produce a method name of a {@code HostedMethod} for the debug info. + * + * @param method method to produce a name for + * @return method name for the debug info + */ + @Override + protected String getMethodName(SharedMethod method) { + String name; + if (method instanceof HostedMethod hostedMethod) { + name = hostedMethod.getName(); + // replace (method name of a constructor) with the class name + if (hostedMethod.isConstructor()) { + name = hostedMethod.getDeclaringClass().toJavaName(); + if (name.indexOf('.') >= 0) { + name = name.substring(name.lastIndexOf('.') + 1); + } + if (name.indexOf('$') >= 0) { + name = name.substring(name.lastIndexOf('$') + 1); + } + } + } else { + name = super.getMethodName(method); } - infos.add(objHeader); + return name; + } - return infos.stream(); + @Override + public boolean isOverride(SharedMethod method) { + return method instanceof HostedMethod && allOverrides.contains(method); } - private class NativeImageDebugEnumTypeInfo extends NativeImageDebugInstanceTypeInfo implements DebugEnumTypeInfo { + @Override + public boolean isVirtual(SharedMethod method) { + return method instanceof HostedMethod hostedMethod && hostedMethod.hasVTableIndex(); + } - NativeImageDebugEnumTypeInfo(HostedInstanceClass enumClass) { - super(enumClass); - } + /** + * Fetch a methods symbol produced by the {@link BFDNameProvider}. + * + * @param method method to get the symbol name for + * @return symbol name of the method + */ + @Override + public String getSymbolName(SharedMethod method) { + return NativeImage.localSymbolNameForMethod(method); + } - @Override - public DebugTypeKind typeKind() { - return DebugTypeKind.ENUM; + /** + * Process interfaces from the hosted type and add the class entry as an implementor. This + * ensures all interfaces are installed as debug entries. + * + * @param type the given type + * @param classEntry the {@code ClassEntry} of the type + */ + private void processInterfaces(HostedType type, ClassEntry classEntry) { + for (HostedType interfaceType : type.getInterfaces()) { + TypeEntry entry = lookupTypeEntry(interfaceType); + if (entry instanceof InterfaceClassEntry interfaceClassEntry) { + debug.log("typename %s adding interface %s%n", classEntry.getTypeName(), interfaceType.toJavaName()); + interfaceClassEntry.addImplementor(classEntry); + } else { + // don't model the interface relationship when the Java interface actually + // identifies a + // foreign type + assert entry instanceof ForeignTypeEntry && classEntry instanceof ForeignTypeEntry; + } } } - private class NativeImageDebugInstanceTypeInfo extends NativeImageDebugTypeInfo implements DebugInstanceTypeInfo { - NativeImageDebugInstanceTypeInfo(HostedType hostedType) { - super(hostedType); - } + /** + * For arrays, we add a synthetic field for their length. This ensures that the length can be + * exposed in the object files debug info. + * + * @param type the given array type + * @param arrayTypeEntry the {@code ArrayTypeEntry} of the type + */ + private void processArrayFields(HostedType type, ArrayTypeEntry arrayTypeEntry) { + JavaKind arrayKind = type.getBaseType().getJavaKind(); + int headerSize = getObjectLayout().getArrayBaseOffset(arrayKind); + int arrayLengthOffset = getObjectLayout().getArrayLengthOffset(); + int arrayLengthSize = getObjectLayout().sizeInBytes(JavaKind.Int); + assert arrayLengthOffset + arrayLengthSize <= headerSize; + arrayTypeEntry.addField(createSyntheticFieldEntry("len", arrayTypeEntry, (HostedType) metaAccess.lookupJavaType(JavaKind.Int.toJavaClass()), arrayLengthOffset, arrayLengthSize)); + } - @Override - public long typeSignature(String prefix) { - return super.typeSignature(prefix + loaderName()); + /** + * Process {@link StructFieldInfo fields} for {@link StructInfo foreign structs}. Fields are + * ordered by offset and added as {@link FieldEntry field entries} to the foreign type entry. + * + * @param type the given type + * @param foreignTypeEntry the {@code ForeignTypeEntry} of the type + */ + private void processForeignTypeFields(HostedType type, ForeignTypeEntry foreignTypeEntry) { + ElementInfo elementInfo = nativeLibs.findElementInfo(type); + if (elementInfo instanceof StructInfo) { + elementInfo.getChildren().stream().filter(NativeImageDebugInfoProvider::isTypedField) + .map(StructFieldInfo.class::cast) + .sorted(Comparator.comparingInt(field -> field.getOffsetInfo().getProperty())) + .forEach(field -> { + HostedType fieldType = getFieldType(field); + FieldEntry fieldEntry = createFieldEntry(foreignTypeEntry.getFileEntry(), field.getName(), foreignTypeEntry, fieldType, field.getOffsetInfo().getProperty(), + field.getSizeInBytes(), fieldTypeIsEmbedded(field), 0); + foreignTypeEntry.addField(fieldEntry); + }); } + } - @Override - public DebugTypeKind typeKind() { - return DebugTypeKind.INSTANCE; + /** + * Processes instance fields and static fields of hosted types to and adds {@link FieldEntry + * field entries} to the structured type entry. + * + * @param type the given type + * @param structureTypeEntry the {@code StructuredTypeEntry} of the type + */ + private void processFieldEntries(HostedType type, StructureTypeEntry structureTypeEntry) { + for (HostedField field : type.getInstanceFields(false)) { + structureTypeEntry.addField(createFieldEntry(field, structureTypeEntry)); } - @Override - public String loaderName() { - return UniqueShortNameProvider.singleton().uniqueShortLoaderName(hostedType.getJavaClass().getClassLoader()); + for (ResolvedJavaField field : type.getStaticFields()) { + assert field instanceof HostedField; + structureTypeEntry.addField(createFieldEntry((HostedField) field, structureTypeEntry)); } + } - @Override - public Stream fieldInfoProvider() { - Stream instanceFieldsStream = Arrays.stream(hostedType.getInstanceFields(false)).map(this::createDebugFieldInfo); - if (hostedType instanceof HostedInstanceClass && hostedType.getStaticFields().length > 0) { - Stream staticFieldsStream = Arrays.stream(hostedType.getStaticFields()).map(this::createDebugStaticFieldInfo); - return Stream.concat(instanceFieldsStream, staticFieldsStream); + /** + * Creates a new field entry for a hosted field. + * + * @param field the given hfield + * @param ownerType the structured type owning the hosted field + * @return a {@code FieldEntry} representing the hosted field + */ + private FieldEntry createFieldEntry(HostedField field, StructureTypeEntry ownerType) { + FileEntry fileEntry = lookupFileEntry(field); + String fieldName = field.getName(); + HostedType valueType = field.getType(); + JavaKind storageKind = field.getType().getStorageKind(); + int size = getObjectLayout().sizeInBytes(storageKind); + int modifiers = field.getModifiers(); + int offset = field.getLocation(); + /* + * For static fields we need to add in the appropriate partition base but only if we have a + * real offset + */ + if (Modifier.isStatic(modifiers) && offset >= 0) { + if (storageKind.isPrimitive()) { + offset += primitiveStartOffset; } else { - return instanceFieldsStream; + offset += referenceStartOffset; } } - @Override - public Stream methodInfoProvider() { - return Arrays.stream(hostedType.getAllDeclaredMethods()).map(this::createDebugMethodInfo); - } + return createFieldEntry(fileEntry, fieldName, ownerType, valueType, offset, size, false, modifiers); + } - @Override - public ResolvedJavaType superClass() { - HostedClass superClass = hostedType.getSuperclass(); + /** + * Creates an unprocessed type entry. The type entry contains all static information, which is + * its name, size, classOffset, loader entry and type signatures. For {@link ClassEntry} types, + * it also contains the superclass {@code ClassEntry}. + * + *

    + * The returned type entry does not hold information on its fields, methods, and interfaces + * implementors. This information is patched later in {@link #processTypeEntry}. This is done to + * avoid cycles in the type entry lookup. + * + * @param type the {@code SharedType} to process + * @return an unprocessed type entry + */ + @Override + protected TypeEntry createTypeEntry(SharedType type) { + assert type instanceof HostedType; + HostedType hostedType = (HostedType) type; + + String typeName = hostedType.toJavaName(); + int size = getTypeSize(hostedType); + long classOffset = getClassOffset(hostedType); + LoaderEntry loaderEntry = lookupLoaderEntry(hostedType); + String loaderName = loaderEntry == null ? "" : loaderEntry.loaderId(); + long typeSignature = getTypeSignature(typeName + loaderName); + long compressedTypeSignature = useHeapBase ? getTypeSignature(INDIRECT_PREFIX + typeName + loaderName) : typeSignature; + + if (hostedType.isPrimitive()) { + JavaKind kind = hostedType.getStorageKind(); + debug.log("typename %s (%d bits)%n", typeName, kind == JavaKind.Void ? 0 : kind.getBitCount()); + return new PrimitiveTypeEntry(typeName, size, classOffset, typeSignature, kind); + } else { /* - * Unwrap the hosted type's super class to the original to provide the correct identity - * type. + * this is a structured type (array or class entry), or a foreign type entry (uses the + * layout signature even for not structured types) */ - if (superClass != null) { - return getOriginal(superClass); - } - return null; - } - - @Override - public Stream interfaces() { - // map through getOriginal so we can use the result as an id type - return Arrays.stream(hostedType.getInterfaces()).map(interfaceType -> getOriginal(interfaceType)); - } - - private NativeImageDebugFieldInfo createDebugFieldInfo(HostedField field) { - return new NativeImageDebugFieldInfo(field); - } - - private NativeImageDebugFieldInfo createDebugStaticFieldInfo(ResolvedJavaField field) { - return new NativeImageDebugFieldInfo((HostedField) field); - } - - private NativeImageDebugMethodInfo createDebugMethodInfo(HostedMethod method) { - return new NativeImageDebugMethodInfo(method); - } - - private class NativeImageDebugFieldInfo extends NativeImageDebugFileInfo implements DebugFieldInfo { - private final HostedField field; - - NativeImageDebugFieldInfo(HostedField field) { - super(field); - this.field = field; - } + long layoutTypeSignature = getTypeSignature(LAYOUT_PREFIX + typeName + loaderName); + if (hostedType.isArray()) { + TypeEntry elementTypeEntry = lookupTypeEntry(hostedType.getComponentType()); + debug.log("typename %s element type %s base size %d length offset %d%n", typeName, elementTypeEntry.getTypeName(), + getObjectLayout().getArrayBaseOffset(hostedType.getComponentType().getStorageKind()), getObjectLayout().getArrayLengthOffset()); + return new ArrayTypeEntry(typeName, size, classOffset, typeSignature, compressedTypeSignature, + layoutTypeSignature, elementTypeEntry, loaderEntry); + } else { + // this is a class entry or a foreign type entry + ClassEntry superClass = hostedType.getSuperclass() == null ? null : (ClassEntry) lookupTypeEntry(hostedType.getSuperclass()); - @Override - public String name() { - return field.getName(); - } + if (debug.isLogEnabled() && superClass != null) { + debug.log("typename %s adding super %s%n", typeName, superClass.getTypeName()); + } - @Override - public ResolvedJavaType valueType() { - return getOriginal(field.getType()); - } + FileEntry fileEntry = lookupFileEntry(hostedType); + if (isForeignWordType(hostedType)) { + /* + * A foreign type is either a generic word type, struct type, integer type, + * float type, or a pointer. + */ + if (debug.isLogEnabled()) { + logForeignTypeInfo(hostedType); + } - @Override - public int offset() { - int offset = field.getLocation(); - /* - * For static fields we need to add in the appropriate partition base but only if we - * have a real offset - */ - if (isStatic() && offset >= 0) { - if (isPrimitive()) { - offset += primitiveStartOffset; - } else { - offset += referenceStartOffset; + ElementInfo elementInfo = nativeLibs.findElementInfo(hostedType); + SizableInfo.ElementKind elementKind = elementInfo instanceof SizableInfo ? ((SizableInfo) elementInfo).getKind() : null; + size = elementSize(elementInfo); + + switch (elementInfo) { + case PointerToInfo pointerToInfo -> { + /* + * This must be a pointer. If the target type is known use it to declare + * the pointer type, otherwise default to 'void *' + */ + TypeEntry pointerToEntry = null; + if (elementKind == SizableInfo.ElementKind.POINTER) { + /* + * any target type for the pointer will be defined by a CPointerTo + * or RawPointerTo annotation + */ + CPointerTo cPointerTo = type.getAnnotation(CPointerTo.class); + if (cPointerTo != null) { + HostedType pointerTo = heap.hMetaAccess.lookupJavaType(cPointerTo.value()); + pointerToEntry = lookupTypeEntry(pointerTo); + } + RawPointerTo rawPointerTo = type.getAnnotation(RawPointerTo.class); + if (rawPointerTo != null) { + HostedType pointerTo = heap.hMetaAccess.lookupJavaType(rawPointerTo.value()); + pointerToEntry = lookupTypeEntry(pointerTo); + } + + if (pointerToEntry != null) { + debug.log("foreign type %s referent %s ", typeName, pointerToEntry.getTypeName()); + } else { + debug.log("foreign type %s", typeName); + } + } else if (elementKind == SizableInfo.ElementKind.INTEGER || elementKind == SizableInfo.ElementKind.FLOAT) { + pointerToEntry = lookupNativePrimitiveType(pointerToInfo); + } + + if (pointerToEntry == null) { + pointerToEntry = lookupTypeEntry(voidType); + } + if (pointerToEntry == null) { + pointerToEntry = lookupTypeEntry(voidType); + } + + /* + * Setting the layout type to the type we point to reuses an available + * type unit, so we do not have to write are separate type unit. + */ + layoutTypeSignature = pointerToEntry.getTypeSignature(); + + return new ForeignPointerTypeEntry(typeName, size, classOffset, typeSignature, layoutTypeSignature, superClass, fileEntry, loaderEntry, pointerToEntry); + return new ForeignPointerTypeEntry(typeName, size, classOffset, typeSignature, layoutTypeSignature, superClass, fileEntry, loaderEntry, pointerToEntry); + } + case StructInfo structInfo -> { + String typedefName = typedefName(structInfo); + ForeignStructTypeEntry parentEntry = null; + for (HostedInterface hostedInterface : hostedType.getInterfaces()) { + ElementInfo otherInfo = nativeLibs.findElementInfo(hostedInterface); + if (otherInfo instanceof StructInfo) { + parentEntry = (ForeignStructTypeEntry) lookupTypeEntry(hostedInterface); + } + } + return new ForeignStructTypeEntry(typeName, size, classOffset, typeSignature, layoutTypeSignature, superClass, fileEntry, loaderEntry, typedefName, parentEntry); + } + case null, default -> { + if (isForeignPointerType(hostedType)) { + // unknown pointer type maps to void* + TypeEntry pointerToEntry = lookupTypeEntry(voidType); + layoutTypeSignature = pointerToEntry.getTypeSignature(); + return new ForeignPointerTypeEntry(typeName, size, classOffset, typeSignature, layoutTypeSignature, superClass, fileEntry, loaderEntry, pointerToEntry); + } else { + // if not a pointer type, this must be a word type + boolean isSigned = nativeLibs.isSigned(hostedType); + return new ForeignWordTypeEntry(typeName, size, classOffset, typeSignature, layoutTypeSignature, + superClass, fileEntry, loaderEntry, isSigned); + } + } } + } else if (hostedType.isEnum()) { + return new EnumClassEntry(typeName, size, classOffset, typeSignature, compressedTypeSignature, + layoutTypeSignature, superClass, fileEntry, loaderEntry); + } else if (hostedType.isInstanceClass()) { + return new ClassEntry(typeName, size, classOffset, typeSignature, compressedTypeSignature, + layoutTypeSignature, superClass, fileEntry, loaderEntry); + } else if (hostedType.isInterface()) { + return new InterfaceClassEntry(typeName, size, classOffset, typeSignature, compressedTypeSignature, + layoutTypeSignature, superClass, fileEntry, loaderEntry); + } else { + throw new RuntimeException("Unknown type kind " + hostedType.getName()); } - return offset; - } - - @Override - public int size() { - return getObjectLayout().sizeInBytes(field.getType().getStorageKind()); } + } + } - @Override - public boolean isEmbedded() { - return false; + private TypeEntry lookupNativePrimitiveType(PointerToInfo pointerToInfo) { + return nativePrimitiveTypeIndex.computeIfAbsent(pointerToInfo, p -> { + try (DebugContext.Scope s = debug.scope("DebugInfoType")) { + assert p.getKind() == SizableInfo.ElementKind.INTEGER || p.getKind() == SizableInfo.ElementKind.FLOAT; + String typeName = p.getName(); + int classOffset = -1; + long typeSignature = getTypeSignature(FOREIGN_PREFIX + typeName); + int size = p.getSizeInBytes(); + int bitCount = size * 8; + boolean isNumericInteger = p.getKind() == SizableInfo.ElementKind.INTEGER; + boolean isNumericFloat = p.getKind() == SizableInfo.ElementKind.FLOAT; + boolean isUnsigned = p.isUnsigned(); + + debug.log("typename %s (%d bits)%n", typeName, bitCount); + return new PrimitiveTypeEntry(typeName, size, classOffset, typeSignature, bitCount, isNumericInteger, isNumericFloat, isUnsigned); + } catch (Throwable e) { + throw debug.handle(e); } + }); + } - @Override - public int modifiers() { - ResolvedJavaField targetField = field.wrapped.wrapped; - if (targetField instanceof SubstitutionField) { - targetField = ((SubstitutionField) targetField).getOriginal(); - } - return targetField.getModifiers(); - } + /* The following methods provide some logging for foreign type entries. */ + private void logForeignTypeInfo(HostedType hostedType) { + if (!isForeignPointerType(hostedType)) { + // non pointer type must be an interface because an instance needs to be pointed to + assert hostedType.isInterface(); + // foreign word types never have element info + debug.log(DebugContext.VERBOSE_LEVEL, "Foreign word type %s", hostedType.toJavaName()); + } else { + ElementInfo elementInfo = nativeLibs.findElementInfo(hostedType); + logForeignPointerType(hostedType, elementInfo); + } + } - private boolean isStatic() { - return Modifier.isStatic(modifiers()); + private void logForeignPointerType(HostedType hostedType, ElementInfo elementInfo) { + if (elementInfo == null) { + // can happen for a generic (void*) pointer or a class + if (hostedType.isInterface()) { + debug.log(DebugContext.VERBOSE_LEVEL, "Foreign pointer type %s", hostedType.toJavaName()); + } else { + debug.log(DebugContext.VERBOSE_LEVEL, "Foreign pointer type %s (class)", hostedType.toJavaName()); } - - private boolean isPrimitive() { - return field.getType().getStorageKind().isPrimitive(); + } else if (elementInfo instanceof PointerToInfo) { + logPointerToInfo(hostedType, (PointerToInfo) elementInfo); + } else if (elementInfo instanceof StructInfo) { + if (elementInfo instanceof RawStructureInfo) { + logRawStructureInfo(hostedType, (RawStructureInfo) elementInfo); + } else { + logStructInfo(hostedType, (StructInfo) elementInfo); } } + } - private class NativeImageDebugMethodInfo extends NativeImageDebugHostedMethodInfo implements DebugMethodInfo { - NativeImageDebugMethodInfo(HostedMethod hostedMethod) { - super(hostedMethod); - } + private void logPointerToInfo(HostedType hostedType, PointerToInfo pointerToInfo) { + debug.log(DebugContext.VERBOSE_LEVEL, "Foreign pointer type %s %s", hostedType.toJavaName(), elementKind(pointerToInfo)); + assert hostedType.isInterface(); + int size = elementSize(pointerToInfo); + boolean isUnsigned = pointerToInfo.isUnsigned(); + String typedefName = pointerToInfo.getTypedefName(); + debug.log("element size = %d", size); + debug.log("%s", (isUnsigned ? "" : "")); + if (typedefName != null) { + debug.log("typedefname = %s", typedefName); } + dumpElementInfo(pointerToInfo); } - private class NativeImageDebugInterfaceTypeInfo extends NativeImageDebugInstanceTypeInfo implements DebugInterfaceTypeInfo { - - NativeImageDebugInterfaceTypeInfo(HostedInterface interfaceClass) { - super(interfaceClass); + private void logStructInfo(HostedType hostedType, StructInfo structInfo) { + debug.log(DebugContext.VERBOSE_LEVEL, "Foreign struct type %s %s", hostedType.toJavaName(), elementKind(structInfo)); + assert hostedType.isInterface(); + boolean isIncomplete = structInfo.isIncomplete(); + if (isIncomplete) { + debug.log(""); + } else { + debug.log("complete : element size = %d", elementSize(structInfo)); } - - @Override - public DebugTypeKind typeKind() { - return DebugTypeKind.INTERFACE; + String typedefName = structInfo.getTypedefName(); + if (typedefName != null) { + debug.log(" typedefName = %s", typedefName); } + dumpElementInfo(structInfo); } - private class NativeImageDebugForeignTypeInfo extends NativeImageDebugInstanceTypeInfo implements DebugForeignTypeInfo { - - ElementInfo elementInfo; - - NativeImageDebugForeignTypeInfo(HostedType hostedType) { - this(hostedType, nativeLibs.findElementInfo(hostedType)); - } - - NativeImageDebugForeignTypeInfo(HostedType hostedType, ElementInfo elementInfo) { - super(hostedType); - assert isForeignWordType(hostedType); - this.elementInfo = elementInfo; - assert verifyElementInfo() : "unexpected element info kind"; - } - - private boolean verifyElementInfo() { - // word types and some pointer types do not have element info - if (elementInfo == null || !(elementInfo instanceof SizableInfo)) { - return true; - } - switch (elementKind((SizableInfo) elementInfo)) { - // we may see these as the target kinds for foreign pointer types - case INTEGER: - case POINTER: - case FLOAT: - case UNKNOWN: - return true; - // we may not see these as the target kinds for foreign pointer types - case STRING: - case BYTEARRAY: - case OBJECT: - default: - return false; - } - } - - @Override - public DebugTypeKind typeKind() { - return DebugTypeKind.FOREIGN; - } - - @Override - public Stream fieldInfoProvider() { - // TODO - generate fields for Struct and RawStruct types derived from element info - return orderedFieldsStream(elementInfo).map(this::createDebugForeignFieldInfo); - } - - @Override - public int size() { - return elementSize(elementInfo); - } - - DebugFieldInfo createDebugForeignFieldInfo(StructFieldInfo structFieldInfo) { - return new NativeImageDebugForeignFieldInfo(hostedType, structFieldInfo); - } - - @Override - public String typedefName() { - String name = null; - if (elementInfo != null) { - if (elementInfo instanceof PointerToInfo) { - name = ((PointerToInfo) elementInfo).getTypedefName(); - } else if (elementInfo instanceof StructInfo) { - name = ((StructInfo) elementInfo).getTypedefName(); - } - if (name == null) { - name = elementName(elementInfo); - } - } - return name; - } - - @Override - public boolean isWord() { - return !isForeignPointerType(hostedType); - } - - @Override - public boolean isStruct() { - return elementInfo instanceof StructInfo; - } - - @Override - public boolean isPointer() { - if (elementInfo != null && elementInfo instanceof SizableInfo) { - return elementKind((SizableInfo) elementInfo) == ElementKind.POINTER; - } else { - return false; - } - } - - @Override - public boolean isIntegral() { - if (elementInfo != null && elementInfo instanceof SizableInfo) { - return elementKind((SizableInfo) elementInfo) == ElementKind.INTEGER; - } else { - return false; - } - } - - @Override - public boolean isFloat() { - if (elementInfo != null) { - return elementKind((SizableInfo) elementInfo) == ElementKind.FLOAT; - } else { - return false; - } - } - - @Override - public boolean isSigned() { - // pretty much everything is unsigned by default - // special cases are SignedWord which, obviously, points to a signed word and - // anything pointing to an integral type that is not tagged as unsigned - return (nativeLibs.isSigned(hostedType.getWrapped()) || (isIntegral() && !((SizableInfo) elementInfo).isUnsigned())); - } - - @Override - public ResolvedJavaType parent() { - if (isStruct()) { - // look for the first interface that also has an associated StructInfo - for (HostedInterface hostedInterface : hostedType.getInterfaces()) { - ElementInfo otherInfo = nativeLibs.findElementInfo(hostedInterface); - if (otherInfo instanceof StructInfo) { - return getOriginal(hostedInterface); - } - } - } - return null; - } - - @Override - public ResolvedJavaType pointerTo() { - if (isPointer()) { - // any target type for the pointer will be defined by a CPointerTo or RawPointerTo - // annotation - CPointerTo cPointerTo = hostedType.getAnnotation(CPointerTo.class); - if (cPointerTo != null) { - HostedType pointerTo = heap.hMetaAccess.lookupJavaType(cPointerTo.value()); - return getOriginal(pointerTo); - } - RawPointerTo rawPointerTo = hostedType.getAnnotation(RawPointerTo.class); - if (rawPointerTo != null) { - HostedType pointerTo = heap.hMetaAccess.lookupJavaType(rawPointerTo.value()); - return getOriginal(pointerTo); - } - } - return null; - } - } - - private class NativeImageDebugForeignFieldInfo extends NativeImageDebugFileInfo implements DebugInfoProvider.DebugFieldInfo { - StructFieldInfo structFieldInfo; - - NativeImageDebugForeignFieldInfo(HostedType hostedType, StructFieldInfo structFieldInfo) { - super(hostedType); - this.structFieldInfo = structFieldInfo; - } - - @Override - public int size() { - return structFieldInfo.getSizeInBytes(); - } - - @Override - public int offset() { - return structFieldInfo.getOffsetInfo().getProperty(); - } - - @Override - public String name() { - return structFieldInfo.getName(); - } - - @Override - public ResolvedJavaType valueType() { - // we need to ensure the hosted type identified for the field value gets translated to - // an original in order to be consistent with id types for substitutions - return getOriginal(getFieldType(structFieldInfo)); - } - - @Override - public boolean isEmbedded() { - // this is true when the field has an ADDRESS accessor type - return fieldTypeIsEmbedded(structFieldInfo); - } - - @Override - public int modifiers() { - return 0; - } - } - - private class NativeImageDebugArrayTypeInfo extends NativeImageDebugTypeInfo implements DebugArrayTypeInfo { - HostedArrayClass arrayClass; - List fieldInfos; - - NativeImageDebugArrayTypeInfo(HostedArrayClass arrayClass) { - super(arrayClass); - this.arrayClass = arrayClass; - this.fieldInfos = new LinkedList<>(); - JavaKind arrayKind = arrayClass.getBaseType().getJavaKind(); - int headerSize = getObjectLayout().getArrayBaseOffset(arrayKind); - int arrayLengthOffset = getObjectLayout().getArrayLengthOffset(); - int arrayLengthSize = getObjectLayout().sizeInBytes(JavaKind.Int); - assert arrayLengthOffset + arrayLengthSize <= headerSize; - - addField("len", javaKindToHostedType.get(JavaKind.Int), arrayLengthOffset, arrayLengthSize); - } - - void addField(String name, ResolvedJavaType valueType, int offset, @SuppressWarnings("hiding") int size) { - NativeImageDebugHeaderFieldInfo fieldinfo = new NativeImageDebugHeaderFieldInfo(name, valueType, offset, size); - fieldInfos.add(fieldinfo); - } - - @Override - public long typeSignature(String prefix) { - HostedType elementType = hostedType.getComponentType(); - while (elementType.isArray()) { - elementType = elementType.getComponentType(); - } - String loaderId = ""; - if (elementType.isInstanceClass() || elementType.isInterface() || elementType.isEnum()) { - loaderId = UniqueShortNameProvider.singleton().uniqueShortLoaderName(elementType.getJavaClass().getClassLoader()); - } - return super.typeSignature(prefix + loaderId); - } - - @Override - public DebugTypeKind typeKind() { - return DebugTypeKind.ARRAY; - } - - @Override - public int baseSize() { - return getObjectLayout().getArrayBaseOffset(arrayClass.getComponentType().getStorageKind()); - } - - @Override - public int lengthOffset() { - return getObjectLayout().getArrayLengthOffset(); - } - - @Override - public ResolvedJavaType elementType() { - HostedType elementType = arrayClass.getComponentType(); - return getOriginal(elementType); - } - - @Override - public Stream fieldInfoProvider() { - return fieldInfos.stream(); - } - } - - private class NativeImageDebugPrimitiveTypeInfo extends NativeImageDebugTypeInfo implements DebugPrimitiveTypeInfo { - private final HostedPrimitiveType primitiveType; - - NativeImageDebugPrimitiveTypeInfo(HostedPrimitiveType primitiveType) { - super(primitiveType); - this.primitiveType = primitiveType; - } - - @Override - public long typeSignature(String prefix) { - /* - * primitive types never need an indirection so use the same signature for places where - * we might want a special type - */ - return super.typeSignature(""); - } - - @Override - public DebugTypeKind typeKind() { - return DebugTypeKind.PRIMITIVE; - } - - @Override - public int bitCount() { - JavaKind javaKind = primitiveType.getStorageKind(); - return (javaKind == JavaKind.Void ? 0 : javaKind.getBitCount()); - } - - @Override - public char typeChar() { - return primitiveType.getStorageKind().getTypeChar(); - } - - @Override - public int flags() { - char typeChar = primitiveType.getStorageKind().getTypeChar(); - switch (typeChar) { - case 'B': - case 'S': - case 'I': - case 'J': { - return FLAG_NUMERIC | FLAG_INTEGRAL | FLAG_SIGNED; - } - case 'C': { - return FLAG_NUMERIC | FLAG_INTEGRAL; - } - case 'F': - case 'D': { - return FLAG_NUMERIC; - } - default: { - assert typeChar == 'V' || typeChar == 'Z'; - return 0; - } - } - } - } - - @SuppressWarnings("try") - private NativeImageDebugTypeInfo createDebugTypeInfo(HostedType hostedType) { - try (DebugContext.Scope s = debugContext.scope("DebugTypeInfo", hostedType.toJavaName())) { - if (isForeignWordType(hostedType)) { - assert hostedType.isInterface() || hostedType.isInstanceClass() : "foreign type must be instance class or interface!"; - logForeignTypeInfo(hostedType); - return new NativeImageDebugForeignTypeInfo(hostedType); - } else if (hostedType.isEnum()) { - return new NativeImageDebugEnumTypeInfo((HostedInstanceClass) hostedType); - } else if (hostedType.isInstanceClass()) { - return new NativeImageDebugInstanceTypeInfo(hostedType); - } else if (hostedType.isInterface()) { - return new NativeImageDebugInterfaceTypeInfo((HostedInterface) hostedType); - } else if (hostedType.isArray()) { - return new NativeImageDebugArrayTypeInfo((HostedArrayClass) hostedType); - } else if (hostedType.isPrimitive()) { - return new NativeImageDebugPrimitiveTypeInfo((HostedPrimitiveType) hostedType); - } else { - throw new RuntimeException("Unknown type kind " + hostedType.getName()); - } - } catch (Throwable e) { - throw debugContext.handle(e); - } - } - - private void logForeignTypeInfo(HostedType hostedType) { - if (!isForeignPointerType(hostedType)) { - // non pointer type must be an interface because an instance needs to be pointed to - assert hostedType.isInterface(); - // foreign word types never have element info - debugContext.log(DebugContext.VERBOSE_LEVEL, "Foreign word type %s", hostedType.toJavaName()); - } else { - ElementInfo elementInfo = nativeLibs.findElementInfo(hostedType); - logForeignPointerType(hostedType, elementInfo); - } - } - - private void logForeignPointerType(HostedType hostedType, ElementInfo elementInfo) { - if (elementInfo == null) { - // can happen for a generic (void*) pointer or a class - if (hostedType.isInterface()) { - debugContext.log(DebugContext.VERBOSE_LEVEL, "Foreign pointer type %s", hostedType.toJavaName()); - } else { - debugContext.log(DebugContext.VERBOSE_LEVEL, "Foreign pointer type %s (class)", hostedType.toJavaName()); - } - } else if (elementInfo instanceof PointerToInfo) { - logPointerToInfo(hostedType, (PointerToInfo) elementInfo); - } else if (elementInfo instanceof StructInfo) { - if (elementInfo instanceof RawStructureInfo) { - logRawStructureInfo(hostedType, (RawStructureInfo) elementInfo); - } else { - logStructInfo(hostedType, (StructInfo) elementInfo); - } - } - } - - private void logPointerToInfo(HostedType hostedType, PointerToInfo pointerToInfo) { - debugContext.log(DebugContext.VERBOSE_LEVEL, "Foreign pointer type %s %s", hostedType.toJavaName(), elementKind(pointerToInfo)); - assert hostedType.isInterface(); - int size = elementSize(pointerToInfo); - boolean isUnsigned = pointerToInfo.isUnsigned(); - String typedefName = pointerToInfo.getTypedefName(); - debugContext.log("element size = %d", size); - debugContext.log("%s", (isUnsigned ? "" : "")); - if (typedefName != null) { - debugContext.log("typedefname = %s", typedefName); - } - dumpElementInfo(pointerToInfo); - } - - private void logStructInfo(HostedType hostedType, StructInfo structInfo) { - debugContext.log(DebugContext.VERBOSE_LEVEL, "Foreign struct type %s %s", hostedType.toJavaName(), elementKind(structInfo)); - assert hostedType.isInterface(); - boolean isIncomplete = structInfo.isIncomplete(); - if (isIncomplete) { - debugContext.log(""); - } else { - debugContext.log("complete : element size = %d", elementSize(structInfo)); - } - String typedefName = structInfo.getTypedefName(); - if (typedefName != null) { - debugContext.log(" typedefName = %s", typedefName); - } - dumpElementInfo(structInfo); - } - - private void logRawStructureInfo(HostedType hostedType, RawStructureInfo rawStructureInfo) { - debugContext.log(DebugContext.VERBOSE_LEVEL, "Foreign raw struct type %s %s", hostedType.toJavaName(), elementKind(rawStructureInfo)); - assert hostedType.isInterface(); - debugContext.log("element size = %d", elementSize(rawStructureInfo)); - String typedefName = rawStructureInfo.getTypedefName(); - if (typedefName != null) { - debugContext.log(" typedefName = %s", typedefName); + private void logRawStructureInfo(HostedType hostedType, RawStructureInfo rawStructureInfo) { + debug.log(DebugContext.VERBOSE_LEVEL, "Foreign raw struct type %s %s", hostedType.toJavaName(), elementKind(rawStructureInfo)); + assert hostedType.isInterface(); + debug.log("element size = %d", elementSize(rawStructureInfo)); + String typedefName = rawStructureInfo.getTypedefName(); + if (typedefName != null) { + debug.log(" typedefName = %s", typedefName); } dumpElementInfo(rawStructureInfo); } - private int structFieldComparator(StructFieldInfo f1, StructFieldInfo f2) { - int offset1 = f1.getOffsetInfo().getProperty(); - int offset2 = f2.getOffsetInfo().getProperty(); - return offset1 - offset2; - } - - private Stream orderedFieldsStream(ElementInfo elementInfo) { - if (elementInfo instanceof RawStructureInfo || elementInfo instanceof StructInfo) { - return elementInfo.getChildren().stream().filter(elt -> isTypedField(elt)) - .map(elt -> ((StructFieldInfo) elt)) - .sorted(this::structFieldComparator); - } else { - return Stream.empty(); - } - } - private void dumpElementInfo(ElementInfo elementInfo) { if (elementInfo != null) { - debugContext.log("Element Info {%n%s}", formatElementInfo(elementInfo)); + debug.log("Element Info {%n%s}", formatElementInfo(elementInfo)); } else { - debugContext.log("Element Info {}"); + debug.log("Element Info {}"); } } @@ -1089,1580 +928,108 @@ private static void formatPropertyInfo(PropertyInfo propertyInfo, StringB } private static void indentElementInfo(StringBuilder stringBuilder, int indent) { - for (int i = 0; i <= indent; i++) { - stringBuilder.append(" "); - } + stringBuilder.append(" ".repeat(Math.max(0, indent + 1))); } - protected abstract class NativeImageDebugBaseMethodInfo extends NativeImageDebugFileInfo implements DebugMethodInfo { - protected final ResolvedJavaMethod method; - protected int line; - protected final List paramInfo; - protected final DebugLocalInfo thisParamInfo; - - NativeImageDebugBaseMethodInfo(ResolvedJavaMethod m) { - super(m); - // We occasionally see an AnalysisMethod as input to this constructor. - // That can happen if the points to analysis builds one into a node - // source position when building the initial graph. The global - // replacement that is supposed to ensure the compiler sees HostedXXX - // types rather than AnalysisXXX types appears to skip translating - // method references in node source positions. So, we do the translation - // here just to make sure we use a HostedMethod wherever possible. - method = promoteAnalysisToHosted(m); - LineNumberTable lineNumberTable = method.getLineNumberTable(); - line = (lineNumberTable != null ? lineNumberTable.getLineNumber(0) : 0); - this.paramInfo = createParamInfo(method, line); - // We use the target modifiers to decide where to install any first param - // even though we may have added it according to whether method is static. - // That's because in a few special cases method is static but the original - // DebugFrameLocals - // from which it is derived is an instance method. This appears to happen - // when a C function pointer masquerades as a method. Whatever parameters - // we pass through need to match the definition of the original. - if (Modifier.isStatic(modifiers())) { - this.thisParamInfo = null; - } else { - this.thisParamInfo = paramInfo.remove(0); + /** + * Lookup the file entry of a {@code ResolvedJavaType}. + * + *

    + * First tries to find the source file using the {@link SourceManager}. If no file was found, a + * file name is generated from the full class name. + * + * @param type the given {@code ResolvedJavaType} + * @return the {@code FileEntry} for the type + */ + @Override + @SuppressWarnings("try") + public FileEntry lookupFileEntry(ResolvedJavaType type) { + Class clazz = OriginalClassProvider.getJavaClass(type); + SourceManager sourceManager = ImageSingletons.lookup(SourceManager.class); + try (DebugContext.Scope s = debug.scope("DebugFileInfo", type)) { + Path filePath = sourceManager.findAndCacheSource(type, clazz, debug); + if (filePath != null) { + return lookupFileEntry(filePath); } + } catch (Throwable e) { + throw debug.handle(e); } - private ResolvedJavaMethod promoteAnalysisToHosted(ResolvedJavaMethod m) { - if (m instanceof AnalysisMethod) { - return heap.hUniverse.lookup(m); - } - if (!(m instanceof HostedMethod)) { - debugContext.log(DebugContext.DETAILED_LEVEL, "Method is neither Hosted nor Analysis : %s.%s%s", m.getDeclaringClass().getName(), m.getName(), - m.getSignature().toMethodDescriptor()); - } - return m; - } + // fallback to default file entry lookup + return super.lookupFileEntry(type); + } - private ResolvedJavaMethod originalMethod() { - // unwrap to an original method as far as we can - ResolvedJavaMethod targetMethod = method; - while (targetMethod instanceof WrappedJavaMethod) { - targetMethod = ((WrappedJavaMethod) targetMethod).getWrapped(); - } - // if we hit a substitution then we can translate to the original - // for identity otherwise we use whatever we unwrapped to. - if (targetMethod instanceof SubstitutionMethod) { - targetMethod = ((SubstitutionMethod) targetMethod).getOriginal(); - } - return targetMethod; + /** + * Lookup a {@code FileEntry} for a {@code HostedMethod}. For a {@link SubstitutionMethod}, this + * uses the source file of the annotated method. + * + * @param method the given {@code ResolvedJavaMethod} + * @return the {@code FilEntry} for the method + */ + @Override + public FileEntry lookupFileEntry(ResolvedJavaMethod method) { + ResolvedJavaMethod targetMethod = method; + if (targetMethod instanceof HostedMethod hostedMethod && hostedMethod.getWrapped().getWrapped() instanceof SubstitutionMethod substitutionMethod) { + // we always want to look up the file of the annotated method + targetMethod = substitutionMethod.getAnnotated(); } + return super.lookupFileEntry(targetMethod); + } - /** - * Return the unique type that owns this method. - *

    - * In the absence of substitutions the returned type result is simply the original JVMCI - * implementation type that declares the associated Java method. Identifying this type may - * involve unwrapping from Hosted universe to Analysis universe to the original JVMCI - * universe. - *

    - * - * In the case where the method itself is either an (annotated) substitution or declared by - * a class that is a (annotated) substitution then the link from substitution to original is - * also 'unwrapped' to arrive at the original type. In cases where a substituted method has - * no original the class of the substitution is used, for want of anything better. - *

    - * - * This unwrapping strategy avoids two possible ambiguities that would compromise use of the - * returned value as a unique 'owner'. Firstly, if the same method is ever presented via - * both its HostedMethod incarnation and its original ResolvedJavaMethod incarnation then - * ownership will always be signalled via the original type. This ensures that views of the - * method presented via the list of included HostedTypes and via the list of CompiledMethod - * objects and their embedded substructure (such as inline caller hierarchies) are correctly - * identified. - *

    - * - * Secondly, when a substituted method or method of a substituted class is presented it ends - * up being collated with methods of the original class rather than the class which actually - * defines it, avoiding an artificial split of methods that belong to the same underlying - * runtime type into two distinct types in the debuginfo model. As an example, this ensures - * that all methods of substitution class DynamicHub are collated with methods of class - * java.lang.Class, the latter being the type whose name gets written into the debug info - * and gets used to name the method in the debugger. Note that this still allows the - * method's line info to be associated with the correct file i.e. it does not compromise - * breaking and stepping through the code. - * - * @return the unique type that owns this method - */ - @Override - public ResolvedJavaType ownerType() { - ResolvedJavaType result; - if (method instanceof HostedMethod) { - result = getDeclaringClass((HostedMethod) method, true); - } else { - result = method.getDeclaringClass(); - } - while (result instanceof WrappedJavaType) { - result = ((WrappedJavaType) result).getWrapped(); + private static int getTypeSize(HostedType type) { + switch (type) { + case HostedInstanceClass hostedInstanceClass -> { + /* We know the actual instance size in bytes. */ + return hostedInstanceClass.getInstanceSize(); } - return result; - } - - @Override - public ResolvedJavaMethod idMethod() { - // translating to the original ensures we equate a - // substituted method with the original in case we ever see both - return originalMethod(); - } - - @Override - public String name() { - ResolvedJavaMethod targetMethod = originalMethod(); - String name = targetMethod.getName(); - if (name.equals("")) { - if (method instanceof HostedMethod) { - name = getDeclaringClass((HostedMethod) method, true).toJavaName(); - if (name.indexOf('.') >= 0) { - name = name.substring(name.lastIndexOf('.') + 1); - } - if (name.indexOf('$') >= 0) { - name = name.substring(name.lastIndexOf('$') + 1); - } - } else { - name = targetMethod.format("%h"); - if (name.indexOf('$') >= 0) { - name = name.substring(name.lastIndexOf('$') + 1); - } - } + case HostedArrayClass hostedArrayClass -> { + /* Use the size of header common to all arrays of this type. */ + return getObjectLayout().getArrayBaseOffset(hostedArrayClass.getComponentType().getStorageKind()); } - return name; - } - - @Override - public ResolvedJavaType valueType() { - ResolvedJavaType resultType = (ResolvedJavaType) method.getSignature().getReturnType(null); - if (resultType instanceof HostedType) { - return getOriginal((HostedType) resultType); + case HostedInterface hostedInterface -> { + /* Use the size of the header common to all implementors. */ + return getObjectLayout().getFirstFieldOffset(); } - return resultType; - } - - @Override - public DebugLocalInfo[] getParamInfo() { - return paramInfo.toArray(new DebugLocalInfo[paramInfo.size()]); - } - - @Override - public DebugLocalInfo getThisParamInfo() { - return thisParamInfo; - } - - @Override - public String symbolNameForMethod() { - return NativeImage.localSymbolNameForMethod(method); - } - - @Override - public boolean isDeoptTarget() { - if (method instanceof HostedMethod) { - return ((HostedMethod) method).isDeoptTarget(); + case HostedPrimitiveType hostedPrimitiveType -> { + /* Use the number of bytes needed to store the value. */ + JavaKind javaKind = hostedPrimitiveType.getStorageKind(); + return javaKind == JavaKind.Void ? 0 : javaKind.getByteCount(); } - return name().endsWith(StableMethodNameFormatter.MULTI_METHOD_KEY_SEPARATOR); - } - - @Override - public int modifiers() { - if (method instanceof HostedMethod) { - return getOriginalModifiers((HostedMethod) method); + default -> { + return 0; } - return method.getModifiers(); - } - - @Override - public boolean isConstructor() { - return method.isConstructor(); } - - @Override - public boolean isVirtual() { - return method instanceof HostedMethod && ((HostedMethod) method).hasVTableIndex(); - } - - @Override - public boolean isOverride() { - return method instanceof HostedMethod && allOverrides.contains(method); - } - - @Override - public int vtableOffset() { - /* TODO - convert index to offset (+ sizeof DynamicHub) */ - return isVirtual() ? ((HostedMethod) method).getVTableIndex() : -1; - } - } - - private List createParamInfo(ResolvedJavaMethod method, int line) { - Signature signature = method.getSignature(); - int parameterCount = signature.getParameterCount(false); - List paramInfos = new ArrayList<>(parameterCount); - LocalVariableTable table = method.getLocalVariableTable(); - int slot = 0; - ResolvedJavaType ownerType = method.getDeclaringClass(); - if (!method.isStatic()) { - JavaKind kind = ownerType.getJavaKind(); - JavaKind storageKind = isForeignWordType(ownerType, ownerType) ? JavaKind.Long : kind; - assert kind == JavaKind.Object : "must be an object"; - paramInfos.add(new NativeImageDebugLocalInfo("this", storageKind, ownerType, slot, line)); - slot += kind.getSlotCount(); - } - for (int i = 0; i < parameterCount; i++) { - Local local = (table == null ? null : table.getLocal(slot, 0)); - String name = (local != null ? local.getName() : "__" + i); - ResolvedJavaType paramType = (ResolvedJavaType) signature.getParameterType(i, null); - JavaKind kind = paramType.getJavaKind(); - JavaKind storageKind = isForeignWordType(paramType, ownerType) ? JavaKind.Long : kind; - paramInfos.add(new NativeImageDebugLocalInfo(name, storageKind, paramType, slot, line)); - slot += kind.getSlotCount(); - } - return paramInfos; - } - - private static boolean isIntegralKindPromotion(JavaKind promoted, JavaKind original) { - return (promoted == JavaKind.Int && - (original == JavaKind.Boolean || original == JavaKind.Byte || original == JavaKind.Short || original == JavaKind.Char)); } - protected abstract class NativeImageDebugHostedMethodInfo extends NativeImageDebugBaseMethodInfo { - protected final HostedMethod hostedMethod; - - NativeImageDebugHostedMethodInfo(HostedMethod method) { - super(method); - this.hostedMethod = method; - } - - @Override - public int line() { - return line; - } - - @Override - public boolean isVirtual() { - return hostedMethod.hasVTableIndex(); - } - - @Override - public int vtableOffset() { - /* - * TODO - provide correct offset, not index. In Graal, the vtable is appended after the - * dynamicHub object, so can't just multiply by sizeof(pointer). - */ - return hostedMethod.hasVTableIndex() ? hostedMethod.getVTableIndex() : -1; - } - - /** - * Returns true if this is an override virtual method. Used in Windows CodeView output. - * - * @return true if this is a virtual method and overrides an existing method. + private long getClassOffset(HostedType type) { + /* + * Only query the heap for reachable types. These are guaranteed to have been seen by the + * analysis and to exist in the shadow heap. */ - @Override - public boolean isOverride() { - return allOverrides.contains(hostedMethod); + if (type.getWrapped().isReachable()) { + NativeImageHeap.ObjectInfo objectInfo = heap.getObjectInfo(type.getHub()); + if (objectInfo != null) { + return objectInfo.getOffset(); + } } + return -1; } /** - * Implementation of the DebugCodeInfo API interface that allows code info to be passed to an - * ObjectFile when generation of debug info is enabled. + * Return the offset into the initial heap at which the object identified by constant is located + * or -1 if the object is not present in the initial heap. + * + * @param constant must have JavaKind Object and must be non-null. + * @return the offset into the initial heap at which the object identified by constant is + * located or -1 if the object is not present in the initial heap. */ - private class NativeImageDebugCodeInfo extends NativeImageDebugHostedMethodInfo implements DebugCodeInfo { - private final CompilationResult compilation; - - NativeImageDebugCodeInfo(HostedMethod method, CompilationResult compilation) { - super(method); - this.compilation = compilation; - } - - @SuppressWarnings("try") - @Override - public void debugContext(Consumer action) { - try (DebugContext.Scope s = debugContext.scope("DebugCodeInfo", hostedMethod)) { - action.accept(debugContext); - } catch (Throwable e) { - throw debugContext.handle(e); - } + @Override + public long objectOffset(JavaConstant constant) { + assert constant.getJavaKind() == JavaKind.Object && !constant.isNull() : "invalid constant for object offset lookup"; + NativeImageHeap.ObjectInfo objectInfo = heap.getConstantInfo(constant); + if (objectInfo != null) { + return objectInfo.getOffset(); } - - @Override - public int addressLo() { - return hostedMethod.getCodeAddressOffset(); - } - - @Override - public int addressHi() { - return hostedMethod.getCodeAddressOffset() + compilation.getTargetCodeSize(); - } - - @Override - public Stream locationInfoProvider() { - // can we still provide locals if we have no file name? - if (fileName().length() == 0) { - return Stream.empty(); - } - boolean omitInline = SubstrateOptions.OmitInlinedMethodDebugLineInfo.getValue(); - int maxDepth = SubstrateOptions.DebugCodeInfoMaxDepth.getValue(); - boolean useSourceMappings = SubstrateOptions.DebugCodeInfoUseSourceMappings.getValue(); - if (omitInline) { - if (!SubstrateOptions.DebugCodeInfoMaxDepth.hasBeenSet()) { - /* TopLevelVisitor will not go deeper than level 2 */ - maxDepth = 2; - } - if (!SubstrateOptions.DebugCodeInfoUseSourceMappings.hasBeenSet()) { - /* Skip expensive CompilationResultFrameTree building with SourceMappings */ - useSourceMappings = false; - } - } - final CallNode root = new Builder(debugContext, compilation.getTargetCodeSize(), maxDepth, useSourceMappings, true).build(compilation); - if (root == null) { - return Stream.empty(); - } - final List locationInfos = new ArrayList<>(); - int frameSize = getFrameSize(); - final Visitor visitor = (omitInline ? new TopLevelVisitor(locationInfos, frameSize) : new MultiLevelVisitor(locationInfos, frameSize)); - // arguments passed by visitor to apply are - // NativeImageDebugLocationInfo caller location info - // CallNode nodeToEmbed parent call node to convert to entry code leaf - // NativeImageDebugLocationInfo leaf into which current leaf may be merged - root.visitChildren(visitor, (Object) null, (Object) null, (Object) null); - // try to add a location record for offset zero - updateInitialLocation(locationInfos); - return locationInfos.stream(); - } - - private int findMarkOffset(SubstrateMarkId markId) { - for (CompilationResult.CodeMark mark : compilation.getMarks()) { - if (mark.id.equals(markId)) { - return mark.pcOffset; - } - } - return -1; - } - - private void updateInitialLocation(List locationInfos) { - int prologueEnd = findMarkOffset(SubstrateMarkId.PROLOGUE_END); - if (prologueEnd < 0) { - // this is not a normal compiled method so give up - return; - } - int stackDecrement = findMarkOffset(SubstrateMarkId.PROLOGUE_DECD_RSP); - if (stackDecrement < 0) { - // this is not a normal compiled method so give up - return; - } - // if there are any location info records then the first one will be for - // a nop which follows the stack decrement, stack range check and pushes - // of arguments into the stack frame. - // - // We can construct synthetic location info covering the first instruction - // based on the method arguments and the calling convention and that will - // normally be valid right up to the nop. In exceptional cases a call - // might pass arguments on the stack, in which case the stack decrement will - // invalidate the original stack locations. Providing location info for that - // case requires adding two locations, one for initial instruction that does - // the stack decrement and another for the range up to the nop. They will - // be essentially the same but the stack locations will be adjusted to account - // for the different value of the stack pointer. - - if (locationInfos.isEmpty()) { - // this is not a normal compiled method so give up - return; - } - NativeImageDebugLocationInfo firstLocation = (NativeImageDebugLocationInfo) locationInfos.get(0); - int firstLocationOffset = firstLocation.addressLo(); - - if (firstLocationOffset == 0) { - // this is not a normal compiled method so give up - return; - } - if (firstLocationOffset < prologueEnd) { - // this is not a normal compiled method so give up - return; - } - // create a synthetic location record including details of passed arguments - ParamLocationProducer locProducer = new ParamLocationProducer(hostedMethod); - debugContext.log(DebugContext.DETAILED_LEVEL, "Add synthetic Location Info : %s (0, %d)", method.getName(), firstLocationOffset - 1); - NativeImageDebugLocationInfo locationInfo = new NativeImageDebugLocationInfo(method, firstLocationOffset, locProducer); - // if the prologue extends beyond the stack extend and uses the stack then the info - // needs - // splitting at the extend point with the stack offsets adjusted in the new info - if (locProducer.usesStack() && firstLocationOffset > stackDecrement) { - NativeImageDebugLocationInfo splitLocationInfo = locationInfo.split(stackDecrement, getFrameSize()); - debugContext.log(DebugContext.DETAILED_LEVEL, "Split synthetic Location Info : %s (%d, %d) (%d, %d)", locationInfo.name(), 0, - locationInfo.addressLo() - 1, locationInfo.addressLo(), locationInfo.addressHi() - 1); - locationInfos.add(0, splitLocationInfo); - } - locationInfos.add(0, locationInfo); - } - - // indices for arguments passed to SingleLevelVisitor::apply - - protected static final int CALLER_INFO = 0; - protected static final int PARENT_NODE_TO_EMBED = 1; - protected static final int LAST_LEAF_INFO = 2; - - private abstract class SingleLevelVisitor implements Visitor { - - protected final List locationInfos; - protected final int frameSize; - - SingleLevelVisitor(List locationInfos, int frameSize) { - this.locationInfos = locationInfos; - this.frameSize = frameSize; - } - - public NativeImageDebugLocationInfo process(FrameNode node, NativeImageDebugLocationInfo callerInfo) { - NativeImageDebugLocationInfo locationInfo; - if (node instanceof CallNode) { - // this node represents an inline call range so - // add a locationinfo to cover the range of the call - locationInfo = createCallLocationInfo((CallNode) node, callerInfo, frameSize); - } else if (isBadLeaf(node, callerInfo)) { - locationInfo = createBadLeafLocationInfo(node, callerInfo, frameSize); - } else { - // this is leaf method code so add details of its range - locationInfo = createLeafLocationInfo(node, callerInfo, frameSize); - } - return locationInfo; - } - } - - private class TopLevelVisitor extends SingleLevelVisitor { - TopLevelVisitor(List locationInfos, int frameSize) { - super(locationInfos, frameSize); - } - - @Override - public void apply(FrameNode node, Object... args) { - if (skipNode(node)) { - // this is a bogus wrapper so skip it and transform the wrapped node instead - node.visitChildren(this, args); - } else { - NativeImageDebugLocationInfo locationInfo = process(node, null); - if (node instanceof CallNode) { - locationInfos.add(locationInfo); - // erase last leaf (if present) since there is an intervening call range - invalidateMerge(args); - } else { - locationInfo = tryMerge(locationInfo, args); - if (locationInfo != null) { - locationInfos.add(locationInfo); - } - } - } - } - } - - public class MultiLevelVisitor extends SingleLevelVisitor { - MultiLevelVisitor(List locationInfos, int frameSize) { - super(locationInfos, frameSize); - } - - @Override - public void apply(FrameNode node, Object... args) { - if (skipNode(node)) { - // this is a bogus wrapper so skip it and transform the wrapped node instead - node.visitChildren(this, args); - } else { - NativeImageDebugLocationInfo callerInfo = (NativeImageDebugLocationInfo) args[CALLER_INFO]; - CallNode nodeToEmbed = (CallNode) args[PARENT_NODE_TO_EMBED]; - if (nodeToEmbed != null) { - if (embedWithChildren(nodeToEmbed, node)) { - // embed a leaf range for the method start that was included in the - // parent CallNode - // its end range is determined by the start of the first node at this - // level - NativeImageDebugLocationInfo embeddedLocationInfo = createEmbeddedParentLocationInfo(nodeToEmbed, node, callerInfo, frameSize); - locationInfos.add(embeddedLocationInfo); - // since this is a leaf node we can merge later leafs into it - initMerge(embeddedLocationInfo, args); - } - // reset args so we only embed the parent node before the first node at - // this level - args[PARENT_NODE_TO_EMBED] = nodeToEmbed = null; - } - NativeImageDebugLocationInfo locationInfo = process(node, callerInfo); - if (node instanceof CallNode) { - CallNode callNode = (CallNode) node; - locationInfos.add(locationInfo); - // erase last leaf (if present) since there is an intervening call range - invalidateMerge(args); - if (hasChildren(callNode)) { - // a call node may include an initial leaf range for the call that must - // be - // embedded under the newly created location info so pass it as an - // argument - callNode.visitChildren(this, locationInfo, callNode, (Object) null); - } else { - // we need to embed a leaf node for the whole call range - locationInfo = createEmbeddedParentLocationInfo(callNode, null, locationInfo, frameSize); - locationInfos.add(locationInfo); - } - } else { - locationInfo = tryMerge(locationInfo, args); - if (locationInfo != null) { - locationInfos.add(locationInfo); - } - } - } - } - } - - /** - * Report whether a call node has any children. - * - * @param callNode the node to check - * @return true if it has any children otherwise false. - */ - private boolean hasChildren(CallNode callNode) { - Object[] result = new Object[]{false}; - callNode.visitChildren(new Visitor() { - @Override - public void apply(FrameNode node, Object... args) { - args[0] = true; - } - }, result); - return (boolean) result[0]; - } - - /** - * Create a location info record for a leaf subrange. - * - * @param node is a simple FrameNode - * @return the newly created location info record - */ - private NativeImageDebugLocationInfo createLeafLocationInfo(FrameNode node, NativeImageDebugLocationInfo callerInfo, int framesize) { - assert !(node instanceof CallNode); - NativeImageDebugLocationInfo locationInfo = new NativeImageDebugLocationInfo(node, callerInfo, framesize); - debugContext.log(DebugContext.DETAILED_LEVEL, "Create leaf Location Info : %s depth %d (%d, %d)", locationInfo.name(), locationInfo.depth(), locationInfo.addressLo(), - locationInfo.addressHi() - 1); - return locationInfo; - } - - /** - * Create a location info record for a subrange that encloses an inline call. - * - * @param callNode is the top level inlined call frame - * @return the newly created location info record - */ - private NativeImageDebugLocationInfo createCallLocationInfo(CallNode callNode, NativeImageDebugLocationInfo callerInfo, int framesize) { - BytecodePosition callerPos = realCaller(callNode); - NativeImageDebugLocationInfo locationInfo = new NativeImageDebugLocationInfo(callerPos, callNode.getStartPos(), callNode.getEndPos() + 1, callerInfo, framesize); - debugContext.log(DebugContext.DETAILED_LEVEL, "Create call Location Info : %s depth %d (%d, %d)", locationInfo.name(), locationInfo.depth(), locationInfo.addressLo(), - locationInfo.addressHi() - 1); - return locationInfo; - } - - /** - * Create a location info record for the initial range associated with a parent call node - * whose position and start are defined by that call node and whose end is determined by the - * first child of the call node. - * - * @param parentToEmbed a parent call node which has already been processed to create the - * caller location info - * @param firstChild the first child of the call node - * @param callerLocation the location info created to represent the range for the call - * @return a location info to be embedded as the first child range of the caller location. - */ - private NativeImageDebugLocationInfo createEmbeddedParentLocationInfo(CallNode parentToEmbed, FrameNode firstChild, NativeImageDebugLocationInfo callerLocation, int framesize) { - BytecodePosition pos = parentToEmbed.frame; - int startPos = parentToEmbed.getStartPos(); - int endPos = (firstChild != null ? firstChild.getStartPos() : parentToEmbed.getEndPos() + 1); - NativeImageDebugLocationInfo locationInfo = new NativeImageDebugLocationInfo(pos, startPos, endPos, callerLocation, framesize); - debugContext.log(DebugContext.DETAILED_LEVEL, "Embed leaf Location Info : %s depth %d (%d, %d)", locationInfo.name(), locationInfo.depth(), locationInfo.addressLo(), - locationInfo.addressHi() - 1); - return locationInfo; - } - - private NativeImageDebugLocationInfo createBadLeafLocationInfo(FrameNode node, NativeImageDebugLocationInfo callerLocation, int framesize) { - assert !(node instanceof CallNode) : "bad leaf location cannot be a call node!"; - assert callerLocation == null : "should only see bad leaf at top level!"; - BytecodePosition pos = node.frame; - BytecodePosition callerPos = pos.getCaller(); - assert callerPos != null : "bad leaf must have a caller"; - assert callerPos.getCaller() == null : "bad leaf caller must be root method"; - int startPos = node.getStartPos(); - int endPos = node.getEndPos() + 1; - NativeImageDebugLocationInfo locationInfo = new NativeImageDebugLocationInfo(callerPos, startPos, endPos, null, framesize); - debugContext.log(DebugContext.DETAILED_LEVEL, "Embed leaf Location Info : %s depth %d (%d, %d)", locationInfo.name(), locationInfo.depth(), locationInfo.addressLo(), - locationInfo.addressHi() - 1); - return locationInfo; - } - - private boolean isBadLeaf(FrameNode node, NativeImageDebugLocationInfo callerLocation) { - // Sometimes we see a leaf node marked as belonging to an inlined method - // that sits directly under the root method rather than under a call node. - // It needs replacing with a location info for the root method that covers - // the relevant code range. - if (callerLocation == null) { - BytecodePosition pos = node.frame; - BytecodePosition callerPos = pos.getCaller(); - if (callerPos != null && !callerPos.getMethod().equals(pos.getMethod())) { - if (callerPos.getCaller() == null) { - return true; - } - } - } - return false; - } - - /** - * Test whether a bytecode position represents a bogus frame added by the compiler when a - * substitution or snippet call is injected. - * - * @param pos the position to be tested - * @return true if the frame is bogus otherwise false - */ - private boolean skipPos(BytecodePosition pos) { - return (pos.getBCI() == -1 && pos instanceof NodeSourcePosition && ((NodeSourcePosition) pos).isSubstitution()); - } - - /** - * Skip caller nodes with bogus positions, as determined by - * {@link #skipPos(BytecodePosition)}, returning first caller node position that is not - * bogus. - * - * @param node the node whose callers are to be traversed - * @return the first non-bogus position in the caller chain. - */ - private BytecodePosition realCaller(CallNode node) { - BytecodePosition pos = node.frame.getCaller(); - while (skipPos(pos)) { - pos = pos.getCaller(); - } - return pos; - } - - /** - * Test whether the position associated with a child node should result in an entry in the - * inline tree. The test is for a call node with a bogus position as determined by - * {@link #skipPos(BytecodePosition)}. - * - * @param node A node associated with a child frame in the compilation result frame tree. - * @return True an entry should be included or false if it should be omitted. - */ - private boolean skipNode(FrameNode node) { - return node instanceof CallNode && skipPos(node.frame); - } - - /* - * Test whether the position associated with a call node frame should be embedded along with - * the locations generated for the node's children. This is needed because call frames - * include a valid source position that precedes the first child position. - * - * @param node A node associated with a frame in the compilation result frame tree. - * - * @return True if an inline frame should be included or false if it should be omitted. - */ - - /** - * Test whether the position associated with a call node frame should be embedded along with - * the locations generated for the node's children. This is needed because call frames may - * include a valid source position that precedes the first child position. - * - * @param parent The call node whose children are currently being visited - * @param firstChild The first child of that call node - * @return true if the node should be embedded otherwise false - */ - private boolean embedWithChildren(CallNode parent, FrameNode firstChild) { - // we only need to insert a range for the caller if it fills a gap - // at the start of the caller range before the first child - if (parent.getStartPos() < firstChild.getStartPos()) { - return true; - } - return false; - } - - /** - * Try merging a new location info for a leaf range into the location info for the last leaf - * range added at this level. - * - * @param newLeaf the new leaf location info - * @param args the visitor argument vector used to pass parameters from one child visit to - * the next possibly including the last leaf - * @return the new location info if it could not be merged or null to indicate that it was - * merged - */ - private NativeImageDebugLocationInfo tryMerge(NativeImageDebugLocationInfo newLeaf, Object[] args) { - // last leaf node added at this level is 3rd element of arg vector - NativeImageDebugLocationInfo lastLeaf = (NativeImageDebugLocationInfo) args[LAST_LEAF_INFO]; - - if (lastLeaf != null) { - // try merging new leaf into last one - lastLeaf = lastLeaf.merge(newLeaf); - if (lastLeaf != null) { - // null return indicates new leaf has been merged into last leaf - return null; - } - } - // update last leaf and return new leaf for addition to local info list - args[LAST_LEAF_INFO] = newLeaf; - return newLeaf; - } - - /** - * Set the last leaf node at the current level to the supplied leaf node. - * - * @param lastLeaf the last leaf node created at this level - * @param args the visitor argument vector used to pass parameters from one child visit to - * the next - */ - private void initMerge(NativeImageDebugLocationInfo lastLeaf, Object[] args) { - args[LAST_LEAF_INFO] = lastLeaf; - } - - /** - * Clear the last leaf node at the current level from the visitor arguments by setting the - * arg vector entry to null. - * - * @param args the visitor argument vector used to pass parameters from one child visit to - * the next - */ - private void invalidateMerge(Object[] args) { - args[LAST_LEAF_INFO] = null; - } - - @Override - public int getFrameSize() { - return compilation.getTotalFrameSize(); - } - - @Override - public List getFrameSizeChanges() { - List frameSizeChanges = new LinkedList<>(); - for (CompilationResult.CodeMark mark : compilation.getMarks()) { - /* We only need to observe stack increment or decrement points. */ - if (mark.id.equals(SubstrateMarkId.PROLOGUE_DECD_RSP)) { - NativeImageDebugFrameSizeChange sizeChange = new NativeImageDebugFrameSizeChange(mark.pcOffset, EXTEND); - frameSizeChanges.add(sizeChange); - // } else if (mark.id.equals("PROLOGUE_END")) { - // can ignore these - // } else if (mark.id.equals("EPILOGUE_START")) { - // can ignore these - } else if (mark.id.equals(SubstrateMarkId.EPILOGUE_INCD_RSP)) { - NativeImageDebugFrameSizeChange sizeChange = new NativeImageDebugFrameSizeChange(mark.pcOffset, CONTRACT); - frameSizeChanges.add(sizeChange); - } else if (mark.id.equals(SubstrateMarkId.EPILOGUE_END) && mark.pcOffset < compilation.getTargetCodeSize()) { - /* There is code after this return point so notify a stack extend again. */ - NativeImageDebugFrameSizeChange sizeChange = new NativeImageDebugFrameSizeChange(mark.pcOffset, EXTEND); - frameSizeChanges.add(sizeChange); - } - } - return frameSizeChanges; - } - } - - /** - * Implementation of the DebugLocationInfo API interface that allows line number and local var - * info to be passed to an ObjectFile when generation of debug info is enabled. - */ - private class NativeImageDebugLocationInfo extends NativeImageDebugBaseMethodInfo implements DebugLocationInfo { - private final int bci; - private int lo; - private int hi; - private DebugLocationInfo callersLocationInfo; - private List localInfoList; - private boolean isLeaf; - - NativeImageDebugLocationInfo(FrameNode frameNode, NativeImageDebugLocationInfo callersLocationInfo, int framesize) { - this(frameNode.frame, frameNode.getStartPos(), frameNode.getEndPos() + 1, callersLocationInfo, framesize); - } - - NativeImageDebugLocationInfo(BytecodePosition bcpos, int lo, int hi, NativeImageDebugLocationInfo callersLocationInfo, int framesize) { - super(bcpos.getMethod()); - this.bci = bcpos.getBCI(); - this.lo = lo; - this.hi = hi; - this.callersLocationInfo = callersLocationInfo; - this.localInfoList = initLocalInfoList(bcpos, framesize); - // assume this is a leaf until we find out otherwise - this.isLeaf = true; - // tag the caller as a non-leaf - if (callersLocationInfo != null) { - callersLocationInfo.isLeaf = false; - } - } - - // special constructor for synthetic location info added at start of method - NativeImageDebugLocationInfo(ResolvedJavaMethod method, int hi, ParamLocationProducer locProducer) { - super(method); - // bci is always 0 and lo is always 0. - this.bci = 0; - this.lo = 0; - this.hi = hi; - // this is always going to be a top-level leaf range. - this.callersLocationInfo = null; - // location info is synthesized off the method signature - this.localInfoList = initSyntheticInfoList(locProducer); - // assume this is a leaf until we find out otherwise - this.isLeaf = true; - } - - // special constructor for synthetic location info which splits off the initial segment - // of the first range to accommodate a stack access prior to the stack extend - NativeImageDebugLocationInfo(NativeImageDebugLocationInfo toSplit, int stackDecrement, int frameSize) { - super(toSplit.method); - this.lo = stackDecrement; - this.hi = toSplit.hi; - toSplit.hi = this.lo; - this.bci = toSplit.bci; - this.callersLocationInfo = toSplit.callersLocationInfo; - this.isLeaf = toSplit.isLeaf; - toSplit.isLeaf = true; - this.localInfoList = new ArrayList<>(toSplit.localInfoList.size()); - for (DebugLocalValueInfo localInfo : toSplit.localInfoList) { - if (localInfo.localKind() == DebugLocalValueInfo.LocalKind.STACKSLOT) { - // need to redefine the value for this param using a stack slot value - // that allows for the stack being extended by framesize. however we - // also need to remove any adjustment that was made to allow for the - // difference between the caller SP and the pre-extend callee SP - // because of a stacked return address. - int adjustment = frameSize - PRE_EXTEND_FRAME_SIZE; - NativeImageDebugLocalValue value = NativeImageDebugStackValue.create(localInfo, adjustment); - NativeImageDebugLocalValueInfo nativeLocalInfo = (NativeImageDebugLocalValueInfo) localInfo; - NativeImageDebugLocalValueInfo newLocalinfo = new NativeImageDebugLocalValueInfo(nativeLocalInfo.name, - value, - nativeLocalInfo.kind, - nativeLocalInfo.type, - nativeLocalInfo.slot, - nativeLocalInfo.line); - localInfoList.add(newLocalinfo); - } else { - localInfoList.add(localInfo); - } - } - } - - private List initLocalInfoList(BytecodePosition bcpos, int framesize) { - if (!(bcpos instanceof BytecodeFrame)) { - return null; - } - - BytecodeFrame frame = (BytecodeFrame) bcpos; - if (frame.numLocals == 0) { - return null; - } - // deal with any inconsistencies in the layout of the frame locals - // NativeImageDebugFrameInfo debugFrameInfo = new NativeImageDebugFrameInfo(frame); - - LineNumberTable lineNumberTable = frame.getMethod().getLineNumberTable(); - Local[] localsBySlot = getLocalsBySlot(); - if (localsBySlot == null) { - return Collections.emptyList(); - } - int count = Integer.min(localsBySlot.length, frame.numLocals); - ArrayList localInfos = new ArrayList<>(count); - for (int i = 0; i < count; i++) { - Local l = localsBySlot[i]; - if (l != null) { - // we have a local with a known name, type and slot - String name = l.getName(); - ResolvedJavaType ownerType = method.getDeclaringClass(); - ResolvedJavaType type = l.getType().resolve(ownerType); - JavaKind kind = type.getJavaKind(); - int slot = l.getSlot(); - debugContext.log(DebugContext.DETAILED_LEVEL, "locals[%d] %s type %s slot %d", i, name, type.getName(), slot); - JavaValue value = (slot < frame.numLocals ? frame.getLocalValue(slot) : Value.ILLEGAL); - JavaKind storageKind = (slot < frame.numLocals ? frame.getLocalValueKind(slot) : JavaKind.Illegal); - debugContext.log(DebugContext.DETAILED_LEVEL, " => %s kind %s", value, storageKind); - int bciStart = l.getStartBCI(); - int firstLine = (lineNumberTable != null ? lineNumberTable.getLineNumber(bciStart) : -1); - // only add the local if the kinds match - if ((storageKind == kind) || - isIntegralKindPromotion(storageKind, kind) || - (isForeignWordType(type, ownerType) && kind == JavaKind.Object && storageKind == JavaKind.Long)) { - localInfos.add(new NativeImageDebugLocalValueInfo(name, value, framesize, storageKind, type, slot, firstLine)); - } else if (storageKind != JavaKind.Illegal) { - debugContext.log(DebugContext.DETAILED_LEVEL, " value kind incompatible with var kind %s!", type.getJavaKind()); - } - } - } - return localInfos; - } - - private List initSyntheticInfoList(ParamLocationProducer locProducer) { - Signature signature = method.getSignature(); - int parameterCount = signature.getParameterCount(false); - ArrayList localInfos = new ArrayList<>(); - LocalVariableTable table = method.getLocalVariableTable(); - LineNumberTable lineNumberTable = method.getLineNumberTable(); - int firstLine = (lineNumberTable != null ? lineNumberTable.getLineNumber(0) : -1); - int slot = 0; - int localIdx = 0; - ResolvedJavaType ownerType = method.getDeclaringClass(); - if (!method.isStatic()) { - String name = "this"; - JavaKind kind = ownerType.getJavaKind(); - JavaKind storageKind = isForeignWordType(ownerType, ownerType) ? JavaKind.Long : kind; - assert kind == JavaKind.Object : "must be an object"; - NativeImageDebugLocalValue value = locProducer.thisLocation(); - debugContext.log(DebugContext.DETAILED_LEVEL, "locals[%d] %s type %s slot %d", localIdx, name, ownerType.getName(), slot); - debugContext.log(DebugContext.DETAILED_LEVEL, " => %s kind %s", value, storageKind); - localInfos.add(new NativeImageDebugLocalValueInfo(name, value, storageKind, ownerType, slot, firstLine)); - slot += kind.getSlotCount(); - localIdx++; - } - for (int i = 0; i < parameterCount; i++) { - Local local = (table == null ? null : table.getLocal(slot, 0)); - String name = (local != null ? local.getName() : "__" + i); - ResolvedJavaType paramType = (ResolvedJavaType) signature.getParameterType(i, ownerType); - JavaKind kind = paramType.getJavaKind(); - JavaKind storageKind = isForeignWordType(paramType, ownerType) ? JavaKind.Long : kind; - NativeImageDebugLocalValue value = locProducer.paramLocation(i); - debugContext.log(DebugContext.DETAILED_LEVEL, "locals[%d] %s type %s slot %d", localIdx, name, ownerType.getName(), slot); - debugContext.log(DebugContext.DETAILED_LEVEL, " => %s kind %s", value, storageKind); - localInfos.add(new NativeImageDebugLocalValueInfo(name, value, storageKind, paramType, slot, firstLine)); - slot += kind.getSlotCount(); - localIdx++; - } - return localInfos; - } - - private Local[] getLocalsBySlot() { - LocalVariableTable lvt = method.getLocalVariableTable(); - Local[] nonEmptySortedLocals = null; - if (lvt != null) { - Local[] locals = lvt.getLocalsAt(bci); - if (locals != null && locals.length > 0) { - nonEmptySortedLocals = Arrays.copyOf(locals, locals.length); - Arrays.sort(nonEmptySortedLocals, (Local l1, Local l2) -> l1.getSlot() - l2.getSlot()); - } - } - return nonEmptySortedLocals; - } - - @Override - public int addressLo() { - return lo; - } - - @Override - public int addressHi() { - return hi; - } - - @Override - public int line() { - LineNumberTable lineNumberTable = method.getLineNumberTable(); - if (lineNumberTable != null && bci >= 0) { - return lineNumberTable.getLineNumber(bci); - } - return -1; - } - - @Override - public DebugLocationInfo getCaller() { - return callersLocationInfo; - } - - @Override - public DebugLocalValueInfo[] getLocalValueInfo() { - if (localInfoList != null) { - return localInfoList.toArray(new DebugLocalValueInfo[localInfoList.size()]); - } else { - return EMPTY_LOCAL_VALUE_INFOS; - } - } - - @Override - public boolean isLeaf() { - return isLeaf; - } - - public int depth() { - int depth = 1; - DebugLocationInfo caller = getCaller(); - while (caller != null) { - depth++; - caller = caller.getCaller(); - } - return depth; - } - - private int localsSize() { - if (localInfoList != null) { - return localInfoList.size(); - } else { - return 0; - } - } - - /** - * Merge the supplied leaf location info into this leaf location info if they have - * contiguous ranges, the same method and line number and the same live local variables with - * the same values. - * - * @param that a leaf location info to be merged into this one - * @return this leaf location info if the merge was performed otherwise null - */ - NativeImageDebugLocationInfo merge(NativeImageDebugLocationInfo that) { - assert callersLocationInfo == that.callersLocationInfo; - assert isLeaf == that.isLeaf; - assert depth() == that.depth() : "should only compare sibling ranges"; - assert this.hi <= that.lo : "later nodes should not overlap earlier ones"; - if (this.hi != that.lo) { - return null; - } - if (!method.equals(that.method)) { - return null; - } - if (line() != that.line()) { - return null; - } - int size = localsSize(); - if (size != that.localsSize()) { - return null; - } - for (int i = 0; i < size; i++) { - NativeImageDebugLocalValueInfo thisLocal = (NativeImageDebugLocalValueInfo) localInfoList.get(i); - NativeImageDebugLocalValueInfo thatLocal = (NativeImageDebugLocalValueInfo) that.localInfoList.get(i); - if (!thisLocal.equals(thatLocal)) { - return null; - } - } - debugContext.log(DebugContext.DETAILED_LEVEL, "Merge leaf Location Info : %s depth %d (%d, %d) into (%d, %d)", that.name(), that.depth(), that.lo, that.hi - 1, this.lo, this.hi - 1); - // merging just requires updating lo and hi range as everything else is equal - this.hi = that.hi; - - return this; - } - - public NativeImageDebugLocationInfo split(int stackDecrement, int frameSize) { - // this should be for an initial range extending beyond the stack decrement - assert lo == 0 && lo < stackDecrement && stackDecrement < hi : "invalid split request"; - return new NativeImageDebugLocationInfo(this, stackDecrement, frameSize); - } - - } - - private static final DebugLocalValueInfo[] EMPTY_LOCAL_VALUE_INFOS = new DebugLocalValueInfo[0]; - - static final Register[] AARCH64_GPREG = { - AArch64.r0, - AArch64.r1, - AArch64.r2, - AArch64.r3, - AArch64.r4, - AArch64.r5, - AArch64.r6, - AArch64.r7 - }; - static final Register[] AARCH64_FREG = { - AArch64.v0, - AArch64.v1, - AArch64.v2, - AArch64.v3, - AArch64.v4, - AArch64.v5, - AArch64.v6, - AArch64.v7 - }; - static final Register[] AMD64_GPREG_LINUX = { - AMD64.rdi, - AMD64.rsi, - AMD64.rdx, - AMD64.rcx, - AMD64.r8, - AMD64.r9 - }; - static final Register[] AMD64_FREG_LINUX = { - AMD64.xmm0, - AMD64.xmm1, - AMD64.xmm2, - AMD64.xmm3, - AMD64.xmm4, - AMD64.xmm5, - AMD64.xmm6, - AMD64.xmm7 - }; - static final Register[] AMD64_GPREG_WINDOWS = { - AMD64.rdx, - AMD64.r8, - AMD64.r9, - AMD64.rdi, - AMD64.rsi, - AMD64.rcx - }; - static final Register[] AMD64_FREG_WINDOWS = { - AMD64.xmm0, - AMD64.xmm1, - AMD64.xmm2, - AMD64.xmm3 - }; - - /** - * Size in bytes of the frame at call entry before any stack extend. Essentially this accounts - * for any automatically pushed return address whose presence depends upon the architecture. - */ - static final int PRE_EXTEND_FRAME_SIZE = ConfigurationValues.getTarget().arch.getReturnAddressSize(); - - class ParamLocationProducer { - private final HostedMethod hostedMethod; - private final CallingConvention callingConvention; - private boolean usesStack; - - ParamLocationProducer(HostedMethod method) { - this.hostedMethod = method; - this.callingConvention = getCallingConvention(hostedMethod); - // assume no stack slots until we find out otherwise - this.usesStack = false; - } - - NativeImageDebugLocalValue thisLocation() { - assert !hostedMethod.isStatic(); - return unpack(callingConvention.getArgument(0)); - } - - NativeImageDebugLocalValue paramLocation(int paramIdx) { - assert paramIdx < hostedMethod.getSignature().getParameterCount(false); - int idx = paramIdx; - if (!hostedMethod.isStatic()) { - idx++; - } - return unpack(callingConvention.getArgument(idx)); - } - - private NativeImageDebugLocalValue unpack(AllocatableValue value) { - if (value instanceof RegisterValue) { - RegisterValue registerValue = (RegisterValue) value; - return NativeImageDebugRegisterValue.create(registerValue); - } else { - // call argument must be a stack slot if it is not a register - StackSlot stackSlot = (StackSlot) value; - this.usesStack = true; - // the calling convention provides offsets from the SP relative to the current - // frame size. At the point of call the frame may or may not include a return - // address depending on the architecture. - return NativeImageDebugStackValue.create(stackSlot, PRE_EXTEND_FRAME_SIZE); - } - } - - public boolean usesStack() { - return usesStack; - } - } - - public class NativeImageDebugLocalInfo implements DebugLocalInfo { - protected final String name; - protected ResolvedJavaType type; - protected final JavaKind kind; - protected int slot; - protected int line; - - NativeImageDebugLocalInfo(String name, JavaKind kind, ResolvedJavaType resolvedType, int slot, int line) { - this.name = name; - this.kind = kind; - this.slot = slot; - this.line = line; - // if we don't have a type default it for the JavaKind - // it may still end up null when kind is Undefined. - this.type = (resolvedType != null ? resolvedType : hostedTypeForKind(kind)); - } - - @Override - public ResolvedJavaType valueType() { - if (type != null && type instanceof HostedType) { - return getOriginal((HostedType) type); - } - return type; - } - - @Override - public String name() { - return name; - } - - @Override - public String typeName() { - ResolvedJavaType valueType = valueType(); - return (valueType == null ? "" : valueType().toJavaName()); - } - - @Override - public int slot() { - return slot; - } - - @Override - public int slotCount() { - return kind.getSlotCount(); - } - - @Override - public JavaKind javaKind() { - return kind; - } - - @Override - public int line() { - return line; - } - } - - public class NativeImageDebugLocalValueInfo extends NativeImageDebugLocalInfo implements DebugLocalValueInfo { - private final NativeImageDebugLocalValue value; - private LocalKind localKind; - - NativeImageDebugLocalValueInfo(String name, JavaValue value, int framesize, JavaKind kind, ResolvedJavaType resolvedType, int slot, int line) { - super(name, kind, resolvedType, slot, line); - if (value instanceof RegisterValue) { - this.localKind = LocalKind.REGISTER; - this.value = NativeImageDebugRegisterValue.create((RegisterValue) value); - } else if (value instanceof StackSlot) { - this.localKind = LocalKind.STACKSLOT; - this.value = NativeImageDebugStackValue.create((StackSlot) value, framesize); - } else if (value instanceof JavaConstant) { - JavaConstant constant = (JavaConstant) value; - if (constant instanceof PrimitiveConstant || constant.isNull()) { - this.localKind = LocalKind.CONSTANT; - this.value = NativeImageDebugConstantValue.create(constant); - } else { - long heapOffset = objectOffset(constant); - if (heapOffset >= 0) { - this.localKind = LocalKind.CONSTANT; - this.value = new NativeImageDebugConstantValue(constant, heapOffset); - } else { - this.localKind = LocalKind.UNDEFINED; - this.value = null; - } - } - } else { - this.localKind = LocalKind.UNDEFINED; - this.value = null; - } - } - - NativeImageDebugLocalValueInfo(String name, NativeImageDebugLocalValue value, JavaKind kind, ResolvedJavaType type, int slot, int line) { - super(name, kind, type, slot, line); - if (value == null) { - this.localKind = LocalKind.UNDEFINED; - } else if (value instanceof NativeImageDebugRegisterValue) { - this.localKind = LocalKind.REGISTER; - } else if (value instanceof NativeImageDebugStackValue) { - this.localKind = LocalKind.STACKSLOT; - } else if (value instanceof NativeImageDebugConstantValue) { - this.localKind = LocalKind.CONSTANT; - } - this.value = value; - } - - @Override - public boolean equals(Object o) { - if (!(o instanceof NativeImageDebugLocalValueInfo)) { - return false; - } - NativeImageDebugLocalValueInfo that = (NativeImageDebugLocalValueInfo) o; - // values need to have the same name - if (!name().equals(that.name())) { - return false; - } - // values need to be for the same line - if (line != that.line) { - return false; - } - // location kinds must match - if (localKind != that.localKind) { - return false; - } - // locations must match - switch (localKind) { - case REGISTER: - case STACKSLOT: - case CONSTANT: - return value.equals(that.value); - default: - return true; - } - } - - @Override - public int hashCode() { - return Objects.hash(name(), value) * 31 + line; - } - - @Override - public String toString() { - switch (localKind) { - case REGISTER: - return "reg[" + regIndex() + "]"; - case STACKSLOT: - return "stack[" + stackSlot() + "]"; - case CONSTANT: - return "constant[" + (constantValue() != null ? constantValue().toValueString() : "null") + "]"; - default: - return "-"; - } - } - - @Override - public LocalKind localKind() { - return localKind; - } - - @Override - public int regIndex() { - return ((NativeImageDebugRegisterValue) value).getNumber(); - } - - @Override - public int stackSlot() { - return ((NativeImageDebugStackValue) value).getOffset(); - } - - @Override - public long heapOffset() { - return ((NativeImageDebugConstantValue) value).getHeapOffset(); - } - - @Override - public JavaConstant constantValue() { - return ((NativeImageDebugConstantValue) value).getConstant(); - } - } - - public abstract static class NativeImageDebugLocalValue { - } - - public static final class NativeImageDebugRegisterValue extends NativeImageDebugLocalValue { - private static EconomicMap registerValues = EconomicMap.create(); - private int number; - private String name; - - private NativeImageDebugRegisterValue(int number, String name) { - this.number = number; - this.name = "reg:" + name; - } - - static NativeImageDebugRegisterValue create(RegisterValue value) { - int number = value.getRegister().number; - String name = value.getRegister().name; - return memoizedCreate(number, name); - } - - static NativeImageDebugRegisterValue memoizedCreate(int number, String name) { - NativeImageDebugRegisterValue reg = registerValues.get(number); - if (reg == null) { - reg = new NativeImageDebugRegisterValue(number, name); - registerValues.put(number, reg); - } - return reg; - } - - public int getNumber() { - return number; - } - - @Override - public String toString() { - return name; - } - - @Override - public boolean equals(Object o) { - if (!(o instanceof NativeImageDebugRegisterValue)) { - return false; - } - NativeImageDebugRegisterValue that = (NativeImageDebugRegisterValue) o; - return number == that.number; - } - - @Override - public int hashCode() { - return number * 31; - } - } - - public static final class NativeImageDebugStackValue extends NativeImageDebugLocalValue { - private static EconomicMap stackValues = EconomicMap.create(); - private int offset; - private String name; - - private NativeImageDebugStackValue(int offset) { - this.offset = offset; - this.name = "stack:" + offset; - } - - static NativeImageDebugStackValue create(StackSlot value, int framesize) { - // Work around a problem on AArch64 where StackSlot asserts if it is - // passed a zero frame size, even though this is what is expected - // for stack slot offsets provided at the point of entry (because, - // unlike x86, lr has not been pushed). - int offset = (framesize == 0 ? value.getRawOffset() : value.getOffset(framesize)); - return memoizedCreate(offset); - } - - static NativeImageDebugStackValue create(DebugLocalValueInfo previous, int adjustment) { - assert previous.localKind() == DebugLocalValueInfo.LocalKind.STACKSLOT; - return memoizedCreate(previous.stackSlot() + adjustment); - } - - private static NativeImageDebugStackValue memoizedCreate(int offset) { - NativeImageDebugStackValue value = stackValues.get(offset); - if (value == null) { - value = new NativeImageDebugStackValue(offset); - stackValues.put(offset, value); - } - return value; - } - - public int getOffset() { - return offset; - } - - @Override - public String toString() { - return name; - } - - @Override - public boolean equals(Object o) { - if (!(o instanceof NativeImageDebugStackValue)) { - return false; - } - NativeImageDebugStackValue that = (NativeImageDebugStackValue) o; - return offset == that.offset; - } - - @Override - public int hashCode() { - return offset * 31; - } - } - - public static final class NativeImageDebugConstantValue extends NativeImageDebugLocalValue { - private static EconomicMap constantValues = EconomicMap.create(); - private JavaConstant value; - private long heapoffset; - - private NativeImageDebugConstantValue(JavaConstant value, long heapoffset) { - this.value = value; - this.heapoffset = heapoffset; - } - - static NativeImageDebugConstantValue create(JavaConstant value) { - return create(value, -1); - } - - static NativeImageDebugConstantValue create(JavaConstant value, long heapoffset) { - NativeImageDebugConstantValue c = constantValues.get(value); - if (c == null) { - c = new NativeImageDebugConstantValue(value, heapoffset); - constantValues.put(value, c); - } - assert c.heapoffset == heapoffset; - return c; - } - - public JavaConstant getConstant() { - return value; - } - - public long getHeapOffset() { - return heapoffset; - } - - @Override - public String toString() { - return "constant:" + value.toString(); - } - - @Override - public boolean equals(Object o) { - if (!(o instanceof NativeImageDebugConstantValue)) { - return false; - } - NativeImageDebugConstantValue that = (NativeImageDebugConstantValue) o; - return heapoffset == that.heapoffset && value.equals(that.value); - } - - @Override - public int hashCode() { - return Objects.hash(value) * 31 + (int) heapoffset; - } - } - - /** - * Implementation of the DebugFrameSizeChange API interface that allows stack frame size change - * info to be passed to an ObjectFile when generation of debug info is enabled. - */ - private class NativeImageDebugFrameSizeChange implements DebugFrameSizeChange { - private int offset; - private Type type; - - NativeImageDebugFrameSizeChange(int offset, Type type) { - this.offset = offset; - this.type = type; - } - - @Override - public int getOffset() { - return offset; - } - - @Override - public Type getType() { - return type; - } - } - - private class NativeImageDebugDataInfo implements DebugDataInfo { - private final NativeImageHeap.ObjectInfo objectInfo; - - @SuppressWarnings("try") - @Override - public void debugContext(Consumer action) { - try (DebugContext.Scope s = debugContext.scope("DebugDataInfo")) { - action.accept(debugContext); - } catch (Throwable e) { - throw debugContext.handle(e); - } - } - - /* Accessors. */ - - NativeImageDebugDataInfo(ObjectInfo objectInfo) { - this.objectInfo = objectInfo; - } - - @Override - public String getProvenance() { - return objectInfo.toString(); - } - - @Override - public String getTypeName() { - return objectInfo.getClazz().toJavaName(); - } - - @Override - public String getPartition() { - ImageHeapPartition partition = objectInfo.getPartition(); - return partition.getName() + "{" + partition.getSize() + "}@" + partition.getStartOffset(); - } - - @Override - public long getOffset() { - return objectInfo.getOffset(); - } - - @Override - public long getSize() { - return objectInfo.getSize(); - } - } - - private boolean acceptObjectInfo(ObjectInfo objectInfo) { - /* This condition rejects filler partition objects. */ - return (objectInfo.getPartition().getStartOffset() > 0); - } - - private DebugDataInfo createDebugDataInfo(ObjectInfo objectInfo) { - return new NativeImageDebugDataInfo(objectInfo); - } - - @Override - public void recordActivity() { - DeadlockWatchdog.singleton().recordActivity(); + return -1; } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoProviderBase.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoProviderBase.java deleted file mode 100644 index fdafdf2d6493..000000000000 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoProviderBase.java +++ /dev/null @@ -1,485 +0,0 @@ -/* - * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2023, 2023, Red Hat Inc. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ -package com.oracle.svm.hosted.image; - -import static com.oracle.svm.hosted.c.info.AccessorInfo.AccessorKind.ADDRESS; -import static com.oracle.svm.hosted.c.info.AccessorInfo.AccessorKind.GETTER; -import static com.oracle.svm.hosted.c.info.AccessorInfo.AccessorKind.SETTER; - -import java.nio.file.FileSystems; -import java.nio.file.Path; -import java.util.HashMap; - -import org.graalvm.nativeimage.ImageSingletons; -import org.graalvm.word.WordBase; - -import com.oracle.svm.core.StaticFieldsSupport; -import com.oracle.svm.core.SubstrateOptions; -import com.oracle.svm.core.config.ConfigurationValues; -import com.oracle.svm.core.config.ObjectLayout; -import com.oracle.svm.core.graal.code.SubstrateCallingConvention; -import com.oracle.svm.core.graal.code.SubstrateCallingConventionKind; -import com.oracle.svm.core.graal.code.SubstrateCallingConventionType; -import com.oracle.svm.core.graal.meta.RuntimeConfiguration; -import com.oracle.svm.core.graal.meta.SubstrateRegisterConfig; -import com.oracle.svm.core.heap.Heap; -import com.oracle.svm.core.heap.ObjectHeader; -import com.oracle.svm.core.util.VMError; -import com.oracle.svm.hosted.c.NativeLibraries; -import com.oracle.svm.hosted.c.info.AccessorInfo; -import com.oracle.svm.hosted.c.info.ElementInfo; -import com.oracle.svm.hosted.c.info.SizableInfo; -import com.oracle.svm.hosted.c.info.StructFieldInfo; -import com.oracle.svm.hosted.c.info.StructInfo; -import com.oracle.svm.hosted.meta.HostedField; -import com.oracle.svm.hosted.meta.HostedMetaAccess; -import com.oracle.svm.hosted.meta.HostedMethod; -import com.oracle.svm.hosted.meta.HostedType; -import com.oracle.svm.hosted.substitute.InjectedFieldsType; -import com.oracle.svm.hosted.substitute.SubstitutionMethod; -import com.oracle.svm.hosted.substitute.SubstitutionType; - -import jdk.graal.compiler.core.common.CompressEncoding; -import jdk.graal.compiler.core.target.Backend; -import jdk.vm.ci.code.RegisterConfig; -import jdk.vm.ci.meta.JavaConstant; -import jdk.vm.ci.meta.JavaKind; -import jdk.vm.ci.meta.JavaType; -import jdk.vm.ci.meta.ResolvedJavaMethod; -import jdk.vm.ci.meta.ResolvedJavaType; - -/** - * Abstract base class for implementation of DebugInfoProvider API providing a suite of useful - * static and instance methods useful to provider implementations. - */ -public abstract class NativeImageDebugInfoProviderBase { - protected final NativeImageHeap heap; - protected final NativeImageCodeCache codeCache; - protected final NativeLibraries nativeLibs; - protected final RuntimeConfiguration runtimeConfiguration; - protected final boolean useHeapBase; - protected final int compressionShift; - protected final int referenceSize; - protected final int pointerSize; - protected final int objectAlignment; - protected final int primitiveStartOffset; - protected final int referenceStartOffset; - protected final int reservedBitsMask; - - protected final HostedType hubType; - protected final HostedType wordBaseType; - final HashMap javaKindToHostedType; - private final Path cachePath = SubstrateOptions.getDebugInfoSourceCacheRoot(); - - public NativeImageDebugInfoProviderBase(NativeImageCodeCache codeCache, NativeImageHeap heap, NativeLibraries nativeLibs, HostedMetaAccess metaAccess, RuntimeConfiguration runtimeConfiguration) { - this.heap = heap; - this.codeCache = codeCache; - this.nativeLibs = nativeLibs; - this.runtimeConfiguration = runtimeConfiguration; - this.hubType = metaAccess.lookupJavaType(Class.class); - this.wordBaseType = metaAccess.lookupJavaType(WordBase.class); - this.pointerSize = ConfigurationValues.getTarget().wordSize; - ObjectHeader objectHeader = Heap.getHeap().getObjectHeader(); - NativeImageHeap.ObjectInfo primitiveFields = heap.getObjectInfo(StaticFieldsSupport.getStaticPrimitiveFields()); - NativeImageHeap.ObjectInfo objectFields = heap.getObjectInfo(StaticFieldsSupport.getStaticObjectFields()); - this.reservedBitsMask = objectHeader.getReservedBitsMask(); - if (SubstrateOptions.SpawnIsolates.getValue()) { - CompressEncoding compressEncoding = ImageSingletons.lookup(CompressEncoding.class); - this.useHeapBase = compressEncoding.hasBase(); - this.compressionShift = (compressEncoding.hasShift() ? compressEncoding.getShift() : 0); - } else { - this.useHeapBase = false; - this.compressionShift = 0; - } - this.referenceSize = getObjectLayout().getReferenceSize(); - this.objectAlignment = getObjectLayout().getAlignment(); - /* Offsets need to be adjusted relative to the heap base plus partition-specific offset. */ - primitiveStartOffset = (int) primitiveFields.getOffset(); - referenceStartOffset = (int) objectFields.getOffset(); - javaKindToHostedType = initJavaKindToHostedTypes(metaAccess); - } - - /* - * HostedType wraps an AnalysisType and both HostedType and AnalysisType punt calls to - * getSourceFilename to the wrapped class so for consistency we need to do type names and path - * lookup relative to the doubly unwrapped HostedType. - * - * However, note that the result of the unwrap on the AnalysisType may be a SubstitutionType - * which wraps both an original type and the annotated type that substitutes it. Unwrapping - * normally returns the AnnotatedType which we need to use to resolve the file name. However, we - * frequently (but not always) need to use the original to name the owning type to ensure that - * names found in method param and return types resolve correctly. - * - * Likewise, unwrapping of an AnalysisMethod or AnalysisField may encounter a SubstitutionMethod - * or SubstitutionField. It may also encounter a SubstitutionType when unwrapping the owner type - * is unwrapped. In those cases also we may need to use the substituted metadata rather than the - * substitution to ensure that names resolve correctly. - * - * The following static routines provide logic to perform unwrapping and, where necessary, - * traversal from the various substitution metadata instance to the corresponding substituted - * metadata instance. - */ - - protected static ResolvedJavaType getDeclaringClass(HostedType hostedType, boolean wantOriginal) { - // unwrap to the underlying class either the original or target class - if (wantOriginal) { - return getOriginal(hostedType); - } - // we want any substituted target if there is one. directly unwrapping will - // do what we want. - return hostedType.getWrapped().getWrapped(); - } - - protected static ResolvedJavaType getDeclaringClass(HostedMethod hostedMethod, boolean wantOriginal) { - if (wantOriginal) { - return getOriginal(hostedMethod.getDeclaringClass()); - } - // we want a substituted target if there is one. if there is a substitution at the end of - // the method chain fetch the annotated target class - ResolvedJavaMethod javaMethod = NativeImageDebugInfoProviderBase.getAnnotatedOrOriginal(hostedMethod); - return javaMethod.getDeclaringClass(); - } - - @SuppressWarnings("unused") - protected static ResolvedJavaType getDeclaringClass(HostedField hostedField, boolean wantOriginal) { - /* for now fields are always reported as belonging to the original class */ - return getOriginal(hostedField.getDeclaringClass()); - } - - protected static ResolvedJavaType getOriginal(HostedType hostedType) { - /* - * partially unwrap then traverse through substitutions to the original. We don't want to - * get the original type of LambdaSubstitutionType to keep the stable name - */ - ResolvedJavaType javaType = hostedType.getWrapped().getWrapped(); - if (javaType instanceof SubstitutionType) { - return ((SubstitutionType) javaType).getOriginal(); - } else if (javaType instanceof InjectedFieldsType) { - return ((InjectedFieldsType) javaType).getOriginal(); - } - return javaType; - } - - private static ResolvedJavaMethod getAnnotatedOrOriginal(HostedMethod hostedMethod) { - ResolvedJavaMethod javaMethod = hostedMethod.getWrapped().getWrapped(); - // This method is only used when identifying the modifiers or the declaring class - // of a HostedMethod. Normally the method unwraps to the underlying JVMCI method - // which is the one that provides bytecode to the compiler as well as, line numbers - // and local info. If we unwrap to a SubstitutionMethod then we use the annotated - // method, not the JVMCI method that the annotation refers to since that will be the - // one providing the bytecode etc used by the compiler. If we unwrap to any other, - // custom substitution method we simply use it rather than dereferencing to the - // original. The difference is that the annotated method's bytecode will be used to - // replace the original and the debugger needs to use it to identify the file and access - // permissions. A custom substitution may exist alongside the original, as is the case - // with some uses for reflection. So, we don't want to conflate the custom substituted - // method and the original. In this latter case the method code will be synthesized without - // reference to the bytecode of the original. Hence there is no associated file and the - // permissions need to be determined from the custom substitution method itself. - - if (javaMethod instanceof SubstitutionMethod) { - SubstitutionMethod substitutionMethod = (SubstitutionMethod) javaMethod; - javaMethod = substitutionMethod.getAnnotated(); - } - return javaMethod; - } - - protected static int getOriginalModifiers(HostedMethod hostedMethod) { - return NativeImageDebugInfoProviderBase.getAnnotatedOrOriginal(hostedMethod).getModifiers(); - } - - /* - * GraalVM uses annotated interfaces to model foreign types. The following helpers support - * detection and categorization of these types, whether presented as a JavaType or HostedType. - */ - - /** - * Identify a Java type which is being used to model a foreign memory word or pointer type. - * - * @param type the type to be tested - * @param accessingType another type relative to which the first type may need to be resolved - * @return true if the type models a foreign memory word or pointer type - */ - protected boolean isForeignWordType(JavaType type, ResolvedJavaType accessingType) { - HostedType resolvedJavaType = (HostedType) type.resolve(accessingType); - return isForeignWordType(resolvedJavaType); - } - - /** - * Identify a hosted type which is being used to model a foreign memory word or pointer type. - * - * @param hostedType the type to be tested - * @return true if the type models a foreign memory word or pointer type - */ - protected boolean isForeignWordType(HostedType hostedType) { - // unwrap because native libs operates on the analysis type universe - return nativeLibs.isWordBase(hostedType.getWrapped()); - } - - /** - * Identify a hosted type which is being used to model a foreign pointer type. - * - * @param hostedType the type to be tested - * @return true if the type models a foreign pointer type - */ - protected boolean isForeignPointerType(HostedType hostedType) { - // unwrap because native libs operates on the analysis type universe - return nativeLibs.isPointerBase(hostedType.getWrapped()); - } - - /* - * Foreign pointer types have associated element info which describes the target type. The - * following helpers support querying of and access to this element info. - */ - - protected static boolean isTypedField(ElementInfo elementInfo) { - if (elementInfo instanceof StructFieldInfo) { - for (ElementInfo child : elementInfo.getChildren()) { - if (child instanceof AccessorInfo) { - switch (((AccessorInfo) child).getAccessorKind()) { - case GETTER: - case SETTER: - case ADDRESS: - return true; - } - } - } - } - return false; - } - - protected HostedType getFieldType(StructFieldInfo field) { - // we should always have some sort of accessor, preferably a GETTER or a SETTER - // but possibly an ADDRESS accessor - for (ElementInfo elt : field.getChildren()) { - if (elt instanceof AccessorInfo) { - AccessorInfo accessorInfo = (AccessorInfo) elt; - if (accessorInfo.getAccessorKind() == GETTER) { - return heap.hUniverse.lookup(accessorInfo.getReturnType()); - } - } - } - for (ElementInfo elt : field.getChildren()) { - if (elt instanceof AccessorInfo) { - AccessorInfo accessorInfo = (AccessorInfo) elt; - if (accessorInfo.getAccessorKind() == SETTER) { - return heap.hUniverse.lookup(accessorInfo.getParameterType(0)); - } - } - } - for (ElementInfo elt : field.getChildren()) { - if (elt instanceof AccessorInfo) { - AccessorInfo accessorInfo = (AccessorInfo) elt; - if (accessorInfo.getAccessorKind() == ADDRESS) { - return heap.hUniverse.lookup(accessorInfo.getReturnType()); - } - } - } - assert false : "Field %s must have a GETTER, SETTER, ADDRESS or OFFSET accessor".formatted(field); - // treat it as a word? - // n.b. we want a hosted type not an analysis type - return heap.hUniverse.lookup(wordBaseType); - } - - protected static boolean fieldTypeIsEmbedded(StructFieldInfo field) { - // we should always have some sort of accessor, preferably a GETTER or a SETTER - // but possibly an ADDRESS - for (ElementInfo elt : field.getChildren()) { - if (elt instanceof AccessorInfo) { - AccessorInfo accessorInfo = (AccessorInfo) elt; - if (accessorInfo.getAccessorKind() == GETTER) { - return false; - } - } - } - for (ElementInfo elt : field.getChildren()) { - if (elt instanceof AccessorInfo) { - AccessorInfo accessorInfo = (AccessorInfo) elt; - if (accessorInfo.getAccessorKind() == SETTER) { - return false; - } - } - } - for (ElementInfo elt : field.getChildren()) { - if (elt instanceof AccessorInfo) { - AccessorInfo accessorInfo = (AccessorInfo) elt; - if (accessorInfo.getAccessorKind() == ADDRESS) { - return true; - } - } - } - throw VMError.shouldNotReachHere("Field %s must have a GETTER, SETTER, ADDRESS or OFFSET accessor".formatted(field)); - } - - protected static int elementSize(ElementInfo elementInfo) { - if (!(elementInfo instanceof SizableInfo) || elementInfo instanceof StructInfo structInfo && structInfo.isIncomplete()) { - return 0; - } - Integer size = ((SizableInfo) elementInfo).getSizeInBytes(); - assert size != null; - return size; - } - - protected static String elementName(ElementInfo elementInfo) { - if (elementInfo == null) { - return ""; - } else { - return elementInfo.getName(); - } - } - - protected static SizableInfo.ElementKind elementKind(SizableInfo sizableInfo) { - return sizableInfo.getKind(); - } - - /* - * Debug info generation requires knowledge of a variety of parameters that determine object - * sizes and layouts and the organization of the code and code cache. The following helper - * methods provide access to this information. - */ - - static ObjectLayout getObjectLayout() { - return ConfigurationValues.getObjectLayout(); - } - - public boolean useHeapBase() { - return useHeapBase; - } - - public int compressionShift() { - return compressionShift; - } - - public int referenceSize() { - return referenceSize; - } - - public int pointerSize() { - return pointerSize; - } - - public int objectAlignment() { - return objectAlignment; - } - - public int reservedBitsMask() { - return reservedBitsMask; - } - - public int compiledCodeMax() { - return codeCache.getCodeCacheSize(); - } - - /** - * Return the offset into the initial heap at which the object identified by constant is located - * or -1 if the object is not present in the initial heap. - * - * @param constant must have JavaKind Object and must be non-null. - * @return the offset into the initial heap at which the object identified by constant is - * located or -1 if the object is not present in the initial heap. - */ - public long objectOffset(JavaConstant constant) { - assert constant.getJavaKind() == JavaKind.Object && !constant.isNull() : "invalid constant for object offset lookup"; - NativeImageHeap.ObjectInfo objectInfo = heap.getConstantInfo(constant); - if (objectInfo != null) { - return objectInfo.getOffset(); - } - return -1; - } - - protected HostedType hostedTypeForKind(JavaKind kind) { - return javaKindToHostedType.get(kind); - } - - private static HashMap initJavaKindToHostedTypes(HostedMetaAccess metaAccess) { - HashMap map = new HashMap<>(); - for (JavaKind kind : JavaKind.values()) { - Class clazz; - switch (kind) { - case Illegal: - clazz = null; - break; - case Object: - clazz = java.lang.Object.class; - break; - default: - clazz = kind.toJavaClass(); - } - HostedType javaType = clazz != null ? metaAccess.lookupJavaType(clazz) : null; - map.put(kind, javaType); - } - return map; - } - - /** - * Retrieve details of the native calling convention for a top level compiled method, including - * details of which registers or stack slots are used to pass parameters. - * - * @param method The method whose calling convention is required. - * @return The calling convention for the method. - */ - protected SubstrateCallingConvention getCallingConvention(HostedMethod method) { - SubstrateCallingConventionKind callingConventionKind = method.getCallingConventionKind(); - HostedType declaringClass = method.getDeclaringClass(); - HostedType receiverType = method.isStatic() ? null : declaringClass; - var signature = method.getSignature(); - final SubstrateCallingConventionType type; - if (callingConventionKind.isCustom()) { - type = method.getCustomCallingConventionType(); - } else { - type = callingConventionKind.toType(false); - } - Backend backend = runtimeConfiguration.lookupBackend(method); - RegisterConfig registerConfig = backend.getCodeCache().getRegisterConfig(); - assert registerConfig instanceof SubstrateRegisterConfig; - return (SubstrateCallingConvention) registerConfig.getCallingConvention(type, signature.getReturnType(), signature.toParameterTypes(receiverType), backend); - } - - /* - * The following helpers aid construction of file paths for class source files. - */ - - protected static Path fullFilePathFromClassName(HostedType hostedInstanceClass) { - String[] elements = hostedInstanceClass.toJavaName().split("\\."); - int count = elements.length; - String name = elements[count - 1]; - while (name.startsWith("$")) { - name = name.substring(1); - } - if (name.contains("$")) { - name = name.substring(0, name.indexOf('$')); - } - if (name.equals("")) { - name = "_nofile_"; - } - elements[count - 1] = name + ".java"; - return FileSystems.getDefault().getPath("", elements); - } - - public Path getCachePath() { - return cachePath; - } -} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageViaCC.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageViaCC.java index 53e4149885fd..b5ab3f81ed2c 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageViaCC.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageViaCC.java @@ -167,7 +167,7 @@ private void runLinkerCommand(String imageName, LinkerInvocation inv, List clazz, De path = locateSource(fileName, packageName, clazz); if (path == null) { // as a last ditch effort derive path from the Java class name - if (debugContext.areScopesEnabled()) { - debugContext.log(DebugContext.INFO_LEVEL, "Failed to find source file for class %s%n", resolvedType.toJavaName()); - } - if (packageName.length() > 0) { + debugContext.log(DebugContext.INFO_LEVEL, "Failed to find source file for class %s%n", resolvedType.toJavaName()); + if (!packageName.isEmpty()) { path = Paths.get("", packageName.split("\\.")); path = path.resolve(fileName); } @@ -93,7 +90,7 @@ public Path findAndCacheSource(ResolvedJavaType resolvedType, Class clazz, De * the source name embedded in the class file or the class name itself. * * @param resolvedType the resolved java type whose source file name is required - * @return the file name or null if it the class cannot be associated with a source file + * @return the file name or null if the class cannot be associated with a source file */ private static String computeBaseName(ResolvedJavaType resolvedType) { if (resolvedType.isPrimitive()) { @@ -146,10 +143,10 @@ private static String computePackageName(ResolvedJavaType javaType) { * * @param fileName the base file name for the source file * @param packageName the name of the package for the associated Java class - * @return a protoype name for the source file + * @return a prototype name for the source file */ private static Path computePrototypeName(String fileName, String packageName) { - if (packageName.length() == 0) { + if (packageName.isEmpty()) { return Paths.get("", fileName); } else { return Paths.get("", packageName.split("\\.")).resolve(fileName); @@ -160,7 +157,7 @@ private static Path computePrototypeName(String fileName, String packageName) { * A map from a Java type to an associated source paths which is known to have an up to date * entry in the relevant source file cache. This is used to memoize previous lookups. */ - private static HashMap verifiedPaths = new HashMap<>(); + private static final HashMap verifiedPaths = new HashMap<>(); /** * An invalid path used as a marker to track failed lookups so we don't waste time looking up diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/RuntimeCompileDebugInfoTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/RuntimeCompileDebugInfoTest.java new file mode 100644 index 000000000000..a690987621d6 --- /dev/null +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/RuntimeCompileDebugInfoTest.java @@ -0,0 +1,421 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.test.debug.helper; + +import static com.oracle.svm.core.util.VMError.shouldNotReachHere; + +import java.util.ArrayList; +import java.util.List; + +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.VMRuntime; +import org.graalvm.nativeimage.c.function.CFunctionPointer; +import org.graalvm.nativeimage.hosted.Feature; +import org.graalvm.nativeimage.hosted.RuntimeClassInitialization; +import org.graalvm.word.WordFactory; + +import com.oracle.svm.core.AlwaysInline; +import com.oracle.svm.core.NeverInline; +import com.oracle.svm.core.c.InvokeJavaFunctionPointer; +import com.oracle.svm.graal.SubstrateGraalUtils; +import com.oracle.svm.graal.hosted.runtimecompilation.RuntimeCompilationFeature; +import com.oracle.svm.graal.meta.SubstrateMethod; +import com.oracle.svm.hosted.FeatureImpl.BeforeAnalysisAccessImpl; +import com.oracle.svm.util.ModuleSupport; + +import jdk.vm.ci.code.InstalledCode; + +class RuntimeCompilations { + + Integer a; + int b; + String c; + Object d; + + static Integer sa = 42; + static int sb; + static String sc; + static Object sd; + + RuntimeCompilations(int a) { + this.a = a; + } + + @NeverInline("For testing") + private void breakHere() { + } + + @NeverInline("For testing") + private void breakHere(@SuppressWarnings("unused") Object... pin) { + } + + @NeverInline("For testing") + public Integer paramMethod(Integer param1, int param2, String param3, Object param4) { + a = param1; + breakHere(); + b = param2; + breakHere(); + c = param3; + breakHere(); + d = param4; + breakHere(param1); + return a; + } + + @NeverInline("For testing") + public Integer noParamMethod() { + return a; + } + + @NeverInline("For testing") + public void voidMethod() { + a *= 2; + breakHere(); + } + + @NeverInline("For testing") + public int primitiveReturnMethod(int param1) { + b += param1; + breakHere(); + return b; + } + + @NeverInline("For testing") + public Integer[] arrayMethod(int[] param1, @SuppressWarnings("unused") String[] param2) { + a = param1[0]; + breakHere(); + return new Integer[]{a}; + } + + @NeverInline("For testing") + @SuppressWarnings("unused") + public float localsMethod(String param1) { + float f = 1.5f; + String x = param1; + breakHere(); + byte[] bytes = x.getBytes(); + breakHere(); + return f + bytes.length; + } + + @NeverInline("For testing") + public static Integer staticMethod(Integer param1, int param2, String param3, Object param4) { + sa = param1; + sb = param2; + sc = param3; + sd = param4; + return sa + sb; + } + + @NeverInline("For testing") + public void inlineTest(String param1) { + inlineMethod1(param1); + breakHere(); + inlineMethod2(param1); + breakHere(); + noInlineMethod1(param1); + breakHere(); + noInlineMethod2(param1); + } + + @AlwaysInline("For testing") + private void inlineMethod1(String param1) { + String inlineParam = param1; + breakHere(inlineParam); + } + + @AlwaysInline("For testing") + protected void inlineMethod2(@SuppressWarnings("unused") String param1) { + String p1 = "1"; + inlineMethod1(p1); + breakHere(); + + String p2 = "2"; + noInlineMethod1(p2); + breakHere(); + } + + @NeverInline("For testing") + private void noInlineMethod1(String param1) { + String noInlineParam = param1; + breakHere(noInlineParam); + } + + @NeverInline("For testing") + protected void noInlineMethod2(@SuppressWarnings("unused") String param1) { + String p1 = "a"; + noInlineMethod1(p1); + breakHere(); + + String p2 = "b"; + inlineMethod1(p2); + breakHere(); + + String p3 = "c"; + inlineMethod2(p3); + breakHere(); + } +} + +public class RuntimeCompileDebugInfoTest { + static class RuntimeHolder { + SubstrateMethod testMethod1; + SubstrateMethod testMethod2; + SubstrateMethod testMethod3; + SubstrateMethod testMethod4; + SubstrateMethod testMethod5; + SubstrateMethod testMethod6; + SubstrateMethod testMethod7; + SubstrateMethod testMethod8; + } + + public static class RegisterMethodsFeature implements Feature { + RegisterMethodsFeature() { + ModuleSupport.accessPackagesToClass(ModuleSupport.Access.EXPORT, RegisterMethodsFeature.class, false, + "org.graalvm.nativeimage.builder", + "com.oracle.svm.graal", "com.oracle.svm.graal.hosted.runtimecompilation", "com.oracle.svm.hosted"); + ModuleSupport.accessPackagesToClass(ModuleSupport.Access.EXPORT, RegisterMethodsFeature.class, false, + "jdk.internal.vm.ci", + "jdk.vm.ci.code"); + ModuleSupport.accessPackagesToClass(ModuleSupport.Access.EXPORT, RegisterMethodsFeature.class, false, + "jdk.graal.compiler", + "jdk.graal.compiler.api.directives"); + } + + @Override + public List> getRequiredFeatures() { + return List.of(RuntimeCompilationFeature.class); + } + + @Override + public void beforeAnalysis(BeforeAnalysisAccess a) { + BeforeAnalysisAccessImpl config = (BeforeAnalysisAccessImpl) a; + + RuntimeHolder holder = new RuntimeHolder(); + RuntimeClassInitialization.initializeAtBuildTime(RuntimeHolder.class); + ImageSingletons.add(RuntimeHolder.class, holder); + + RuntimeCompilationFeature runtimeCompilationFeature = RuntimeCompilationFeature.singleton(); + runtimeCompilationFeature.initializeRuntimeCompilationForTesting(config); + + holder.testMethod1 = prepareMethodForRuntimeCompilation(config, runtimeCompilationFeature, RuntimeCompilations.class, "paramMethod", Integer.class, int.class, String.class, Object.class); + holder.testMethod2 = prepareMethodForRuntimeCompilation(config, runtimeCompilationFeature, RuntimeCompilations.class, "noParamMethod"); + holder.testMethod3 = prepareMethodForRuntimeCompilation(config, runtimeCompilationFeature, RuntimeCompilations.class, "voidMethod"); + holder.testMethod4 = prepareMethodForRuntimeCompilation(config, runtimeCompilationFeature, RuntimeCompilations.class, "primitiveReturnMethod", int.class); + holder.testMethod5 = prepareMethodForRuntimeCompilation(config, runtimeCompilationFeature, RuntimeCompilations.class, "arrayMethod", int[].class, String[].class); + holder.testMethod6 = prepareMethodForRuntimeCompilation(config, runtimeCompilationFeature, RuntimeCompilations.class, "localsMethod", String.class); + holder.testMethod7 = prepareMethodForRuntimeCompilation(config, runtimeCompilationFeature, RuntimeCompilations.class, "staticMethod", Integer.class, int.class, String.class, Object.class); + holder.testMethod8 = prepareMethodForRuntimeCompilation(config, runtimeCompilationFeature, RuntimeCompilations.class, "inlineTest", String.class); + } + + private static SubstrateMethod prepareMethodForRuntimeCompilation(BeforeAnalysisAccessImpl config, RuntimeCompilationFeature runtimeCompilationFeature, Class holder, String methodName, + Class... signature) { + RuntimeClassInitialization.initializeAtBuildTime(holder); + try { + return runtimeCompilationFeature.prepareMethodForRuntimeCompilation(holder.getMethod(methodName, signature), config); + } catch (NoSuchMethodException ex) { + throw shouldNotReachHere(ex); + } + } + } + + interface TestFunctionPointer extends CFunctionPointer { + @InvokeJavaFunctionPointer + Integer invoke(Object receiver, Integer arg1, int arg2, String arg3, Object arg4); + + @InvokeJavaFunctionPointer + Object invoke(Object receiver); + + @InvokeJavaFunctionPointer + int invoke(Object receiver, int arg1); + + @InvokeJavaFunctionPointer + Integer[] invoke(Object receiver, int[] arg1, String[] arg2); + + @InvokeJavaFunctionPointer + float invoke(Object receiver, String arg1); + + @InvokeJavaFunctionPointer + Integer invoke(Integer arg1, int arg2, String arg3, Object arg4); + } + + private static RuntimeHolder getHolder() { + return ImageSingletons.lookup(RuntimeHolder.class); + } + + private static Integer invoke(TestFunctionPointer functionPointer, Object receiver, Integer arg1, int arg2, String arg3, Object arg4) { + return functionPointer.invoke(receiver, arg1, arg2, arg3, arg4); + } + + private static Object invoke(TestFunctionPointer functionPointer, Object receiver) { + return functionPointer.invoke(receiver); + } + + private static int invoke(TestFunctionPointer functionPointer, Object receiver, int arg1) { + return functionPointer.invoke(receiver, arg1); + } + + private static Integer[] invoke(TestFunctionPointer functionPointer, Object receiver, int[] arg1, String[] arg2) { + return functionPointer.invoke(receiver, arg1, arg2); + } + + private static float invoke(TestFunctionPointer functionPointer, Object receiver, String arg1) { + return functionPointer.invoke(receiver, arg1); + } + + private static Integer invoke(TestFunctionPointer functionPointer, Integer arg1, int arg2, String arg3, Object arg4) { + return functionPointer.invoke(arg1, arg2, arg3, arg4); + } + + private static TestFunctionPointer getFunctionPointer(InstalledCode installedCode) { + return WordFactory.pointer(installedCode.getEntryPoint()); + } + + @SuppressWarnings("unused") + public static void testParams() { + + InstalledCode installedCode = SubstrateGraalUtils.compileAndInstall(getHolder().testMethod1); + InstalledCode installedCodeStatic = SubstrateGraalUtils.compileAndInstall(getHolder().testMethod7); + + RuntimeCompilations x = new RuntimeCompilations(11); + Integer param1 = 42; + int param2 = 27; + String param3 = "test"; + Object param4 = new ArrayList<>(); + + Integer result = invoke(getFunctionPointer(installedCode), x, param1, param2, param3, param4); + Integer resultStatic = invoke(getFunctionPointer(installedCodeStatic), param1, param2, param3, param4); + + installedCode.invalidate(); + installedCodeStatic.invalidate(); + } + + @SuppressWarnings("unused") + public static void testNoParam() { + + InstalledCode installedCode = SubstrateGraalUtils.compileAndInstall(getHolder().testMethod2); + + RuntimeCompilations x = new RuntimeCompilations(11); + + Integer result = (Integer) invoke(getFunctionPointer(installedCode), x); + + installedCode.invalidate(); + } + + public static void testVoid() { + + InstalledCode installedCode = SubstrateGraalUtils.compileAndInstall(getHolder().testMethod3); + + RuntimeCompilations x = new RuntimeCompilations(11); + + invoke(getFunctionPointer(installedCode), x); + + installedCode.invalidate(); + } + + @SuppressWarnings("unused") + public static void testPrimitiveReturn() { + + InstalledCode installedCode = SubstrateGraalUtils.compileAndInstall(getHolder().testMethod4); + + RuntimeCompilations x = new RuntimeCompilations(11); + int param1 = 42; + + int result = invoke(getFunctionPointer(installedCode), x, param1); + + installedCode.invalidate(); + } + + @SuppressWarnings("unused") + public static void testArray() { + + InstalledCode installedCode = SubstrateGraalUtils.compileAndInstall(getHolder().testMethod5); + + RuntimeCompilations x = new RuntimeCompilations(11); + int[] param1 = new int[]{1, 2, 3, 4}; + String[] param2 = new String[]{"this", "is", "an", "array"}; + + Integer[] result = invoke(getFunctionPointer(installedCode), x, param1, param2); + + installedCode.invalidate(); + } + + @SuppressWarnings("unused") + public static void testLocals() { + + InstalledCode installedCode = SubstrateGraalUtils.compileAndInstall(getHolder().testMethod6); + + RuntimeCompilations x = new RuntimeCompilations(11); + String param1 = "test"; + + float result = invoke(getFunctionPointer(installedCode), x, param1); + + installedCode.invalidate(); + } + + public static void testInlining() { + + InstalledCode installedCode = SubstrateGraalUtils.compileAndInstall(getHolder().testMethod8); + + RuntimeCompilations x = new RuntimeCompilations(11); + String param1 = "test"; + + invoke(getFunctionPointer(installedCode), x, param1); + + installedCode.invalidate(); + } + + @SuppressWarnings("unused") + public static void testAOTCompiled() { + + RuntimeCompilations x = new RuntimeCompilations(11); + Integer param1 = 42; + int param2 = 27; + String param3 = "test"; + Object param4 = new ArrayList<>(); + + Integer result = x.paramMethod(param1, param2, param3, param4); + Integer result2 = RuntimeCompilations.staticMethod(param1, param2, param3, param4); + Integer result3 = x.noParamMethod(); + } + + public static void main(String[] args) { + /* Run startup hooks to, e.g., install the segfault handler so that we get crash reports. */ + VMRuntime.initialize(); + + // aot compiled code for comparing generated debug infos + testAOTCompiled(); + + // use runtime compiled code + testParams(); + testNoParam(); + testVoid(); + testPrimitiveReturn(); + testArray(); + testLocals(); + testInlining(); + } +} diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/gdb_helper.py b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/gdb_helper.py index 3754c24380c9..3666c1f1050a 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/gdb_helper.py +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/gdb_helper.py @@ -32,7 +32,7 @@ import gdb -logfile = os.environ.get('gdbdebughelperstest_logfile', 'debug_helper.log') +logfile = os.environ.get('gdb_logfile', 'debug_helper.log') logging.basicConfig(filename=logfile, format='%(name)s %(levelname)s: %(message)s', level=logging.DEBUG) logger = logging.getLogger('[DebugTest]') @@ -100,7 +100,7 @@ def gdb_advanced_print(var: str, output_format: str = None) -> str: def gdb_set_breakpoint(location: str) -> None: logger.info(f"Setting breakpoint at: {location}") - gdb_execute(f"break {location}") + gdb.Breakpoint(location) def gdb_set_param(name: str, value: str) -> None: diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/test_cinterface.py b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/test_cinterface.py index e0db933104e7..b736fbcd326c 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/test_cinterface.py +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/test_cinterface.py @@ -53,6 +53,10 @@ def test_auto_load(self): self.assertIn("SubstrateVM", exec_string, 'pretty-printer was not loaded') # assume that there are no other pretty-printers were attached to an objfile self.assertIn("objfile", exec_string, 'pretty-printer was not attached to an objfile') + # check frame filter + exec_string = gdb_execute('info frame-filter') + self.assertIn('libcinterfacetutorial.so.debug frame-filters:', exec_string) + self.assertIn('SubstrateVM FrameFilter', exec_string) def test_manual_load(self): backup_auto_load_param = gdb_get_param("auto-load python-scripts") @@ -65,6 +69,10 @@ def test_manual_load(self): exec_string = gdb_execute('info pretty-printer') self.assertIn("objfile", exec_string) # check if any objfile has a pretty-printer self.assertIn("SubstrateVM", exec_string) + # check frame filter + exec_string = gdb_execute('info frame-filter') + self.assertIn('libcinterfacetutorial.so.debug frame-filters:', exec_string) + self.assertIn('SubstrateVM FrameFilter', exec_string) finally: # make sure auto-loading is re-enabled for other tests gdb_set_param("auto-load python-scripts", backup_auto_load_param) @@ -80,10 +88,16 @@ def test_manual_load_without_executable(self): def test_auto_reload(self): gdb_start() - gdb_start() # all loaded shared libraries get freed (their pretty printers are removed) and newly attached + # all loaded shared libraries get freed and newly attached + # pretty printers, frame filters and frame unwinders should also be reattached + gdb_start() exec_string = gdb_execute('info pretty-printer') self.assertIn("SubstrateVM", exec_string, 'pretty-printer was not loaded') self.assertIn("objfile", exec_string, 'pretty-printer was not attached to an objfile') + # check frame filter + exec_string = gdb_execute('info frame-filter') + self.assertIn('libcinterfacetutorial.so.debug frame-filters:', exec_string) + self.assertIn('SubstrateVM FrameFilter', exec_string) class TestCInterface(unittest.TestCase): diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/test_class_loader.py b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/test_class_loader.py index 38d06d71d756..577763f9bf8e 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/test_class_loader.py +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/test_class_loader.py @@ -72,6 +72,7 @@ def setUp(self): self.maxDiff = None set_up_test() set_up_gdb_debughelpers() + self.svm_util = SVMUtil() def tearDown(self): gdb_delete_breakpoints() @@ -82,8 +83,8 @@ def test_get_classloader_namespace(self): gdb_run() this = gdb.parse_and_eval('this') # type = com.oracle.svm.test.missing.classes.TestClass -> testClassLoader field = gdb.parse_and_eval('this.instanceField') # instanceField is null - self.assertRegex(SVMUtil.get_classloader_namespace(this), f'testClassLoader_{hex_rexp.pattern}') - self.assertEqual(SVMUtil.get_classloader_namespace(field), "") + self.assertRegex(self.svm_util.get_classloader_namespace(this), f'testClassLoader_{hex_rexp.pattern}') + self.assertEqual(self.svm_util.get_classloader_namespace(field), "") # redirect unittest output to terminal diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/test_runtime_compilation.py b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/test_runtime_compilation.py new file mode 100644 index 000000000000..8687ffd698ad --- /dev/null +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/test_runtime_compilation.py @@ -0,0 +1,267 @@ +# +# Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# This code is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 only, as +# published by the Free Software Foundation. Oracle designates this +# particular file as subject to the "Classpath" exception as provided +# by Oracle in the LICENSE file that accompanied this code. +# +# This code is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# version 2 for more details (a copy is included in the LICENSE file that +# accompanied this code). +# +# You should have received a copy of the GNU General Public License version +# 2 along with this work; if not, write to the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA +# or visit www.oracle.com if you need additional information or have any +# questions. +# + +import os +import sys +import unittest + +import gdb + +# add test directory to path to allow import of gdb_helper.py +sys.path.insert(0, os.path.join(os.path.dirname(os.path.realpath(__file__)))) + +from gdb_helper import * + + +# shouldn't require the gdb patch to be available +# however, running it without the patch causes an error in gdb +# this just tests the jit compilation interface, not the generated debug info +# however still requires symbols to be available for setting a breakpoint +class TestJITCompilationInterface(unittest.TestCase): + @classmethod + def setUp(cls): + set_up_test() + gdb_delete_breakpoints() + gdb_start() + gdb_execute("set dwarf-type-signature-fallback main") + + @classmethod + def tearDown(cls): + gdb_delete_breakpoints() + gdb_kill() + + # this test requires gdb to automatically add a breakpoint when a runtime compilation occurs + # if a breakpoint for the compiled method existed before + def test_update_breakpoint(self): + # set breakpoint in runtime compiled function and stop there + # store initial breakpoint info for comparison + gdb_set_breakpoint("com.oracle.svm.test.debug.helper.RuntimeCompilations::inlineTest") + breakpoint_info_before = gdb_execute('info breakpoints') + gdb_continue() + + # get current breakpoint info and do checks + breakpoint_info_after = gdb_execute('info breakpoints') + # check if we got more breakpoints than before + self.assertGreater(len(breakpoint_info_after), len(breakpoint_info_before)) + # check if old breakpoints still exist, code address must be the same + # split at code address as multi-breakpoints are printed very different to single breakpoints + self.assertIn(breakpoint_info_before.split('0x')[-1], breakpoint_info_after) + # check if exactly one new correct breakpoint was added + # new breakpoint address is always added after + self.assertEqual(breakpoint_info_after.split(breakpoint_info_before.split('0x')[-1])[-1].count('com.oracle.svm.test.debug.helper.RuntimeCompilations::inlineTest'), 1) + + # run until the runtime compilation is invalidated and check if the breakpoint is removed + gdb_set_breakpoint('com.oracle.svm.graal.meta.SubstrateInstalledCodeImpl::invalidate') + gdb_continue() # run until invalidation + gdb_finish() # finish invalidation - this should trigger an unregister call to gdb + breakpoint_info_after_invalidation = gdb_execute('info breakpoints') + # check if additional breakpoint is cleared after invalidate + # breakpoint info is still printed as multi-breakpoint, thus we check if exactly one valid breakpoint is remaining + self.assertEqual(breakpoint_info_after_invalidation.count('com.oracle.svm.test.debug.helper.RuntimeCompilations::inlineTest'), 1) + # breakpoint info must change after invalidation + self.assertNotEquals(breakpoint_info_after, breakpoint_info_after_invalidation) + + # this test requires gdb to first load a new objfile at runtime and then remove it as the compilation is invalidated + def test_load_objfile(self): + # sanity check, we should not have in-memory objfiles at this point (to avoid false-positives later) + objfiles = gdb.objfiles() + self.assertFalse(any([o.filename.startswith('') + self.assertEqual(gdb_output('param3'), '"test"') + self.assertEqual(gdb_output('param4'), 'java.util.ArrayList(0)') + this = gdb_output('this') + self.assertTrue(this.startswith('com.oracle.svm.test.debug.helper.RuntimeCompilations = {')) + self.assertIn('a = 42', this) + self.assertIn('b = 27', this) + self.assertIn('c = null', this) + self.assertIn('d = null', this) + # step 3 set c to param3 + gdb_continue() + gdb_finish() + self.assertEqual(gdb_output('param1'), '42') + self.assertEqual(gdb_output('param2'), '') + self.assertEqual(gdb_output('param3'), '') + self.assertEqual(gdb_output('param4'), 'java.util.ArrayList(0)') + this = gdb_output('this') + self.assertTrue(this.startswith('com.oracle.svm.test.debug.helper.RuntimeCompilations = {')) + self.assertIn('a = 42', this) + self.assertIn('b = 27', this) + self.assertIn('c = "test"', this) + self.assertIn('d = null', this) + # step 4 set d to param4 (pin of param1 ends here) + gdb_continue() + gdb_finish() + self.assertEqual(gdb_output('param1'), '') + self.assertEqual(gdb_output('param2'), '') + self.assertEqual(gdb_output('param3'), '') + self.assertEqual(gdb_output('param4'), '') + this = gdb_output('this') + self.assertTrue(this.startswith('com.oracle.svm.test.debug.helper.RuntimeCompilations = {')) + self.assertIn('a = 42', this) + self.assertIn('b = 27', this) + self.assertIn('c = "test"', this) + self.assertIn('d = java.util.ArrayList(0)', this) + + # compares params and param types of AOT and JIT compiled method + def test_compare_AOT_to_JIT(self): + gdb_set_breakpoint("com.oracle.svm.test.debug.helper.RuntimeCompilations::paramMethod") + # first stop for the AOT compiled variant + gdb_continue() + this = gdb_output('this') + this_t = gdb_print_type('this') + param1 = gdb_output('param1') + param1_t = gdb_print_type('param1') + param2 = gdb_output('param2') + param2_t = gdb_print_type('param2') + param3 = gdb_output('param3') + param3_t = gdb_print_type('param3') + param4 = gdb_output('param4') + param4_t = gdb_print_type('param4') + + # now stop for runtime compiled variant and check if equal + gdb_continue() + self.assertEqual(gdb_output('this'), this) + self.assertEqual(gdb_print_type('this'), this_t) + self.assertEqual(gdb_output('param1'), param1) + self.assertEqual(gdb_print_type('param1'), param1_t) + self.assertEqual(gdb_output('param2'), param2) + self.assertEqual(gdb_print_type('param2'), param2_t) + self.assertEqual(gdb_output('param3'), param3) + self.assertEqual(gdb_print_type('param3'), param3_t) + self.assertEqual(gdb_output('param4'), param4) + self.assertEqual(gdb_print_type('param4'), param4_t) + + +# redirect unittest output to terminal +result = unittest.main(testRunner=unittest.TextTestRunner(stream=sys.__stdout__), exit=False) +# close gdb +gdb_quit(0 if result.result.wasSuccessful() else 1) diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/test_runtime_deopt.py b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/test_runtime_deopt.py new file mode 100644 index 000000000000..5a856eb15186 --- /dev/null +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/test_runtime_deopt.py @@ -0,0 +1,139 @@ +# +# Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# This code is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 only, as +# published by the Free Software Foundation. Oracle designates this +# particular file as subject to the "Classpath" exception as provided +# by Oracle in the LICENSE file that accompanied this code. +# +# This code is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# version 2 for more details (a copy is included in the LICENSE file that +# accompanied this code). +# +# You should have received a copy of the GNU General Public License version +# 2 along with this work; if not, write to the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA +# or visit www.oracle.com if you need additional information or have any +# questions. +# + +import os +import sys +import unittest + +import gdb + +# add test directory to path to allow import of gdb_helper.py +sys.path.insert(0, os.path.join(os.path.dirname(os.path.realpath(__file__)))) + +from gdb_helper import * + + +# requires the gdb patch to be available +class TestRuntimeDeopt(unittest.TestCase): + @classmethod + def setUp(cls): + cls.maxDiff = None + set_up_test() + gdb_start() + + @classmethod + def tearDown(cls): + gdb_delete_breakpoints() + gdb_kill() + + def test_frame_unwinder_registration(self): + # run to a method where the frame unwinder is registered + gdb_set_breakpoint('com.oracle.truffle.runtime.OptimizedCallTarget::profiledPERoot') + gdb_continue() + set_up_gdb_debughelpers() # setup debughelpers after we know it exists + # check frame unwinder + unwinder_info = gdb_execute('info unwinder') + self.assertIn('libjsvm.so.debug:', unwinder_info) + self.assertIn('SubstrateVM FrameUnwinder', unwinder_info) + + # for shared libraries, the frame unwinder is removed when the shared library is unloaded + # when it is loaded again, the gdb script is not run again + # the gdb-debughelpers should still be able to reload the frame unwinder + def test_frame_unwinder_reload(self): + # run to a method where the frame unwinder is registered + gdb_set_breakpoint('com.oracle.truffle.runtime.OptimizedCallTarget::profiledPERoot') + gdb_continue() + set_up_gdb_debughelpers() # setup debughelpers after we know it exists + + # stops previous execution and reloads the shared library + # gdb-debughelpers should then reload the frame unwinder for the shared library + gdb_run() + # check frame unwinder + unwinder_info = gdb_execute('info unwinder') + self.assertIn('libjsvm.so.debug:', unwinder_info) + self.assertIn('SubstrateVM FrameUnwinder', unwinder_info) + + def test_backtrace_with_deopt(self): + # run until method is deoptimized + gdb_set_breakpoint("com.oracle.svm.core.deopt.Deoptimizer::invalidateMethodOfFrame") + gdb_continue() + gdb_finish() + + # check backtrace + backtrace = gdb_execute('backtrace 5') + # check if eager deopt frame + if 'EAGER DEOPT FRAME' in backtrace: + self.assertIn('[EAGER DEOPT FRAME] com.oracle.truffle.runtime.OptimizedCallTarget::profiledPERoot', backtrace) + self.assertIn('(deoptFrameValues=2, __0=com.oracle.svm.truffle.api.SubstrateOptimizedCallTarget = {...}, __1=java.lang.Object[5] = {...}) at OptimizedCallTarget.java', backtrace) + self.assertIn('com.oracle.svm.truffle.api.SubstrateOptimizedCallTargetInstalledCode::doInvoke', backtrace) + self.assertNotIn('??', backtrace) + self.assertNotIn('Unknown Frame at', backtrace) + else: + # must be lazy deopt frame + # we can't be sure it is handled properly, but at least it should show up as lazy deopt frame in the backtrace + self.assertIn('[LAZY DEOPT FRAME] at', backtrace) + + # the js deopt test uses the jsvm-library + # so the debugging symbols do not originate from the main objfile, but from the shared library + # this requires gdb to use the full type signature fallback + def test_type_signature_fallback_full(self): + # stop at a method where we know that the runtime compiled frame is in the backtrace + gdb_execute("set dwarf-type-signature-fallback full") + gdb_set_breakpoint("com.oracle.svm.core.deopt.Deoptimizer::invalidateMethodOfFrame") + gdb_continue() + + # check backtrace + backtrace = gdb_execute('backtrace 5') + self.assertIn('com.oracle.truffle.runtime.OptimizedCallTarget::profiledPERoot', backtrace) + self.assertIn('(this=, originalArguments=com.oracle.svm.core.option.RuntimeOptionKey = {...})', backtrace) + self.assertNotIn('this=, originalArguments=com.oracle.svm.core.option.RuntimeOptionKey = {...})', backtrace) + self.assertIn('this=