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

> 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)

Reply via email to