diff options
author | Tom Tromey <tom@tromey.com> | 2018-02-09 13:31:51 -0700 |
---|---|---|
committer | Tom Tromey <tom@tromey.com> | 2018-02-26 09:21:08 -0700 |
commit | c9317f214b274b805190b8e878c79f4181d93bb4 (patch) | |
tree | 5951977ca7528f2fd938aae44205255c31e3c8dc /gdb/dwarf2read.c | |
parent | 7c22600aabfd10e190e98fff0b7c2d69cd191325 (diff) | |
download | gdb-c9317f214b274b805190b8e878c79f4181d93bb4.zip gdb-c9317f214b274b805190b8e878c79f4181d93bb4.tar.gz gdb-c9317f214b274b805190b8e878c79f4181d93bb4.tar.bz2 |
Convert Rust to use discriminated unions
A Rust enum is, essentially, a discriminated union. Currently the
Rust language support handles Rust enums locally, in rust-lang.c.
However, because I am changing the Rust compiler to use
DW_TAG_variant* to represent enums, it seemed better to have a single
internal representation for Rust enums in gdb.
This patch implements this idea by moving the current Rust enum
handling code to dwarf2read. This allows the simplification of some
parts of rust-lang.c as well.
2018-02-26 Tom Tromey <tom@tromey.com>
* rust-lang.h (rust_last_path_segment): Declare.
* rust-lang.c (rust_last_path_segment): Now public. Change
contract.
(struct disr_info): Remove.
(RUST_ENUM_PREFIX, RUST_ENCODED_ENUM_REAL)
(RUST_ENCODED_ENUM_HIDDEN, rust_union_is_untagged)
(rust_get_disr_info, rust_tuple_variant_type_p): Remove.
(rust_enum_p, rust_enum_variant): New function.
(rust_underscore_fields): Remove "offset" parameter.
(rust_print_enum): New function.
(rust_val_print) <TYPE_CODE_UNION>: Remove enum code.
<TYPE_CODE_STRUCT>: Call rust_print_enum when appropriate.
(rust_print_struct_def): Add "for_rust_enum" parameter. Handle
enums.
(rust_internal_print_type): New function, from rust_print_type.
Remove enum code.
(rust_print_type): Call rust_internal_print_type.
(rust_evaluate_subexp) <STRUCTOP_ANONYMOUS, STRUCTOP_STRUCT>:
Update enum handling.
* dwarf2read.c (struct dwarf2_cu) <rust_unions>: New field.
(rust_fully_qualify, alloc_discriminant_info, quirk_rust_enum)
(rust_union_quirks): New functions.
(process_full_comp_unit, process_full_type_unit): Call
rust_union_quirks.
(process_structure_scope): Update rust_unions if necessary.
2018-02-26 Tom Tromey <tom@tromey.com>
* gdb.rust/simple.exp: Accept more possible results in enum test.
Diffstat (limited to 'gdb/dwarf2read.c')
-rw-r--r-- | gdb/dwarf2read.c | 313 |
1 files changed, 313 insertions, 0 deletions
diff --git a/gdb/dwarf2read.c b/gdb/dwarf2read.c index ff808be..dab5cfb 100644 --- a/gdb/dwarf2read.c +++ b/gdb/dwarf2read.c @@ -86,6 +86,7 @@ #include <cmath> #include <set> #include <forward_list> +#include "rust-lang.h" /* When == 1, print basic high level tracing messages. When > 1, be more verbose. @@ -762,6 +763,14 @@ struct dwarf2_cu whether the DW_AT_ranges attribute came from the skeleton or DWO. */ ULONGEST ranges_base = 0; + /* When reading debug info generated by older versions of rustc, we + have to rewrite some union types to be struct types with a + variant part. This rewriting must be done after the CU is fully + read in, because otherwise at the point of rewriting some struct + type might not have been fully processed. So, we keep a list of + all such types here and process them after expansion. */ + std::vector<struct type *> rust_unions; + /* Mark used when releasing cached dies. */ unsigned int mark : 1; @@ -10318,6 +10327,302 @@ fixup_go_packaging (struct dwarf2_cu *cu) } } +/* Allocate a fully-qualified name consisting of the two parts on the + obstack. */ + +static const char * +rust_fully_qualify (struct obstack *obstack, const char *p1, const char *p2) +{ + return obconcat (obstack, p1, "::", p2, (char *) NULL); +} + +/* A helper that allocates a struct discriminant_info to attach to a + union type. */ + +static struct discriminant_info * +alloc_discriminant_info (struct type *type, int discriminant_index, + int default_index) +{ + gdb_assert (TYPE_CODE (type) == TYPE_CODE_UNION); + gdb_assert (default_index == -1 + || (default_index > 0 && default_index < TYPE_NFIELDS (type))); + + TYPE_FLAG_DISCRIMINATED_UNION (type) = 1; + + struct discriminant_info *disc + = ((struct discriminant_info *) + TYPE_ZALLOC (type, + offsetof (struct discriminant_info, discriminants) + + TYPE_NFIELDS (type) * sizeof (disc->discriminants[0]))); + disc->default_index = default_index; + disc->discriminant_index = discriminant_index; + + struct dynamic_prop prop; + prop.kind = PROP_UNDEFINED; + prop.data.baton = disc; + + add_dyn_prop (DYN_PROP_DISCRIMINATED, prop, type); + + return disc; +} + +/* Some versions of rustc emitted enums in an unusual way. + + Ordinary enums were emitted as unions. The first element of each + structure in the union was named "RUST$ENUM$DISR". This element + held the discriminant. + + These versions of Rust also implemented the "non-zero" + optimization. When the enum had two values, and one is empty and + the other holds a pointer that cannot be zero, the pointer is used + as the discriminant, with a zero value meaning the empty variant. + Here, the union's first member is of the form + RUST$ENCODED$ENUM$<fieldno>$<fieldno>$...$<variantname> + where the fieldnos are the indices of the fields that should be + traversed in order to find the field (which may be several fields deep) + and the variantname is the name of the variant of the case when the + field is zero. + + This function recognizes whether TYPE is of one of these forms, + and, if so, smashes it to be a variant type. */ + +static void +quirk_rust_enum (struct type *type, struct objfile *objfile) +{ + gdb_assert (TYPE_CODE (type) == TYPE_CODE_UNION); + + /* We don't need to deal with empty enums. */ + if (TYPE_NFIELDS (type) == 0) + return; + +#define RUST_ENUM_PREFIX "RUST$ENCODED$ENUM$" + if (TYPE_NFIELDS (type) == 1 + && startswith (TYPE_FIELD_NAME (type, 0), RUST_ENUM_PREFIX)) + { + const char *name = TYPE_FIELD_NAME (type, 0) + strlen (RUST_ENUM_PREFIX); + + /* Decode the field name to find the offset of the + discriminant. */ + ULONGEST bit_offset = 0; + struct type *field_type = TYPE_FIELD_TYPE (type, 0); + while (name[0] >= '0' && name[0] <= '9') + { + char *tail; + unsigned long index = strtoul (name, &tail, 10); + name = tail; + if (*name != '$' + || index >= TYPE_NFIELDS (field_type) + || (TYPE_FIELD_LOC_KIND (field_type, index) + != FIELD_LOC_KIND_BITPOS)) + { + complaint (&symfile_complaints, + _("Could not parse Rust enum encoding string \"%s\"" + "[in module %s]"), + TYPE_FIELD_NAME (type, 0), + objfile_name (objfile)); + return; + } + ++name; + + bit_offset += TYPE_FIELD_BITPOS (field_type, index); + field_type = TYPE_FIELD_TYPE (field_type, index); + } + + /* Make a union to hold the variants. */ + struct type *union_type = alloc_type (objfile); + TYPE_CODE (union_type) = TYPE_CODE_UNION; + TYPE_NFIELDS (union_type) = 3; + TYPE_FIELDS (union_type) + = (struct field *) TYPE_ZALLOC (type, 3 * sizeof (struct field)); + TYPE_LENGTH (union_type) = TYPE_LENGTH (type); + + /* Put the discriminant must at index 0. */ + TYPE_FIELD_TYPE (union_type, 0) = field_type; + TYPE_FIELD_ARTIFICIAL (union_type, 0) = 1; + TYPE_FIELD_NAME (union_type, 0) = "<<discriminant>>"; + SET_FIELD_BITPOS (TYPE_FIELD (union_type, 0), bit_offset); + + /* The order of fields doesn't really matter, so put the real + field at index 1 and the data-less field at index 2. */ + struct discriminant_info *disc + = alloc_discriminant_info (union_type, 0, 1); + TYPE_FIELD (union_type, 1) = TYPE_FIELD (type, 0); + TYPE_FIELD_NAME (union_type, 1) + = rust_last_path_segment (TYPE_NAME (TYPE_FIELD_TYPE (union_type, 1))); + TYPE_NAME (TYPE_FIELD_TYPE (union_type, 1)) + = rust_fully_qualify (&objfile->objfile_obstack, TYPE_NAME (type), + TYPE_FIELD_NAME (union_type, 1)); + + const char *dataless_name + = rust_fully_qualify (&objfile->objfile_obstack, TYPE_NAME (type), + name); + struct type *dataless_type = init_type (objfile, TYPE_CODE_VOID, 0, + dataless_name); + TYPE_FIELD_TYPE (union_type, 2) = dataless_type; + /* NAME points into the original discriminant name, which + already has the correct lifetime. */ + TYPE_FIELD_NAME (union_type, 2) = name; + SET_FIELD_BITPOS (TYPE_FIELD (union_type, 2), 0); + disc->discriminants[2] = 0; + + /* Smash this type to be a structure type. We have to do this + because the type has already been recorded. */ + TYPE_CODE (type) = TYPE_CODE_STRUCT; + TYPE_NFIELDS (type) = 1; + TYPE_FIELDS (type) + = (struct field *) TYPE_ZALLOC (type, sizeof (struct field)); + + /* Install the variant part. */ + TYPE_FIELD_TYPE (type, 0) = union_type; + SET_FIELD_BITPOS (TYPE_FIELD (type, 0), 0); + TYPE_FIELD_NAME (type, 0) = "<<variants>>"; + } + else if (TYPE_NFIELDS (type) == 1) + { + /* We assume that a union with a single field is a univariant + enum. */ + /* Smash this type to be a structure type. We have to do this + because the type has already been recorded. */ + TYPE_CODE (type) = TYPE_CODE_STRUCT; + + /* Make a union to hold the variants. */ + struct type *union_type = alloc_type (objfile); + TYPE_CODE (union_type) = TYPE_CODE_UNION; + TYPE_NFIELDS (union_type) = TYPE_NFIELDS (type); + TYPE_LENGTH (union_type) = TYPE_LENGTH (type); + TYPE_FIELDS (union_type) = TYPE_FIELDS (type); + + struct type *field_type = TYPE_FIELD_TYPE (union_type, 0); + const char *variant_name + = rust_last_path_segment (TYPE_NAME (field_type)); + TYPE_FIELD_NAME (union_type, 0) = variant_name; + TYPE_NAME (field_type) + = rust_fully_qualify (&objfile->objfile_obstack, + TYPE_NAME (field_type), variant_name); + + /* Install the union in the outer struct type. */ + TYPE_NFIELDS (type) = 1; + TYPE_FIELDS (type) + = (struct field *) TYPE_ZALLOC (union_type, sizeof (struct field)); + TYPE_FIELD_TYPE (type, 0) = union_type; + TYPE_FIELD_NAME (type, 0) = "<<variants>>"; + SET_FIELD_BITPOS (TYPE_FIELD (type, 0), 0); + + alloc_discriminant_info (union_type, -1, 0); + } + else + { + struct type *disr_type = nullptr; + for (int i = 0; i < TYPE_NFIELDS (type); ++i) + { + disr_type = TYPE_FIELD_TYPE (type, i); + + if (TYPE_NFIELDS (disr_type) == 0) + { + /* Could be data-less variant, so keep going. */ + } + else if (strcmp (TYPE_FIELD_NAME (disr_type, 0), + "RUST$ENUM$DISR") != 0) + { + /* Not a Rust enum. */ + return; + } + else + { + /* Found one. */ + break; + } + } + + /* If we got here without a discriminant, then it's probably + just a union. */ + if (disr_type == nullptr) + return; + + /* Smash this type to be a structure type. We have to do this + because the type has already been recorded. */ + TYPE_CODE (type) = TYPE_CODE_STRUCT; + + /* Make a union to hold the variants. */ + struct field *disr_field = &TYPE_FIELD (disr_type, 0); + struct type *union_type = alloc_type (objfile); + TYPE_CODE (union_type) = TYPE_CODE_UNION; + TYPE_NFIELDS (union_type) = 1 + TYPE_NFIELDS (type); + TYPE_LENGTH (union_type) = TYPE_LENGTH (type); + TYPE_FIELDS (union_type) + = (struct field *) TYPE_ZALLOC (union_type, + (TYPE_NFIELDS (union_type) + * sizeof (struct field))); + + memcpy (TYPE_FIELDS (union_type) + 1, TYPE_FIELDS (type), + TYPE_NFIELDS (type) * sizeof (struct field)); + + /* Install the discriminant at index 0 in the union. */ + TYPE_FIELD (union_type, 0) = *disr_field; + TYPE_FIELD_ARTIFICIAL (union_type, 0) = 1; + TYPE_FIELD_NAME (union_type, 0) = "<<discriminant>>"; + + /* Install the union in the outer struct type. */ + TYPE_FIELD_TYPE (type, 0) = union_type; + TYPE_FIELD_NAME (type, 0) = "<<variants>>"; + TYPE_NFIELDS (type) = 1; + + /* Set the size and offset of the union type. */ + SET_FIELD_BITPOS (TYPE_FIELD (type, 0), 0); + + /* We need a way to find the correct discriminant given a + variant name. For convenience we build a map here. */ + struct type *enum_type = FIELD_TYPE (*disr_field); + std::unordered_map<std::string, ULONGEST> discriminant_map; + for (int i = 0; i < TYPE_NFIELDS (enum_type); ++i) + { + if (TYPE_FIELD_LOC_KIND (enum_type, i) == FIELD_LOC_KIND_ENUMVAL) + { + const char *name + = rust_last_path_segment (TYPE_FIELD_NAME (enum_type, i)); + discriminant_map[name] = TYPE_FIELD_ENUMVAL (enum_type, i); + } + } + + int n_fields = TYPE_NFIELDS (union_type); + struct discriminant_info *disc + = alloc_discriminant_info (union_type, 0, -1); + /* Skip the discriminant here. */ + for (int i = 1; i < n_fields; ++i) + { + /* Find the final word in the name of this variant's type. + That name can be used to look up the correct + discriminant. */ + const char *variant_name + = rust_last_path_segment (TYPE_NAME (TYPE_FIELD_TYPE (union_type, + i))); + + auto iter = discriminant_map.find (variant_name); + if (iter != discriminant_map.end ()) + disc->discriminants[i] = iter->second; + + /* Remove the discriminant field. */ + struct type *sub_type = TYPE_FIELD_TYPE (union_type, i); + --TYPE_NFIELDS (sub_type); + ++TYPE_FIELDS (sub_type); + TYPE_FIELD_NAME (union_type, i) = variant_name; + TYPE_NAME (sub_type) + = rust_fully_qualify (&objfile->objfile_obstack, + TYPE_NAME (type), variant_name); + } + } +} + +/* Rewrite some Rust unions to be structures with variants parts. */ + +static void +rust_union_quirks (struct dwarf2_cu *cu) +{ + gdb_assert (cu->language == language_rust); + for (struct type *type : cu->rust_unions) + quirk_rust_enum (type, cu->per_cu->dwarf2_per_objfile->objfile); +} + /* Return the symtab for PER_CU. This works properly regardless of whether we're using the index or psymtabs. */ @@ -10502,6 +10807,9 @@ process_full_comp_unit (struct dwarf2_per_cu_data *per_cu, physnames. */ compute_delayed_physnames (cu); + if (cu->language == language_rust) + rust_union_quirks (cu); + /* Some compilers don't define a DW_AT_high_pc attribute for the compilation unit. If the DW_AT_high_pc is missing, synthesize it, by scanning the DIE's below the compilation unit. */ @@ -10604,6 +10912,9 @@ process_full_type_unit (struct dwarf2_per_cu_data *per_cu, physnames. */ compute_delayed_physnames (cu); + if (cu->language == language_rust) + rust_union_quirks (cu); + /* TUs share symbol tables. If this is the first TU to use this symtab, complete the construction of it with end_expandable_symtab. Otherwise, complete the addition of @@ -16184,6 +16495,8 @@ process_structure_scope (struct die_info *die, struct dwarf2_cu *cu) } quirk_gcc_member_function_pointer (type, objfile); + if (cu->language == language_rust && die->tag == DW_TAG_union_type) + cu->rust_unions.push_back (type); /* NOTE: carlton/2004-03-16: GCC 3.4 (or at least one of its snapshots) has been known to create a die giving a declaration |