diff options
author | Tomas Kukosa <tomas.kukosa@siemens.com> | 2007-11-28 10:18:16 +0000 |
---|---|---|
committer | Tomas Kukosa <tomas.kukosa@siemens.com> | 2007-11-28 10:18:16 +0000 |
commit | ff7c20938ea3da3ae7d494b35809a2387f497dec (patch) | |
tree | cff4fe924fdcfa5a5f882426b40e7fef64045810 | |
parent | 60686647d2aefff5065a8869899d15d212115d03 (diff) |
Various changes with focus to startup speedup
The startup timeout on Win32 is reduced to 80% without assembler and to 50% with assembler usage (which is optional)
proto.c
- do not look up in filed tree and inserts in two steps but do it at once
- next few small speedups
- some often called elementary functions can be optionally implemented in assembler
- dispart some functions to see more exact result from profiling
packet-tpnc.c
- do not reallocate memory for each filed
svn path=/trunk/; revision=23643
-rw-r--r-- | Makefile.nmake | 1 | ||||
-rw-r--r-- | config.nmake | 9 | ||||
-rw-r--r-- | epan/Makefile.am | 4 | ||||
-rw-r--r-- | epan/Makefile.nmake | 16 | ||||
-rw-r--r-- | epan/asm_utils.c | 67 | ||||
-rw-r--r-- | epan/asm_utils.h | 37 | ||||
-rw-r--r-- | epan/asm_utils_win32_x86.asm | 147 | ||||
-rw-r--r-- | epan/dissectors/packet-tpncp.c | 17 | ||||
-rw-r--r-- | epan/proto.c | 110 |
9 files changed, 360 insertions, 48 deletions
diff --git a/Makefile.nmake b/Makefile.nmake index 6983f5997d..1a993d341b 100644 --- a/Makefile.nmake +++ b/Makefile.nmake @@ -592,6 +592,7 @@ REQUIRED_TOOLS=\ /usr/bin/find \ $(PERL) \ $(PYTHON) \ + $(NASM) \ sed \ unzip \ wget diff --git a/config.nmake b/config.nmake index 709500696d..fe02e279b4 100644 --- a/config.nmake +++ b/config.nmake @@ -80,6 +80,15 @@ MSVC_VARIANT=MSVC6 # Visual C++ 8.0, _MSC_VER 1400, msvcr80.dll #MSVC_VARIANT=DOTNET20 +# +# Optional: To compile some time critical code from assembler instead of C +# +# If you have the NASM compiler, set this to the NASM executable. +# +# If you don't have NASM, comment this line out, so that NASM +# isn't defined. +# +#NASM=c:\progs\nasm\nasm.exe ##### Libraries ##### diff --git a/epan/Makefile.am b/epan/Makefile.am index 902c76b40f..a566f076d9 100644 --- a/epan/Makefile.am +++ b/epan/Makefile.am @@ -74,7 +74,9 @@ EXTRA_libwireshark_la_SOURCES = \ g_ascii_strtoull.c \ g_ascii_strtoull.h \ inet_aton.c \ - inet_aton.h + inet_aton.h \ + asm_utils.c \ + asm_utils.h EXTRA_DIST = \ diam_dict.l \ diff --git a/epan/Makefile.nmake b/epan/Makefile.nmake index fd8772c1e7..7951a54179 100644 --- a/epan/Makefile.nmake +++ b/epan/Makefile.nmake @@ -55,7 +55,13 @@ EXTRA_OBJECTS = \ inet_pton.obj \ inet_ntop.obj \ mkstemp.obj \ - strptime.obj + strptime.obj \ +!IFDEF NASM + asm_utils_win32_x86.obj +!ELSE + asm_utils.obj +!ENDIF + !IFDEF DOXYGEN @@ -245,6 +251,14 @@ reassemble_test_install: if exist reassemble_test.exe xcopy reassemble_test.exe $(INSTALL_DIR) /d +# +# Compile some time crtical code from assembler if NASM available +# +!IFDEF NASM +asm_utils_win32_x86.obj: asm_utils_win32_x86.asm + $(NASM) -f win32 -o $@ $? +!ENDIF + # (Windows only) Copy some sources from /trunk to /trunk/epan. # It is a cleaner to compile these sources seperately with this makefile than # using the object code compiled by the makefile in /trunk for both dynamically diff --git a/epan/asm_utils.c b/epan/asm_utils.c new file mode 100644 index 0000000000..98044c14bc --- /dev/null +++ b/epan/asm_utils.c @@ -0,0 +1,67 @@ +/* asm_utils.c + * Functions optionally implemented in assembler + * + * $Id$ + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <string.h> +#include <ctype.h> +#include <glib.h> + +#include "asm_utils.h" + +/* String comparison func for dfilter_token GTree */ +int +wrs_strcmp(gconstpointer a, gconstpointer b) +{ + return strcmp((const char*)a, (const char*)b); +} + +int +wrs_strcmp_with_data(gconstpointer a, gconstpointer b, gpointer user_data _U_) +{ + return strcmp((const char*)a, (const char*)b); +} + +guchar +wrs_check_charset(const guchar table[256], const char *str) +{ + const char *p = str; + guchar c; + + do { + c = *(p++); + } while (table[c]); + return c; +} + +guint +wrs_str_hash(gconstpointer v) +{ + /* 31 bit hash function */ + const signed char *p = v; + guint32 h = *p; + if (h) + for (p += 1; *p != '\0'; p++) + h = (h << 5) - h + *p; + return h; +} + diff --git a/epan/asm_utils.h b/epan/asm_utils.h new file mode 100644 index 0000000000..21a2ef55eb --- /dev/null +++ b/epan/asm_utils.h @@ -0,0 +1,37 @@ +/* asm_utils.h + * Functions optionally implemented in assembler + * + * $Id$ + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef __ASM_UTILS_H__ +#define __ASM_UTILS_H__ + +int wrs_strcmp(gconstpointer a, gconstpointer b); +int wrs_strcmp_with_data(gconstpointer a, gconstpointer b, gpointer user_data); + +guchar wrs_check_charset(const guchar table[256], const char *str); + +guint wrs_str_hash(gconstpointer v); + +/* int wrs_count_bitshift(guint32 bitmask); */ + +#endif /* __ASM_UTILS_H__ */ diff --git a/epan/asm_utils_win32_x86.asm b/epan/asm_utils_win32_x86.asm new file mode 100644 index 0000000000..767d303994 --- /dev/null +++ b/epan/asm_utils_win32_x86.asm @@ -0,0 +1,147 @@ +; asm_utils_win32_x86.c +; Functions optionally implemented in assembler +; +; $Id$ +; +; Wireshark - Network traffic analyzer +; By Gerald Combs <gerald@wireshark.org> +; Copyright 1998 Gerald Combs +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation; either version 2 +; of the License, or (at your option) any later version. +; +; This program is distributed in the hope that it will be useful, +; but WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +; GNU General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, write to the Free Software +; Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +; + +SECTION .text + +GLOBAL _wrs_strcmp +GLOBAL _wrs_strcmp_with_data +GLOBAL _wrs_check_charset +GLOBAL _wrs_str_hash + + align 16 +_wrs_strcmp +_wrs_strcmp_with_data + mov ecx, dword [esp + 4] ; a + mov edx, dword [esp + 8] ; b + push ebx +CMP_LOOP: + mov eax, dword [ecx] + mov ebx, dword [edx] + cmp al, bl + jne CMP_NEQ_END + or al, al + jz CMP_EQ_END + cmp ah, bh + jne CMP_NEQ_END + or ah, ah + jz CMP_EQ_END + shr eax, 16 + shr ebx, 16 + add ecx, byte 4 + add edx, byte 4 + cmp al, bl + jne CMP_NEQ_END + or al, al + jz CMP_EQ_END + cmp ah, bh + jne CMP_NEQ_END + or ah, ah + jnz CMP_LOOP +CMP_EQ_END: + pop ebx + xor eax, eax + retn +CMP_NEQ_END: + ; returns 1 or -1 based on CF flag from the last comparision + sbb eax, eax + pop ebx + shl eax, 1 + inc eax + retn + + align 16 +_wrs_check_charset: + mov edx, dword [esp + 4] ; table + mov ecx, dword [esp + 8] ; str + push edi + push ebx + mov edi, edx + mov bl, byte 0xFF +CHK_LOOP: + mov eax, dword [ecx] + movzx edx, al + test bl, byte [edi+edx] + jz CHK_AL_END + movzx edx, ah + test bl, byte [edi+edx] + jz CHK_AH_END + shr eax, 16 + add ecx, byte 4 + movzx edx, al + test bl, byte [edi+edx] + jz CHK_AL_END + movzx edx, ah + test bl, byte [edi+edx] + jnz CHK_LOOP +CHK_AH_END + movzx eax, ah + pop ebx + pop edi + retn +CHK_AL_END + movzx eax, al + pop ebx + pop edi + retn + + align 16 +_wrs_str_hash: + mov edx, dword [esp + 4] ; v + push ebx + xor eax, eax + mov ecx, dword [edx] + or cl, cl + movzx ebx, cl + jz HASH_END +HASH_LOOP: + sub ebx, eax + shl eax, 5 + add eax, ebx + or ch, ch + movzx ebx, ch + jz HASH_END + sub ebx, eax + shl eax, 5 + add eax, ebx + shr ecx, 16 + add edx, byte 4 + or cl, cl + movzx ebx, cl + jz HASH_END + sub ebx, eax + shl eax, 5 + add eax, ebx + or ch, ch + movzx ebx, ch + jz HASH_END + sub ebx, eax + shl eax, 5 + add eax, ebx + mov ecx, dword [edx] + or cl, cl + movzx ebx, cl + jnz HASH_LOOP +HASH_END: + pop ebx + retn diff --git a/epan/dissectors/packet-tpncp.c b/epan/dissectors/packet-tpncp.c index b696a5e9db..acc3398068 100644 --- a/epan/dissectors/packet-tpncp.c +++ b/epan/dissectors/packet-tpncp.c @@ -123,6 +123,7 @@ static value_string tpncp_enums_id_vals[MAX_ENUMS_NUM][MAX_ENUM_ENTRIES]; static gchar *tpncp_enums_name_vals[MAX_ENUMS_NUM]; static gint hf_size = 1; +static gint hf_allocated = 0; static hf_register_info *hf = NULL; static hf_register_info hf_tpncp[] = { { @@ -575,9 +576,10 @@ static gint init_tpncp_data_fields_info(tpncp_data_field_info *data_fields_info, if (!was_registered) { /* Register non-standard data should be done only once. */ + hf_allocated = hf_size+array_length(hf_tpncp)-1; + if ((hf = (hf_register_info *)g_realloc(hf, hf_allocated * sizeof(hf_register_info))) == NULL) + return (-1); for (index = 0; index < array_length(hf_tpncp); index++) { - if ((hf = (hf_register_info *)realloc(hf, hf_size*sizeof(hf_register_info))) == NULL) - return (-1); memcpy(hf + (hf_size - 1), hf_tpncp + index, sizeof(hf_register_info)); hf_size++; } @@ -616,8 +618,8 @@ static gint init_tpncp_data_fields_info(tpncp_data_field_info *data_fields_info, current_data_id = data_id; } else { - if ((current_tpncp_data_field_info->p_next = - (tpncp_data_field_info *)calloc(1, sizeof(tpncp_data_field_info))) + if ((current_tpncp_data_field_info->p_next = + (tpncp_data_field_info *)g_malloc0(sizeof(tpncp_data_field_info))) == NULL) return (-1); current_tpncp_data_field_info = current_tpncp_data_field_info->p_next; @@ -655,8 +657,11 @@ static gint init_tpncp_data_fields_info(tpncp_data_field_info *data_fields_info, break; } /* Register initialized hf_register_info in global database. */ - if ((hf = (hf_register_info *)realloc(hf, hf_size*sizeof(hf_register_info))) == NULL) - return (-1); + if (hf_size > hf_allocated) { + hf_allocated += 1024; + if ((hf = (hf_register_info *)g_realloc(hf, hf_allocated * sizeof(hf_register_info))) == NULL) + return (-1); + } memcpy(hf + hf_size - 1, &hf_entr, sizeof(hf_register_info)); hf_size++; current_tpncp_data_field_info->tpncp_data_field_sign = tpncp_data_field_sign; diff --git a/epan/proto.c b/epan/proto.c index 48d567c447..bce9b59082 100644 --- a/epan/proto.c +++ b/epan/proto.c @@ -44,6 +44,7 @@ #include "tvbuff.h" #include "emem.h" #include "charsets.h" +#include "asm_utils.h" #ifdef NEED_G_ASCII_STRCASECMP_H #include "g_ascii_strcasecmp.h" @@ -68,6 +69,19 @@ struct ptvcursor { gint offset; }; +/* Candidates for assembler */ +int +wrs_count_bitshift(guint32 bitmask) +{ + int bitshift = 0; + + while ((bitmask & (1 << bitshift)) == 0) + bitshift++; + return bitshift; +} + + + #if GLIB_MAJOR_VERSION < 2 static void *discard_const(const void *const_ptr) { @@ -209,9 +223,6 @@ proto_tree_set_uint64_tvb(field_info *fi, tvbuff_t *tvb, gint start, guint lengt static int proto_register_field_init(header_field_info *hfinfo, int parent); -/* Comparision function for tree insertion. A wrapper around strcmp() */ -static int g_strcmp(gconstpointer a, gconstpointer b); - /* special-case header field used within proto.c */ int hf_text_only = -1; @@ -287,6 +298,12 @@ gpa_hfinfo_t gpa_hfinfo; /* Balanced tree of abbreviations and IDs */ static GTree *gpa_name_tree = NULL; +static header_field_info *same_name_hfinfo; + +static void save_same_name_hfinfo(gpointer data) +{ + same_name_hfinfo = (header_field_info*)data; +} /* Points to the first element of an array of Booleans, indexed by a subtree item type; that array element is TRUE if subtrees of @@ -326,8 +343,8 @@ proto_init(void (register_all_protocols)(register_cb cb, gpointer client_data), proto_names = g_hash_table_new(g_int_hash, g_int_equal); - proto_short_names = g_hash_table_new(g_int_hash, g_int_equal); - proto_filter_names = g_hash_table_new(g_int_hash, g_int_equal); + proto_short_names = g_hash_table_new(g_str_hash, g_str_equal); + proto_filter_names = g_hash_table_new(g_str_hash, g_str_equal); proto_cleanup(); @@ -339,7 +356,7 @@ proto_init(void (register_all_protocols)(register_cb cb, gpointer client_data), gpa_hfinfo.len=0; gpa_hfinfo.allocated_len=0; gpa_hfinfo.hfi=NULL; - gpa_name_tree = g_tree_new(g_strcmp); + gpa_name_tree = g_tree_new_full(wrs_strcmp_with_data, NULL, NULL, save_same_name_hfinfo); /* Initialize the ftype subsystem */ ftypes_initialize(); @@ -386,13 +403,6 @@ proto_init(void (register_all_protocols)(register_cb cb, gpointer client_data), memset(tree_is_expanded, 0, num_tree_types*sizeof (gboolean)); } -/* String comparison func for dfilter_token GTree */ -static int -g_strcmp(gconstpointer a, gconstpointer b) -{ - return strcmp((const char*)a, (const char*)b); -} - void proto_cleanup(void) { @@ -3498,14 +3508,12 @@ proto_register_protocol(const char *name, const char *short_name, const char *fi } g_hash_table_insert(proto_names, key, (gpointer)name); - key = g_malloc (sizeof(gint)); - *key = g_str_hash(short_name); - existing_name = g_hash_table_lookup(proto_short_names, key); + existing_name = g_hash_table_lookup(proto_short_names, (gpointer)short_name); if (existing_name != NULL) { g_error("Duplicate protocol short_name \"%s\"!" " This might be caused by an inappropriate plugin or a development error.", short_name); } - g_hash_table_insert(proto_short_names, key, (gpointer)short_name); + g_hash_table_insert(proto_short_names, (gpointer)short_name, (gpointer)short_name); found_invalid = FALSE; for (i = 0; i < strlen(filter_name); i++) { @@ -3519,14 +3527,12 @@ proto_register_protocol(const char *name, const char *short_name, const char *fi " Allowed are lower characters, digits, '-', '_' and '.'." " This might be caused by an inappropriate plugin or a development error.", filter_name); } - key = g_malloc (sizeof(gint)); - *key = g_str_hash(filter_name); - existing_name = g_hash_table_lookup(proto_filter_names, key); + existing_name = g_hash_table_lookup(proto_filter_names, (gpointer)filter_name); if (existing_name != NULL) { g_error("Duplicate protocol filter_name \"%s\"!" " This might be caused by an inappropriate plugin or a development error.", filter_name); } - g_hash_table_insert(proto_filter_names, key, (gpointer)filter_name); + g_hash_table_insert(proto_filter_names, (gpointer)filter_name, (gpointer)filter_name); /* Add this protocol to the list of known protocols; the list is sorted by protocol short name. */ @@ -3765,9 +3771,29 @@ proto_register_field_array(int parent, hf_register_info *hf, int num_records) } } -static int -proto_register_field_init(header_field_info *hfinfo, int parent) -{ +/* chars allowed in field abbrev */ +static +const guchar fld_abbrev_chars[256] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x00-0x0F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x10-0x1F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, /* 0x20-0x2F '-', '.' */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, /* 0x30-0x3F '0'-'9' */ + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x40-0x4F 'A'-'O' */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, /* 0x50-0x5F 'P'-'Z', '_' */ + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x60-0x6F 'a'-'o' */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, /* 0x70-0x7F 'p'-'z' */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x80-0x8F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x90-0x9F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xA0-0xAF */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xB0-0xBF */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xC0-0xCF */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xD0-0xDF */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xE0-0xEF */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xF0-0xFF */ +}; + +/* temporary function containing assert part for easier profiling */ +static void tmp_fld_check_assert(header_field_info *hfinfo) { /* The field must have a name (with length > 0) */ DISSECTOR_ASSERT(hfinfo->name && hfinfo->name[0]); @@ -3812,10 +3838,17 @@ proto_register_field_init(header_field_info *hfinfo, int parent) default: break; } +} + +static int +proto_register_field_init(header_field_info *hfinfo, int parent) +{ + + tmp_fld_check_assert(hfinfo); + /* if this is a bitfield, compute bitshift */ if (hfinfo->bitmask) { - while ((hfinfo->bitmask & (1 << hfinfo->bitshift)) == 0) - hfinfo->bitshift++; + hfinfo->bitshift = wrs_count_bitshift(hfinfo->bitmask); } hfinfo->parent = parent; @@ -3839,19 +3872,17 @@ proto_register_field_init(header_field_info *hfinfo, int parent) /* if we have real names, enter this field in the name tree */ if ((hfinfo->name[0] != 0) && (hfinfo->abbrev[0] != 0 )) { - header_field_info *same_name_hfinfo, *same_name_next_hfinfo; - const char *p; + header_field_info *same_name_next_hfinfo; guchar c; /* Check that the filter name (abbreviation) is legal; * it must contain only alphanumerics, '-', "_", and ".". */ - for (p = hfinfo->abbrev; (c = *p) != '\0'; p++) { - if (!(isalnum(c) || c == '-' || c == '_' || c == '.')) { - fprintf(stderr, "OOPS: '%c' in '%s'\n", c, hfinfo->abbrev); - DISSECTOR_ASSERT(isalnum(c) || c == '-' || c == '_' || - c == '.'); - } + c = wrs_check_charset(fld_abbrev_chars, hfinfo->abbrev); + if (c) { + fprintf(stderr, "OOPS: '%c' in '%s'\n", c, hfinfo->abbrev); + DISSECTOR_ASSERT(!c); } + /* We allow multiple hfinfo's to be registered under the same * abbreviation. This was done for X.25, as, depending * on whether it's modulo-8 or modulo-128 operation, @@ -3859,11 +3890,11 @@ proto_register_field_init(header_field_info *hfinfo, int parent) * a byte, and we want to be able to refer to that field * with one name regardless of whether the packets * are modulo-8 or modulo-128 packets. */ -#if GLIB_MAJOR_VERSION < 2 - same_name_hfinfo = g_tree_lookup(gpa_name_tree, discard_const(hfinfo->abbrev)); -#else - same_name_hfinfo = g_tree_lookup(gpa_name_tree, hfinfo->abbrev); -#endif + same_name_hfinfo = NULL; + g_tree_insert(gpa_name_tree, (gpointer) (hfinfo->abbrev), hfinfo); + /* if it is already present + * the previous hfinfo with the same name is saved + * to same_name_hfinfo by value destroy callback */ if (same_name_hfinfo) { /* There's already a field with this name. * Put it after that field in the list of @@ -3882,7 +3913,6 @@ proto_register_field_init(header_field_info *hfinfo, int parent) same_name_hfinfo->same_name_next = hfinfo; hfinfo->same_name_prev = same_name_hfinfo; } - g_tree_insert(gpa_name_tree, (gpointer) (hfinfo->abbrev), hfinfo); } return hfinfo->id; |