bfd/
authorJakub Jelinek <jakub@redhat.com>
Wed, 22 Oct 2003 06:58:17 +0000 (06:58 +0000)
committerJakub Jelinek <jakub@redhat.com>
Wed, 22 Oct 2003 06:58:17 +0000 (06:58 +0000)
* elflink.c (_bfd_elf_export_symbol): Adjust for globals and locals
field changes.
(_bfd_elf_link_assign_sym_version): Likewise.
* elflink.h (size_dynamic_sections): Likewise.
include/
* bfdlink.h (struct bfd_elf_version_expr): Remove match field.
Add wildcard and mask fields.
(BFD_ELF_VERSION_C_TYPE): Define.
(BFD_ELF_VERSION_CXX_TYPE): Likewise.
(BFD_ELF_VERSION_JAVA_TYPE): Likewise.
(struct bfd_elf_version_expr_head): New.
(struct bfd_elf_version_tree): Add match field.
Change type of globals and locals fields
to struct bfd_elf_version_expr_head.
ld/
* ldlang.c: Include hashtab.h.
(lang_vers_match_lang_c, lang_vers_match_lang_cplusplus,
lang_vers_match_lang_java): Remove.
(lang_vers_match): New function.
(lang_new_vers_pattern): Initialize wildcard and mask
fields, don't initialize match.
(lang_new_vers_node): Use xcalloc.  Adjust for globals and
locals field type changes.  Set match field.
(version_expr_head_hash, version_expr_head_eq): New functions.
(lang_finalize_version_expr_head): New function.
(lang_register_vers_node): Call lang_finalize_version_expr_head.
Search in hash table if not wildcard when looking for duplicates.
* emultempl/ppc64elf.em (new_vers_pattern): Don't bother with
duplicate checking.  Initialize all fields of dot_entry from entry
with the exception of pattern and next.

bfd/ChangeLog
bfd/elflink.c
bfd/elflink.h
include/ChangeLog
include/bfdlink.h
ld/ChangeLog
ld/emultempl/ppc64elf.em
ld/ldlang.c

index 2a002c166c26aa327ef04f61997139be05c6168b..2d47b54ed9e36ed327215a9428837d35b67e065a 100644 (file)
@@ -1,3 +1,10 @@
+2003-10-22  Jakub Jelinek  <jakub@redhat.com>
+
+       * elflink.c (_bfd_elf_export_symbol): Adjust for globals and locals
+       field changes.
+       (_bfd_elf_link_assign_sym_version): Likewise.
+       * elflink.h (size_dynamic_sections): Likewise.
+
 2003-10-21  Alexandre Oliva  <aoliva@redhat.com>,
            Michael Snyder  <msnyder@redhat.com>
 
index 0e06903c4be048838378caf1d26f7a43f335d351..19f6d4d7b8b44fcedc5e41870bc8b42223be61a1 100644 (file)
@@ -1505,22 +1505,18 @@ _bfd_elf_export_symbol (struct elf_link_hash_entry *h, void *data)
 
       for (t = eif->verdefs; t != NULL; t = t->next)
        {
-         if (t->globals != NULL)
+         if (t->globals.list != NULL)
            {
-             for (d = t->globals; d != NULL; d = d->next)
-               {
-                 if ((*d->match) (d, h->root.root.string))
-                   goto doit;
-               }
+             d = (*t->match) (&t->globals, NULL, h->root.root.string);
+             if (d != NULL)
+               goto doit;
            }
 
-         if (t->locals != NULL)
+         if (t->locals.list != NULL)
            {
-             for (d = t->locals ; d != NULL; d = d->next)
-               {
-                 if ((*d->match) (d, h->root.root.string))
-                   return TRUE;
-               }
+             d = (*t->match) (&t->locals, NULL, h->root.root.string);
+             if (d != NULL)
+               return TRUE;
            }
        }
 
@@ -1699,31 +1695,19 @@ _bfd_elf_link_assign_sym_version (struct elf_link_hash_entry *h, void *data)
              t->used = TRUE;
              d = NULL;
 
-             if (t->globals != NULL)
-               {
-                 for (d = t->globals; d != NULL; d = d->next)
-                   if ((*d->match) (d, alc))
-                     break;
-               }
+             if (t->globals.list != NULL)
+               d = (*t->match) (&t->globals, NULL, alc);
 
              /* See if there is anything to force this symbol to
                 local scope.  */
