+2021-03-18  Nick Alcock  <nick.alcock@oracle.com>
+
+       * ctf-impl.h (ctf_dtdef_t) <dtd_vlen_alloc>: New.
+       (ctf_str_move_pending): Declare.
+       * ctf-string.c (ctf_str_add_ref_internal): Fix error return.
+       (ctf_str_move_pending): New.
+       * ctf-create.c (ctf_grow_vlen): New.
+       (ctf_dtd_delete): Zero out the vlen_alloc after free.  Free the
+       vlen later: iterate over it and free enum name refs first.
+       (ctf_add_generic): Populate dtd_vlen_alloc from vlen.
+       (ctf_add_enum): populate the vlen; do it by hand if promoting
+       forwards.
+       (ctf_add_enumerator): Set up the vlen rather than the dmd.  Expand
+       it as needed, repointing string refs via ctf_str_move_pending. Add
+       the enumerand names as pending strings.
+       * ctf-serialize.c (ctf_copy_emembers): Remove.
+       (ctf_emit_type_sect): Copy the vlen into place and ref the
+       strings.
+       * ctf-types.c (ctf_enum_next): The dynamic portion now uses
+       the same code as the non-dynamic.
+       (ctf_enum_name): Likewise.
+       (ctf_enum_value): Likewise.
+       * testsuite/libctf-lookup/enum-many-ctf.c: New test.
+       * testsuite/libctf-lookup/enum-many.lk: New test.
+
 2021-03-18  Nick Alcock  <nick.alcock@oracle.com>
 
        * ctf-hash.c (ctf_dynset_elements): New.
 
   return 0;
 }
 
+/* Make sure a vlen has enough space: expand it otherwise.  Unlike the ptrtab,
+   which grows quite slowly, the vlen grows in big jumps because it is quite
+   expensive to expand: the caller has to scan the old vlen for string refs
+   first and remove them, then re-add them afterwards.  The initial size is
+   more or less arbitrary.  */
+static int
+ctf_grow_vlen (ctf_dict_t *fp, ctf_dtdef_t *dtd, size_t vlen)
+{
+  unsigned char *old = dtd->dtd_vlen;
+
+  if (dtd->dtd_vlen_alloc > vlen)
+    return 0;
+
+  if ((dtd->dtd_vlen = realloc (dtd->dtd_vlen,
+                               dtd->dtd_vlen_alloc * 2)) == NULL)
+    {
+      dtd->dtd_vlen = old;
+      return (ctf_set_errno (fp, ENOMEM));
+    }
+  memset (dtd->dtd_vlen + dtd->dtd_vlen_alloc, 0, dtd->dtd_vlen_alloc);
+  dtd->dtd_vlen_alloc *= 2;
+  return 0;
+}
+
 /* To create an empty CTF dict, we just declare a zeroed header and call
    ctf_bufopen() on it.  If ctf_bufopen succeeds, we mark the new dict r/w and
    initialize the dynamic members.  We start assigning type IDs at 1 because
 {
   ctf_dmdef_t *dmd, *nmd;
   int kind = LCTF_INFO_KIND (fp, dtd->dtd_data.ctt_info);
+  size_t vlen = LCTF_INFO_VLEN (fp, dtd->dtd_data.ctt_info);
   int name_kind = kind;
   const char *name;
 
   ctf_dynhash_remove (fp->ctf_dthash, (void *) (uintptr_t) dtd->dtd_type);
-  free (dtd->dtd_vlen);
 
   switch (kind)
     {
     case CTF_K_STRUCT:
     case CTF_K_UNION:
-    case CTF_K_ENUM:
       for (dmd = ctf_list_next (&dtd->dtd_u.dtu_members);
           dmd != NULL; dmd = nmd)
        {
          free (dmd);
        }
       break;
+    case CTF_K_ENUM:
+      {
+       ctf_enum_t *en = (ctf_enum_t *) dtd->dtd_vlen;
+       size_t i;
+
+       for (i = 0; i < vlen; i++)
+         ctf_str_remove_ref (fp, ctf_strraw (fp, en[i].cte_name),
+                             &en[i].cte_name);
+      }
+      break;
     case CTF_K_FORWARD:
       name_kind = dtd->dtd_data.ctt_type;
       break;
     }
+  free (dtd->dtd_vlen);
+  dtd->dtd_vlen_alloc = 0;
 
   if (dtd->dtd_data.ctt_name
       && (name = ctf_strraw (fp, dtd->dtd_data.ctt_name)) != NULL
   return 0;
 }
 
+/* Note: vlen is the amount of space *allocated* for the vlen.  It may well not
+   be the amount of space used (yet): the space used is declared in per-kind
+   fashion in the dtd_data's info word.  */
 static ctf_id_t
 ctf_add_generic (ctf_dict_t *fp, uint32_t flag, const char *name, int kind,
                 size_t vlen, ctf_dtdef_t **rp)
   if ((dtd = calloc (1, sizeof (ctf_dtdef_t))) == NULL)
     return (ctf_set_errno (fp, EAGAIN));
 
