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.
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.
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.
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?
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)
>
> > 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.
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))
> > {
> >