From 1e443b2daf3de617b20c2263d57de8a484b49dbc Mon Sep 17 00:00:00 2001 From: Eric Anholt Date: Wed, 11 Dec 2013 12:55:57 -0800 Subject: [PATCH] Use ifuncs on Linux to avoid dispatch table overhead. These tell the linker to generate GNU_IFUNC relocs that rewrite the PLT entries in the user's address space to point to our resolved GL function, so there's no extra function pointer. It also, as a bonus, cuts 400k out of the library. This requires a toolchain from 2010 or so. Unfortunately, it's going to take a bit more investigation to find out what specific bits are required. Fixes #4 --- README.md | 18 ++++++++++++++++++ src/gen_dispatch.py | 44 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/README.md b/README.md index 57890e2..489943e 100644 --- a/README.md +++ b/README.md @@ -80,3 +80,21 @@ We had to solve some of GLEW's problems for piglit and solving them meant replacing every single piece of GLEW, so we built piglit-dispatch from scratch. And since we wanted to reuse it in other GL-related projects, this is the result. + +Caveats +------- + +* libepoxy isn't intended to be dlopen()ed. On ifunc platforms, + RTLD_NOW would cause early resolution of all GL symbols, and the + following error from reentering the linker: + +``` +Inconsistency detected by ld.so: dl-open.c: 235: dl_open_worker: +Assertion `_dl_debug_initialize (0, args->nsid)->r_state == +RT_CONSISTENT' failed! +``` + +* libepoxy isn't intended to be built statically. It usess ifuncs on + linux, since it can avoid its dispatch table entirely in that case. + Building statically disables ifuncs, dropping you to a + higher-overhead path! diff --git a/src/gen_dispatch.py b/src/gen_dispatch.py index 6a83c46..4bcf4e3 100755 --- a/src/gen_dispatch.py +++ b/src/gen_dispatch.py @@ -560,6 +560,23 @@ class Generator(object): self.outln('}') self.outln('') + def write_ifunc(self, func): + # Writes out the ifunc symbol that will have its PLT entry + # replaced with the resolved GL symbol when it's called. + + # Tell the linker that even if we're in a static build, we need a PLT. + self.outln('__asm__(".type epoxy_{0}, @gnu_indirect_function");'.format(func.name)); + + # Tell the linker that epoxy_glWhatever() needs to be resolved + # by epoxy_ifunc_glWhatever(). + self.outln('void *epoxy_ifunc_{0}(void) __asm__("epoxy_{0}");'.format(func.wrapped_name)) + + # Write out the actual epoxy_ifunc_glWhatever(). + self.outln('{0} void *epoxy_ifunc_{1}(void)'.format(func.public, func.wrapped_name)) + self.outln('{') + self.outln(' return epoxy_{0}_resolver();'.format(func.alias_name)) + self.outln('}') + def write_provider_enums(self): self.outln('enum {0}_provider {{'.format(self.target)) @@ -634,6 +651,22 @@ class Generator(object): self.outln('#include "epoxy/{0}.h"'.format(self.target)) self.outln('') + # ifuncs are a newer-gcc and newer-dynamic-linker feature that + # let the library rewrite the application's PLT entry with our + # resolved function pointer. + # + # Note that dlsym() when we're called from the dynamic linker + # while it's loading libraries will break. So, libepoxy can't + # be loaded with RTLD_NOW when ifuncs are in use. We also + # can't use ifuncs for static builds, since static builds + # resolve just like RTLD_NOW. + self.outln('#if defined(__PIC__) && defined(__linux__)') + self.outln('#define USING_IFUNCS 1') + self.outln('#else') + self.outln('#define USING_IFUNCS 0') + self.outln('#endif') + self.outln('') + self.outln('struct dispatch_table {') for func in self.sorted_functions: # Aliases don't get their own slot, since they use a shared resolver. @@ -646,9 +679,11 @@ class Generator(object): # bottom. (I want the function_ptr_resolver as the first # per-GL-call code, since it's the most interesting to see # when you search for the implementation of a call) + self.outln('#if !USING_IFUNCS') self.outln('static inline struct dispatch_table *') self.outln('get_dispatch_table(void);') self.outln('') + self.outln('#endif') self.write_provider_enums() self.write_provider_enum_strings() @@ -658,6 +693,8 @@ class Generator(object): if not func.alias_func: self.write_function_ptr_resolver(func) + self.outln('#if !USING_IFUNCS') + for func in self.sorted_functions: if not func.alias_func: self.write_dispatch_table_rewrite_stub(func) @@ -681,6 +718,13 @@ class Generator(object): self.outln('}') self.outln('') + self.outln('#else /* !USING_IFUNCS */') + + for func in self.sorted_functions: + self.write_ifunc(func) + + self.outln('#endif /* !USING_IFUNCS */') + argparser = argparse.ArgumentParser(description='Generate GL dispatch wrappers.') argparser.add_argument('files', metavar='file.xml', nargs='+', help='GL API XML files to be parsed') argparser.add_argument('--dir', metavar='dir', required=True, help='Destination directory')