+  dtd->dtd_vlen_alloc = vlen;
   if (vlen > 0)
     {
       if ((dtd->dtd_vlen = calloc (1, vlen)) == NULL)
 {
   ctf_dtdef_t *dtd;
   ctf_id_t type = 0;
+  size_t initial_vlen = sizeof (ctf_enum_t) * 16;
 
   /* Promote root-visible forwards to enums.  */
   if (name != NULL)
   if (type != 0 && ctf_type_kind (fp, type) == CTF_K_FORWARD)
     dtd = ctf_dtd_lookup (fp, type);
   else if ((type = ctf_add_generic (fp, flag, name, CTF_K_ENUM,
-                                   0, &dtd)) == CTF_ERR)
+                                   initial_vlen, &dtd)) == CTF_ERR)
     return CTF_ERR;            /* errno is set for us.  */
 
+  /* Forwards won't have any vlen yet.  */
+  if (dtd->dtd_vlen_alloc == 0)
+    {
+      if ((dtd->dtd_vlen = calloc (1, initial_vlen)) == NULL)
+       return (ctf_set_errno (fp, ENOMEM));
+      dtd->dtd_vlen_alloc = initial_vlen;
+    }
+
   dtd->dtd_data.ctt_info = CTF_TYPE_INFO (CTF_K_ENUM, flag, 0);
   dtd->dtd_data.ctt_size = fp->ctf_dmodel->ctd_int;
 
                    int value)
 {
   ctf_dtdef_t *dtd = ctf_dtd_lookup (fp, enid);
-  ctf_dmdef_t *dmd;
+  unsigned char *old_vlen;
+  ctf_enum_t *en;
+  size_t i;
 
   uint32_t kind, vlen, root;
-  char *s;
 
   if (name == NULL)
     return (ctf_set_errno (fp, EINVAL));
   if (vlen == CTF_MAX_VLEN)
     return (ctf_set_errno (fp, ECTF_DTFULL));
 
-  for (dmd = ctf_list_next (&dtd->dtd_u.dtu_members);
-       dmd != NULL; dmd = ctf_list_next (dmd))
+  old_vlen = dtd->dtd_vlen;
+  if (ctf_grow_vlen (fp, dtd, sizeof (ctf_enum_t) * (vlen + 1)) < 0)
+    return -1;                                 /* errno is set for us.  */
+  en = (ctf_enum_t *) dtd->dtd_vlen;
+
+  if (dtd->dtd_vlen != old_vlen)
     {
-      if (strcmp (dmd->dmd_name, name) == 0)
-       return (ctf_set_errno (fp, ECTF_DUPLICATE));
-    }
+      ptrdiff_t move = (signed char *) dtd->dtd_vlen - (signed char *) old_vlen;
 
-  if ((dmd = malloc (sizeof (ctf_dmdef_t))) == NULL)
-    return (ctf_set_errno (fp, EAGAIN));
+      /* Remove pending refs in the old vlen region and reapply them.  */
 
-  if ((s = strdup (name)) == NULL)
-    {
-      free (dmd);
-      return (ctf_set_errno (fp, EAGAIN));
+      for (i = 0; i < vlen; i++)
+       ctf_str_move_pending (fp, &en[i].cte_name, move);
     }
 
-  dmd->dmd_name = s;
-  dmd->dmd_type = CTF_ERR;
-  dmd->dmd_offset = 0;
-  dmd->dmd_value = value;
+  for (i = 0; i < vlen; i++)
+    if (strcmp (ctf_strptr (fp, en[i].cte_name), name) == 0)
+      return (ctf_set_errno (fp, ECTF_DUPLICATE));
+
+  en[i].cte_name = ctf_str_add_pending (fp, name, &en[i].cte_name);
+  en[i].cte_value = value;
+
+  if (en[i].cte_name == 0 && name != NULL && name[0] != '\0')
+    return -1;                                 /* errno is set for us. */
 
   dtd->dtd_data.ctt_info = CTF_TYPE_INFO (kind, root, vlen + 1);
-  ctf_list_append (&dtd->dtd_u.dtu_members, dmd);
 
   fp->ctf_flags |= LCTF_DIRTY;
 
 
 #include <sys/types.h>
 #include <stdlib.h>
 #include <stdarg.h>
+#include <stddef.h>
 #include <stdio.h>
 #include <stdint.h>
 #include <limits.h>
   ctf_list_t dtd_list;         /* List forward/back pointers.  */
   ctf_id_t dtd_type;           /* Type identifier for this definition.  */
   ctf_type_t dtd_data;         /* Type node, including name.  */
+  size_t dtd_vlen_alloc;       /* Total vlen space allocated.  */
   unsigned char *dtd_vlen;     /* Variable-length data for this type.  */
   union
   {
 extern uint32_t ctf_str_add (ctf_dict_t *, const char *);
 extern uint32_t ctf_str_add_ref (ctf_dict_t *, const char *, uint32_t *ref);
 extern uint32_t ctf_str_add_pending (ctf_dict_t *, const char *, uint32_t *);
+extern int ctf_str_move_pending (ctf_dict_t *, uint32_t *, ptrdiff_t);
 extern int ctf_str_add_external (ctf_dict_t *, const char *, uint32_t offset);
 extern void ctf_str_remove_ref (ctf_dict_t *, const char *, uint32_t *ref);
 extern void ctf_str_rollback (ctf_dict_t *, ctf_snapshot_id_t);
 
   return t;
 }
 
-static unsigned char *
-ctf_copy_emembers (ctf_dict_t *fp, ctf_dtdef_t *dtd, unsigned char *t)
-{
-  ctf_dmdef_t *dmd = ctf_list_next (&dtd->dtd_u.dtu_members);
-  ctf_enum_t cte;
-
-  for (; dmd != NULL; dmd = ctf_list_next (dmd))
-    {
-      ctf_enum_t *copied;
-
-      cte.cte_value = dmd->dmd_value;
-      memcpy (t, &cte, sizeof (cte));
-      copied = (ctf_enum_t *) t;
-      ctf_str_add_ref (fp, dmd->dmd_name, &copied->cte_name);
-      t += sizeof (cte);
-    }
-
-  return t;
-}
-
 /* Iterate through the dynamic type definition list and compute the
    size of the CTF type section.  */
 
       size_t len;
       ctf_stype_t *copied;
       const char *name;
+      size_t i;
 
       if (dtd->dtd_data.ctt_size != CTF_LSIZE_SENT)
        len = sizeof (ctf_stype_t);
          break;
 
        case CTF_K_ENUM:
-         t = ctf_copy_emembers (fp, dtd, t);
-         break;
+         {
+           ctf_enum_t *dtd_vlen = (struct ctf_enum *) dtd->dtd_vlen;
+           ctf_enum_t *t_vlen = (struct ctf_enum *) t;
+
+           memcpy (t, dtd->dtd_vlen, sizeof (struct ctf_enum) * vlen);
+           for (i = 0; i < vlen; i++)
+             {
+               const char *name = ctf_strraw (fp, dtd_vlen[i].cte_name);
+
+               ctf_str_add_ref (fp, name, &t_vlen[i].cte_name);
+               ctf_str_add_ref (fp, name, &dtd_vlen[i].cte_name);
+             }
+           t += sizeof (struct ctf_enum) * vlen;
+
+           break;
+         }
        }
     }
 
 
 
 #include <ctf-impl.h>
 #include <string.h>
