]> git.ipfire.org Git - thirdparty/binutils-gdb.git/commitdiff
libctf: dedup: preserve non-root flag across normal links
authorNick Alcock <nick.alcock@oracle.com>
Fri, 30 May 2025 21:12:37 +0000 (22:12 +0100)
committerNick Alcock <nick.alcock@oracle.com>
Wed, 4 Jun 2025 11:51:25 +0000 (12:51 +0100)
The previous commits dropped preservation of the non-root flag in ctf_link
and arranged to use it somewhat differently to track conflicting types in
cu-mapped CUs when doing cu-mapped links.  This was necessary to prevent
entirely spuriously hidden types from appearing on the output of such links.

Bring it (and the test for it) back.  The problem with the previous design
was that it implicitly assumed that the non-root flag it saw on the input
was always meant to be preserved (when in the final phase of cu-mapped links
it merely means that conflicting types were found in intermediate links),
and also that it could figure out what the non-root flag on the input was by
sucking in the non-root flag of the input type corresponding to an output in
the output mapping (which maps type hashes to a corresponding type on some
input).

This method of getting properties of the input type *does* work *if* that
property was one of those hashed by the ctf_dedup_hash_type process.  In
that case, every type with a given hash will have the same value for all
hashed-in properties, so it doesn't matter which one is consulted (the
output mapping points at an arbitrary one of those input types).  But the
non-root flag is explicitly *not* hashed in: as a comment in
ctf_dedup_rhash_type notes, being non-root is not a property of a type, and
two types (one non-root, one not) can perfectly well be the same type even
though one is visible and one isn't.  So just copying the non-root flag from
the output mapping's idea of the input type will copy in a value that is not
stabilized by the hash, so is more-or-less random!

So we cannot do that.  We have to do something else, which means we have to
decide what to do if two identical types with different nonroot flag values
pop up.  The most sensible thing to do is probably to say that if all
instances of a type are non-root-visible, the linked output should also be
non-root-visible: any root-visible types in that set, and the output type is
root-visible again.

We implement this with a new cd_nonroot_consistency dynhash, which maps type
hashes to the value 0 ("all instances root-visible"), 1 ("all instances
non-root-visible") or 2 ("inconsistent").  After hashing is over, we save a
bit of memory by deleting everything from this hashtab that doesn't have a
value of 1 ("non-root-visible"), then use this to decide whether to emit any
given type as non-root-visible or not.

However... that's not quite enough.  In cu-mapped links, we want to
disregard this whole thing because we just hide everything -- but in phase
2, when we take the smushed-together CUs resulting from phase 1 and
deduplicate them against each other, we want to do what the previous commits
implemented and ignore the non-root flag entirely, instead falling back to
preventing clashes by hiding anything that would be considered conflicting.
We extend the existing cu_mapped parameter to various bits of ctf_dedup so
that it is now tristate: 0 means a normal link, 1 means the smush-it-
together phase of cu-mapped links, and 2 means the final phase of cu-mapped
links.  We do the hide-conflicting stuff only in phase 2, meaning that
normal links by GNU ld can always respect the value of the nonroot flag put
on types in the input.

(One extra thing added as part of this: you can now efficiently delete the
last value returned by ctf_dynhash_next() by calling
ctf_dynhash_next_remove.)

We bring back the ctf-nonroot-linking test with one tweak: linking now works
on mingw as long as you're using the ucrt libc, so re-enable it for better
test coverage on that platform.

libctf/
PR libctf/33047
* ctf-hash.c (ctf_dynhash_next_remove): New.
* ctf-impl.h (struct ctf_dedup) [cd_nonroot_consistency]: New.
* ctf-link.c (ctf_link_deduplicating):  Differentiate between
cu-mapped and non-cu-mapped links, even in the final phase.
* ctf-dedup.c (ctf_dedup_hash_type): Callback prototype addition.
Get the non-root flag and pass it down.
(ctf_dedup_rhash_type): Callback prototype addition. Document
restrictions on use of the nonroot flag.
(ctf_dedup_populate_mappings): Populate cd_nonroot_consistency.
(ctf_dedup_hash_type_fini): New function: delete now-unnecessary
values from cd_nonroot_consistency.
(ctf_dedup_init): Initialize it.
(ctf_dedup_fini): Destroy it.
(ctf_dedup): cu_mapping is now cu_mapping_phase.  Call
ctf_dedup_hash_type_fini.
(ctf_dedup_emit_type): Use cu_mapping_phase and
cd_nonroot_consistency to propagate the non-root flag into outputs
for normal links, and to do name-based conflict checking only for
phase 2 of cu-mapped links.
(ctf_dedup_emit): cu_mapping is now cu_mapping_phase.  Adjust
assertion accordingly.
* testsuite/libctf-writable/ctf-nonroot-linking.c: Bring back.
* testsuite/libctf-writable/ctf-nonroot-linking.lk: Likewise.

libctf/ctf-dedup.c
libctf/ctf-hash.c
libctf/ctf-impl.h
libctf/ctf-link.c
libctf/testsuite/libctf-writable/ctf-nonroot-linking.c [new file with mode: 0644]
libctf/testsuite/libctf-writable/ctf-nonroot-linking.lk [new file with mode: 0644]

index 5a3c7bd6af260dd29b378ca18d409e047cc11bc4..0b1bfc27c40bf30a0ee15c3f772dfffebafb5008 100644 (file)
@@ -96,9 +96,9 @@
    value: it is also stashed in the *output mapping*, a mapping from hash value
    to the set of GIDs corresponding to that type in all inputs.  We also keep
    track of the GID of the first appearance of the type in any input (in
-   cd_output_first_gid), and the GID of structs, unions, and forwards that only
-   appear in one TU (in cd_struct_origin).  See below for where these things are
-   used.
+   cd_output_first_gid), the GID of structs, unions, and forwards that only
+   appear in one TU (in cd_struct_origin), and an indication of whether this
+   type is root-visible or not.  See below for where these things are used.
 
    Everything in this phase is time-critical, because it is operating over
    non-deduplicated types and so may have hundreds or thousands of times the
