+2020-07-22  Nick Alcock  <nick.alcock@oracle.com>
+
+       * ctf-impl.h (ctf_file_t): Improve comments.
+       <ctf_link_cu_mapping>: Split into...
+       <ctf_link_in_cu_mapping>: ... this...
+       <ctf_link_out_cu_mapping>: ... and this.
+       * ctf-create.c (ctf_serialize): Adjust.
+       * ctf-open.c (ctf_file_close): Likewise.
+       * ctf-link.c (ctf_create_per_cu): Look things up in the
+       in_cu_mapping instead of the cu_mapping.
+       (ctf_link_add_cu_mapping): The deduplicating link will define
+       what happens if many FROMs share a TO.
+       (ctf_link_add_cu_mapping): Create in_cu_mapping and
+       out_cu_mapping. Do not create ctf_link_outputs here any more, or
+       create per-CU dicts here: they are already created when needed.
+       (ctf_link_one_variable): Log a debug message if we skip a
+       variable due to its type being concealed in a CU-mapped link.
+       (This is probably too common a case to make into a warning.)
+       (ctf_link): Create empty per-CU dicts if requested.
+
 2020-07-22  Nick Alcock  <nick.alcock@oracle.com>
 
        * ctf-link.c (ctf_link_write): Close the fd.
 
   ctf_list_t ctf_errs_warnings;          /* CTF errors and warnings.  */
   ctf_dynhash_t *ctf_link_inputs; /* Inputs to this link.  */
   ctf_dynhash_t *ctf_link_outputs; /* Additional outputs from this link.  */
-  ctf_dynhash_t *ctf_link_type_mapping; /* Map input types to output types.  */
-  ctf_dynhash_t *ctf_link_cu_mapping;  /* Map CU names to CTF dict names.  */
-  /* Allow the caller to Change the name of link archive members.  */
+
+  /* Map input types to output types: populated in each output dict.
+     Key is a ctf_link_type_key_t: value is a type ID.  Used by
+     nondeduplicating links and ad-hoc ctf_add_type calls only.  */
+  ctf_dynhash_t *ctf_link_type_mapping;
+
+  /* Map input CU names to output CTF dict names: populated in the top-level
+     output dict.
+
+     Key and value are dynamically-allocated strings.  */
+  ctf_dynhash_t *ctf_link_in_cu_mapping;
+
+  /* Map output CTF dict names to input CU names: populated in the top-level
+     output dict.  A hash of string to hash (set) of strings.  Key and
+     individual value members are shared with ctf_link_in_cu_mapping.  */
+  ctf_dynhash_t *ctf_link_out_cu_mapping;
+
   /* CTF linker flags.  */
   int ctf_link_flags;
 
+  /* Allow the caller to change the name of link archive members.  */
   ctf_link_memb_name_changer_f *ctf_link_memb_name_changer;
   void *ctf_link_memb_name_changer_arg; /* Argument for it.  */
   ctf_dynhash_t *ctf_add_processing; /* Types ctf_add_type is working on now.  */
 
      dictionary name.  We prefer the filename because this is easier for likely
      callers to determine.  */
 