+#include <assert.h>
 
 /* Convert an encoded CTF string name into a pointer to a C string, using an
   explicit internal strtab rather than the fp-based one.  */
   free (atom);
   free (aref);
   free (newstr);
+  ctf_set_errno (fp, ENOMEM);
   return NULL;
 }
 
   return atom->csa_offset;
 }
 
+/* Note that a pending ref now located at NEW_REF has moved by BYTES bytes.  */
+int
+ctf_str_move_pending (ctf_dict_t *fp, uint32_t *new_ref, ptrdiff_t bytes)
+{
+  if (bytes == 0)
+    return 0;
+
+  if (ctf_dynset_insert (fp->ctf_str_pending_ref, (void *) new_ref) < 0)
+    return (ctf_set_errno (fp, ENOMEM));
+
+  ctf_dynset_remove (fp->ctf_str_pending_ref,
+                    (void *) ((signed char *) new_ref - bytes));
+  return 0;
+}
+
 /* Add an external strtab reference at OFFSET.  Returns zero if the addition
    failed, nonzero otherwise.  */
 int
 
 
       dtd = ctf_dynamic_type (fp, type);
       i->ctn_iter_fun = (void (*) (void)) ctf_enum_next;
-
-      /* We depend below on the RDWR state indicating whether the DTD-related
-        fields or the DMD-related fields have been initialized.  */
-
-      assert ((dtd && (fp->ctf_flags & LCTF_RDWR))
-             || (!dtd && (!(fp->ctf_flags & LCTF_RDWR))));
+      i->ctn_n = LCTF_INFO_VLEN (fp, tp->ctt_info);
 
       if (dtd == NULL)
