On Tue, 31 Mar 2026, Martin Uecker wrote:
> Am Dienstag, dem 31.03.2026 um 10:05 +0200 schrieb Richard Biener:
> > On Mon, 30 Mar 2026, Martin Uecker wrote:
> >
> > >
> > > Here is a patch (stage1) for a feature which I think we have been
> > > missing for a long time as we have __builtin_call_with_static_chain
> > > to call into other languages but no way to use it for nested
> > > functions in C (as a hack, one could extract the address from the
> > > trampoline).
> > >
> > >
> > > My personal motivation is mostly to get around the problem that we
> > > currently cannot "devirtualize" nested functions (PR49666) which
> > > prevents important optimizations for my main use case (higher-order
> > > loop kernels in numerics). With this patch this works beautifully,
> > > e.g. the function "foo" inĀ the first test is compiled to
> > >
> > > foo:
> > > leal (%rdi,%rdi), %eax
> > > ret
> > >
> > > which is perfect as all the overhead has completely disappeared.
> > >
> > > But the built-ins also allows one to get get rid of the trampolines
> > > more generally.
> > >
> > > The implementation is relatively straightforward. The built-ins
> > > are expanded in tree-nested.cc and either disable trampoline
> > > generation to return the code address or return the static chain.
> >
> > I think this is reasonable but I wonder whether a single builtin
> > that returns a descriptor structure would be "better" (more in
> > line what you get with C++ lambda)?
>
> You mean something like a compiler-generated structure such as
>
> struct __gcc_closure {
> void *code;
> void *data;
> };
>
> or something else?
Yes, something like. I'd have it anonymous, aka only specified
there are code and data fields, C should now have enough ways
to store it, from
auto descr = __builtin_... (foo);
to
typeof (__builtin_... (foo)) descr;
Possibly the type is not dependent on 'foo' unless it uses
stronly typed 'code' (which it could).
> From a user perspective a proper type-safe wide pointer (descriptor) type
> comparable to std::function_ref would be ideal but this needs a bit
> more work.
>
>
> Martin
>
>
> >
> > > Bootstrapped and regression tested on x86_64.
> > >
> > > Martin
> > >
> > >
> > >
> > > c: Built-ins to access code pointer and static chain of nested
> > > function.
> > >
> > > This patch adds two new built-ins, __builtin_nested_code and
> > > __builtin_nested_chain, to extract the code pointer and the static
> > > chain pointer from a nested function. Those can then be used to
> > > call the nested function using the existing built-in
> > > __builtin_call_with_static_chain. This can be used to avoid the
> > > creation of trampolines and often allows writing more efficient
> > > code, e.g. when trampolines prevent devirtualization (PR49666).
> > >
> > > gcc/ChangeLog:
> > > * builtins.def: Add new built-ins.
> > > * builtins.cc (expand_builtin): Trivially expand built-ins.
> > > (is_simple_builtin): Add new built-ins.
> > > * tree-nested.cc (convert_tramp_reference_stmt): Ingore
> > > new built-ins.
> > > (convert_gimple_call): Expand built-ins.
> > > * tree-inline.cc (initialize_inlined_parameters): Replace
> > > assertion with error.
> > >
> > > gcc/doc/ChangeLog:
> > > * extend.texi: Document new built-ins.
> > >
> > > gcc/testsuite/ChangeLog:
> > > * gcc.dg/builtin-nested-1.c: New test.
> > > * gcc.dg/builtin-nested-2.c: New test.
> > > * gcc.dg/builtin-nested-3.c: New test.
> > >
> > > diff --git a/gcc/builtins.cc b/gcc/builtins.cc
> > > index 692e20088c2..e12790ca0b0 100644
> > > --- a/gcc/builtins.cc
> > > +++ b/gcc/builtins.cc
> > > @@ -8093,6 +8093,14 @@ expand_builtin (tree exp, rtx target, rtx
> > > subtarget, machine_mode mode,
> > > expand_builtin_return (expand_normal (CALL_EXPR_ARG (exp, 0)));
> > > return const0_rtx;
> > >
> > > + case BUILT_IN_NESTED_CODEPTR:
> > > + /* If not expanded in tree-nested.cc. */
> > > + return expand_normal (CALL_EXPR_ARG (exp, 0));
> > > +
> > > + case BUILT_IN_NESTED_CHAIN:
> > > + /* If not expanded in tree-nested.cc. */
> > > + return const0_rtx;
> > > +
> > > case BUILT_IN_SAVEREGS:
> > > return expand_builtin_saveregs ();
> > >
> > > @@ -12311,6 +12319,8 @@ is_simple_builtin (tree decl)
> > > case BUILT_IN_STACK_SAVE:
> > > case BUILT_IN_STACK_RESTORE:
> > > case BUILT_IN_DWARF_CFA:
> > > + case BUILT_IN_NESTED_CODEPTR:
> > > + case BUILT_IN_NESTED_CHAIN:
> > > /* Exception state returns or moves registers around. */
> > > case BUILT_IN_EH_FILTER:
> > > case BUILT_IN_EH_POINTER:
> > > diff --git a/gcc/builtins.def b/gcc/builtins.def
> > > index 8ab0599b17f..817c39ecd94 100644
> > > --- a/gcc/builtins.def
> > > +++ b/gcc/builtins.def
> > > @@ -1164,6 +1164,10 @@ DEF_BUILTIN_STUB (BUILT_IN_NONLOCAL_GOTO,
> > > "__builtin_nonlocal_goto")
> > > DEF_EXT_LIB_BUILTIN (BUILT_IN_GCC_NESTED_PTR_CREATED,
> > > "__gcc_nested_func_ptr_created", BT_FN_VOID_PTR_PTR_PTR,
> > > ATTR_NOTHROW_LIST)
> > > DEF_EXT_LIB_BUILTIN (BUILT_IN_GCC_NESTED_PTR_DELETED,
> > > "__gcc_nested_func_ptr_deleted", BT_FN_VOID, ATTR_NOTHROW_LIST)
> > >
> > > +/* Using nested functions. */
> > > +DEF_GCC_BUILTIN (BUILT_IN_NESTED_CHAIN, "nested_chain", BT_FN_PTR_PTR,
> > > ATTR_NULL)
> > > +DEF_GCC_BUILTIN (BUILT_IN_NESTED_CODEPTR, "nested_codeptr",
> > > BT_FN_PTR_PTR, ATTR_NULL)
> > > +
> > > /* Implementing __builtin_setjmp. */
> > > DEF_BUILTIN_STUB (BUILT_IN_SETJMP_SETUP, "__builtin_setjmp_setup")
> > > DEF_BUILTIN_STUB (BUILT_IN_SETJMP_RECEIVER, "__builtin_setjmp_receiver")
> > > diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi
> > > index 47b0bdf1340..8fbb088226f 100644
> > > --- a/gcc/doc/extend.texi
> > > +++ b/gcc/doc/extend.texi
> > > @@ -16610,6 +16610,25 @@ This builtin can be used to call Go closures
> > > from C.
> > >
> > > @enddefbuiltin
> > >
> > > +@defbuiltin{@var{type} __builtin_nested_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
> > > +is needed.
> > > +
> > > +@enddefbuiltin
> > > +
> > > +@defbuiltin{@var{type} __builtin_nested_codeptr (@var{pointer_exp})}
> > > +
> > > +The @var{pointer_exp} expression must designate a function.
> > > +The result is the static address of the function. For a nested function,
> > > +the address represents the address of the underlying machine code and
> > > +not of a trampoline that would otherwise be generated to setup the
> > > +static chain.
> > > +
> > > +@enddefbuiltin
> > > +
> > > @node Return Address
> > > @section Getting the Return or Frame Address of a Function
> > >
> > > diff --git a/gcc/testsuite/gcc.dg/builtin-nested-1.c
> > > b/gcc/testsuite/gcc.dg/builtin-nested-1.c
> > > new file mode 100644
> > > index 00000000000..d1e887357b1
> > > --- /dev/null
> > > +++ b/gcc/testsuite/gcc.dg/builtin-nested-1.c
> > > @@ -0,0 +1,34 @@
> > > +/* { dg-do run } */
> > > +/* { dg-options "-Wtrampolines" } */
> > > +
> > > +struct closure {
> > > + int (*fun)(int);
> > > + void *data;
> > > +};
> > > +
> > > +int apply(struct closure c, int value)
> > > +{
> > > + return __builtin_call_with_static_chain(c.fun(value), c.data);
> > > +}
> > > +
> > > +int foo(int x)
> > > +{
> > > + int add(int y)
> > > + {
> > > + return x + y;
> > > + }
> > > +
> > > + struct closure c = {
> > > + __builtin_nested_codeptr(add),
> > > + __builtin_nested_chain(add),
> > > + };
> > > +
> > > + return apply(c, x);
> > > +}
> > > +
> > > +int main()
> > > +{
> > > + if (4 != foo(2))
> > > + __builtin_abort();
> > > +}
> > > +
> > > diff --git a/gcc/testsuite/gcc.dg/builtin-nested-2.c
> > > b/gcc/testsuite/gcc.dg/builtin-nested-2.c
> > > new file mode 100644
> > > index 00000000000..af2cd207149
> > > --- /dev/null
> > > +++ b/gcc/testsuite/gcc.dg/builtin-nested-2.c
> > > @@ -0,0 +1,46 @@
> > > +/* { dg-do run } */
> > > +/* { dg-options "-Wtrampolines" } */
> > > +
> > > +/* Check that we get the expected pointers in
> > > + different context. */
> > > +
> > > +int f(int x)
> > > +{
> > > + static void *chain, *code;
> > > +
> > > + if (f != __builtin_nested_codeptr(f))
> > > + __builtin_abort();
> > > +
> > > + if ((void*)0 != __builtin_nested_chain(f))
> > > + __builtin_abort();
> > > +
> > > +
> > > + int g(int y)
> > > + {
> > > + if (code != __builtin_nested_codeptr(g))
> > > + __builtin_abort();
> > > +
> > > + if (chain != __builtin_nested_chain(g))
> > > + __builtin_abort();
> > > +
> > > + return x + y;
> > > + }
> > > +
> > > + chain = __builtin_nested_chain(g);
> > > + code = __builtin_nested_codeptr(g);
> > > +
> > > + return g(x);
> > > +}
> > > +
> > > +int main()
> > > +{
> > > + if (f != __builtin_nested_codeptr(f))
> > > + __builtin_abort();
> > > +
> > > + if ((void*)0 != __builtin_nested_chain(f))
> > > + __builtin_abort();
> > > +
> > > + if (6 != f(3))
> > > + __builtin_abort();
> > > +}
> > > +
> > > diff --git a/gcc/testsuite/gcc.dg/builtin-nested-3.c
> > > b/gcc/testsuite/gcc.dg/builtin-nested-3.c
> > > new file mode 100644
> > > index 00000000000..2423cca737f
> > > --- /dev/null
> > > +++ b/gcc/testsuite/gcc.dg/builtin-nested-3.c
> > > @@ -0,0 +1,24 @@
> > > +/* { dg-do compile } */
> > > +/* { dg-options "-O2 -Wtrampolines -Wreturn-local-addr" } */
> > > +
> > > +
> > > +void * foo(int n)
> > > +{
> > > + int g(int x)
> > > + {
> > > + return n + x;
> > > + }
> > > +
> > > + return __builtin_nested_codeptr(g); // ok
> > > +}
> > > +
> > > +void * bar(int n)
> > > +{
> > > + int g(int x)
> > > + {
> > > + return n + x;
> > > + }
> > > +
> > > + return __builtin_nested_chain(g); /* { dg-warning "returns
> > > address of local variable" } */
> > > +}
> > > +
> > > diff --git a/gcc/tree-inline.cc b/gcc/tree-inline.cc
> > > index 087fcc8a8b8..8f5701ab693 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");
> > >
> > > setup_one_parameter (id, p, static_chain, fn, bb, &vars);
> > > }
> > > diff --git a/gcc/tree-nested.cc b/gcc/tree-nested.cc
> > > index cdccc51d33e..0be2b5840ef 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_NESTED_CHAIN == DECL_FUNCTION_CODE (decl)
> > > + || BUILT_IN_NESTED_CODEPTR == 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,47 @@ 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_NESTED_CODEPTR
> > > + || DECL_FUNCTION_CODE (decl) == BUILT_IN_NESTED_CHAIN))
> > > + {
> > > + bool chain_p = DECL_FUNCTION_CODE (decl) == BUILT_IN_NESTED_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),
> > > + chain_p ? "argument to %<__builtin_nested_chain%>"
> > > + " must be a function"
> > > + : "argument to %<__builtin_nested_codeptr%>"
> > > + " must be a function");
> > > + }
> > > + else if (chain_p)
> > > + {
> > > + 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);
> > > + }
> > > + }
> > > + else
> > > + {
> > > + /* Return code pointer. */
> > > + ret = build_addr (TREE_OPERAND (d, 0));
> > > + TREE_NO_TRAMPOLINE (ret) = 1;
> > > + }
> > > + 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)