From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from rock.gnat.com (rock.gnat.com [IPv6:2620:20:4000:0:a9e:1ff:fe9b:1d1]) by sourceware.org (Postfix) with ESMTPS id B6DF63852778 for ; Fri, 29 Jul 2022 06:30:44 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.1 sourceware.org B6DF63852778 Received: from localhost (localhost.localdomain [127.0.0.1]) by filtered-rock.gnat.com (Postfix) with ESMTP id 492AC1168D2; Fri, 29 Jul 2022 02:30:44 -0400 (EDT) X-Virus-Scanned: Debian amavisd-new at gnat.com Received: from rock.gnat.com ([127.0.0.1]) by localhost (rock.gnat.com [127.0.0.1]) (amavisd-new, port 10024) with LMTP id nTL-wYEJU20o; Fri, 29 Jul 2022 02:30:44 -0400 (EDT) Received: from free.home (tron.gnat.com [IPv6:2620:20:4000:0:46a8:42ff:fe0e:e294]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by rock.gnat.com (Postfix) with ESMTPS id A280A1168CD; Fri, 29 Jul 2022 02:30:43 -0400 (EDT) Received: from livre (livre.home [172.31.160.2]) by free.home (8.15.2/8.15.2) with ESMTPS id 26T6USQm1852155 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Fri, 29 Jul 2022 03:30:29 -0300 From: Alexandre Oliva To: gcc-patches@gcc.gnu.org Cc: Jeremy Bennett , Craig Blackmore , Graham Markall , Martin Jambor , Jan Hubicka , Richard Biener , Jim Wilson Subject: [PATCH v2 09/10] Introduce strub: strubm (mode assignment) pass Organization: Free thinker, does not speak for AdaCore References: Errors-To: aoliva@lxoliva.fsfla.org Date: Fri, 29 Jul 2022 03:30:28 -0300 In-Reply-To: (Alexandre Oliva's message of "Fri, 29 Jul 2022 03:16:41 -0300") Message-ID: User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.2 (gnu/linux) MIME-Version: 1.0 Content-Type: text/plain X-Scanned-By: MIMEDefang 2.84 X-Spam-Status: No, score=-6.3 required=5.0 tests=BAYES_00, KAM_DMARC_STATUS, SPF_HELO_NONE, SPF_PASS, TXREP autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server2.sourceware.org X-BeenThere: gcc-patches@gcc.gnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Gcc-patches mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Fri, 29 Jul 2022 06:30:49 -0000 This middle fragment of ipa-strub.cc covers strub mode selection and assignment logic, and most of the pass that performs that assignment. +/* Return TRUE iff NODE calls builtin va_start. */ + +static bool +calls_builtin_va_start_p (cgraph_node *node) +{ + bool result = false; + + for (cgraph_edge *e = node->callees; e; e = e->next_callee) + { + tree cdecl = e->callee->decl; + if (fndecl_built_in_p (cdecl, BUILT_IN_VA_START)) + return true; + } + + return result; +} + +/* Return TRUE iff NODE calls builtin apply_args, and optionally REPORT it. */ + +static bool +calls_builtin_apply_args_p (cgraph_node *node, bool report = false) +{ + bool result = false; + + for (cgraph_edge *e = node->callees; e; e = e->next_callee) + { + tree cdecl = e->callee->decl; + if (!fndecl_built_in_p (cdecl, BUILT_IN_APPLY_ARGS)) + continue; + + result = true; + + if (!report) + break; + + sorry_at (gimple_location (e->call_stmt), + "at-calls % does not support call to %qD", + cdecl); + } + + return result; +} + +/* Return TRUE iff NODE carries the always_inline attribute. */ + +static inline bool +strub_always_inline_p (cgraph_node *node) +{ + return lookup_attribute ("always_inline", DECL_ATTRIBUTES (node->decl)); +} + +/* Return TRUE iff NODE is potentially eligible for any strub-enabled mode, and + optionally REPORT the reasons for ineligibility. */ + +static inline bool +can_strub_p (cgraph_node *node, bool report = false) +{ + bool result = true; + + if (!report && strub_always_inline_p (node)) + return result; + + if (lookup_attribute ("noipa", DECL_ATTRIBUTES (node->decl))) + { + result = false; + + if (!report) + return result; + + sorry_at (DECL_SOURCE_LOCATION (node->decl), + "%qD is not eligible for %" + " because of attribute %", + node->decl); + } + + /* We can't, and don't want to vectorize the watermark and other + strub-introduced parms. */ + if (lookup_attribute ("simd", DECL_ATTRIBUTES (node->decl))) + { + result = false; + + if (!report) + return result; + + sorry_at (DECL_SOURCE_LOCATION (node->decl), + "%qD is not eligible for %" + " because of attribute %", + node->decl); + } + + return result; +} + +/* Return TRUE iff NODE is eligible for at-calls strub, and optionally REPORT + the reasons for ineligibility. Besides general non-eligibility for + strub-enabled modes, at-calls rules out calling builtin apply_args. */ + +static bool +can_strub_at_calls_p (cgraph_node *node, bool report = false) +{ + bool result = !report || can_strub_p (node, report); + + if (!result && !report) + return result; + + return !calls_builtin_apply_args_p (node, report); +} + +/* Return TRUE iff the called function (pointer or, if available, + decl) undergoes a significant type conversion for the call. Strub + mode changes between function types, and other non-useless type + conversions, are regarded as significant. When the function type + is overridden, the effective strub mode for the call is that of the + call fntype, rather than that of the pointer or of the decl. + Functions called with type overrides cannot undergo type changes; + it's as if their address was taken, so they're considered + non-viable for implicit at-calls strub mode. */ + +static inline bool +strub_call_fntype_override_p (const gcall *gs) +{ + if (gimple_call_internal_p (gs)) + return false; + tree fn_type = TREE_TYPE (TREE_TYPE (gimple_call_fn (gs))); + if (tree decl = gimple_call_fndecl (gs)) + fn_type = TREE_TYPE (decl); + + /* We do NOT want to take the mode from the decl here. This + function is used to tell whether we can change the strub mode of + a function, and whether the effective mode for the call is to be + taken from the decl or from an overrider type. When the strub + mode is explicitly declared, or overridden with a type cast, the + difference will be noticed in function types. However, if the + strub mode is implicit due to e.g. strub variables or -fstrub=* + command-line flags, we will adjust call types along with function + types. In either case, the presence of type or strub mode + overriders in calls will prevent a function from having its strub + modes changed in ways that would imply type changes, but taking + strub modes from decls would defeat this, since we set strub + modes and then call this function to tell whether the original + type was overridden to decide whether to adjust the call. We + need the answer to be about the type, not the decl. */ + enum strub_mode mode = get_strub_mode_from_type (fn_type); + return (get_strub_mode_from_type (gs->u.fntype) != mode + || !useless_type_conversion_p (gs->u.fntype, fn_type)); +} + +/* Return TRUE iff NODE is called directly with a type override. */ + +static bool +called_directly_with_type_override_p (cgraph_node *node, void *) +{ + for (cgraph_edge *e = node->callers; e; e = e->next_caller) + if (strub_call_fntype_override_p (e->call_stmt)) + return true; + + return false; +} + +/* Return TRUE iff NODE or any other nodes aliased to it are called + with type overrides. We can't safely change the type of such + functions. */ + +static bool +called_with_type_override_p (cgraph_node *node) +{ + return (node->call_for_symbol_thunks_and_aliases + (called_directly_with_type_override_p, NULL, true, true)); +} + +/* Symbolic macro for the max number of arguments that internal strub may add to + a function. */ + +#define STRUB_INTERNAL_MAX_EXTRA_ARGS 3 + +/* We can't perform internal strubbing if the function body involves certain + features: + + - a non-default __builtin_va_start (e.g. x86's __builtin_ms_va_start) is + currently unsupported because we can't discover the corresponding va_copy and + va_end decls in the wrapper, and we don't convey the alternate variable + arguments ABI to the modified wrapped function. The default + __builtin_va_start is supported by calling va_start/va_end at the wrapper, + that takes variable arguments, passing a pointer to the va_list object to the + wrapped function, that runs va_copy from it where the original function ran + va_start. + + __builtin_next_arg is currently unsupported because the wrapped function + won't be a variable argument function. We could process it in the wrapper, + that remains a variable argument function, and replace calls in the wrapped + body, but we currently don't. + + __builtin_return_address is rejected because it's generally used when the + actual caller matters, and introducing a wrapper breaks such uses as those in + the unwinder. */ + +static bool +can_strub_internally_p (cgraph_node *node, bool report = false) +{ + bool result = !report || can_strub_p (node, report); + + if (!result && !report) + return result; + + if (!report && strub_always_inline_p (node)) + return result; + + /* Since we're not changing the function identity proper, just + moving its full implementation, we *could* disable + fun->cannot_be_copied_reason and/or temporarily drop a noclone + attribute, but we'd have to prevent remapping of the labels. */ + if (lookup_attribute ("noclone", DECL_ATTRIBUTES (node->decl))) + { + result = false; + + if (!report) + return result; + + sorry_at (DECL_SOURCE_LOCATION (node->decl), + "%qD is not eligible for internal %" + " because of attribute %", + node->decl); + } + + if (node->has_gimple_body_p ()) + { + for (cgraph_edge *e = node->callees; e; e = e->next_callee) + { + tree cdecl = e->callee->decl; + if (!((fndecl_built_in_p (cdecl, BUILT_IN_VA_START) + && cdecl != builtin_decl_explicit (BUILT_IN_VA_START)) + || fndecl_built_in_p (cdecl, BUILT_IN_NEXT_ARG) + || fndecl_built_in_p (cdecl, BUILT_IN_RETURN_ADDRESS))) + continue; + + result = false; + + if (!report) + return result; + + sorry_at (gimple_location (e->call_stmt), + "%qD is not eligible for internal % " + "because it calls %qD", + node->decl, cdecl); + } + + struct function *fun = DECL_STRUCT_FUNCTION (node->decl); + if (fun->has_nonlocal_label) + { + result = false; + + if (!report) + return result; + + sorry_at (DECL_SOURCE_LOCATION (node->decl), + "%qD is not eligible for internal % " + "because it contains a non-local goto target", + node->decl); + } + + if (fun->has_forced_label_in_static) + { + result = false; + + if (!report) + return result; + + sorry_at (DECL_SOURCE_LOCATION (node->decl), + "%qD is not eligible for internal % " + "because the address of a local label escapes", + node->decl); + } + + /* Catch any other case that would prevent versioning/cloning + so as to also have it covered above. */ + gcc_checking_assert (!result /* || !node->has_gimple_body_p () */ + || tree_versionable_function_p (node->decl)); + + + /* Label values references are not preserved when copying. If referenced + in nested functions, as in 920415-1.c and 920721-4.c their decls get + remapped independently. The exclusion below might be too broad, in + that we might be able to support correctly cases in which the labels + are only used internally in a function, but disconnecting forced labels + from their original declarations is undesirable in general. */ + basic_block bb; + FOR_EACH_BB_FN (bb, DECL_STRUCT_FUNCTION (node->decl)) + for (gimple_stmt_iterator gsi = gsi_start_bb (bb); + !gsi_end_p (gsi); gsi_next (&gsi)) + { + glabel *label_stmt = dyn_cast (gsi_stmt (gsi)); + tree target; + + if (!label_stmt) + break; + + target = gimple_label_label (label_stmt); + + if (!FORCED_LABEL (target)) + continue; + + result = false; + + if (!report) + return result; + + sorry_at (gimple_location (label_stmt), + "internal % does not support forced labels"); + } + } + + if (list_length (TYPE_ARG_TYPES (TREE_TYPE (node->decl))) + >= (((HOST_WIDE_INT) 1 << IPA_PARAM_MAX_INDEX_BITS) + - STRUB_INTERNAL_MAX_EXTRA_ARGS)) + { + result = false; + + if (!report) + return result; + + sorry_at (DECL_SOURCE_LOCATION (node->decl), + "%qD has too many arguments for internal %", + node->decl); + } + + return result; +} + +/* Return TRUE iff NODE has any strub-requiring local variable, or accesses (as + in reading) any variable through a strub-requiring type. */ + +static bool +strub_from_body_p (cgraph_node *node) +{ + if (!node->has_gimple_body_p ()) + return false; + + /* If any local variable is marked for strub... */ + unsigned i; + tree var; + FOR_EACH_LOCAL_DECL (DECL_STRUCT_FUNCTION (node->decl), + i, var) + if (get_strub_mode_from_type (TREE_TYPE (var)) + != STRUB_DISABLED) + return true; + + /* Now scan the body for loads with strub-requiring types. + ??? Compound types don't propagate the strub requirement to + component types. */ + basic_block bb; + FOR_EACH_BB_FN (bb, DECL_STRUCT_FUNCTION (node->decl)) + for (gimple_stmt_iterator gsi = gsi_start_bb (bb); + !gsi_end_p (gsi); gsi_next (&gsi)) + { + gimple *stmt = gsi_stmt (gsi); + + if (!gimple_assign_load_p (stmt)) + continue; + + tree rhs = gimple_assign_rhs1 (stmt); + if (get_strub_mode_from_type (TREE_TYPE (rhs)) + != STRUB_DISABLED) + return true; + } + + return false; +} + +/* Return TRUE iff node is associated with a builtin that should be callable + from strub contexts. */ + +static inline bool +strub_callable_builtin_p (cgraph_node *node) +{ + if (DECL_BUILT_IN_CLASS (node->decl) != BUILT_IN_NORMAL) + return false; + + enum built_in_function fcode = DECL_FUNCTION_CODE (node->decl); + + switch (fcode) + { + case BUILT_IN_NONE: + gcc_unreachable (); + + /* This temporarily allocates stack for the call, and we can't reasonably + update the watermark for that. Besides, we don't check the actual call + target, nor its signature, and it seems to be overkill to as much as + try to do so. */ + case BUILT_IN_APPLY: + return false; + + /* Conversely, this shouldn't be called from within strub contexts, since + the caller may have had its signature modified. STRUB_INTERNAL is ok, + the call will remain in the STRUB_WRAPPER, and removed from the + STRUB_WRAPPED clone. */ + case BUILT_IN_APPLY_ARGS: + return false; + + /* ??? Make all other builtins callable. We wish to make any builtin call + the compiler might introduce on its own callable. Anything that is + predictable enough as to be known not to allow stack data that should + be strubbed to unintentionally escape to non-strub contexts can be + allowed, and pretty much every builtin appears to fit this description. + The exceptions to this rule seem to be rare, and only available as + explicit __builtin calls, so let's keep it simple and allow all of + them... */ + default: + return true; + } +} + +/* Compute the strub mode to be used for NODE. STRUB_ATTR should be the strub + attribute,found for NODE, if any. */ + +static enum strub_mode +compute_strub_mode (cgraph_node *node, tree strub_attr) +{ + enum strub_mode req_mode = get_strub_mode_from_attr (strub_attr); + + gcc_checking_assert (flag_strub >= -2 && flag_strub <= 3); + + /* Symbolic encodings of the -fstrub-* flags. */ + /* Enable strub when explicitly requested through attributes to functions or + variables, reporting errors if the requests cannot be satisfied. */ + const bool strub_flag_auto = flag_strub < 0; + /* strub_flag_auto with strub call verification; without this, functions are + implicitly callable. */ + const bool strub_flag_strict = flag_strub < -1; + /* Disable strub altogether, ignore attributes entirely. */ + const bool strub_flag_disabled = flag_strub == 0; + /* On top of _auto, also enable strub implicitly for functions that can + safely undergo at-calls strubbing. Internal mode will still be used in + functions that request it explicitly with attribute strub(2), or when the + function body requires strubbing and at-calls strubbing is not viable. */ + const bool strub_flag_at_calls = flag_strub == 1; + /* On top of default, also enable strub implicitly for functions that can + safely undergo internal strubbing. At-calls mode will still be used in + functions that requiest it explicitly with attribute strub() or strub(1), + or when the function body requires strubbing and internal strubbing is not + viable. */ + const bool strub_flag_internal = flag_strub == 2; + /* On top of default, also enable strub implicitly for functions that can + safely undergo strubbing in either mode. When both modes are viable, + at-calls is preferred. */ + const bool strub_flag_either = flag_strub == 3; + /* Besides the default behavior, enable strub implicitly for all viable + functions. */ + const bool strub_flag_viable = flag_strub > 0; + + /* The consider_* variables should be TRUE if selecting the corresponding + strub modes would be consistent with requests from attributes and command + line flags. Attributes associated with functions pretty much mandate a + selection, and should report an error if not satisfied; strub_flag_auto + implicitly enables some viable strub mode if that's required by references + to variables marked for strub; strub_flag_viable enables strub if viable + (even when favoring one mode, body-requested strub can still be satisfied + by either mode), and falls back to callable, silently unless variables + require strubbing. */ + + const bool consider_at_calls + = (!strub_flag_disabled + && (strub_attr + ? req_mode == STRUB_AT_CALLS + : true)); + const bool consider_internal + = (!strub_flag_disabled + && (strub_attr + ? req_mode == STRUB_INTERNAL + : true)); + + const bool consider_callable + = (!strub_flag_disabled + && (strub_attr + ? req_mode == STRUB_CALLABLE + : (!strub_flag_strict + || strub_callable_builtin_p (node)))); + + /* This is a shorthand for either strub-enabled mode. */ + const bool consider_strub + = (consider_at_calls || consider_internal); + + /* We can cope with always_inline functions even with noipa and noclone, + because we just leave them alone. */ + const bool is_always_inline + = strub_always_inline_p (node); + + /* Strubbing in general, and each specific strub mode, may have its own set of + requirements. We require noipa for strubbing, either because of cloning + required for internal strub, or because of caller enumeration required for + at-calls strub. We don't consider the at-calls mode eligible if it's not + even considered, it has no further requirements. Internal mode requires + cloning and the absence of certain features in the body and, like at-calls, + it's not eligible if it's not even under consideration. + + ??? Do we need target hooks for further constraints? E.g., x86's + "interrupt" attribute breaks internal strubbing because the wrapped clone + carries the attribute and thus isn't callable; in this case, we could use a + target hook to adjust the clone instead. */ + const bool strub_eligible + = (consider_strub + && (is_always_inline || can_strub_p (node))); + const bool at_calls_eligible + = (consider_at_calls && strub_eligible + && can_strub_at_calls_p (node)); + const bool internal_eligible + = (consider_internal && strub_eligible + && (is_always_inline + || can_strub_internally_p (node))); + + /* In addition to the strict eligibility requirements, some additional + constraints are placed on implicit selection of certain modes. These do + not prevent the selection of a mode if explicitly specified as part of a + function interface (the strub attribute), but they may prevent modes from + being selected by the command line or by function bodies. The only actual + constraint is on at-calls mode: since we change the function's exposed + signature, we won't do it implicitly if the function can possibly be used + in ways that do not expect the signature change, e.g., if the function is + available to or interposable by other units, if its address is taken, + etc. */ + const bool at_calls_viable + = (at_calls_eligible + && (strub_attr + || (node->has_gimple_body_p () + && (!node->externally_visible + || (node->binds_to_current_def_p () + && node->can_be_local_p ())) + && node->only_called_directly_p () + && !called_with_type_override_p (node)))); + const bool internal_viable + = (internal_eligible); + + /* Shorthand. */ + const bool strub_viable + = (at_calls_viable || internal_viable); + + /* We wish to analyze the body, to look for implicit requests for strub, both + to implicitly enable it when the body calls for it, and to report errors if + the body calls for it but neither mode is viable (even if that follows from + non-eligibility because of the explicit specification of some non-strubbing + mode). We can refrain from scanning the body only in rare circumstances: + when strub is enabled by a function attribute (scanning might be redundant + in telling us to also enable it), and when we are enabling strub implicitly + but there are non-viable modes: we want to know whether strubbing is + required, to fallback to another mode, even if we're only enabling a + certain mode, or, when either mode would do, to report an error if neither + happens to be viable. */ + const bool analyze_body + = (strub_attr + ? !consider_strub + : (strub_flag_auto + || (strub_flag_viable && (!at_calls_viable && !internal_viable)) + || (strub_flag_either && !strub_viable))); + + /* Cases in which strubbing is enabled or disabled by strub_flag_auto. + Unsatisfiable requests ought to be reported. */ + const bool strub_required + = ((strub_attr && consider_strub) + || (analyze_body && strub_from_body_p (node))); + + /* Besides the required cases, we want to abide by the requests to enabling on + an if-viable basis. */ + const bool strub_enable + = (strub_required + || (strub_flag_at_calls && at_calls_viable) + || (strub_flag_internal && internal_viable) + || (strub_flag_either && strub_viable)); + + /* And now we're finally ready to select a mode that abides by the viability + and eligibility constraints, and that satisfies the strubbing requirements + and requests, subject to the constraints. If both modes are viable and + strub is to be enabled, pick STRUB_AT_CALLS unless STRUB_INTERNAL was named + as preferred. */ + const enum strub_mode mode + = ((strub_enable && is_always_inline) + ? (strub_required ? STRUB_INLINABLE : STRUB_CALLABLE) + : (strub_enable && internal_viable + && (strub_flag_internal || !at_calls_viable)) + ? STRUB_INTERNAL + : (strub_enable && at_calls_viable) + ? (strub_required && !strub_attr + ? STRUB_AT_CALLS_OPT + : STRUB_AT_CALLS) + : consider_callable + ? STRUB_CALLABLE + : STRUB_DISABLED); + + switch (mode) + { + case STRUB_CALLABLE: + if (is_always_inline) + break; + /* Fall through. */ + + case STRUB_DISABLED: + if (strub_enable && !strub_attr) + { + gcc_checking_assert (analyze_body); + error_at (DECL_SOURCE_LOCATION (node->decl), + "%qD requires %," + " but no viable % mode was found", + node->decl); + break; + } + /* Fall through. */ + + case STRUB_AT_CALLS: + case STRUB_INTERNAL: + case STRUB_INLINABLE: + /* Differences from an mode requested through a function attribute are + reported in set_strub_mode_to. */ + break; + + case STRUB_AT_CALLS_OPT: + /* Functions that select this mode do so because of references to strub + variables. Even if we choose at-calls as an optimization, the + requirements for internal strub must still be satisfied. Optimization + options may render implicit at-calls strub not viable (-O0 sets + force_output for static non-inline functions), and it would not be good + if changing optimization options turned a well-formed into an + ill-formed one. */ + if (!internal_viable) + can_strub_internally_p (node, true); + break; + + case STRUB_WRAPPED: + case STRUB_WRAPPER: + default: + gcc_unreachable (); + } + + return mode; +} + +/* Set FNDT's strub mode to MODE; FNDT may be a function decl or + function type. If OVERRIDE, do not check whether a mode is already + set. */ + +static void +strub_set_fndt_mode_to (tree fndt, enum strub_mode mode, bool override) +{ + gcc_checking_assert (override + || !(DECL_P (fndt) + ? get_strub_attr_from_decl (fndt) + : get_strub_attr_from_type (fndt))); + + tree attr = tree_cons (get_identifier ("strub"), + get_strub_mode_attr_value (mode), + NULL_TREE); + tree *attrp = NULL; + if (DECL_P (fndt)) + { + gcc_checking_assert (FUNC_OR_METHOD_TYPE_P (TREE_TYPE (fndt))); + attrp = &DECL_ATTRIBUTES (fndt); + } + else if (FUNC_OR_METHOD_TYPE_P (fndt)) + attrp = &TYPE_ATTRIBUTES (fndt); + else + gcc_unreachable (); + + TREE_CHAIN (attr) = *attrp; + *attrp = attr; +} + +/* Set FNDT's strub mode to callable. + FNDT may be a function decl or a function type. */ + +void +strub_make_callable (tree fndt) +{ + strub_set_fndt_mode_to (fndt, STRUB_CALLABLE, false); +} + +/* Set NODE to strub MODE. Report incompatibilities between MODE and the mode + requested through explicit attributes, and cases of non-eligibility. */ + +static void +set_strub_mode_to (cgraph_node *node, enum strub_mode mode) +{ + tree attr = get_strub_attr_from_decl (node->decl); + enum strub_mode req_mode = get_strub_mode_from_attr (attr); + + if (attr) + { + /* Check for and report incompatible mode changes. */ + if (mode != req_mode + && !(req_mode == STRUB_INTERNAL + && (mode == STRUB_WRAPPED + || mode == STRUB_WRAPPER)) + && !((req_mode == STRUB_INTERNAL + || req_mode == STRUB_AT_CALLS + || req_mode == STRUB_CALLABLE) + && mode == STRUB_INLINABLE)) + { + error_at (DECL_SOURCE_LOCATION (node->decl), + "% mode %qE selected for %qD, when %qE was requested", + get_strub_mode_attr_parm (mode), + node->decl, + get_strub_mode_attr_parm (req_mode)); + if (node->alias) + { + cgraph_node *target = node->ultimate_alias_target (); + if (target != node) + error_at (DECL_SOURCE_LOCATION (target->decl), + "the incompatible selection was determined" + " by ultimate alias target %qD", + target->decl); + } + + /* Report any incompatibilities with explicitly-requested strub. */ + switch (req_mode) + { + case STRUB_AT_CALLS: + can_strub_at_calls_p (node, true); + break; + + case STRUB_INTERNAL: + can_strub_internally_p (node, true); + break; + + default: + break; + } + } + + /* Drop any incompatible strub attributes leading the decl attribute + chain. Return if we find one with the mode we need. */ + for (;;) + { + if (mode == req_mode) + return; + + if (DECL_ATTRIBUTES (node->decl) != attr) + break; + + DECL_ATTRIBUTES (node->decl) = TREE_CHAIN (attr); + attr = get_strub_attr_from_decl (node->decl); + if (!attr) + break; + + req_mode = get_strub_mode_from_attr (attr); + } + } + else if (mode == req_mode) + return; + + strub_set_fndt_mode_to (node->decl, mode, attr); +} + +/* Compute and set NODE's strub mode. */ + +static void +set_strub_mode (cgraph_node *node) +{ + tree attr = get_strub_attr_from_decl (node->decl); + + if (attr) + switch (get_strub_mode_from_attr (attr)) + { + /* These can't have been requested through user attributes, so we must + have already gone through them. */ + case STRUB_WRAPPER: + case STRUB_WRAPPED: + case STRUB_INLINABLE: + case STRUB_AT_CALLS_OPT: + return; + + case STRUB_DISABLED: + case STRUB_AT_CALLS: + case STRUB_INTERNAL: + case STRUB_CALLABLE: + break; + + default: + gcc_unreachable (); + } + + cgraph_node *xnode = node; + if (node->alias) + xnode = node->ultimate_alias_target (); + /* Weakrefs may remain unresolved (the above will return node) if + their targets are not defined, so make sure we compute a strub + mode for them, instead of defaulting to STRUB_DISABLED and + rendering them uncallable. */ + enum strub_mode mode = (xnode != node && !xnode->alias + ? get_strub_mode (xnode) + : compute_strub_mode (node, attr)); + + set_strub_mode_to (node, mode); +} + + +/* Non-strub functions shouldn't be called from within strub contexts, + except through callable ones. Always inline strub functions can + only be called from strub functions. */ + +static bool +strub_callable_from_p (strub_mode caller_mode, strub_mode callee_mode) +{ + switch (caller_mode) + { + case STRUB_WRAPPED: + case STRUB_AT_CALLS_OPT: + case STRUB_AT_CALLS: + case STRUB_INTERNAL: + case STRUB_INLINABLE: + break; + + case STRUB_WRAPPER: + case STRUB_DISABLED: + case STRUB_CALLABLE: + return callee_mode != STRUB_INLINABLE; + + default: + gcc_unreachable (); + } + + switch (callee_mode) + { + case STRUB_WRAPPED: + case STRUB_AT_CALLS: + case STRUB_INLINABLE: + break; + + case STRUB_AT_CALLS_OPT: + case STRUB_INTERNAL: + case STRUB_WRAPPER: + return (flag_strub >= -1); + + case STRUB_DISABLED: + return false; + + case STRUB_CALLABLE: + break; + + default: + gcc_unreachable (); + } + + return true; +} + +/* Return TRUE iff CALLEE can be inlined into CALLER. We wish to avoid inlining + WRAPPED functions back into their WRAPPERs. More generally, we wish to avoid + inlining strubbed functions into non-strubbed ones. CALLER doesn't have to + be an immediate caller of CALLEE: the immediate caller may have already been + cloned for inlining, and then CALLER may be further up the original call + chain. ??? It would be nice if our own caller would retry inlining callee + if caller gets inlined. */ + +bool +strub_inlinable_to_p (cgraph_node *callee, cgraph_node *caller) +{ + strub_mode callee_mode = get_strub_mode (callee); + + switch (callee_mode) + { + case STRUB_WRAPPED: + case STRUB_AT_CALLS: + case STRUB_INTERNAL: + case STRUB_INLINABLE: + case STRUB_AT_CALLS_OPT: + break; + + case STRUB_WRAPPER: + case STRUB_DISABLED: + case STRUB_CALLABLE: + /* When we consider inlining, we've already verified callability, so we + can even inline callable and then disabled into a strub context. That + will get strubbed along with the context, so it's hopefully not a + problem. */ + return true; + + default: + gcc_unreachable (); + } + + strub_mode caller_mode = get_strub_mode (caller); + + switch (caller_mode) + { + case STRUB_WRAPPED: + case STRUB_AT_CALLS: + case STRUB_INTERNAL: + case STRUB_INLINABLE: + case STRUB_AT_CALLS_OPT: + return true; + + case STRUB_WRAPPER: + case STRUB_DISABLED: + case STRUB_CALLABLE: + break; + + default: + gcc_unreachable (); + } + + return false; +} + +/* Check that types T1 and T2 are strub-compatible. Return 1 if the strub modes + are the same, 2 if they are interchangeable, and 0 otherwise. */ + +int +strub_comptypes (tree t1, tree t2) +{ + if (TREE_CODE (t1) != TREE_CODE (t2)) + return 0; + + enum strub_mode m1 = get_strub_mode_from_type (t1); + enum strub_mode m2 = get_strub_mode_from_type (t2); + + if (m1 == m2) + return 1; + + /* We're dealing with types, so only strub modes that can be selected by + attributes in the front end matter. If either mode is at-calls (for + functions) or internal (for variables), the conversion is not + compatible. */ + bool var_p = !FUNC_OR_METHOD_TYPE_P (t1); + enum strub_mode mr = var_p ? STRUB_INTERNAL : STRUB_AT_CALLS; + if (m1 == mr || m2 == mr) + return 0; + + return 2; +} + +/* Return the effective strub mode used for CALL, and set *TYPEP to + the effective type used for the call. The effective type and mode + are those of the callee, unless the call involves a typecast. */ + +static enum strub_mode +effective_strub_mode_for_call (gcall *call, tree *typep) +{ + tree type; + enum strub_mode mode; + + if (strub_call_fntype_override_p (call)) + { + type = gimple_call_fntype (call); + mode = get_strub_mode_from_type (type); + } + else + { + type = TREE_TYPE (TREE_TYPE (gimple_call_fn (call))); + tree decl = gimple_call_fndecl (call); + if (decl) + mode = get_strub_mode_from_fndecl (decl); + else + mode = get_strub_mode_from_type (type); + } + + if (typep) + *typep = type; + + return mode; +} + +/* Create a distinct copy of the type of NODE's function, and change + the fntype of all calls to it with the same main type to the new + type. */ + +static void +distinctify_node_type (cgraph_node *node) +{ + tree old_type = TREE_TYPE (node->decl); + tree new_type = build_distinct_type_copy (old_type); + tree new_ptr_type = NULL_TREE; + + /* Remap any calls to node->decl that use old_type, or a variant + thereof, to new_type as well. We don't look for aliases, their + declarations will have their types changed independently, and + we'll adjust their fntypes then. */ + for (cgraph_edge *e = node->callers; e; e = e->next_caller) + { + tree fnaddr = gimple_call_fn (e->call_stmt); + gcc_checking_assert (TREE_CODE (fnaddr) == ADDR_EXPR + && TREE_OPERAND (fnaddr, 0) == node->decl); + if (strub_call_fntype_override_p (e->call_stmt)) + continue; + if (!new_ptr_type) + new_ptr_type = build_pointer_type (new_type); + TREE_TYPE (fnaddr) = new_ptr_type; + gimple_call_set_fntype (e->call_stmt, new_type); + } + + TREE_TYPE (node->decl) = new_type; +} + +/* Return TRUE iff TYPE and any variants have the same strub mode. */ + +static bool +same_strub_mode_in_variants_p (tree type) +{ + enum strub_mode mode = get_strub_mode_from_type (type); + + for (tree other = TYPE_MAIN_VARIANT (type); + other != NULL_TREE; other = TYPE_NEXT_VARIANT (other)) + if (type != other && mode != get_strub_mode_from_type (other)) + return false; + + /* Check that the canonical type, if set, either is in the same + variant chain, or has the same strub mode as type. Also check + the variants of the canonical type. */ + if (TYPE_CANONICAL (type) + && (TYPE_MAIN_VARIANT (TYPE_CANONICAL (type)) + != TYPE_MAIN_VARIANT (type))) + { + if (mode != get_strub_mode_from_type (TYPE_CANONICAL (type))) + return false; + else + return same_strub_mode_in_variants_p (TYPE_CANONICAL (type)); + } + + return true; +} + +/* Check that strub functions don't call non-strub functions, and that + always_inline strub functions are only called by strub + functions. */ + +static void +verify_strub () +{ + cgraph_node *node; + + /* It's expected that check strub-wise pointer type compatibility of variables + and of functions is already taken care of by front-ends, on account of the + attribute's being marked as affecting type identity and of the creation of + distinct types. */ + + /* Check that call targets in strub contexts have strub-callable types. */ + + FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node) + { + enum strub_mode caller_mode = get_strub_mode (node); + + for (cgraph_edge *e = node->indirect_calls; e; e = e->next_callee) + { + gcc_checking_assert (e->indirect_unknown_callee); + + enum strub_mode callee_mode + = effective_strub_mode_for_call (e->call_stmt, NULL); + + if (!strub_callable_from_p (caller_mode, callee_mode)) + error_at (gimple_location (e->call_stmt), + "indirect non-% call in % context %qD", + node->decl); + } + + for (cgraph_edge *e = node->callees; e; e = e->next_callee) + { + gcc_checking_assert (!e->indirect_unknown_callee); + + tree callee_fntype; + enum strub_mode callee_mode + = effective_strub_mode_for_call (e->call_stmt, &callee_fntype); + + if (!strub_callable_from_p (caller_mode, callee_mode)) + { + if (callee_mode == STRUB_INLINABLE) + error_at (gimple_location (e->call_stmt), + "calling % % %qD" + " in non-% context %qD", + e->callee->decl, node->decl); + else if (fndecl_built_in_p (e->callee->decl, BUILT_IN_APPLY_ARGS) + && caller_mode == STRUB_INTERNAL) + /* This is ok, it will be kept in the STRUB_WRAPPER, and removed + from the STRUB_WRAPPED's strub context. */ + continue; + else if (!strub_call_fntype_override_p (e->call_stmt)) + error_at (gimple_location (e->call_stmt), + "calling non-% %qD in % context %qD", + e->callee->decl, node->decl); + else + error_at (gimple_location (e->call_stmt), + "calling %qD using non-% type %qT" + " in % context %qD", + e->callee->decl, callee_fntype, node->decl); + } + } + } +} + +namespace { + +/* Define a pass to compute strub modes. */ +const pass_data pass_data_ipa_strub_mode = { + SIMPLE_IPA_PASS, + "strubm", + OPTGROUP_NONE, + TV_NONE, + PROP_cfg, // properties_required + 0, // properties_provided + 0, // properties_destroyed + 0, // properties_start + 0, // properties_finish +}; + +class pass_ipa_strub_mode : public simple_ipa_opt_pass +{ +public: + pass_ipa_strub_mode (gcc::context *ctxt) + : simple_ipa_opt_pass (pass_data_ipa_strub_mode, ctxt) + {} + opt_pass *clone () { return new pass_ipa_strub_mode (m_ctxt); } + virtual bool gate (function *) { + /* In relaxed (-3) and strict (-4) settings, that only enable strub at a + function or variable attribute's request, the attribute handler changes + flag_strub to -1 or -2, respectively, if any strub-enabling occurence of + the attribute is found. Therefore, if it remains at -3 or -4, nothing + that would enable strub was found, so we can disable it and avoid the + overhead. */ + if (flag_strub < -2) + flag_strub = 0; + return flag_strub; + } + virtual unsigned int execute (function *); +}; + -- Alexandre Oliva, happy hacker https://FSFLA.org/blogs/lxo/ Free Software Activist GNU Toolchain Engineer Disinformation flourishes because many people care deeply about injustice but very few check the facts. Ask me about