-       {
-         i->ctn_n = LCTF_INFO_VLEN (fp, tp->ctt_info);
-
-         i->u.ctn_en = (const ctf_enum_t *) ((uintptr_t) tp +
-                                             i->ctn_increment);
-       }
+       i->u.ctn_en = (const ctf_enum_t *) ((uintptr_t) tp +
+                                           i->ctn_increment);
       else
-       i->u.ctn_dmd = ctf_list_next (&dtd->dtd_u.dtu_members);
+       i->u.ctn_en = (const ctf_enum_t *) dtd->dtd_vlen;
 
       *it = i;
     }
       return NULL;
     }
 
-  if (!(fp->ctf_flags & LCTF_RDWR))
-    {
-      if (i->ctn_n == 0)
-       goto end_iter;
-
-      name = ctf_strptr (fp, i->u.ctn_en->cte_name);
-      if (val)
-       *val = i->u.ctn_en->cte_value;
-      i->u.ctn_en++;
-      i->ctn_n--;
-    }
-  else
-    {
-      if (i->u.ctn_dmd == NULL)
-       goto end_iter;
+  if (i->ctn_n == 0)
+    goto end_iter;
 
-      name = i->u.ctn_dmd->dmd_name;
-      if (val)
-       *val = i->u.ctn_dmd->dmd_value;
-      i->u.ctn_dmd = ctf_list_next (i->u.ctn_dmd);
-    }
+  name = ctf_strptr (fp, i->u.ctn_en->cte_name);
+  if (val)
+    *val = i->u.ctn_en->cte_value;
+  i->u.ctn_en++;
+  i->ctn_n--;
 
   return name;
 
 
   if (LCTF_INFO_KIND (fp, tp->ctt_info) != CTF_K_ENUM)
     {
-      (void) ctf_set_errno (ofp, ECTF_NOTENUM);
+      ctf_set_errno (ofp, ECTF_NOTENUM);
       return NULL;
     }
 
-  (void) ctf_get_ctt_size (fp, tp, NULL, &increment);
+  ctf_get_ctt_size (fp, tp, NULL, &increment);
 
   if ((dtd = ctf_dynamic_type (ofp, type)) == NULL)
