From: Alexandre Oliva <oliva@adacore.com>
To: gcc-patches@gcc.gnu.org
Cc: Jeremy Bennett <jeremy.bennett@embecosm.com>,
Craig Blackmore <craig.blackmore@embecosm.com>,
Graham Markall <graham.markall@embecosm.com>,
Martin Jambor <mjambor@suse.cz>, Jan Hubicka <hubicka@ucw.cz>,
Richard Biener <richard.guenther@gmail.com>,
Jim Wilson <wilson@tuliptree.org>
Subject: [PATCH v2 09/10] Introduce strub: strubm (mode assignment) pass
Date: Fri, 29 Jul 2022 03:30:28 -0300 [thread overview]
Message-ID: <orzggsl9bv.fsf_-_@lxoliva.fsfla.org> (raw)
In-Reply-To: <or35eko33q.fsf_-_@lxoliva.fsfla.org> (Alexandre Oliva's message of "Fri, 29 Jul 2022 03:16:41 -0300")
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 %<strub%> 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 %<strub%>"
+ " because of attribute %<noipa%>",
+ 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 %<strub%>"
+ " because of attribute %<simd%>",
+ 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 %<strub%>"
+ " because of attribute %<noclone%>",
+ 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 %<strub%> "
+ "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 %<strub%> "
+ "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 %<strub%> "
+ "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 <glabel *> (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 %<strub%> 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 %<strub%>",
+ 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 %<strub%>,"
+ " but no viable %<strub%> 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),
+ "%<strub%> 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);
+}
+
+\f
+/* 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-%<strub%> call in %<strub%> 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 %<always_inline%> %<strub%> %qD"
+ " in non-%<strub%> 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-%<strub%> %qD in %<strub%> context %qD",
+ e->callee->decl, node->decl);
+ else
+ error_at (gimple_location (e->call_stmt),
+ "calling %qD using non-%<strub%> type %qT"
+ " in %<strub%> 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 <https://stallmansupport.org>
next prev parent reply other threads:[~2022-07-29 6:30 UTC|newest]
Thread overview: 59+ messages / expand[flat|nested] mbox.gz Atom feed top
[not found] <ormtqpsbuc.fsf@lxoliva.fsfla.org>
2021-09-09 7:11 ` [PATCH] strub: machine-independent stack scrubbing Alexandre Oliva
2022-07-29 6:16 ` [PATCH v2 00/10] Introduce " Alexandre Oliva
2022-07-29 6:24 ` [PATCH v2 01/10] Introduce strub: documentation, and new command-line options Alexandre Oliva
2022-07-29 6:25 ` [PATCH v2 02/10] Introduce strub: torture tests for C and C++ Alexandre Oliva
2022-08-09 13:34 ` Alexandre Oliva
2022-07-29 6:25 ` [PATCH v2 03/10] Introduce strub: non-torture " Alexandre Oliva
2022-07-29 6:26 ` [PATCH v2 04/10] Introduce strub: tests for C++ and Ada Alexandre Oliva
2022-07-29 6:26 ` [PATCH v2 05/10] Introduce strub: builtins and runtime Alexandre Oliva
2022-07-29 6:27 ` [PATCH v2 06/10] Introduce strub: attributes Alexandre Oliva
2022-07-29 6:28 ` [PATCH v2 07/10] Introduce strub: infrastructure interfaces and adjustments Alexandre Oliva
2022-07-29 6:28 ` [PATCH v2 08/10] Introduce strub: strub modes Alexandre Oliva
2022-07-29 6:30 ` Alexandre Oliva [this message]
2022-07-29 6:34 ` [PATCH v2 10/10] Introduce strub: strub pass Alexandre Oliva
2022-07-29 6:36 ` [PATCH v2 00/10] Introduce strub: machine-independent stack scrubbing Alexandre Oliva
2022-10-10 8:48 ` Richard Biener
2022-10-11 11:57 ` Alexandre Oliva
2022-10-11 11:59 ` Richard Biener
2022-10-11 13:33 ` Alexandre Oliva
2022-10-13 11:38 ` Richard Biener
2022-10-13 13:15 ` Alexandre Oliva
2023-06-16 6:09 ` [PATCH v3] " Alexandre Oliva
2023-06-27 21:28 ` Qing Zhao
2023-06-28 8:20 ` Alexandre Oliva
2023-10-20 6:03 ` [PATCH v4] " Alexandre Oliva
2023-10-26 6:15 ` Alexandre Oliva
2023-11-20 12:40 ` Alexandre Oliva
2023-11-22 14:14 ` Richard Biener
2023-11-23 10:56 ` Alexandre Oliva
2023-11-23 12:05 ` Richard Biener
2023-11-29 8:53 ` Alexandre Oliva
2023-11-29 12:48 ` Richard Biener
2023-11-30 4:13 ` Alexandre Oliva
2023-11-30 12:00 ` Richard Biener
2023-12-02 17:56 ` [PATCH v5] " Alexandre Oliva
2023-12-05 6:25 ` Alexandre Oliva
2023-12-06 1:04 ` Alexandre Oliva
2023-12-05 9:01 ` Richard Biener
2023-12-06 8:36 ` Causes to nvptx bootstrap fail: " Tobias Burnus
2023-12-06 11:32 ` Thomas Schwinge
2023-12-06 22:12 ` Alexandre Oliva
2023-12-07 3:33 ` [PATCH] strub: enable conditional support Alexandre Oliva
2023-12-07 7:24 ` Richard Biener
2023-12-07 16:44 ` Thomas Schwinge
2023-12-07 17:52 ` [PATCH] Alexandre Oliva
2023-12-08 6:46 ` [PATCH] Richard Biener
2023-12-08 9:33 ` [PATCH] strub: skip emutls after strubm errors Thomas Schwinge
2023-12-10 9:16 ` FX Coudert
2023-12-07 7:21 ` Causes to nvptx bootstrap fail: [PATCH v5] Introduce strub: machine-independent stack scrubbing Richard Biener
2023-12-06 10:22 ` Jan Hubicka
2023-12-07 21:19 ` Alexandre Oliva
2023-12-07 21:39 ` Alexandre Oliva
2023-12-09 2:08 ` [PATCH] strub: add note on attribute access Alexandre Oliva
2023-12-11 7:26 ` Richard Biener
2023-12-12 14:21 ` Jan Hubicka
2023-12-11 8:40 ` [PATCH] testsuite: Disable -fstack-protector* for some strub tests Jakub Jelinek
2023-12-11 8:59 ` Richard Biener
2023-12-20 8:15 ` [PATCH FYI] www: new AdaCore-contributed hardening features in gcc 13 and 14 Alexandre Oliva
2023-11-30 5:04 ` [PATCH v4] Introduce strub: machine-independent stack scrubbing Alexandre Oliva
2023-11-30 11:56 ` Richard Biener
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=orzggsl9bv.fsf_-_@lxoliva.fsfla.org \
--to=oliva@adacore.com \
--cc=craig.blackmore@embecosm.com \
--cc=gcc-patches@gcc.gnu.org \
--cc=graham.markall@embecosm.com \
--cc=hubicka@ucw.cz \
--cc=jeremy.bennett@embecosm.com \
--cc=mjambor@suse.cz \
--cc=richard.guenther@gmail.com \
--cc=wilson@tuliptree.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).