-             if (d == NULL && t->locals != NULL)
+             if (d == NULL && t->locals.list != NULL)
                {
-                 for (d = t->locals; d != NULL; d = d->next)
-                   {
-                     if ((*d->match) (d, alc))
-                       {
-                         if (h->dynindx != -1
-                             && info->shared
-                             && ! info->export_dynamic)
-                           {
-                             (*bed->elf_backend_hide_symbol) (info, h, TRUE);
-                           }
-
-                         break;
-                       }
-                   }
+                 d = (*t->match) (&t->locals, NULL, alc);
+                 if (d != NULL
+                     && h->dynindx != -1
+                     && info->shared
+                     && ! info->export_dynamic)
+                   (*bed->elf_backend_hide_symbol) (info, h, TRUE);
                }
 
              free (alc);
@@ -1744,18 +1728,14 @@ _bfd_elf_link_assign_sym_version (struct elf_link_hash_entry *h, void *data)
            return TRUE;
 
          amt = sizeof *t;
-         t = bfd_alloc (sinfo->output_bfd, amt);
+         t = bfd_zalloc (sinfo->output_bfd, amt);
          if (t == NULL)
            {
              sinfo->failed = TRUE;
              return FALSE;
            }
 
-         t->next = NULL;
          t->name = p;
-         t->globals = NULL;
-         t->locals = NULL;
-         t->deps = NULL;
          t->name_indx = (unsigned int) -1;
          t->used = TRUE;
 
