On Tue, 16 Jun 2026, Martin Uecker wrote:
>
> Thanks for the review! See below for some answers and question.
>
> Am Dienstag, dem 16.06.2026 um 10:53 +0200 schrieb Richard Biener:
> > On Sat, 13 Jun 2026, Martin Uecker wrote:
> > >
>
> > >
> > > ++@defbuiltin{@var{type} __builtin_call_static_chain (@var{pointer_exp})}
> > > ++
> > > ++The @var{pointer_exp} expression must designate a function.
> > > ++The result is the static chain pointer that that is needed to call
> > > ++the function call in its current context, or a null pointer if none
> >
> > the function @var{pointer_exp} in the current context
> >
> > ? It's the context of the __builtin_call_static_chain call, right?
>
> Yes, it is the static chain (context) which would be used if the
> nested function would be called directly at this point.
>
>
>
> ...
>
> > >
> > > +/* Fold __builtin_call_code_address builtin. This handles only the
> > > + trivial left-over cases not processed in tree-nested.cc. */
> > > +
> > > +static bool
> > > +gimple_fold_builtin_call_code_address (gimple_stmt_iterator *gsi)
> > > +{
> > > + gcall *stmt = as_a <gcall *>(gsi_stmt (*gsi));
> > > + tree arg = gimple_call_arg (stmt, 0);
> > > +
> > > + if (TREE_CODE (arg) != ADDR_EXPR || !DECL_P (TREE_OPERAND (arg, 0))
> > > + || FUNCTION_DECL != TREE_CODE (TREE_OPERAND (arg, 0)))
> >
> > !DECL_P || != FUNCTION_DECL makes the !DECL_P check redundant.
> >
> > > + {
> > > + error_at (gimple_location (stmt),
> > > + "argument to %<__builtin_call_code_address%> "
> > > + "must be a function");
> >
> > Hmm, late errors ...
> >
> > > + return false;
> >
> > ... and this means you'd repeatedly emit it. I'd suggest to
> > at least replace the call with something else, like, for
> > example a trap?
>
> How about expanding in gimple_fold only in the valid case, and
> then emitting the error in builtins.cc? This is how it is
> done for some other builtins.
Sure, that would work as well.
> We could also catch this in the FE, but it does not seem
> worth the trouble at this time.
>
> > > + }
> > > +
> > > + /* The case with static chain is handled in tree-nested.cc. */
> > > + gcc_assert (!DECL_STATIC_CHAIN (TREE_OPERAND (arg, 0)));
> >
> > That's a correctness assert? I just wonder if enough obfuscation,
> > like
> >
> > foo()
> > {
> > bar(){}
> >
> > void *p = bar;
> > p = p + 2;
> > p = p - 2;
> > x = __builtin_call_code_address (p)
> >
> > might eventually end us up here, ICEing?
>
> At this point, tree-nested.cc would have added a
> __builtin_adjust_trampoline already and we would then
> get the error because the argument does not have the
> expected form. Exluding any such scenario was my
> motivation to make the argument check so restrictive.
>
>
> > > diff --git a/gcc/tree-inline.cc b/gcc/tree-inline.cc
> > > index 302ab8d6b7c..9461d531275 100644
> > > --- a/gcc/tree-inline.cc
> > > +++ b/gcc/tree-inline.cc
> > > @@ -3760,8 +3760,8 @@ initialize_inlined_parameters (copy_body_data *id,
> > > gimple *stmt,
> > > gcc_assert (fn != current_function_decl);
> > > if (p)
> > > {
> > > - /* No static chain? Seems like a bug in tree-nested.cc. */
> > > - gcc_assert (static_chain);
> > > + if (!static_chain)
> > > + error ("called function requires a static chain");
> >
> > but then you simply continue ... I'd rather make sure we do not
> > try inlining such a function, thus the assert does not hit
> > (and we can leave it in place)
>
> If I check this before inlining, the assertion will never trigger,
> but also not detect bugs in tree-nested. In this case, I would
> rather remove it.
Fine with me as well.
> The problem here is that the programmer has tried to call a function that
> requires a static chain to be set up using the bare code address.
> Inlining just happens to detect this, so it seems helpful to give
> the error.
>
> For example, it would give an error in the following example:
>
> int foo(int y)
> {
> int b(int x)
> {
> return x + y;
> }
>
> void *q = __builtin_call_code_address(b);
>
> typeof(b) p = q; // naively cast to the correct type
> return p(0); // should use __builtin_call_with_static_chain!
> }
>
>
> One could also argue that the programmer should know what he is doing,
> and just allow this. In this case, one could remove the assertion, set the
> static chain to NULL and simply continue to inline the function.
I wonder if we'd want to catch this in the isolate-path pass (but
that runs after inlining), which could then replace the direct
call with a trap() and diagnose the bogus call? Note even RTL
optimization might expose the fndecl, so the issue is that
C doesn't have strong type requirements on function pointer type
(conversions) and/or the function pointer doesn't encode that
it needs a static chain in the first place?
> For example, the following would then be possible:
>
> int foo(int y)
> {
> int b(bool flag, int x)
> {
> if (flag) return x; // no static chain needed
> return x + y;
> }
>
> void *q = __builtin_call_code_address(b);
>
> typeof(b) p = q; // just cast to the correct type
> return p(true, 0); // for true does not need the chain.
> }
>
> Maybe the later is preferable for built-ins meant as a
> low-level tool?
I suppose that if calling a function that expects a static
chain without a static chain (again, what about indirect
calls?) is only UB at runtime then we shouldn't diagnose
anything but DWIM? Btw,
> int b(bool flag, int x)
> {
> if (flag) return x; // no static chain needed
> return x + y;
> }
so it's invalid for the compiler to hoist the access to y
before if (flag)? Because I think we mark accesses via
the static chain as not trapping (aka we assume it's
correctly set up).
I realize nested functions are a GNU extension, so we should
amend our section about UBs in GNU extensions?
> On the language level, I hope that at some point we get some
> type-safe feature that distinguished between functions pointer
> that require a static chain and those that do not, which would
> prevent this error (when not circumventing this by casts)
It should be possible to mark FUNCTION_TYPE with a flag that
indicates a static chain is required so that
auto f = __builtin_call_code_address (b);
would get you an 'f' that's appropriately marked? We could
simply add a new attribute for this which might play nicely
with _Generic and other C features?
> >
> > > setup_one_parameter (id, p, static_chain, fn, bb, &vars);
> > > }
> > > diff --git a/gcc/tree-nested.cc b/gcc/tree-nested.cc
> > > index cdccc51d33e..d478461c1b4 100644
> > > --- a/gcc/tree-nested.cc
> > > +++ b/gcc/tree-nested.cc
> > > @@ -36,6 +36,7 @@
> > > #include "gimplify.h"
> > > #include "gimple-iterator.h"
> > > #include "gimple-walk.h"
> > > +#include "gimple-fold.h"
> > > #include "tree-cfg.h"
> > > #include "explow.h"
> > > #include "langhooks.h"
> > > @@ -2882,6 +2883,12 @@ convert_tramp_reference_stmt (gimple_stmt_iterator
> > > *gsi, bool *handled_ops_p,
> > > {
> > > case GIMPLE_CALL:
> > > {
> > > + tree decl = gimple_call_fndecl (stmt);
> > > + if (decl && fndecl_built_in_p (decl, BUILT_IN_NORMAL)
> > > + && (BUILT_IN_CALL_CODE_ADDRESS == DECL_FUNCTION_CODE (decl)
> > > + || BUILT_IN_CALL_STATIC_CHAIN == DECL_FUNCTION_CODE (decl)))
> > > + break;
> > > +
> > > /* Only walk call arguments, lest we generate trampolines for
> > > direct calls. */
> > > unsigned long i, nargs = gimple_call_num_args (stmt);
> > > @@ -2994,11 +3001,59 @@ convert_gimple_call (gimple_stmt_iterator *gsi,
> > > bool *handled_ops_p,
> > > switch (gimple_code (stmt))
> > > {
> > > case GIMPLE_CALL:
> > > - if (gimple_call_chain (stmt))
> > > - break;
> > > decl = gimple_call_fndecl (stmt);
> > > if (!decl)
> > > break;
> > > + if (fndecl_built_in_p (decl, BUILT_IN_NORMAL)
> > > + && DECL_FUNCTION_CODE (decl) == BUILT_IN_CALL_CODE_ADDRESS)
> > > + {
> > > + tree d = gimple_call_arg (stmt, 0);
> > > + tree ret = null_pointer_node;
> > > + if (TREE_CODE (d) != ADDR_EXPR || !DECL_P (TREE_OPERAND (d, 0))
> > > + || FUNCTION_DECL != TREE_CODE (TREE_OPERAND (d, 0)))
> > > + {
> > > + error_at (gimple_location (stmt),
> > > + "argument to %<__builtin_call_code_address%> "
> > > + "must be a function");
> > > + }
> > > + else
> > > + {
> > > + /* Return code pointer. */
> > > + ret = build_addr (TREE_OPERAND (d, 0));
> > > + TREE_NO_TRAMPOLINE (ret) = 1;
> >
> > Seeing this, fold-const.cc:operand_compare::operand_equal_p does
> > not compare this flag so it would consider trampoline vs.
> > no-trampoline addresses the same.
> >
> > What happens if you call a __builtin_call_code_address non-trampoline
> > directly indirectly?
>
> I am not sure I understand the question correctöy, but if the static chain
> is not setup correctly the function will crash if one tries to use it.
> But if we encounter this during inlining, this would run into the error
> above. I am not sure if there other cases to worry about, e.g.
> some interprocedural constant propagation? I would guess it would treat
> the static chain as unknown when not set, but I am not sure.
I guess it's the same issue discussed above. Usually
foo()
{
bar(){}
auto p = bar;
p();
}
works correctly, but if you do
auto p = __builtin_call_code_address (bar);
p()
it will not.
Depending on the ABI a __builtin_call_with_static_chain (x, y)
with a function x that doesn't expect a static chain might either
just ignore it or wreck its calling convention.
Given it's C I don't think we can do better than making those
cases invoke UB?
Richard.
>
> Best,
> Martin
>
> >
> > Otherwise LGTM, I'd like to see approval from a C FE maintainer as well.
> >
> > Thanks,
> > Richard.
> >
> > > + }
> > > + replace_call_with_value (gsi, ret);
> > > + break;
> > > + }
> > > + else if (fndecl_built_in_p (decl, BUILT_IN_NORMAL)
> > > + && DECL_FUNCTION_CODE (decl) == BUILT_IN_CALL_STATIC_CHAIN)
> > > + {
> > > + tree d = gimple_call_arg (stmt, 0);
> > > + tree ret = null_pointer_node;
> > > + if (TREE_CODE (d) != ADDR_EXPR || !DECL_P (TREE_OPERAND (d, 0))
> > > + || FUNCTION_DECL != TREE_CODE (TREE_OPERAND (d, 0)))
> > > + {
> > > + error_at (gimple_location (stmt),
> > > + "argument to %<__builtin_call_static-chain%> "
> > > + "must be a function");
> > > + }
> > > + else
> > > + {
> > > + decl = TREE_OPERAND (d, 0);
> > > + target_context = decl_function_context (decl);
> > > + if (target_context && DECL_STATIC_CHAIN (decl))
> > > + {
> > > + /* Return static chain. */
> > > + info->static_chain_added
> > > + |= (1 << (info->context != target_context));
> > > + ret = get_static_chain (info, target_context, &wi->gsi);
> > > + }
> > > + }
> > > + replace_call_with_value (gsi, ret);
> > > + break;
> > > + }
> > > + if (gimple_call_chain (stmt))
> > > + break;
> > > target_context = decl_function_context (decl);
> > > if (target_context && DECL_STATIC_CHAIN (decl))
> > > {
> > >
>
--
Richard Biener <[email protected]>
SUSE Software Solutions Germany GmbH,
Frankenstrasse 146, 90461 Nuernberg, Germany;
GF: Jochen Jaser, Andrew McDonald, Werner Knoblich; (HRB 36809, AG Nuernberg)