-    {
-      ep = (const ctf_enum_t *) ((uintptr_t) tp + increment);
-
-      for (n = LCTF_INFO_VLEN (fp, tp->ctt_info); n != 0; n--, ep++)
-       {
-         if (ep->cte_value == value)
-           return (ctf_strptr (fp, ep->cte_name));
-       }
-    }
+    ep = (const ctf_enum_t *) ((uintptr_t) tp + increment);
   else
-    {
-      ctf_dmdef_t *dmd;
+    ep = (const ctf_enum_t *) dtd->dtd_vlen;
 
-      for (dmd = ctf_list_next (&dtd->dtd_u.dtu_members);
-          dmd != NULL; dmd = ctf_list_next (dmd))
-       {
-         if (dmd->dmd_value == value)
-           return dmd->dmd_name;
-       }
+  for (n = LCTF_INFO_VLEN (fp, tp->ctt_info); n != 0; n--, ep++)
+    {
+      if (ep->cte_value == value)
+       return (ctf_strptr (fp, ep->cte_name));
     }
 
-  (void) ctf_set_errno (ofp, ECTF_NOENUMNAM);
+  ctf_set_errno (ofp, ECTF_NOENUMNAM);
   return NULL;
 }
 
    matching name can be found.  Otherwise CTF_ERR is returned.  */
 
 int
-ctf_enum_value (ctf_dict_t * fp, ctf_id_t type, const char *name, int *valp)
+ctf_enum_value (ctf_dict_t *fp, ctf_id_t type, const char *name, int *valp)
 {
   ctf_dict_t *ofp = fp;
   const ctf_type_t *tp;
       return -1;
     }
 
-  (void) ctf_get_ctt_size (fp, tp, NULL, &increment);
-
-  ep = (const ctf_enum_t *) ((uintptr_t) tp + increment);
+  ctf_get_ctt_size (fp, tp, NULL, &increment);
 
   if ((dtd = ctf_dynamic_type (ofp, type)) == NULL)
-    {
-      for (n = LCTF_INFO_VLEN (fp, tp->ctt_info); n != 0; n--, ep++)
-       {
-         if (strcmp (ctf_strptr (fp, ep->cte_name), name) == 0)
-           {
-             if (valp != NULL)
-               *valp = ep->cte_value;
-             return 0;
-           }
-       }
-    }
+    ep = (const ctf_enum_t *) ((uintptr_t) tp + increment);
   else
-    {
-      ctf_dmdef_t *dmd;
+    ep = (const ctf_enum_t *) dtd->dtd_vlen;
 
-      for (dmd = ctf_list_next (&dtd->dtd_u.dtu_members);
-          dmd != NULL; dmd = ctf_list_next (dmd))
+  for (n = LCTF_INFO_VLEN (fp, tp->ctt_info); n != 0; n--, ep++)
+    {
+      if (strcmp (ctf_strptr (fp, ep->cte_name), name) == 0)
        {
-         if (strcmp (dmd->dmd_name, name) == 0)
-           {
-             if (valp != NULL)
-               *valp = dmd->dmd_value;
-             return 0;
-           }
+         if (valp != NULL)
+           *valp = ep->cte_value;
+         return 0;
        }
     }
 
-  (void) ctf_set_errno (ofp, ECTF_NOENUMNAM);
+  ctf_set_errno (ofp, ECTF_NOENUMNAM);
   return -1;
 }
 
 
--- /dev/null
+/* Looked up item by item. */
+enum e { ENUMSAMPLE_1 = 0, ENUMSAMPLE_2 = 1 };
+
+/* Looked up via both sorts of iterator in turn.  */
+enum ie { IE_0 = -10, IE_1, IE_2, IE_3, IE_4, IE_5, IE_6, IE_7, IE_8, IE_9, IE_A, IE_B, IE_C, IE_D, IE_E, IE_F,
+         IE_10, IE_11, IE_12, IE_13, IE_14, IE_15, IE_16, IE_17, IE_18, IE_19, IE_1A, IE_1B, IE_1C, IE_1D, IE_1E, IE_1F,
+         IE_20, IE_21, IE_22, IE_23, IE_24, IE_25, IE_26, IE_27, IE_28, IE_29, IE_2A, IE_2B, IE_2C, IE_2D, IE_2E, IE_2F};
+
+enum e foo;
+enum ie bar;
 
