There are some methods to get around this. For example, there's an ELF extension called STT_GNU_IFUNC. It allows a symbol to be resolved at load time using a custom resolver function. This avoids the problem of figuring out which code-path to use on every invocation.
For example, you could have a function
void hash(char *out, const char *in);
with two different possible implementations: a slow one using common instructions, and a fast one using exotic instructions.
You can then can have a resolver like this: void fast_hash(char *out, const char *in);
void slow_hash(char *out, const char *in);
void (*resolve_hasher(void))(char *, const char *)
{
if (cpuSupportsFancyInstructions()) {
return &fast_hash;
} else {
return &slow_hash;
}
}