-  if (fp->ctf_link_cu_mapping)
+  if (fp->ctf_link_in_cu_mapping)
     {
-      if (((ctf_name = ctf_dynhash_lookup (fp->ctf_link_cu_mapping, filename)) == NULL) &&
-         ((ctf_name = ctf_dynhash_lookup (fp->ctf_link_cu_mapping, cuname)) == NULL))
+      if (((ctf_name = ctf_dynhash_lookup (fp->ctf_link_in_cu_mapping,
+                                          filename)) == NULL) &&
+         ((ctf_name = ctf_dynhash_lookup (fp->ctf_link_in_cu_mapping,
+                                          cuname)) == NULL))
        ctf_name = filename;
     }
 
 
 /* Add a mapping directing that the CU named FROM should have its
    conflicting/non-duplicate types (depending on link mode) go into a container
-   named TO.  Many FROMs can share a TO: in this case, the effect on conflicting
-   types is not yet defined (but in time an auto-renaming algorithm will be
-   added: ugly, but there is really no right thing one can do in this
-   situation).
+   named TO.  Many FROMs can share a TO.
 
    We forcibly add a container named TO in every case, even though it may well
    wind up empty, because clients that use this facility usually expect to find
 ctf_link_add_cu_mapping (ctf_file_t *fp, const char *from, const char *to)
 {
   int err;
-  char *f, *t;
+  char *f = NULL, *t = NULL;
+  ctf_dynhash_t *one_out;
+
+  if (fp->ctf_link_in_cu_mapping == NULL)
+    fp->ctf_link_in_cu_mapping = ctf_dynhash_create (ctf_hash_string,
+                                                    ctf_hash_eq_string, free,
+                                                    free);
+  if (fp->ctf_link_in_cu_mapping == NULL)
+    goto oom;
 
-  if (fp->ctf_link_cu_mapping == NULL)
-    fp->ctf_link_cu_mapping = ctf_dynhash_create (ctf_hash_string,
-                                                 ctf_hash_eq_string, free,
-                                                 free);
-  if (fp->ctf_link_cu_mapping == NULL)
-    return ctf_set_errno (fp, ENOMEM);
+  if (fp->ctf_link_out_cu_mapping == NULL)
+    fp->ctf_link_out_cu_mapping = ctf_dynhash_create (ctf_hash_string,
+                                                     ctf_hash_eq_string, free,
+                                                     (ctf_hash_free_fun)
+                                                     ctf_dynhash_destroy);
+  if (fp->ctf_link_out_cu_mapping == NULL)
+    goto oom;
 
-  if (fp->ctf_link_outputs == NULL)
-    fp->ctf_link_outputs = ctf_dynhash_create (ctf_hash_string,
-                                              ctf_hash_eq_string, free,
-                                              (ctf_hash_free_fun)
-                                              ctf_file_close);
+  f = strdup (from);
+  t = strdup (to);
+  if (!f || !t)
+    goto oom;
 
-  if (fp->ctf_link_outputs == NULL)
-    return ctf_set_errno (fp, ENOMEM);
+  /* Track both in a list from FROM to TO and in a list from TO to a list of
+     FROM.  The former is used to create TUs with the mapped-to name at need:
+     the latter is used in deduplicating links to pull in all input CUs
+     corresponding to a single output CU.  */
+
+  if ((err = ctf_dynhash_insert (fp->ctf_link_in_cu_mapping, f, t)) < 0)
+    {
+      ctf_set_errno (fp, err);
+      goto oom_noerrno;
+    }
 
+  /* f and t are now owned by the in_cu_mapping: reallocate them.  */
   f = strdup (from);
   t = strdup (to);
   if (!f || !t)
     goto oom;
 
-  if (ctf_create_per_cu (fp, t, t) == NULL)
-    goto oom_noerrno;                          /* Errno is set for us.  */
+  if ((one_out = ctf_dynhash_lookup (fp->ctf_link_out_cu_mapping, t)) == NULL)
+    {
+      if ((one_out = ctf_dynhash_create (ctf_hash_string, ctf_hash_eq_string,
+                                        free, NULL)) == NULL)
+       goto oom;
+      if ((err = ctf_dynhash_insert (fp->ctf_link_out_cu_mapping,
+                                    t, one_out)) < 0)
+       {
+         ctf_dynhash_destroy (one_out);
+         ctf_set_errno (fp, err);
+         goto oom_noerrno;
+       }
+    }
+  else
+    free (t);
 
-  err = ctf_dynhash_insert (fp->ctf_link_cu_mapping, f, t);
-  if (err)
+  if (ctf_dynhash_insert (one_out, f, NULL) < 0)
     {
       ctf_set_errno (fp, err);
       goto oom_noerrno;
 ctf_link (ctf_file_t *fp, int flags)
 {
   ctf_link_in_member_cb_arg_t arg;
+  ctf_next_t *i = NULL;
+  int err;
 
   memset (&arg, 0, sizeof (struct ctf_link_in_member_cb_arg));
   arg.out_fp = fp;
   if (fp->ctf_link_outputs == NULL)
     return ctf_set_errno (fp, ENOMEM);
 
+  /* Create empty CUs if requested.  We do not currently claim that multiple
+     links in succession with CTF_LINK_EMPTY_CU_MAPPINGS set in some calls and
+     not set in others will do anything especially sensible.  */
+
+  if (fp->ctf_link_out_cu_mapping && (flags & CTF_LINK_EMPTY_CU_MAPPINGS))
+    {
+      void *v;
+
+      while ((err = ctf_dynhash_next (fp->ctf_link_out_cu_mapping, &i, &v,
+                                     NULL)) == 0)
+       {
+         const char *to = (const char *) v;
+         if (ctf_create_per_cu (fp, to, to) == NULL)
+           {
+             ctf_next_destroy (i);
+             return -1;                        /* Errno is set for us.  */
+           }
+       }
+      if (err != ECTF_NEXT_END)
+       {
+         ctf_err_warn (fp, 1, "Iteration error creating empty CUs: %s",
+                       ctf_errmsg (err));
+         ctf_set_errno (fp, err);
+         return -1;
+       }
+    }
+
   ctf_dynhash_iter (fp->ctf_link_inputs, ctf_link_one_input_archive,
                    &arg);