Hi, I've been looking into existing function multiversioning implementations (while working to add support for fmv in GCC on aarch64). It seems there are various inconsistencies among current implementations, and it's unclear to me which (if any) of these differences could be problematic. There's a list of observations of current behaviour at the end of the email.
I've also seen the GCC documentation for the ifunc attribute [1]. This states that "the indirect function needs to be defined in the same translation unit as the resolver function". This is not how function multiversioning is currently implemented. Instead, the resolver functions are added to the translation units of every caller. What is the reason for the restriction specified in the documentation? Does this mean that current function multiversioning implementations are subtly broken when used across different translation units? On a related point - how important is the mangling of the symbols involved (for the function implementations, the resolver, and the symbol that will be resolved at load time)? We're not consistent about how we mangle these on any target - even the explicit mangling requirements in the aarch64 Beta specification [2] apply only to the function implementations, and make no mention of how the other symbols should be mangled. It seems to me that things would have been much simpler if the resolver were always in the same translation unit as the implementations, and the symbol that is resolved at run time used the original function symbol name. This would allow the mangling of the function versions and the resolver function to be a hidden implementation detail, with no need to specify it as part of an explicit or implicit ABI. It would add an explicit restriction that all function implementations must be in the same translation unit, but it seems that might be required for absolute correctness anyway. So, how much of this stuff matters? Is some of this stuff actually broken? Will we have horrible compatibility issues if we try to fix some of it? Thanks, Andrew (cc'd to Daniel Kiss and Pavel Iliin, who have been working on the aarch64 function multiversioning specification and LLVM implementation respectively.) --- Current behaviour: For all combinations of the following, I checked to see whether the compiler would emit a resolver function: a) GCC / Clang b) aarch64 / rs6000 / i386 c) target_clones attribute / target (or target_version) attribute d) function is fully implemented / function is just declared e) function is referenced / function is unreferenced Function multiversioning is not supported for rs6000 (power) in Clang. For aarch64, I used my WIP implementation in GCC. If the function was referenced, then the compiler always emitted a resolver. However, when the function is declared but not implemented, then GCC for rs6000 would emit a resolver that always used the default version. If the function was not referenced, then the compiler usually did not emit a resolver. The exceptions to this were both in GCC, with resolvers being emitted both for rs6000 when using an implemented function with target_clones, and for aarch64 (with my WIP patch) when using an implementations with target_version. There are also many discrepancies in the mangling used in the various symbol names: For the symbol that will be resolved at load time, Clang always uses the original name and append ".ifunc". GCC leaves the symbol name unchanged when using target_clones, but adds a second layer of C++ mangling when using target attribute versions. For the resolver function, the symbol name is always generated by appending ".resolver" to the original mangled function name. For the individual versions, the following changes are made: - Clang for i386 numbers the versions when using target_clones (by appending ".0", ".1", etc.). GCC used to share this behaviour, but the numbering was removed for GCC 12 (commit bfc9250e). - GCC for i386, when using target_clones, will substitute any non-alphanumeric character with "_" when formign the suffix. E.g, an "sse4.2" version of foo has a symbol name of "_Z3foov.sse4.2" using target attributes, but "_Z3foov.sse4_2" using target_clones. - GCC for rs6000 has no target-defined mangling for versions using the "target" attribute, so the compiler output will fail to assemble due to duplicate symbol names. However, there seem to be no errors or warnings to detect this at an earlier stage. (Incidentally, is there any specific documentation for rs6000 function multiversioning? I was surprised to discover during testing that the resolver was ignoring some of my function clones, until I found the list of supported multiversioning targets in rs6000_clone_map.) --- [1] https://gcc.gnu.org/onlinedocs/gcc-13.2.0/gcc/Common-Function-Attributes.html#index-ifunc-function-attribute [2] https://github.com/ARM-software/acle/blob/main/main/acle.md#function-multi-versioning