@@ -492,6 +492,7 @@ ctf_dedup_hash_type (ctf_dict_t *fp, ctf_dict_t *input,
                                          ctf_dict_t **inputs,
                                          int input_num,
                                          ctf_id_t type,
+                                         int isroot,
                                          void *id,
                                          const char *decorated_name,
                                          const char *hash));
@@ -560,6 +561,7 @@ ctf_dedup_rhash_type (ctf_dict_t *fp, ctf_dict_t *input, ctf_dict_t **inputs,
                                           ctf_dict_t **inputs,
                                           int input_num,
                                           ctf_id_t type,
+                                          int isroot,
                                           void *id,
                                           const char *decorated_name,
                                           const char *hash))
@@ -666,7 +668,11 @@ ctf_dedup_rhash_type (ctf_dict_t *fp, ctf_dict_t *input, ctf_dict_t **inputs,
      possible.  Equally, we do not want to hash in the isroot flag: both the
      compiler and the deduplicator set the nonroot flag to indicate clashes with
      *other types in the same TU* with the same name: so two types can easily
-     have distinct nonroot flags, yet be exactly the same type.*/
+     have distinct nonroot flags, yet be exactly the same type.  This means we
+     can never use the non-root-visible flag from the input for anything,
+     because if there are several distinct values the one chosen is basically
+     random.  We unify non-root-visible flags separately: see the uses of
+     cd_nonroot_consistency.  */
 
   ctf_sha1_init (&hash);
   if (name)
@@ -1015,6 +1021,7 @@ ctf_dedup_hash_type (ctf_dict_t *fp, ctf_dict_t *input,
                                          ctf_dict_t **inputs,
                                          int input_num,
                                          ctf_id_t type,
+                                         int isroot,
                                          void *id,
                                          const char *decorated_name,
                                          const char *hash))
