This is the mail archive of the
libc-alpha@sourceware.org
mailing list for the glibc project.
[RFC] Make Iconv aware of hwcaps
- From: Andreas Krebbel <krebbel at linux dot vnet dot ibm dot com>
- To: libc-alpha at sourceware dot org
- Date: Wed, 28 Jan 2009 09:38:51 +0100
- Subject: [RFC] Make Iconv aware of hwcaps
Hello,
I'm working on a few ICONV charset conversion modules for S/390. S/390
provides a set of instructions which could do the job much faster than
the common software loops. Unfortunately these instructions have been
added in several stages. So there are CPUs out there supporting none, a
few or all of the necessary instructions. The idea is to express the
availability of the instructions using the hardware capabilities bitmap
in order to load certain iconv modules only if the current hardware is
actually capable of executing them. The existing dlopen mechanism is
already able to deal with this by adding search paths to the name of a
shared object if the object is specified without a path. Unfortunately
this does not apply to the iconv stuff. The iconv modules currently are
always loaded by passing a fully qualified path to the dlopen call. The
path of an iconv module consists of the directory where the
gconv_modules file has been found plus the module name.
One point is that I don't want to implement a software variant of every
conversion S/390 will be able to do in hardware. That's why I already
while reading in the gconv_module file would like to check if there is a
specialized version of the iconv module available and if not would just
ignore the line in the gconv_modules file and let the iconv algorithm
find a different way to perform the conversion.
I've hacked together a small patch extending the iconv module load
mechanism with the hwcaps paths and would like to hear your opinion
about the general idea.
Jakub mentioned that this might also be solved using the STT_IFUNC
mechanism. What's the status of this feature? Is it planned to be
integrated in the near future?
Bye,
-Andreas-
Index: libc/iconv/gconv_conf.c
===================================================================
--- libc.orig/iconv/gconv_conf.c
+++ libc/iconv/gconv_conf.c
@@ -31,6 +31,7 @@
#include <string.h>
#include <unistd.h>
#include <sys/param.h>
+#include <ldsodefs.h>
#include <bits/libc-lock.h>
#include <gconv_int.h>
@@ -52,6 +53,11 @@ static const struct path_elem empty_path
along the path. */
static const char gconv_conf_filename[] = "gconv-modules";
+/* List of the hardware capabilities we might end up using. */
+static const struct r_strlenpair *capstr = NULL;
+static size_t ncapstr = 0;
+static size_t max_capstrlen = 0;
+
/* Filename extension for the modules. */
#ifndef MODULE_EXT
# define MODULE_EXT ".so"
@@ -242,6 +248,41 @@ insert_module (struct gconv_module *newp
*rootp = newp;
}
+/* Generate a path using DIRECTORY and MODULE_NAME and write it into
+ RESULT. Between DIRECTORY and MODULE_NAME the function puts the
+ most specific hwcaps path where the module can be found in. The
+ function return false if the module hasn't been found at all. As
+ RESULT the caller has to specify a buffer at least as big as
+ dir_len + mod_len + max_capstrlen. */
+static int
+internal_function
+expand_hwcap_paths (char *result,
+ const char *directory, const size_t dir_len,
+ const char *module_name, const size_t mod_len)
+{
+ char *path;
+ char *ins_point;
+ struct stat64 st;
+ int i;
+
+ path = alloca (dir_len + mod_len + max_capstrlen);
+ ins_point = __mempcpy (path, directory, dir_len);
+
+ for (i = 0; i < ncapstr; i++)
+ {
+ memcpy (ins_point, capstr[i].str, capstr[i].len);
+ memcpy (ins_point + capstr[i].len, module_name, mod_len);
+ ins_point[capstr[i].len + mod_len] = 0;
+
+ if (__xstat64 (_STAT_VER, path, &st) == 0)
+ {
+ memcpy (result, path, dir_len + mod_len + capstr[i].len + 1);
+ return 1;
+ }
+ }
+
+ return 0;
+}
/* Add new module. */
static void
@@ -328,10 +369,13 @@ add_module (char *rp, const char *direct
new_module = (struct gconv_module *) calloc (1,
sizeof (struct gconv_module)
+ (wp - from)
- + dir_len + need_ext);
+ + dir_len + need_ext
+ + (dir_len ? max_capstrlen : 0));
if (new_module != NULL)
{
char *tmp;
+ char *mod_name = module;
+ size_t mod_name_len = wp - module + need_ext;
new_module->from_string = tmp = (char *) (new_module + 1);
tmp = __mempcpy (tmp, from, to - from);
@@ -344,19 +388,122 @@ add_module (char *rp, const char *direct
new_module->module_name = tmp;
- if (dir_len != 0)
- tmp = __mempcpy (tmp, directory, dir_len);
-
- tmp = __mempcpy (tmp, module, wp - module);
-
if (need_ext)
- memcpy (tmp - 1, gconv_module_ext, sizeof (gconv_module_ext));
+ {
+ mod_name = alloca (mod_name_len);
+ memcpy (mod_name, module, wp - module - 1);
+ memcpy (mod_name + (wp - module) - 1, gconv_module_ext,
+ sizeof (gconv_module_ext));
+ }
+
+ if (dir_len)
+ {
+ /* The module name does not contain a path. So try to
+ prepend hwcap paths. */
+ if (!expand_hwcap_paths (tmp,
+ directory, dir_len,
+ mod_name, mod_name_len))
+ {
+ /* The module could not be found in any of the hwcap
+ dirs or in the gconv conf file path - ignore it. */
+ free (new_module);
+ return;
+ }
+ }
+ else
+ /* For module name with path information the hwcap dirs are
+ ignored. */
+ memcpy (tmp, mod_name, mod_name_len);
/* Now insert the new module data structure in our search tree. */
insert_module (new_module, 1);
}
}
+/* Generate a list of hwcaps path and store it into the file scope
+ variables CAPSTR, NCAPSTR and MAX_CAPSTRLEN. The list will only
+ contain hwcaps which actually exist when prepending them with
+ DIRECTORY. */
+static void
+internal_function
+prepare_hwcap_dirs (const char *directory, size_t dir_len)
+{
+ int i;
+ char *str = NULL;
+ struct stat64 st;
+ const struct r_strlenpair *temp_capstr = NULL;
+ struct r_strlenpair *new_capstr = NULL;
+ size_t temp_ncapstr, temp_max_capstrlen;
+ int *found_dirs = NULL;
+ char *strbuf = NULL;
+ int total;
+
+ /* This function in the dynamic loader generates a compressed list
+ of paths starting with the most specific path ending with an
+ empty path. */
+ temp_capstr = GLRO(dl_important_hwcaps) (GLRO(dl_platform),
GLRO(dl_platformlen),
+ &temp_ncapstr, &temp_max_capstrlen);
+
+ if (!temp_capstr)
+ return;
+
+ str = malloc (dir_len + temp_max_capstrlen + 1);
+
+ if (!str)
+ goto fail1;
+
+ found_dirs = malloc (temp_ncapstr * sizeof (int));
+
+ if (!found_dirs)
+ goto fail2;
+
+ total = ncapstr = max_capstrlen = 0;
+ memcpy (str, directory, dir_len);
+
+ /* Determine the existing paths and calculate the total size to be
+ allocated for the result array. */
+ for (i = 0; i < temp_ncapstr; i++)
+ {
+ memcpy (&str[dir_len], temp_capstr[i].str, temp_capstr[i].len);
+ str[temp_capstr[i].len + dir_len] = 0;
+
+ if (__xstat64 (_STAT_VER, str, &st) == 0
+ && S_ISDIR (st.st_mode))
+ {
+ found_dirs[ncapstr++] = i;
+ total += temp_capstr[i].len + 1;
+ if (temp_capstr[i].len > max_capstrlen)
+ max_capstrlen = temp_capstr[i].len;
+ }
+ }
+
+ if (!ncapstr)
+ goto fail3;
+
+ new_capstr = malloc (ncapstr * sizeof (struct r_strlenpair) + total);
+ if (!new_capstr)
+ goto fail3;
+
+ strbuf = (char*)(new_capstr + ncapstr);
+
+ for (i = 0; i < ncapstr; i++)
+ {
+ new_capstr[i].len = temp_capstr[found_dirs[i]].len;
+ new_capstr[i].str = strbuf;
+ strbuf = __mempcpy (strbuf, temp_capstr[found_dirs[i]].str,
+ new_capstr[i].len);
+ *(strbuf++) = 0;
+ }
+
+ capstr = new_capstr;
+
+ fail3:
+ free (found_dirs);
+ fail2:
+ free ((void*)temp_capstr);
+ fail1:
+ free (str);
+}
/* Read the next configuration file. */
static void
@@ -376,6 +523,8 @@ read_conf_file (const char *filename, co
if (fp == NULL)
return;
+ prepare_hwcap_dirs (directory, dir_len);
+
/* No threads reading from this stream. */
__fsetlocking (fp, FSETLOCKING_BYCALLER);
@@ -422,6 +571,11 @@ read_conf_file (const char *filename, co
free (line);
+ ncapstr = 0;
+ max_capstrlen = 0;
+ free (capstr);
+ capstr = NULL;
+
fclose (fp);
}
Index: libc/elf/rtld.c
===================================================================
--- libc.orig/elf/rtld.c
+++ libc/elf/rtld.c
@@ -163,6 +163,7 @@ struct rtld_global_ro _rtld_global_ro at
._dl_open = _dl_open,
._dl_close = _dl_close,
._dl_tls_get_addr_soft = _dl_tls_get_addr_soft,
+ ._dl_important_hwcaps = _dl_important_hwcaps,
#ifdef HAVE_DL_DISCOVER_OSVERSION
._dl_discover_osversion = _dl_discover_osversion
#endif
Index: libc/sysdeps/generic/ldsodefs.h
===================================================================
--- libc.orig/sysdeps/generic/ldsodefs.h
+++ libc/sysdeps/generic/ldsodefs.h
@@ -657,6 +657,9 @@ struct rtld_global_ro
Lmid_t nsid, int argc, char *argv[], char *env[]);
void (*_dl_close) (void *map);
void *(*_dl_tls_get_addr_soft) (struct link_map *);
+ const struct r_strlenpair *(*_dl_important_hwcaps) (const char *, size_t,
+ size_t *, size_t *);
+
#ifdef HAVE_DL_DISCOVER_OSVERSION
int (*_dl_discover_osversion) (void);
#endif