@@ -1801,30 +1781,26 @@ _bfd_elf_link_assign_sym_version (struct elf_link_hash_entry *h, void *data)
       local_ver = NULL;
       for (t = sinfo->verdefs; t != NULL; t = t->next)
        {
-         if (t->globals != NULL)
+         if (t->globals.list != NULL)
            {
              bfd_boolean matched;
 
              matched = FALSE;
-             for (d = t->globals; d != NULL; d = d->next)
-               {
-                 if ((*d->match) (d, h->root.root.string))
-                   {
-                     if (d->symver)
-                       matched = TRUE;
-                     else
-                       {
-                         /* There is a version without definition.  Make
-                            the symbol the default definition for this
-                            version.  */
-                         h->verinfo.vertree = t;
-                         local_ver = NULL;
-                         d->script = 1;
-                         break;
-                       }
-                   }
-               }
-
+             d = NULL;
+             while ((d = (*t->match) (&t->globals, d,
+                                      h->root.root.string)) != NULL)
+               if (d->symver)
+                 matched = TRUE;
+               else
+                 {
+                   /* There is a version without definition.  Make
+                      the symbol the default definition for this
+                      version.  */
+                   h->verinfo.vertree = t;
+                   local_ver = NULL;
+                   d->script = 1;
+                   break;
+                 }
              if (d != NULL)
                break;
              else if (matched)
@@ -1833,19 +1809,18 @@ _bfd_elf_link_assign_sym_version (struct elf_link_hash_entry *h, void *data)
                (*bed->elf_backend_hide_symbol) (info, h, TRUE);
            }
 
-         if (t->locals != NULL)
+         if (t->locals.list != NULL)
            {
-             for (d = t->locals; d != NULL; d = d->next)
+             d = NULL;
+             while ((d = (*t->match) (&t->locals, d,
+                                      h->root.root.string)) != NULL)
                {
+                 local_ver = t;
                  /* If the match is "*", keep looking for a more
-                    explicit, perhaps even global, match.  */
-                 if (d->pattern[0] == '*' && d->pattern[1] == '\0')
-                   local_ver = t;
-                 else if ((*d->match) (d, h->root.root.string))
-                   {
-                     local_ver = t;
-                     break;
-                   }
+                    explicit, perhaps even global, match.
+                    XXX: Shouldn't this be !d->wildcard instead?  */
+                 if (d->pattern[0] != '*' || d->pattern[1] != '\0')
+                   break;
                }
 
              if (d != NULL)
index 383cb88b068a5be2760b325aa72e8caa0a2e0bf5..e53911df855bf140609408e1ac795a9a0102176a 100644 (file)
@@ -2058,7 +2058,9 @@ NAME(bfd_elf,size_dynamic_sections) (bfd *output_bfd,
 
       /* Make all global versions with definiton.  */
       for (t = verdefs; t != NULL; t = t->next)
-       for (d = t->globals; d != NULL; d = d->next)
+       for (d = t->globals.list; d != NULL; d = d->next)
+         /* FIXME: Shouldn't this be !d->symver && d->wildcard == 0
+            instead?  */
          if (!d->symver && strchr (d->pattern, '*') == NULL)
            {
              const char *verstr, *name;
@@ -2124,7 +2126,7 @@ NAME(bfd_elf,size_dynamic_sections) (bfd *output_bfd,
          /* Check if all global versions have a definiton.  */
          all_defined = TRUE;
          for (t = verdefs; t != NULL; t = t->next)
-           for (d = t->globals; d != NULL; d = d->next)
+           for (d = t->globals.list; d != NULL; d = d->next)
              if (!d->symver && !d->script)
                {
                  (*_bfd_error_handler)
@@ -2372,7 +2374,7 @@ NAME(bfd_elf,size_dynamic_sections) (bfd *output_bfd,
 
              def.vd_version = VER_DEF_CURRENT;
              def.vd_flags = 0;
-             if (t->globals == NULL && t->locals == NULL && ! t->used)
+             if (t->globals.list == NULL && t->locals.list == NULL && ! t->used)
                def.vd_flags |= VER_FLG_WEAK;
              def.vd_ndx = t->vernum + 1;
              def.vd_cnt = cdeps + 1;
index 34f66f056318ac0181f25c4aeaeefff6ab801a5a..d1e9a1df72b98e0b3677c4bee161d0365e753091 100644 (file)
@@ -1,3 +1,15 @@
+2003-10-22  Jakub Jelinek  <jakub@redhat.com>
+
+       * bfdlink.h (struct bfd_elf_version_expr): Remove match field.
+       Add wildcard and mask fields.
+       (BFD_ELF_VERSION_C_TYPE): Define.
+       (BFD_ELF_VERSION_CXX_TYPE): Likewise.
+       (BFD_ELF_VERSION_JAVA_TYPE): Likewise.
+       (struct bfd_elf_version_expr_head): New.
+       (struct bfd_elf_version_tree): Add match field.
+       Change type of globals and locals fields
+       to struct bfd_elf_version_expr_head.
+
 2003-10-14  Bob Wilson  <bob.wilson@acm.org>
 
        * elf/xtensa.h: Formatting.  Fix comments about property section
index c174dcdc698fe075d877e90547b6ebf344a82ca4..fd77c2944425dd698e9d7040a959730d4916243d 100644 (file)
@@ -617,20 +617,37 @@ extern struct bfd_link_order *bfd_new_link_order (bfd *, asection *);
    BFD, but it would be a pain.  Instead, the regular linker sets up
    these structures, and then passes them into BFD.  */
 
-/* Regular expressions for a version.  */
+/* Glob pattern for a version.  */
 
 struct bfd_elf_version_expr
 {
-  /* Next regular expression for this version.  */
+  /* Next glob pattern for this version.  */
   struct bfd_elf_version_expr *next;
-  /* Regular expression.  */
+  /* Glob pattern.  */
   const char *pattern;
-  /* Matching function.  */
-  int (*match) (struct bfd_elf_version_expr *, const char *);
   /* Defined by ".symver".  */
-  unsigned int symver: 1;
+  unsigned int symver : 1;
   /* Defined by version script.  */
   unsigned int script : 1;
+  /* Is this a wildcard?.  */
+  unsigned int wildcard : 1;
+  /* Pattern type.  */
+#define BFD_ELF_VERSION_C_TYPE         1
+#define BFD_ELF_VERSION_CXX_TYPE       2
+#define BFD_ELF_VERSION_JAVA_TYPE      4
+  unsigned int mask : 3;
+};
+
+struct bfd_elf_version_expr_head
+{
+  /* List of all patterns, both wildcards and non-wildcards.  */
+  struct bfd_elf_version_expr *list;
+  /* Hash table for non-wildcards.  */
+  void *htab;
+  /* Remaining patterns.  */
+  struct bfd_elf_version_expr *remaining;
+  /* What kind of pattern types are present in list (bitmask).  */
+  unsigned int mask;
 };
 
 /* Version dependencies.  */
@@ -654,15 +671,19 @@ struct bfd_elf_version_tree
   /* Version number.  */
   unsigned int vernum;
   /* Regular expressions for global symbols in this version.  */
-  struct bfd_elf_version_expr *globals;
+  struct bfd_elf_version_expr_head globals;
   /* Regular expressions for local symbols in this version.  */
-  struct bfd_elf_version_expr *locals;
+  struct bfd_elf_version_expr_head locals;
   /* List of versions which this version depends upon.  */
   struct bfd_elf_version_deps *deps;
   /* Index of the version name.  This is used within BFD.  */
   unsigned int name_indx;
   /* Whether this version tree was used.  This is used within BFD.  */
   int used;
+  /* Matching hook.  */
+  struct bfd_elf_version_expr *(*match)
+    (struct bfd_elf_version_expr_head *head,
+     struct bfd_elf_version_expr *prev, const char *sym);
 };
 
 #endif
index a495412727ff31adf77a66b27148d0a60d587481..42b035f18622ef320f800031215a742be8dcb2a3 100644 (file)
@@ -1,3 +1,21 @@
+2003-10-22  Jakub Jelinek  <jakub@redhat.com>
+
+       * ldlang.c: Include hashtab.h.
+       (lang_vers_match_lang_c, lang_vers_match_lang_cplusplus,
+       lang_vers_match_lang_java): Remove.
+       (lang_vers_match): New function.
+       (lang_new_vers_pattern): Initialize wildcard and mask
+       fields, don't initialize match.
+       (lang_new_vers_node): Use xcalloc.  Adjust for globals and
+       locals field type changes.  Set match field.
+       (version_expr_head_hash, version_expr_head_eq): New functions.
+       (lang_finalize_version_expr_head): New function.
+       (lang_register_vers_node): Call lang_finalize_version_expr_head.
+       Search in hash table if not wildcard when looking for duplicates.
+       * emultempl/ppc64elf.em (new_vers_pattern): Don't bother with
+       duplicate checking.  Initialize all fields of dot_entry from entry
+       with the exception of pattern and next.
+
 2003-10-21  Nick Clifton  <nickc@redhat.com>
 
         * ldlang.c (lang_memory_region_lookup): Add second parameter -
index b1b3f9ef07aeabe76318eabaa4bcacb09d42a4b4..1ed49e580bde795feb1906de7b1cbaba1ede8e11 100644 (file)
@@ -405,46 +405,17 @@ gld${EMULATION_NAME}_new_vers_pattern (struct bfd_elf_version_expr *entry)
   unsigned int len;
   char *dot_pat;
 
-  if (!dotsyms || entry->pattern[0] == '*')
+  if (!dotsyms || entry->pattern[0] == '*' || entry->pattern[0] == '.')
     return entry;
 
-  /* Is the script adding ".foo" explicitly?  */
-  if (entry->pattern[0] == '.')
-    {
-      /* We may have added this pattern automatically.  Don't add it
-        again.  Quadratic behaviour here is acceptable as the list
-        may be traversed for each input bfd symbol.  */
-      for (next = entry->next; next != NULL; next = next->next)
-       {
-         if (strcmp (next->pattern, entry->pattern) == 0
-             && next->match == entry->match)
-           {
-             next = entry->next;
-             free ((char *) entry->pattern);
-             free (entry);
-             return next;
-           }
-       }
-      return entry;
-    }
-
-  /* Don't add ".foo" if the script has already done so.  */
-  for (next = entry->next; next != NULL; next = next->next)
-    {
-      if (next->pattern[0] == '.'
-         && strcmp (next->pattern + 1, entry->pattern) == 0
-         && next->match == entry->match)
-       return entry;
-    }
-
   dot_entry = xmalloc (sizeof *dot_entry);
+  *dot_entry = *entry;
   dot_entry->next = entry;
   len = strlen (entry->pattern) + 2;
   dot_pat = xmalloc (len);
   dot_pat[0] = '.';
   memcpy (dot_pat + 1, entry->pattern, len - 1);
   dot_entry->pattern = dot_pat;
-  dot_entry->match = entry->match;
   return dot_entry;
 }
 
index ad75d2648e5ffb8697feec9396e760ce249963c1..b180edeb34a3be06f66fb3b0c181d5a56d6dc25d 100644 (file)
@@ -39,6 +39,7 @@
 #include "ldemul.h"
 #include "fnmatch.h"
 #include "demangle.h"
+#include "hashtab.h"
 
 #ifndef offsetof
 #define offsetof(TYPE, MEMBER) ((size_t) & (((TYPE*) 0)->MEMBER))
@@ -4973,65 +4974,108 @@ lang_leave_overlay (etree_type *lma_expr,
 
 struct bfd_elf_version_tree *lang_elf_version_info;
 
-static int
-lang_vers_match_lang_c (struct bfd_elf_version_expr *expr,
-                       const char *sym)
-{
-  if (expr->pattern[0] == '*' && expr->pattern[1] == '\0')
-    return 1;
-  return fnmatch (expr->pattern, sym, 0) == 0;
-}
+/* If PREV is NULL, return first version pattern matching particular symbol.
+   If PREV is non-NULL, return first version pattern matching particular
+   symbol after PREV (previously returned by lang_vers_match).  */
 
-static int
-lang_vers_match_lang_cplusplus (struct bfd_elf_version_expr *expr,
-                               const char *sym)
+static struct bfd_elf_version_expr *
+lang_vers_match (struct bfd_elf_version_expr_head *head,
+                struct bfd_elf_version_expr *prev,
+                const char *sym)
 {
-  char *alt_sym;
-  int result;
-
-  if (expr->pattern[0] == '*' && expr->pattern[1] == '\0')
-    return 1;
+  const char *cxx_sym = sym;
+  const char *java_sym = sym;
+  struct bfd_elf_version_expr *expr = NULL;
 
-  alt_sym = cplus_demangle (sym, /* DMGL_NO_TPARAMS */ 0);
-  if (!alt_sym)
+  if (head->mask & BFD_ELF_VERSION_CXX_TYPE)
     {
-      /* cplus_demangle (also) returns NULL when it is not a C++ symbol.
-        Should we early out FALSE in this case?  */
-      result = fnmatch (expr->pattern, sym, 0) == 0;
+      cxx_sym = cplus_demangle (sym, /* DMGL_NO_TPARAMS */ 0);
+      if (!cxx_sym)
+       cxx_sym = sym;
     }
-  else
+  if (head->mask & BFD_ELF_VERSION_JAVA_TYPE)
     {
-      result = fnmatch (expr->pattern, alt_sym, 0) == 0;
-      free (alt_sym);
+      java_sym = cplus_demangle (sym, DMGL_JAVA);
+      if (!java_sym)
+       java_sym = sym;
     }
 
-  return result;
-}
-
-static int
-lang_vers_match_lang_java (struct bfd_elf_version_expr *expr,
-                          const char *sym)
-{
-  char *alt_sym;
-  int result;
-
-  if (expr->pattern[0] == '*' && expr->pattern[1] == '\0')
-    return 1;
-
-  alt_sym = cplus_demangle (sym, DMGL_JAVA);
-  if (!alt_sym)
+  if (head->htab && (prev == NULL || prev->wildcard == 0))
     {
-      /* cplus_demangle (also) returns NULL when it is not a Java symbol.
-        Should we early out FALSE in this case?  */
-      result = fnmatch (expr->pattern, sym, 0) == 0;
+      struct bfd_elf_version_expr e;
+
+      switch (prev ? prev->mask : 0)
+       {
+         case 0:
+           if (head->mask & BFD_ELF_VERSION_C_TYPE)
+             {
+               e.pattern = sym;
+               expr = htab_find (head->htab, &e);
+               while (expr && strcmp (expr->pattern, sym) == 0)
+                 if (expr->mask == BFD_ELF_VERSION_C_TYPE)
+                   goto out_ret;
+               else
+                 expr = expr->next;
+             }
+           /* Fallthrough */
+         case BFD_ELF_VERSION_C_TYPE:
+           if (head->mask & BFD_ELF_VERSION_CXX_TYPE)
+             {
+               e.pattern = cxx_sym;
+               expr = htab_find (head->htab, &e);
+               while (expr && strcmp (expr->pattern, sym) == 0)
+                 if (expr->mask == BFD_ELF_VERSION_CXX_TYPE)
+                   goto out_ret;
+               else
+                 expr = expr->next;
+             }
+           /* Fallthrough */
+         case BFD_ELF_VERSION_CXX_TYPE:
+           if (head->mask & BFD_ELF_VERSION_JAVA_TYPE)
+             {
+               e.pattern = java_sym;
+               expr = htab_find (head->htab, &e);
+               while (expr && strcmp (expr->pattern, sym) == 0)
+                 if (expr->mask == BFD_ELF_VERSION_JAVA_TYPE)
+                   goto out_ret;
+               else
+                 expr = expr->next;
+             }
+           /* Fallthrough */
+         default:
+           break;
+       }
     }
+
+  /* Finally, try the wildcards.  */
+  if (prev == NULL || prev->wildcard == 0)
+    expr = head->remaining;
   else
+    expr = prev->next;
+  while (expr)
     {
-      result = fnmatch (expr->pattern, alt_sym, 0) == 0;
-      free (alt_sym);
+      const char *s;
+
+      if (expr->pattern[0] == '*' && expr->pattern[1] == '\0')
+       break;
+
+      if (expr->mask == BFD_ELF_VERSION_JAVA_TYPE)
+       s = java_sym;
+      else if (expr->mask == BFD_ELF_VERSION_CXX_TYPE)
+       s = cxx_sym;
+      else
+       s = sym;
+      if (fnmatch (expr->pattern, sym, 0) == 0)
+       break;
+      expr = expr->next;
     }
 
-  return result;
+out_ret:
+  if (cxx_sym != sym)
+    free ((char *) cxx_sym);
+  if (java_sym != sym)
+    free ((char *) java_sym);
+  return expr;
 }
 
 /* This is called for each variable name or match expression.  */
@@ -5048,18 +5092,19 @@ lang_new_vers_pattern (struct bfd_elf_version_expr *orig,
   ret->pattern = new;
   ret->symver = 0;
   ret->script = 0;
+  ret->wildcard = wildcardp (new);
 
   if (lang == NULL || strcasecmp (lang, "C") == 0)
-    ret->match = lang_vers_match_lang_c;
+    ret->mask = BFD_ELF_VERSION_C_TYPE;
   else if (strcasecmp (lang, "C++") == 0)
-    ret->match = lang_vers_match_lang_cplusplus;
+    ret->mask = BFD_ELF_VERSION_CXX_TYPE;
   else if (strcasecmp (lang, "Java") == 0)
-    ret->match = lang_vers_match_lang_java;
+    ret->mask = BFD_ELF_VERSION_JAVA_TYPE;
   else
     {
       einfo (_("%X%P: unknown language `%s' in version information\n"),
             lang);
-      ret->match = lang_vers_match_lang_c;
+      ret->mask = BFD_ELF_VERSION_C_TYPE;
     }
 
   return ldemul_new_vers_pattern (ret);
@@ -5074,15 +5119,11 @@ lang_new_vers_node (struct bfd_elf_version_expr *globals,
 {
   struct bfd_elf_version_tree *ret;
 
-  ret = xmalloc (sizeof *ret);
-  ret->next = NULL;
-  ret->name = NULL;
-  ret->vernum = 0;
-  ret->globals = globals;
-  ret->locals = locals;
-  ret->deps = NULL;
+  ret = xcalloc (1, sizeof *ret);
+  ret->globals.list = globals;
+  ret->locals.list = locals;
+  ret->match = lang_vers_match;
   ret->name_indx = (unsigned int) -1;
-  ret->used = 0;
   return ret;
 }
 
@@ -5090,6 +5131,102 @@ lang_new_vers_node (struct bfd_elf_version_expr *globals,
 
 static int version_index;
 
+static hashval_t
+version_expr_head_hash (const void *p)
+{
+  const struct bfd_elf_version_expr *e = p;
+
+  return htab_hash_string (e->pattern);
+}
+
+static int
+version_expr_head_eq (const void *p1, const void *p2)
+{
+  const struct bfd_elf_version_expr *e1 = p1;
+  const struct bfd_elf_version_expr *e2 = p2;
+
+  return strcmp (e1->pattern, e2->pattern) == 0;
+}
+
+static void
+lang_finalize_version_expr_head (struct bfd_elf_version_expr_head *head)
+{
+  size_t count = 0;
+  struct bfd_elf_version_expr *e, *next;
+  struct bfd_elf_version_expr **list_loc, **remaining_loc;
+
+  for (e = head->list; e; e = e->next)
+    {
+      if (!e->wildcard)
+       count++;
+      head->mask |= e->mask;
+    }
+
+  if (count)
+    {
+      head->htab = htab_create (count * 2, version_expr_head_hash,
+                               version_expr_head_eq, NULL);
+      list_loc = &head->list;
+      remaining_loc = &head->remaining;
+      for (e = head->list; e; e = next)
+       {
+         next = e->next;
+         if (e->wildcard)
+           {
+             *remaining_loc = e;
+             remaining_loc = &e->next;
+           }
+         else
+           {
+             void **loc = htab_find_slot (head->htab, e, INSERT);
+
+             if (*loc)
+               {
+                 struct bfd_elf_version_expr *e1, *last;
+
+                 e1 = *loc;
+                 last = NULL;
+                 do
+                   {
+                     if (e1->mask == e->mask)
+                       {
+                         last = NULL;
+                         break;
+                       }
+                     last = e1;
+                     e1 = e1->next;
+                   }
+                 while (e1 && strcmp (e1->pattern, e->pattern) == 0);
+
+                 if (last == NULL)
+                   {
+                     /* This is a duplicate.  */
+                     /* FIXME: Memory leak.  Sometimes pattern is not
+                        xmalloced alone, but in larger chunk of memory.  */
+                     /* free (e->pattern); */
+                     free (e);
+                   }
+                 else
+                   {
+                     e->next = last->next;
+                     last->next = e;
+                   }
+               }
+             else
+               {
+                 *loc = e;
+                 *list_loc = e;
+                 list_loc = &e->next;
+               }
+           }
+       }
+      *remaining_loc = NULL;
+      *list_loc = head->remaining;
+    }
+  else
+    head->remaining = head->list;
+}
+
 /* This is called when we know the name and dependencies of the
    version.  */
 
@@ -5117,32 +5254,59 @@ lang_register_vers_node (const char *name,
     if (strcmp (t->name, name) == 0)
       einfo (_("%X%P: duplicate version tag `%s'\n"), name);
 
+  lang_finalize_version_expr_head (&version->globals);
+  lang_finalize_version_expr_head (&version->locals);
+
   /* Check the global and local match names, and make sure there
      aren't any duplicates.  */
 
-  for (e1 = version->globals; e1 != NULL; e1 = e1->next)
+  for (e1 = version->globals.list; e1 != NULL; e1 = e1->next)
     {
       for (t = lang_elf_version_info; t != NULL; t = t->next)
        {
          struct bfd_elf_version_expr *e2;
 
-         for (e2 = t->locals; e2 != NULL; e2 = e2->next)
-           if (strcmp (e1->pattern, e2->pattern) == 0)
-             einfo (_("%X%P: duplicate expression `%s' in version information\n"),
-                    e1->pattern);
+         if (t->locals.htab && e1->wildcard == 0)
+           {
+             e2 = htab_find (t->locals.htab, e1);
+             while (e2 && strcmp (e1->pattern, e2->pattern) == 0)
+               {
+                 if (e1->mask == e2->mask)
+                   einfo (_("%X%P: duplicate expression `%s' in version information\n"),
+                          e1->pattern);
+                 e2 = e2->next;
+               }
+           }
+         else if (e1->wildcard)
+           for (e2 = t->locals.remaining; e2 != NULL; e2 = e2->next)
+             if (strcmp (e1->pattern, e2->pattern) == 0 && e1->mask == e2->mask)
+               einfo (_("%X%P: duplicate expression `%s' in version information\n"),
+                      e1->pattern);
        }
     }
 
-  for (e1 = version->locals; e1 != NULL; e1 = e1->next)
+  for (e1 = version->locals.list; e1 != NULL; e1 = e1->next)
     {
       for (t = lang_elf_version_info; t != NULL; t = t->next)
        {
          struct bfd_elf_version_expr *e2;
 
-         for (e2 = t->globals; e2 != NULL; e2 = e2->next)
-           if (strcmp (e1->pattern, e2->pattern) == 0)
-             einfo (_("%X%P: duplicate expression `%s' in version information\n"),
-                    e1->pattern);
+         if (t->globals.htab && e1->wildcard == 0)
+           {
+             e2 = htab_find (t->globals.htab, e1);
+             while (e2 && strcmp (e1->pattern, e2->pattern) == 0)
+               {
+                 if (e1->mask == e2->mask)
+                   einfo (_("%X%P: duplicate expression `%s' in version information\n"),
+                          e1->pattern);
+                 e2 = e2->next;
+               }
+           }
+         else if (e1->wildcard)
+           for (e2 = t->globals.remaining; e2 != NULL; e2 = e2->next)
+             if (strcmp (e1->pattern, e2->pattern) == 0 && e1->mask == e2->mask)
+               einfo (_("%X%P: duplicate expression `%s' in version information\n"),
+                      e1->pattern);
        }
     }