--- /dev/null
+# source: enum-many-ctf.c
+# lookup: enum.c
+# link: on
+Enum e enumerand ENUMSAMPLE_1 has value 0
+Enum e enumerand ENUMSAMPLE_2 has value 1
+iter test: IE_0 has value -10
+iter test: IE_1 has value -9
+iter test: IE_2 has value -8
+iter test: IE_3 has value -7
+iter test: IE_4 has value -6
+iter test: IE_5 has value -5
+iter test: IE_6 has value -4
+iter test: IE_7 has value -3
+iter test: IE_8 has value -2
+iter test: IE_9 has value -1
+iter test: IE_A has value 0
+iter test: IE_B has value 1
+iter test: IE_C has value 2
+iter test: IE_D has value 3
+iter test: IE_E has value 4
+iter test: IE_F has value 5
+iter test: IE_10 has value 6
+iter test: IE_11 has value 7
+iter test: IE_12 has value 8
+iter test: IE_13 has value 9
+iter test: IE_14 has value 10
+iter test: IE_15 has value 11
+iter test: IE_16 has value 12
+iter test: IE_17 has value 13
+iter test: IE_18 has value 14
+iter test: IE_19 has value 15
+iter test: IE_1A has value 16
+iter test: IE_1B has value 17
+iter test: IE_1C has value 18
+iter test: IE_1D has value 19
+iter test: IE_1E has value 20
+iter test: IE_1F has value 21
+iter test: IE_20 has value 22
+iter test: IE_21 has value 23
+iter test: IE_22 has value 24
+iter test: IE_23 has value 25
+iter test: IE_24 has value 26
+iter test: IE_25 has value 27
+iter test: IE_26 has value 28
+iter test: IE_27 has value 29
+iter test: IE_28 has value 30
+iter test: IE_29 has value 31
+iter test: IE_2A has value 32
+iter test: IE_2B has value 33
+iter test: IE_2C has value 34
+iter test: IE_2D has value 35
+iter test: IE_2E has value 36
+iter test: IE_2F has value 37
+next test: IE_0 has value -10
+next test: IE_1 has value -9
+next test: IE_2 has value -8
+next test: IE_3 has value -7
+next test: IE_4 has value -6
+next test: IE_5 has value -5
+next test: IE_6 has value -4
+next test: IE_7 has value -3
+next test: IE_8 has value -2
+next test: IE_9 has value -1
+next test: IE_A has value 0
+next test: IE_B has value 1
+next test: IE_C has value 2
+next test: IE_D has value 3
+next test: IE_E has value 4
+next test: IE_F has value 5
+next test: IE_10 has value 6
+next test: IE_11 has value 7
+next test: IE_12 has value 8
+next test: IE_13 has value 9
+next test: IE_14 has value 10
+next test: IE_15 has value 11
+next test: IE_16 has value 12
+next test: IE_17 has value 13
+next test: IE_18 has value 14
+next test: IE_19 has value 15
+next test: IE_1A has value 16
+next test: IE_1B has value 17
+next test: IE_1C has value 18
+next test: IE_1D has value 19
+next test: IE_1E has value 20
+next test: IE_1F has value 21
+next test: IE_20 has value 22
+next test: IE_21 has value 23
+next test: IE_22 has value 24
+next test: IE_23 has value 25
+next test: IE_24 has value 26
+next test: IE_25 has value 27
+next test: IE_26 has value 28
+next test: IE_27 has value 29
+next test: IE_28 has value 30
+next test: IE_29 has value 31
+next test: IE_2A has value 32
+next test: IE_2B has value 33
+next test: IE_2C has value 34
+next test: IE_2D has value 35
+next test: IE_2E has value 36
+next test: IE_2F has value 37