This implements the handling of the clang-form "availability" attribute, which is the most important case used in the the macOS SDKs.
PR c++/109877 gcc/ChangeLog: * config/darwin-protos.h (darwin_handle_weak_import_attribute): New. (darwin_handle_availability_attribute): New. (darwin_attribute_takes_identifier_p): New. * config/darwin.cc (objc_method_decl): New. (enum version_components): New. (parse_version): New. (version_from_version_array): New. (os_version_from_avail_value): New. (NUM_AV_OSES, NUM_AV_CLAUSES): New. (darwin_handle_availability_attribute): New. (darwin_attribute_takes_identifier_p): New. (darwin_override_options): New. * config/darwin.h (TARGET_ATTRIBUTE_TAKES_IDENTIFIER_P): New. Signed-off-by: Iain Sandoe <i...@sandoe.co.uk> --- gcc/config/darwin-protos.h | 9 +- gcc/config/darwin.cc | 343 +++++++++++++++++++++++++++++++++++++ gcc/config/darwin.h | 7 +- 3 files changed, 355 insertions(+), 4 deletions(-) diff --git a/gcc/config/darwin-protos.h b/gcc/config/darwin-protos.h index 9df358ee7d3..0702db25178 100644 --- a/gcc/config/darwin-protos.h +++ b/gcc/config/darwin-protos.h @@ -86,9 +86,12 @@ extern void darwin_asm_lto_end (void); extern void darwin_mark_decl_preserved (const char *); extern tree darwin_handle_kext_attribute (tree *, tree, tree, int, bool *); -extern tree darwin_handle_weak_import_attribute (tree *node, tree name, - tree args, int flags, - bool * no_add_attrs); +extern tree darwin_handle_weak_import_attribute (tree *, tree, tree, int, + bool *); +extern tree darwin_handle_availability_attribute (tree *, tree, tree, + int, bool *); +extern bool darwin_attribute_takes_identifier_p (const_tree); + extern void machopic_output_stub (FILE *, const char *, const char *); extern void darwin_globalize_label (FILE *, const char *); extern void darwin_assemble_visibility (tree, int); diff --git a/gcc/config/darwin.cc b/gcc/config/darwin.cc index 621a94d74a2..8bb4a439996 100644 --- a/gcc/config/darwin.cc +++ b/gcc/config/darwin.cc @@ -29,6 +29,7 @@ along with GCC; see the file COPYING3. If not see #include "cfghooks.h" #include "df.h" #include "memmodel.h" +#include "c-family/c-common.h" /* enum rid. */ #include "tm_p.h" #include "stringpool.h" #include "attribs.h" @@ -49,6 +50,7 @@ along with GCC; see the file COPYING3. If not see #include "optabs.h" #include "flags.h" #include "opts.h" +#include "c-family/c-objc.h" /* for objc_method_decl(). */ /* Fix and Continue. @@ -102,6 +104,7 @@ int darwin_running_cxx; /* Some code-gen now depends on OS major version numbers (at least). */ int generating_for_darwin_version ; +unsigned long current_os_version = 0; /* For older linkers we need to emit special sections (marked 'coalesced') for for weak or single-definition items. */ @@ -2181,6 +2184,96 @@ darwin_handle_kext_attribute (tree *node, tree name, return NULL_TREE; } +enum version_components { MAJOR, MINOR, TINY }; + +/* Parse a version number in x.y.z form and validate it as a macOS + version. Ideally, we'd put this in a common place usable by the + Darwin backend. */ + +static bool +parse_version (unsigned version_array[3], const char *version_str) +{ + size_t version_len; + char *end; + + version_len = strlen (version_str); + if (version_len < 1) + return false; + + /* Version string must consist of digits and periods only. */ + if (strspn (version_str, "0123456789.") != version_len) + return false; + + if (!ISDIGIT (version_str[0]) || !ISDIGIT (version_str[version_len - 1])) + return false; + + version_array[MAJOR] = strtoul (version_str, &end, 10); + version_str = end + ((*end == '.') ? 1 : 0); + if (version_array[MAJOR] > 99) + return false; + + /* Version string must not contain adjacent periods. */ + if (*version_str == '.') + return false; + + version_array[MINOR] = strtoul (version_str, &end, 10); + version_str = end + ((*end == '.') ? 1 : 0); + if (version_array[MINOR] > 99) + return false; + + version_array[TINY] = strtoul (version_str, &end, 10); + if (version_array[TINY] > 99) + return false; + + /* Version string must contain no more than three tokens. */ + if (*end != '\0') + return false; + + return true; +} + +/* Turn a version expressed as maj.min.tiny into an unsigned long + integer representing the value used in macOS availability macros. */ + +static unsigned long +version_from_version_array (unsigned vers[3]) +{ + unsigned long res = 0; + /* Here, we follow the 'modern' / 'legacy' numbering scheme for versions. */ + if (vers[0] > 10 || vers[1] >= 10) + res = vers[0] * 10000 + vers[1] * 100 + vers[2]; + else + { + res = vers[0] * 100; + if (vers[1] > 9) + res += 90; + else + res += vers[1] * 10; + if (vers[2] > 9) + res += 9; + else + res += vers[1]; + } + return res; +} + +/* Extract a macOS version from an availability attribute argument. */ + +static unsigned long +os_version_from_avail_value (tree value) +{ + unsigned long res = 0; + unsigned vers[3] = {0,0,0}; + if (TREE_CODE (value) == STRING_CST) + { + if (parse_version (&vers[0], TREE_STRING_POINTER (value))) + res = version_from_version_array (&vers[0]); + } + else + gcc_unreachable (); + return res; +} + /* Handle a "weak_import" attribute; arguments as in struct attribute_spec.handler. */ @@ -2202,6 +2295,249 @@ darwin_handle_weak_import_attribute (tree *node, tree name, return NULL_TREE; } +#define NUM_AV_OSES 12 +const char *availability_os[NUM_AV_OSES] + = { "macos", "macosx", "ios", "tvos", "watchos", "driverkit", "swift", + "maccatalyst", "xros", "visionos", "android", "zos" }; + +#define NUM_AV_CLAUSES 6 +const char *availability_clause[NUM_AV_CLAUSES] + = { "unavailable", "introduced", "deprecated", "obsoleted", "message", + "replacement" }; + +/* Validate and act upon the arguments to an 'availability' attribute. */ + +tree +darwin_handle_availability_attribute (tree *node, tree name, tree args, + int flags, bool * no_add_attrs) +{ + tree decl = *node; + + if (! VAR_OR_FUNCTION_DECL_P (decl)) + { + warning (OPT_Wattributes, "%qE attribute ignored", + name); + *no_add_attrs = true; + return NULL_TREE; + } + + location_t loc = DECL_SOURCE_LOCATION (decl); + if (args == NULL_TREE) + { + error_at (loc, "%qE attribute requires at least one argument", + name); + *no_add_attrs = true; + return NULL_TREE; + } + + /* The first argument must name a supported OS - although we could choose + to ignore any OS we don't recognise. */ + gcc_checking_assert (TREE_CODE (args) == TREE_LIST); + tree platform = TREE_VALUE (args); + gcc_checking_assert (TREE_CODE (platform) == IDENTIFIER_NODE); + bool platform_ok = false; + unsigned plat_num = 0; + for (; plat_num < (unsigned) NUM_AV_OSES; plat_num++) + if (strcmp (availability_os[plat_num], IDENTIFIER_POINTER (platform)) == 0) + { + platform_ok = true; + break; + } + if (!platform_ok) + { + error_at (input_location, + "platform %qE is not recognised for the %<availability%> " + "attribute", platform); + *no_add_attrs = true; + return NULL_TREE; + } + else if (plat_num > 1) /* We only compile for macos so far. */ + { + *no_add_attrs = true; + return NULL_TREE; + } + + /* We might be dealing with an object or type. */ + tree target_decl = NULL_TREE; + tree type = NULL_TREE; + bool warn = false; + if (DECL_P (*node)) + { + type = TREE_TYPE (decl); + + if (TREE_CODE (decl) == TYPE_DECL + || TREE_CODE (decl) == PARM_DECL + || VAR_OR_FUNCTION_DECL_P (decl) + || TREE_CODE (decl) == FIELD_DECL + || TREE_CODE (decl) == CONST_DECL + /*|| objc_method_decl (TREE_CODE (decl))*/) + target_decl = decl; + else + warn = true; + } + else if (TYPE_P (*node)) + type = target_decl = *node; + else + warn = true; + + tree what = NULL_TREE; + if (warn) + { + *no_add_attrs = true; + if (type && TYPE_NAME (type)) + { + if (TREE_CODE (TYPE_NAME (type)) == IDENTIFIER_NODE) + what = TYPE_NAME (*node); + else if (TREE_CODE (TYPE_NAME (type)) == TYPE_DECL + && DECL_NAME (TYPE_NAME (type))) + what = DECL_NAME (TYPE_NAME (type)); + } + if (what) + warning (OPT_Wattributes, "%qE attribute ignored for %qE", name, what); + else + warning (OPT_Wattributes, "%qE attribute ignored", name); + return NULL_TREE; + } + + /* Now we have to parse the availability clauses. */ + tree msg = NULL_TREE; + tree replacement = NULL_TREE; + bool unavailable = false; + unsigned introduced = 1000; + unsigned deprecated = current_os_version + 1; + unsigned obsoleted = current_os_version + 1; + for (tree arg = TREE_CHAIN (args); arg; arg = TREE_CHAIN (arg)) + { + tree clause_name = TREE_VALUE (arg); + tree clause_value = TREE_PURPOSE (arg); + unsigned clause_num = 0; + for (; clause_num < (unsigned) NUM_AV_CLAUSES; clause_num++) + if (strcmp (availability_clause[clause_num], + IDENTIFIER_POINTER (clause_name)) == 0) + break; + switch (clause_num) + { + default: + { + error_at (input_location, + "clause %qE is not recognised for the %<availability%> " + "attribute", clause_name); + *no_add_attrs = true; + return NULL_TREE; + } + break; + case 0: + unavailable = true; + break; + case 1: + case 2: + case 3: + if (!clause_value) + { + error ("%qs= requires a value", + clause_num > 2 ? "obsoleted" + : clause_num > 1 ? "deprecated" + : "introduced"); + *no_add_attrs = true; + } + else + { + unsigned version = os_version_from_avail_value (clause_value); + if (version == 0) + { + error ("the value %qE provided to %qs is not a " + "valid OS version", clause_value, + (clause_num > 2 ? "obsoleted" + : clause_num > 1 ? "deprecated" + : "introduced")); + *no_add_attrs = true; + } + else if (clause_num == 1) + introduced = version; + else if (clause_num == 2) + deprecated = version; + else if (clause_num == 3) + obsoleted = version; + } + break; + case 4: + if (TREE_CODE (clause_value) != STRING_CST) + { + error ("%<message=%> requires a string"); + *no_add_attrs = true; + } + else + msg = clause_value; + break; + case 5: + if (TREE_CODE (clause_value) != STRING_CST) + { + error ("%<replacement=%> requires a string"); + *no_add_attrs = true; + } + else + replacement = clause_value; + break; + } + } + /* Now figure out what to do. */ + if (unavailable + || current_os_version >= obsoleted + || current_os_version < introduced) + { + tree new_attr = NULL_TREE; + if (replacement) + new_attr + = tree_cons (get_identifier ("unavailable"), + tree_cons (NULL_TREE, replacement, NULL_TREE), NULL_TREE); + else if (msg) + new_attr + = tree_cons (get_identifier ("unavailable"), + tree_cons (NULL_TREE, msg, NULL_TREE), NULL_TREE); + /* This is the actual consequence of the evaluation. */ + if (TYPE_P (target_decl) && !(flags & (int) ATTR_FLAG_TYPE_IN_PLACE)) + { + *node = build_variant_type_copy (*node); + TYPE_ATTRIBUTES (*node) = chainon (TYPE_ATTRIBUTES (*node), new_attr); + } + else + DECL_ATTRIBUTES (*node) = chainon (DECL_ATTRIBUTES (*node), new_attr); +// *no_add_attrs = true; /* maybe we should add to detect conflicts. */ + TREE_UNAVAILABLE (*node) = true; + return NULL_TREE; + } + else if (current_os_version > deprecated) + { + tree new_attr = NULL_TREE; + if (replacement) + new_attr + = tree_cons (get_identifier ("deprecated"), + tree_cons (NULL_TREE, replacement, NULL_TREE), NULL_TREE); + else if (msg) + new_attr + = tree_cons (get_identifier ("deprecated"), + tree_cons (NULL_TREE, msg, NULL_TREE), NULL_TREE); + /* This is the actual consequence of the evaluation. */ + if (TYPE_P (target_decl) && !(flags & (int) ATTR_FLAG_TYPE_IN_PLACE)) + { + *node = build_variant_type_copy (*node); + TYPE_ATTRIBUTES (*node) = chainon (TYPE_ATTRIBUTES (*node), new_attr); + } + else + DECL_ATTRIBUTES (*node) = chainon (DECL_ATTRIBUTES (*node), new_attr); +// *no_add_attrs = true; /* maybe we should add to detect conflicts. */ + TREE_DEPRECATED (*node) = true; + return NULL_TREE; + } + return NULL_TREE; +} + +bool +darwin_attribute_takes_identifier_p (const_tree attr_id) +{ + return is_attribute_p ("availability", attr_id); +} + /* Emit a label for an FDE, making it global and/or weak if appropriate. The third parameter is nonzero if this is for exception handling. The fourth parameter is nonzero if this is just a placeholder for an @@ -3337,7 +3673,14 @@ darwin_override_options (void) generating_for_darwin_version = 8; /* Earlier versions are not specifically accounted, until required. */ + unsigned vers[3] = {0,0,0}; + if (!parse_version (vers, darwin_macosx_version_min)) + error_at (UNKNOWN_LOCATION, "how did we get a bad OS version? (%s)", + darwin_macosx_version_min); + current_os_version = version_from_version_array (vers); } + else + current_os_version = 1058; /* Some codegen needs to account for the capabilities of the target linker. */ diff --git a/gcc/config/darwin.h b/gcc/config/darwin.h index 5db64a1ad68..684c754ce0f 100644 --- a/gcc/config/darwin.h +++ b/gcc/config/darwin.h @@ -963,7 +963,12 @@ extern GTY(()) section * darwin_sections[NUM_DARWIN_SECTIONS]; { "apple_kext_compatibility", 0, 0, false, true, false, false, \ darwin_handle_kext_attribute, NULL }, \ { "weak_import", 0, 0, true, false, false, false, \ - darwin_handle_weak_import_attribute, NULL } + darwin_handle_weak_import_attribute, NULL }, \ + { "availability", 0, -1, true, false, false, false, \ + darwin_handle_availability_attribute, NULL } + +#undef TARGET_ATTRIBUTE_TAKES_IDENTIFIER_P +#define TARGET_ATTRIBUTE_TAKES_IDENTIFIER_P darwin_attribute_takes_identifier_p /* Make local constant labels linker-visible, so that if one follows a weak_global constant, ld64 will be able to separate the atoms. */ -- 2.39.2 (Apple Git-143)