@@ -1027,6 +1034,7 @@ ctf_dedup_hash_type (ctf_dict_t *fp, ctf_dict_t *input,
   const char *whaterr;
   const char *decorated = NULL;
   uint32_t kind, fwdkind;
+  int isroot;
 
   depth++;
 
@@ -1056,6 +1064,7 @@ ctf_dedup_hash_type (ctf_dict_t *fp, ctf_dict_t *input,
 
   kind = LCTF_INFO_KIND (input, tp->ctt_info);
   name = ctf_strraw (input, tp->ctt_name);
+  isroot = LCTF_INFO_ISROOT (input, tp->ctt_info);
 
   if (tp->ctt_name == 0 || !name || name[0] == '\0')
     name = NULL;
@@ -1086,7 +1095,7 @@ ctf_dedup_hash_type (ctf_dict_t *fp, ctf_dict_t *input,
          ctf_dprintf ("%lu: Known hash for ID %i/%lx: %s\n", depth, input_num,
                       type,  hval);
 #endif
-         populate_fun (fp, input, inputs, input_num, type, type_id,
+         populate_fun (fp, input, inputs, input_num, type, isroot, type_id,
                        decorated, hval);
 
          return hval;
@@ -1121,7 +1130,7 @@ ctf_dedup_hash_type (ctf_dict_t *fp, ctf_dict_t *input,
          goto oom;
        }
 
-      if (populate_fun (fp, input, inputs, input_num, type, type_id,
+      if (populate_fun (fp, input, inputs, input_num, type, isroot, type_id,
                        decorated, hval) < 0)
        {
          whaterr = N_("error calling population function");
@@ -1150,19 +1159,20 @@ ctf_dedup_count_name (ctf_dict_t *fp, const char *name, void *id);
 
 /* Populate a number of useful mappings not directly used by the hashing
    machinery: the output mapping, the cd_name_counts mapping from name -> hash
-   -> count of hashval deduplication state for a given hashed type, and the
-   cd_output_first_tu mapping.  */
+   -> count of hashval deduplication state for a given hashed type; the
+   cd_output_first_gid mapping; and the cd_nonroot_consistency mapping.  */
 
 static int
 ctf_dedup_populate_mappings (ctf_dict_t *fp, ctf_dict_t *input _libctf_unused_,
                             ctf_dict_t **inputs _libctf_unused_,
                             int input_num _libctf_unused_,
-                            ctf_id_t type _libctf_unused_, void *id,
-                            const char *decorated_name,
+                            ctf_id_t type _libctf_unused_, int isroot,
+                            void *id, const char *decorated_name,
                             const char *hval)
 {
   ctf_dedup_t *d = &fp->ctf_dedup;
   ctf_dynset_t *type_ids;
+  void *root_visible;
 
 #ifdef ENABLE_LIBCTF_HASH_DEBUGGING
   ctf_dprintf ("Hash %s, %s, into output mapping for %i/%lx @ %s\n",
@@ -1249,6 +1259,32 @@ ctf_dedup_populate_mappings (ctf_dict_t *fp, ctf_dict_t *input _libctf_unused_,
     }
 #endif
 
+  /* Track the consistency of the non-root flag for this type.
+     0: all root-visible; 1: all non-root-visible; 2: inconsistent.  */
+
+  if (!ctf_dynhash_lookup_kv (d->cd_nonroot_consistency, hval, NULL,
+                             &root_visible))
+    {
+      if (isroot)
+       root_visible = (void *) 0;
+      else
+       root_visible = (void *) 1;
+
+      if (ctf_dynhash_cinsert (d->cd_nonroot_consistency, hval, root_visible) < 0)
+       return ctf_set_errno (fp, errno);
+    }
+  else
+    {
+      if (((uintptr_t) root_visible == 0 && !isroot)
+         || ((uintptr_t) root_visible == 1 && isroot))
+       {
+         root_visible = (void *) 2;
+
+         if (ctf_dynhash_cinsert (d->cd_nonroot_consistency, hval, root_visible) < 0)
+           return ctf_set_errno (fp, errno);
+       }
+    }
+
   /* This function will be repeatedly called for the same types many times:
      don't waste time reinserting the same keys in that case.  */
   if (!ctf_dynset_exists (type_ids, id, NULL)
@@ -1282,6 +1318,33 @@ ctf_dedup_populate_mappings (ctf_dict_t *fp, ctf_dict_t *input _libctf_unused_,
   return 0;
 }
 
+/* Clean up things no longer needed after hashing is over.  */
+static int
+ctf_dedup_hash_type_fini (ctf_dict_t *fp)
+{
+  ctf_next_t *i = NULL;
+  int err;
+  void *hval, *root_visible;
+
+  /* Clean up cd_nonroot_consistency.  We only care now about types we are sure
+     are non-root-visible everywhere: root-visible types and types that are
+     sometimes root-visible and sometimes not are treated as root-visible.  */
+
+  while ((err = ctf_dynhash_next (fp->ctf_dedup.cd_nonroot_consistency, &i,
+                                 &hval, &root_visible)) == 0)
+    {
+      if ((uintptr_t) root_visible != 1)
+       ctf_dynhash_next_remove (&i);
+    }
+    if (err != ECTF_NEXT_END)
+    {
+      ctf_err_warn (fp, 0, err, _("iteration failure cleaning up type hashes"));
+      return ctf_set_errno (fp, err);
+    }
+
+    return 0;
+}
+
 static int
 ctf_dedup_count_name (ctf_dict_t *fp, const char *name, void *id)
 {
@@ -1678,6 +1741,12 @@ ctf_dedup_init (ctf_dict_t *fp)
                             NULL, NULL)) == NULL)
     goto oom;
 
+  if ((d->cd_nonroot_consistency
+       = ctf_dynhash_create (ctf_hash_string,
+                            ctf_hash_eq_string,
+                            NULL, NULL)) == NULL)
+    goto oom;
+
 #ifdef ENABLE_LIBCTF_HASH_DEBUGGING
   if ((d->cd_output_mapping_guard
        = ctf_dynhash_create (ctf_hash_integer,
@@ -1730,6 +1799,7 @@ ctf_dedup_fini (ctf_dict_t *fp, ctf_dict_t **outputs, uint32_t noutputs)
   ctf_dynhash_destroy (d->cd_citers);
   ctf_dynhash_destroy (d->cd_output_mapping);
   ctf_dynhash_destroy (d->cd_output_first_gid);
+  ctf_dynhash_destroy (d->cd_nonroot_consistency);
 #ifdef ENABLE_LIBCTF_HASH_DEBUGGING
   ctf_dynhash_destroy (d->cd_output_mapping_guard);
 #endif
@@ -1912,15 +1982,15 @@ ctf_dedup_conflictify_unshared (ctf_dict_t *output, ctf_dict_t **inputs)
    OUTPUT is the top-level output: INPUTS is the array of input dicts; NINPUTS is the
    size of that array.
 
-   If CU_MAPPED is set, this is a first pass for a link with a non-empty CU
-   mapping: only one output will result.
+   If CU_MAPPING_PHASE is nonzero, this is a link with a non-empty CU mapping:
+   in phase 1, only one output will result.
 
    Only deduplicates: does not emit the types into the output.  Call
    ctf_dedup_emit afterwards to do that.  */
 
 int
 ctf_dedup (ctf_dict_t *output, ctf_dict_t **inputs, uint32_t ninputs,
-          int cu_mapped)
+          int cu_mapping_phase)
 {
   ctf_dedup_t *d = &output->ctf_dedup;
   size_t i;
@@ -1942,12 +2012,13 @@ ctf_dedup (ctf_dict_t *output, ctf_dict_t **inputs, uint32_t ninputs,
        }
     }
 
-  /* Some flags do not apply when CU-mapping: this is not a duplicated link,
-     because there is only one output and we really don't want to end up marking
-     all nonconflicting but appears-only-once types as conflicting (which in the
-     CU-mapped link means we'd mark them all as non-root-visible!).  */
+  /* Some flags do not apply in the first phase of CU-mapped links: this is not
+     a share-duplicated link, because there is only one output and we really
+     don't want to end up marking all nonconflicting but appears-only-once types
+     as conflicting.  */
+
   d->cd_link_flags = output->ctf_link_flags;
-  if (cu_mapped)
+  if (cu_mapping_phase == 1)
     d->cd_link_flags &= ~(CTF_LINK_SHARE_DUPLICATED);
 
   /* Compute hash values for all types, recursively, treating child structures
@@ -1979,6 +2050,10 @@ ctf_dedup (ctf_dict_t *output, ctf_dict_t **inputs, uint32_t ninputs,
        }
     }
 
+  /* Drop state no longer needed after hashing is over.  */
+
+  ctf_dedup_hash_type_fini (output);
+
   /* Go through the cd_name_counts name->hash->count mapping for all CTF
      namespaces: any name with many hashes associated with it at this stage is
      necessarily ambiguous.  Mark all the hashes except the most common as
@@ -2638,7 +2713,7 @@ ctf_dedup_emit_type (const char *hval, ctf_dict_t *output, ctf_dict_t **inputs,
   const ctf_type_t *tp;
   int input_num = CTF_DEDUP_GID_TO_INPUT (id);
   int output_num = (uint32_t) -1;              /* 'shared' */
-  int cu_mapped = *(int *)arg;
+  int cu_mapping_phase = *(int *)arg;
   int isroot = 1;
   int is_conflicting;
 
@@ -2663,7 +2738,7 @@ ctf_dedup_emit_type (const char *hval, ctf_dict_t *output, ctf_dict_t **inputs,
      ctf_link_outputs dict of the output that is its parent.  */
   is_conflicting = ctf_dynset_exists (d->cd_conflicting_types, hval, NULL);
 
-  if (is_conflicting && !cu_mapped)
+  if (is_conflicting && cu_mapping_phase != 1)
     {
       ctf_dprintf ("%i: Type %s in %i/%lx is conflicted: "
                   "inserting into per-CU target.\n",
@@ -2709,33 +2784,58 @@ ctf_dedup_emit_type (const char *hval, ctf_dict_t *output, ctf_dict_t **inputs,
 
   name = ctf_strraw (real_input, tp->ctt_name);
 
-  /* Hide conflicting types, if this type is conflicting and a type with this
-     name already exists in the target and is not a forward.  We don't want to
-     hide them if they are the only type of that name in a per-CU child, as is
-     usually the case, but in cu-mapped links and in the final phase after
-     cu-mapping distinct conflicting types from different CUs can end up in the
-     same cu-mapped target.  Hide all but one in this case: it doesn't matter
-     which, all the conflicting types necessarily lost the popularity contest
-     anyway.
-
-     cu_mapped links get absolutely *everything* marked non-root, named or not.
-     cu-mapped links, when we are merging multiple child CUs into one, are the
-     only point at which we can ever put conflicting and nonconflicting
+  /* cu_mapped links at phase 1 get absolutely *everything* marked non-root,
+     named or not.  Such links, when we are merging multiple child CUs into one,
+     are the only point at which we can ever put conflicting and nonconflicting
      instances of the same type into the same dict, and which one comes first is
      arbitrary.  Rather than having to figure out when we insert a type whether
      another one is coming that might conflict with it without being so marked,
      just mark everything as non-root: we'll disregard it in the next phase of
      cu-mapped linking anyway.
 
-     Note that enums also get their enumerands checked, below.  */
+     In phase 2 (the final dedup phase) of cu-mapped links, we have to deal with
+     the fallout of this, in that single inputs have 100% non-root types (so the
+     non-root bit isn't really meaningful) but some subset of them may be
+     genuinely clashing, conflicting, but already in child dicts (a thing that
+     is impossible in non-CU-mapped links, when child dicts correspond to single
+     CUs).
+
+     So in phase 2, we hide conflicting types, if this type is conflicting and a
+     type with this name already exists in the target and is not a forward.
 
-  if (cu_mapped)
-    isroot = 0;
-  else if (is_conflicting && name
-          && ((maybe_dup = ctf_lookup_by_rawname (target, kind, name)) != 0))
+     Note that enums also get their enumerands checked, below.
+
+     Otherwise, in "phase 0" (i.e. normal links), we can respect the non-root
+     flag the user passed in and simply propagate it directly to the output.
+     If the user provided a mix of root-visible and non-root-visible flags,
+     we treat it as non-root-visible: see ctf_dedup_hash_type_fini.  */
+
+  switch (cu_mapping_phase)
     {
-      if (ctf_type_kind (target, maybe_dup) != CTF_K_FORWARD)
+    case 0: /* Normal link.  Root-visibility explicitly tracked.  */
+      if (ctf_dynhash_lookup (d->cd_nonroot_consistency, hval))
        isroot = 0;
+      break;
+    case 1: /* cu-mapped link.  Never root-visible.  */
+      isroot = 0;
+      break;
+    case 2: /* Final phase of cu-mapped link.  Non-root if already present.  */
+      if (is_conflicting && name
+         && ((maybe_dup = ctf_lookup_by_rawname (target, kind, name)) != 0))
+       {
+         if (ctf_type_kind (target, maybe_dup) != CTF_K_FORWARD)
+           {
+             ctf_dprintf ("%s, kind %i, hval %s: conflicting type marked as "
+                          "non-root because of pre-existing type %s/%lx, "
+                          "kind %i.\n", name, kind, hval, ctf_cuname (target),
+                          maybe_dup, ctf_type_kind (target, maybe_dup));
+             isroot = 0;
+           }
+       }
+      break;
+    default:
+      if (!ctf_assert (output, cu_mapping_phase >= 0 && cu_mapping_phase <= 2))
+       return -1;                              /* errno is set for us.  */
     }
 
   ctf_dprintf ("%i: Emitting type with hash %s (%s), into target %i/%p\n",
@@ -2788,17 +2888,22 @@ ctf_dedup_emit_type (const char *hval, ctf_dict_t *output, ctf_dict_t **inputs,
        int val;
        errtype = _("enum");
 
-       /* Check enumerands for duplication: this is an extension of the isroot
-          check above.  */
+       /* Check enumerands for duplication and nonrootify if clashing: this is
+          an extension of the isroot check above.  */
 
-       if (isroot)
+       if (isroot && cu_mapping_phase == 2)
          {
            const char *enumerand;
            while ((enumerand = ctf_enum_next (input, type, &i, &val)) != NULL)
              {
                if (is_conflicting && name
                    && ctf_dynhash_lookup (target->ctf_names, enumerand) != NULL)
-                 isroot = 0;
+                 {
+                   ctf_dprintf ("%s, kind %i, hval %s: conflicting type marked "
+                                "as non-root because of pre-existing enumerand "
+                                "%s.\n", name, kind, hval, enumerand);
+                   isroot = 0;
+                 }
              }
            if (ctf_errno (input) != ECTF_NEXT_END)
              goto err_input;
@@ -3119,12 +3224,12 @@ ctf_dedup_emit_struct_members (ctf_dict_t *output, ctf_dict_t **inputs,
    Return an array of fps with content emitted into them (starting with OUTPUT,
    which is the parent of all others, then all the newly-generated outputs).
 
-   If CU_MAPPED is set, this is a first pass for a link with a non-empty CU
-   mapping: only one output will result.  */
+   If CU_MAPPING_PHASE is set to 1, this is a first pass for a link with a
+   non-empty CU mapping: only one output will result.  */
 
 ctf_dict_t **
 ctf_dedup_emit (ctf_dict_t *output, ctf_dict_t **inputs, uint32_t ninputs,
-               uint32_t *parents, uint32_t *noutputs, int cu_mapped)
+               uint32_t *parents, uint32_t *noutputs, int cu_mapping_phase)
 {
   size_t num_outputs = 1;              /* Always at least one output: us.  */
   ctf_dict_t **outputs;
@@ -3133,7 +3238,7 @@ ctf_dedup_emit (ctf_dict_t *output, ctf_dict_t **inputs, uint32_t ninputs,
 
   ctf_dprintf ("Triggering emission.\n");
   if (ctf_dedup_walk_output_mapping (output, inputs, ninputs, parents,
-                                    ctf_dedup_emit_type, &cu_mapped) < 0)
+                                    ctf_dedup_emit_type, &cu_mapping_phase) < 0)
     return NULL;                               /* errno is set for us.  */
 
   ctf_dprintf ("Populating struct members.\n");
@@ -3146,7 +3251,8 @@ ctf_dedup_emit (ctf_dict_t *output, ctf_dict_t **inputs, uint32_t ninputs,
        num_outputs++;
     }
 
-  if (!ctf_assert (output, !cu_mapped || (cu_mapped && num_outputs == 1)))
+  if (!ctf_assert (output, (cu_mapping_phase != 1
+                           || (cu_mapping_phase == 1 && num_outputs == 1))))
     return NULL;
 
   if ((outputs = calloc (num_outputs, sizeof (ctf_dict_t *))) == NULL)
index a11d11e189c92d4543162831af669e6bdc679095..abd0140aeaf6f6996cb8dd4bc002389d68be3cf9 100644 (file)
@@ -406,8 +406,9 @@ ctf_dynhash_iter_remove (ctf_dynhash_t *hp, ctf_hash_iter_remove_f fun,
 
 /* Traverse a dynhash in arbitrary order, in _next iterator form.
 
-   Mutating the dynhash while iterating is not supported (just as it isn't for
-   htab_traverse).
+   Adding entries to the dynhash while iterating is not supported (just as it
+   isn't for htab_traverse).  Deleting is fine (see ctf_dynhash_next_remove,
+   below).
 
    Note: unusually, this returns zero on success and a *positive* value on
    error, because it does not take an fp, taking an error pointer would be
@@ -479,6 +480,30 @@ ctf_dynhash_next (ctf_dynhash_t *h, ctf_next_t **it, void **key, void **value)
   return ECTF_NEXT_END;
 }
 
+/* Remove the entry most recently returned by ctf_dynhash_next.
+
+   Returns ECTF_NEXT_END if this is impossible (already removed, iterator not
+   initialized, iterator off the end).  */
+
+int
+ctf_dynhash_next_remove (ctf_next_t * const *it)
+{
+  ctf_next_t *i = *it;
+
+  if ((void (*) (void)) ctf_dynhash_next != i->ctn_iter_fun)
+    return ECTF_NEXT_WRONGFUN;
+
+  if (!i)
+    return ECTF_NEXT_END;
+
+  if (i->ctn_n == 0)
+    return ECTF_NEXT_END;
+
+  htab_clear_slot (i->cu.ctn_h->htab, i->u.ctn_hash_slot - 1);
+
+  return 0;
+}
+
 int
 ctf_dynhash_sort_by_name (const ctf_next_hkv_t *one, const ctf_next_hkv_t *two,
                          void *unused _libctf_unused_)
index 2d191b0a5c9b0fbc90a19e625116f08571622b9c..2691a689d3103237983f3a034d63366ebc7d3110 100644 (file)
@@ -331,6 +331,11 @@ typedef struct ctf_dedup
      hash.  */
   ctf_dynhash_t *cd_output_mapping_guard;
 
+  /* Maps from type hash values to an indication of their nonroot flag.  0 means
+     all root-visible; 1 means non-root-visible; 2 means a mixture.  All values
+     other than 1 are deleted after hashing.  */
+  ctf_dynhash_t *cd_nonroot_consistency;
+
   /* Maps the global type IDs of structures in input TUs whose members still
      need emission to the global type ID of the already-emitted target type
      (which has no members yet) in the appropriate target.  Uniquely, the latter
@@ -671,6 +676,7 @@ extern int ctf_dynhash_next (ctf_dynhash_t *, ctf_next_t **,
 extern int ctf_dynhash_next_sorted (ctf_dynhash_t *, ctf_next_t **,
                                    void **key, void **value, ctf_hash_sort_f,
                                    void *);
+extern int ctf_dynhash_next_remove (ctf_next_t * const *);
 
 extern ctf_dynset_t *ctf_dynset_create (htab_hash, htab_eq, ctf_hash_free_fun);
 extern int ctf_dynset_insert (ctf_dynset_t *, void *);
@@ -717,10 +723,10 @@ extern int ctf_track_enumerator (ctf_dict_t *, ctf_id_t, const char *);
 
 extern int ctf_dedup_atoms_init (ctf_dict_t *);
 extern int ctf_dedup (ctf_dict_t *, ctf_dict_t **, uint32_t ninputs,
-                     int cu_mapped);
+                     int cu_mapped_phase);
 extern ctf_dict_t **ctf_dedup_emit (ctf_dict_t *, ctf_dict_t **,
                                    uint32_t ninputs, uint32_t *parents,
-                                   uint32_t *noutputs, int cu_mapped);
+                                   uint32_t *noutputs, int cu_mapped_phase);
 extern void ctf_dedup_fini (ctf_dict_t *, ctf_dict_t **, uint32_t);
 extern ctf_id_t ctf_dedup_type_mapping (ctf_dict_t *fp, ctf_dict_t *src_fp,
                                        ctf_id_t src_type);
index 2019c11ae9bc49fca6286f823030b71a60981bb8..2d4401d2210c4e6b6b932e6d77d41afe9f3baf76 100644 (file)
@@ -1385,6 +1385,7 @@ ctf_link_deduplicating (ctf_dict_t *fp)
   ssize_t ninputs;
   uint32_t noutputs;
   uint32_t *parents;
+  int cu_phase = 0;
 
   if (ctf_dedup_atoms_init (fp) < 0)
     {
@@ -1392,9 +1393,20 @@ ctf_link_deduplicating (ctf_dict_t *fp)
       return;                                  /* Errno is set for us.  */
     }
 
-  if (fp->ctf_link_out_cu_mapping
-      && (ctf_link_deduplicating_per_cu (fp) < 0))
-    return;                                    /* Errno is set for us.  */
+  /* Trigger a CU-mapped link if need be: one pass of dedups squashing inputs into
+     single child dicts corresponding to each CU mapping, and one pass that
+     treats those as if they are ordinary inputs and links them together.
+
+     This latter pass does need to act very slightly differently from normal, so we
+     keep track of the "CU phase", with 0 being a normal link, 1 being the
+     squash-together phase, and 2 being the final act-as-if-it-were-normal pass.  */
+
+  if (fp->ctf_link_out_cu_mapping)
+    {
+      if (ctf_link_deduplicating_per_cu (fp) < 0)
+       return;                                 /* Errno is set for us.  */
+      cu_phase = 2;
+    }
 
   if ((ninputs = ctf_link_deduplicating_count_inputs (fp, NULL, NULL)) < 0)
     return;                                    /* Errno is set for us.  */
@@ -1406,7 +1418,7 @@ ctf_link_deduplicating (ctf_dict_t *fp)
   if (ninputs == 1 && ctf_cuname (inputs[0]) != NULL)
     ctf_cuname_set (fp, ctf_cuname (inputs[0]));
 
-  if (ctf_dedup (fp, inputs, ninputs, 0) < 0)
+  if (ctf_dedup (fp, inputs, ninputs, cu_phase) < 0)
     {
       ctf_err_warn (fp, 0, 0, _("deduplication failed for %s"),
                    ctf_link_input_name (fp));
@@ -1414,7 +1426,7 @@ ctf_link_deduplicating (ctf_dict_t *fp)
     }
 
   if ((outputs = ctf_dedup_emit (fp, inputs, ninputs, parents, &noutputs,
-                                0)) == NULL)
+                                cu_phase)) == NULL)
     {
       ctf_err_warn (fp, 0, 0, _("deduplicating link type emission failed "
                                "for %s"), ctf_link_input_name (fp));
diff --git a/libctf/testsuite/libctf-writable/ctf-nonroot-linking.c b/libctf/testsuite/libctf-writable/ctf-nonroot-linking.c
new file mode 100644 (file)
index 0000000..67b85eb
--- /dev/null
@@ -0,0 +1,116 @@
+/* Make sure linking a non-root-visible type emits a non-root-visible
+   type, rather than silently promoting it to root-visible.  Do it by dumping,
+   thus also testing the {non-root sigils} you get when dumping
+   non-root-visible types.  */
+
+#include <ctf-api.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+int
+main (int argc, char *argv[])
+{
+  ctf_dict_t *in1;
+  ctf_dict_t *fp;
+  ctf_dict_t *dump_fp;
+  ctf_archive_t *arc1;
+  ctf_archive_t *final_arc;
+  ctf_sect_t s;
+  ctf_encoding_t encoding = { CTF_INT_SIGNED, 0, sizeof (char) };
+  unsigned char *buf1, *buf2;
+  size_t buf1_sz, buf2_sz;
+  ctf_dump_state_t *dump_state = NULL;
+  ctf_next_t *i = NULL;
+  int err;
+
+  if ((fp = ctf_create (&err)) == NULL)
+    goto create_err;
+
+  if ((in1 = ctf_create (&err)) == NULL)
+    goto create_err;
+
+  /* A non-root addition. */
+
+  if ((ctf_add_integer (in1, CTF_ADD_NONROOT, "foo", &encoding)) == CTF_ERR)
+    {
+      fprintf (stderr, "Cannot add: %s\n", ctf_errmsg (ctf_errno (in1)));
+      return 1;
+    }
+
+  /* Write it out and read it back in, to turn it into an archive.
+     This would be unnecessary if ctf_link_add() were public :( */
+  if ((buf1 = ctf_write_mem (in1, &buf1_sz, -1)) == NULL)
+    {
+      fprintf (stderr, "Cannot serialize: %s\n", ctf_errmsg (ctf_errno (in1)));
+      return 1;
+    }
+
+  s.cts_name = "foo";
+  s.cts_data = (void *) buf1;
+  s.cts_size = buf1_sz;
+  s.cts_entsize = 64; /* Unimportant.  */
+
+  if ((arc1 = ctf_arc_bufopen (&s, NULL, NULL, &err)) == NULL)
+    goto open_err;
+
+  ctf_dict_close (in1);
+
+  /* Link!  Even a one-file link does deduplication.  */
+
+  if (ctf_link_add_ctf (fp, arc1, "a") < 0)
+    goto link_err;
+
+  if (ctf_link (fp, 0) < 0)
+    goto link_err;
+
+  /* Write it out.  We need a new buf here, because the archive is still
+     using the other buf.  */
+
+  if ((buf2 = ctf_link_write (fp, &buf2_sz, 4096)) == NULL)
+    goto link_err;
+
+  /* Read it back in.  */
+
+  s.cts_data = (void *) buf2;
+  s.cts_size = buf2_sz;
+
+  if ((final_arc = ctf_arc_bufopen (&s, NULL, NULL, &err)) == NULL)
+    goto open_err;
+
+  /* Dump the types, and search for the {sigils of non-rootedness}.  */
+  while ((dump_fp = ctf_archive_next (final_arc, &i, NULL, 0, &err)) != NULL)
+    {
+      char *dumpstr;
+
+      while ((dumpstr = ctf_dump (dump_fp, &dump_state, CTF_SECT_TYPE,
+                                 NULL, NULL)) != NULL)
+       {
+         if (strchr (dumpstr, '{') != NULL && strchr (dumpstr, '}') != NULL)
+           printf ("Non-root type found.\n");
+         free (dumpstr);
+       }
+      ctf_dict_close (dump_fp);
+    }
+  if (err != ECTF_NEXT_END)
+    {
+      fprintf (stderr, "Archive iteration error: %s\n", ctf_errmsg (err));
+      return 1;
+    }
+
+  ctf_arc_close (final_arc);
+  free (buf1);
+  free (buf2);
+  ctf_dict_close (fp);
+  return 0;
+
+ create_err:
+  fprintf (stderr, "Cannot create: %s\n", ctf_errmsg (err));
+  return 1;
+ open_err:
+  fprintf (stderr, "Cannot open: %s\n", ctf_errmsg (err));
+  return 1;
+ link_err:
+  fprintf (stderr, "Cannot link: %s\n", ctf_errmsg (ctf_errno (fp)));
+  return 1;
+}
diff --git a/libctf/testsuite/libctf-writable/ctf-nonroot-linking.lk b/libctf/testsuite/libctf-writable/ctf-nonroot-linking.lk
new file mode 100644 (file)
index 0000000..e3edda0
--- /dev/null
@@ -0,0 +1 @@
+Non-root type found.