pub(super) fn build_enum_type_di_node<'ll, 'tcx>(
    cx: &CodegenCx<'ll, 'tcx>,
    unique_type_id: UniqueTypeId<'tcx>
) -> DINodeCreationResult<'ll>
Expand description

In CPP-like mode, we generate a union with a field for each variant and an explicit tag field. The field of each variant has a struct type that encodes the discriminant of the variant and it’s data layout. The union also has a nested enumeration type that is only used for encoding variant names in an efficient way. Its enumerator values do not correspond to the enum’s discriminant values. It’s roughly equivalent to the following C/C++ code:

union enum2$<{fully-qualified-name}> {
  struct Variant0 {
    struct {name-of-variant-0} {
       <variant 0 fields>
    } value;

    static VariantNames NAME = {name-of-variant-0};
    static int_type DISCR_EXACT = {discriminant-of-variant-0};
  } variant0;

  <other variant structs>

  int_type tag;

  enum VariantNames {
     <name-of-variant-0> = 0, // The numeric values are variant index,
     <name-of-variant-1> = 1, // not discriminant values.
     <name-of-variant-2> = 2,
     ...
  }
}

As you can see, the type name is wrapped in enum2$<_>. This way we can have a single NatVis rule for handling all enums. The 2 in enum2$<_> is an encoding version tag, so that debuggers can decide to decode this differently than the previous enum$<_> encoding emitted by earlier compiler versions.

Niche-tag enums have one special variant, usually called the “untagged variant”. This variant has a field that doubles as the tag of the enum. The variant is active when the value of that field is within a pre-defined range. Therefore the variant struct has a DISCR_BEGIN and DISCR_END field instead of DISCR_EXACT in that case. Both DISCR_BEGIN and DISCR_END are inclusive bounds. Note that these ranges can wrap around, so that DISCR_END < DISCR_BEGIN.

Single-variant enums don’t actually have a tag field. In this case we emit a static tag field (that always has the value 0) so we can use the same representation (and NatVis).

For niche-layout enums it’s possible to have a 128-bit tag. NatVis, VS, and WinDbg (the main targets for CPP-like debuginfo at the moment) don’t support 128-bit integers, so all values involved get split into two 64-bit fields. Instead of the tag field, we generate two fields tag128_lo and tag128_hi, Instead of DISCR_EXACT, we generate DISCR128_EXACT_LO and DISCR128_EXACT_HI, and so on.

The following pseudocode shows how to decode an enum value in a debugger:


fn find_active_variant(enum_value) -> (VariantName, VariantValue) {
    let is_128_bit = enum_value.has_field("tag128_lo");

    if !is_128_bit {
        // Note: `tag` can be a static field for enums with only one
        //       inhabited variant.
        let tag = enum_value.field("tag").value;

        // For each variant, check if it is a match. Only one of them will match,
        // so if we find it we can return it immediately.
        for variant_field in enum_value.fields().filter(|f| f.name.starts_with("variant")) {
            if variant_field.has_field("DISCR_EXACT") {
                // This variant corresponds to a single tag value
                if variant_field.field("DISCR_EXACT").value == tag {
                    return (variant_field.field("NAME"), variant_field.value);
                }
            } else {
                // This is a range variant
                let begin = variant_field.field("DISCR_BEGIN");
                let end = variant_field.field("DISCR_END");

                if is_in_range(tag, begin, end) {
                    return (variant_field.field("NAME"), variant_field.value);
                }
            }
        }
    } else {
        // Basically the same as with smaller tags, we just have to
        // stitch the values together.
        let tag: u128 = (enum_value.field("tag128_lo").value as u128) |
                        (enum_value.field("tag128_hi").value as u128 << 64);

        for variant_field in enum_value.fields().filter(|f| f.name.starts_with("variant")) {
            if variant_field.has_field("DISCR128_EXACT_LO") {
                let discr_exact = (variant_field.field("DISCR128_EXACT_LO" as u128) |
                                  (variant_field.field("DISCR128_EXACT_HI") as u128 << 64);

                // This variant corresponds to a single tag value
                if discr_exact.value == tag {
                    return (variant_field.field("NAME"), variant_field.value);
                }
            } else {
                // This is a range variant
                let begin = (variant_field.field("DISCR128_BEGIN_LO").value as u128) |
                            (variant_field.field("DISCR128_BEGIN_HI").value as u128 << 64);
                let end = (variant_field.field("DISCR128_END_LO").value as u128) |
                          (variant_field.field("DISCR128_END_HI").value as u128 << 64);

                if is_in_range(tag, begin, end) {
                    return (variant_field.field("NAME"), variant_field.value);
                }
            }
        }
    }

    // We should have found an active variant at this point.
    unreachable!();
}

// Check if a value is within the given range
// (where the range might wrap around the value space)
fn is_in_range(value, start, end) -> bool {
    if start < end {
        value >= start && value <= end
    } else {
        value >= start || value <= end
    }
}