On Mon, 4 Nov 2024, alfie.richa...@arm.com wrote: > /* Subroutine of duplicate_decls. Compare NEWDECL to OLDDECL. > Returns true if the caller should proceed to merge the two, false > if OLDDECL should simply be discarded. As a side effect, issues > @@ -3365,11 +3382,53 @@ pushdecl (tree x) > b->inner_comp = false; > b_use = b; > b_ext = b; > + > + /* Check if the function is part of a group of multiversioned > functions. > + If so, we should check for conflicts or duplicates between any with > + the same version. If there are no conflicts, should just add the new > + binding. */ > + if (b && TREE_CODE (x) == FUNCTION_DECL > + && TREE_CODE (b->decl) == FUNCTION_DECL > + && (DECL_FUNCTION_VERSIONED (b->decl) > + || targetm.target_option.function_versions (x, b->decl))) > + { > + maybe_mark_function_versioned (x); > + for (; b_use; b_use = b_use->shadowed) > + { > + if (!comptypes (type, TREE_TYPE (b_use->decl)) > + || TREE_CODE (b_use->decl) != FUNCTION_DECL) > + { > + /* Found a conflicting declaration. > + duplicate_decls will create teh diagnostics forthis. */
Note typos in this comment: "teh", "forthis". > + b = b_use; > + break; I don't follow the logic you're using here. This loop is going through all bindings for the name in containing scopes, including completely unrelated ones such as object declarations with block scope and automatic storage duration, which looks like it would do the wrong thing. // File scope __attribute__ ((target_version ("default"))) int foo (); { // Block scope int foo; { __attribute__ ((target_version ("default"))) int foo (); __attribute__ ((target_version ("default"))) int foo (); As I read this code, if foo is a multiversioned function then it would find the variable declaration in the intermediate scope and treat it as conflicting. Compare the existing logic looping through scopes for external declarations, which is careful to ignore any declarations not satisfying DECL_FILE_SCOPE_P. There is a lot of complicated logic in pushdecl that it seems a bad idea to bypass for multiversioned functions. For example, everything related to putting declarations with external linkage in the "external scope" in addition to their main scope. I think a patch like this needs to come with a clearly stated semantic model for how the multiversioned functions are meant to be represented inside the front end, and how that translates into consequences for declaration processing, against which the patch can be tested. I suggest some principles like this: * For the purposes of *checking compatible types*, all declarations of the same name - different versions and the dispatcher - should be compared and required to be consistent. Based on the tests you have, it appears at least some of this is taking place - for example, when two declarations have incompatible types. * For the purposes of *combining information from multiple declarations*, at least some of that should happen for different versions as well. For example, if one version has a parameter int (*x)[] and another has a parameter int (*x)[2], the composite type should be formed and used for both versions. * For the purposes of *merging declarations and discarding the new one*, however, only declarations of the same version should be merged. So all effects of diagnose_mismatched_decls should apply between different versions. But only a subset of merge_decls is relevant (such as forming composite types) - and that subset would be relevant in both directions, because both declarations remain in use afterwards. Or if you don't want the complications there, you could define the semantics not to e.g. form composite types between different versions. Given these principles, pretty much all existing logic in pushdecl is relevant for multiversioned functions - twice over. It's fully relevant for matching and merging with other declarations for the same version of the function (but with all the logic that looks for bindings for a given symbol name needing to be adjusted to consider only those for the same version as potentially matching / needing to be merged). And it's mostly relevant for declarations for other versions of the function, to the extent that type compatibility needs to be checked and maybe composite types formed. But maybe we need to start the conceptual design at an earlier stage. In the presence of multiple declarations of different versions of a function, (a) what DECL or DECLs should be recorded as bindings for the function name in the scope in which the declaration appears, and (b) what DECL or DECLs should be present in the IR at all? Note that e.g. lookup_name definitely expects only one binding for a given name (as an ordinary identifier) in a given scope, so it can only find one of the declarations. So any answer you give needs to include also a sensible conclusion on (c) what DECL should be returned by functions such as lookup_name? Maybe only the main (dispatcher?) declaration for a multiversioned function should appear in scopes and pass through pushdecl, and that declaration should then in turn link in some way to the declarations for the other versions? If that's the design, then each time a declaration for a particular version is encountered, it should somehow generate (a) a new DECL for the dispatcher, which is passed through pushdecl to deal with type compatibility checking, composite type formation and ensuring appropriate bindings are present in each scope, and (b) if the previous checks pass, then an internal DECL for the actual version being declared, which would not appear in any scope, but would still get merged with any previous declaration for that version (for composite types, merging attributes, etc.)? -- Joseph S. Myers josmy...@redhat.com