On Wed, 17 Jun 2026, Martin Uecker wrote:

> 
> This versions has the following changes:
> 
> - error handling is moved to builtins.cc
> - the logic for the two builtins is unified inĀ 
>   gimple-fold.cc and tree-nested.cc
> - the documentation is extended and includes an
>   example, cross references and notes about UB
> - if we try to inline a function which need a static
>   chain but none is given, we set it to NULL
> 
> 
> Bootstrapped and regression-tested on x86_64.

LGTM.

Thanks,
Richard.

> 
> 
>     Built-ins to access code pointer and static chain of nested function.
>     
>     This patch adds two new built-ins, __builtin_call_code_address and
>     __builtin_call_static chain, to extract the code address and the static
>     chain pointer from a (nested) function, respectively.  Those can then be
>     used to call the nested function using the existing built-in
>     __builtin_call_with_static_chain.  This feature can be used to avoid the
>     creation of trampolines and often allows writing more efficient
>     code, e.g. where trampolines prevent devirtualization (PR49666).
>     
>     gcc/ChangeLog:
>             * builtins.def (BUILT_IN_CALL_CODE_ADDRESS,
>             BUILT_IN_CALL_STATIC_CHAIN): New.
>             * builtins.cc (expand_builtin): Emit errors for invalid uses.
>             (is_simple_builtin): Add new built-in functions.
>             * gimple-fold.cc (gimple_fold_builtin_call_info): New function.
>             (gimple_fold_builtin): Expand new built-ins for non-nested 
> functions.
>             * tree-nested.cc (convert_tramp_reference_stmt): Ingore new 
> built-ins.
>             (convert_gimple_call): Expand built-ins for nested functions.
>             * tree-inline.cc (initialize_inlined_parameters): Set a missing
>             static chain to NULL.
>     
>     gcc/doc/ChangeLog:
>             * extend.texi (Constructing Calls): Document
>             __builtin_call_static_chain and __builtin_call_code_address
>             and update __builtin_call_with_static_chain.
>             (Nested Functions): Update.
>     
>     gcc/testsuite/ChangeLog:
>             * gcc.dg/builtin-call-info-1.c: New test.
>             * gcc.dg/builtin-call-info-2.c: New test.
>             * gcc.dg/builtin-call-info-3.c: New test.
> 
> diff --git a/gcc/builtins.cc b/gcc/builtins.cc
> index 52f3ee029d0..59ac630e30c 100644
> --- a/gcc/builtins.cc
> +++ b/gcc/builtins.cc
> @@ -8092,6 +8092,18 @@ 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_CALL_CODE_ADDRESS:
> +      /* All valid uses of __builtin_call_code_address () are removed in
> +      in tree-nested.cc or gimple-fold.cc.  */
> +      error ("argument to %<__builtin_call_code_address%> must be a 
> function");
> +      return const0_rtx;
> +
> +    case BUILT_IN_CALL_STATIC_CHAIN:
> +       /* All valid uses of __builtin_call_static_chain () are removed in
> +       in tree-nested.cc or gimple-fold.cc.  */
> +      error ("argument to %<__builtin_call_static_chain%> must be a 
> function");
> +      return const0_rtx;
> +
>      case BUILT_IN_SAVEREGS:
>        return expand_builtin_saveregs ();
>  
> @@ -12368,6 +12380,8 @@ is_simple_builtin (tree decl)
>        case BUILT_IN_STACK_SAVE:
>        case BUILT_IN_STACK_RESTORE:
>        case BUILT_IN_DWARF_CFA:
> +      case BUILT_IN_CALL_CODE_ADDRESS:
> +      case BUILT_IN_CALL_STATIC_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 e39c3427d77..4adeb440ed9 100644
> --- a/gcc/builtins.def
> +++ b/gcc/builtins.def
> @@ -1170,6 +1170,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)
>  
> +/* Information needed to call (nested) functions.  */
> +DEF_GCC_BUILTIN (BUILT_IN_CALL_CODE_ADDRESS, "call_code_address", 
> BT_FN_PTR_PTR, ATTR_NULL)
> +DEF_GCC_BUILTIN (BUILT_IN_CALL_STATIC_CHAIN, "call_static_chain", 
> 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 9f821f04853..f6a536747be 100644
> --- a/gcc/doc/extend.texi
> +++ b/gcc/doc/extend.texi
> @@ -13978,7 +13978,10 @@ safe.
>  GCC implements taking the address of a nested function using a technique
>  called @dfn{trampolines}.  This technique was described in
>  @cite{Lexical Closures for C++} (Thomas M. Breuel, USENIX
> -C++ Conference Proceedings, October 17-21, 1988).
> +C++ Conference Proceedings, October 17-21, 1988).  It is also
> +possible to use the built-in @code{__builtin_call_with_static_chain}
> +(@pxref{Constructing Calls}) to invoke a nested function indirectly
> +without the use of a trampoline.
>  
>  A nested function can jump to a label inherited from a containing
>  function, provided the label is explicitly declared in the containing
> @@ -16674,11 +16677,73 @@ myopen (const char *path, int oflag, ...)
>  The @var{call_exp} expression must be a function call, and the
>  @var{pointer_exp} expression must be a pointer.  The @var{pointer_exp}
>  is passed to the function call in the target's static chain location.
> -The result of builtin is the result of the function call.
> +The result of the built-in is the result of the function call.
> +This built-in can be used to call Go closures from C, and it can be
> +used to call @xref{Nested Functions} using the code address and
> +static chain obtained with @code{__builtin_call_code_address} and
> +@code{__builtin_call_static_chain}, respectively.
>  
> -@emph{Note:} This builtin is only available for C@.
> -This builtin can be used to call Go closures from C.
> +@smallexample
> +int process(int (*callback)(int, void *), void *data)
> +@{
> +  int value = 5;
> +  return (*callback)(value, data);
> +@}
> +
> +struct call_info @{
> +  int (*code)(int);
> +  void *chain;
> +@};
> +
> +int callback(int arg, void *data)
> +@{
> +   struct call_info *ci = data;
> +   return __builtin_call_with_static_chain(ci->code(arg), ci->chain);
> +@}
> +
> +int doit(int x)
> +@{
> +  int worker(int y)
> +  @{
> +    return x + y;
> +  @}
> +
> +  struct call_info ci = @{
> +    ci.code = __builtin_call_code_address(worker),
> +    ci.chain = __builtin_call_static_chain(worker),
> +  @};
> +
> +  return process(callback, &ci);
> +@}
> +@end smallexample
> +
> +@emph{Note:} This built-in is only available for C@.
> +@enddefbuiltin
> +
> +@defbuiltin{@var{type} __builtin_call_static_chain (@var{pointer_exp})}
> +
> +The @var{pointer_exp} expression must a function identifier.
> +The result is the static chain pointer that can be used to call
> +the function in its current context using the built-in
> +@code{__builtin_call_with_static_chain}, or a null pointer if
> +no static chain is needed.
> +
> +@emph{Note:} The lifetime of a nested function that accesses the
> +environment of an enclosing function is limited.  Any use of the
> +static chain after this lifetime has ended has undefined behavior.
> +@enddefbuiltin
> +
> +@defbuiltin{@var{type} __builtin_call_code_address (@var{pointer_exp})}
> +
> +The @var{pointer_exp} expression must be a function identifier.
> +The result is the static address of the function.  For a nested function,
> +it represents the address of the underlying machine code and not of a
> +trampoline that would otherwise be generated to setup the static chain.
> +The code address can be used together with a static chain pointer to
> +call the function with the built-in @code{__builtin_call_with_static_chain}.
>  
> +@emph{Note:} Calling a function that requires a static chain using the
> +code address directly in a function call expression has undefined behavior.
>  @enddefbuiltin
>  
>  @node Return Address
> diff --git a/gcc/gimple-fold.cc b/gcc/gimple-fold.cc
> index fab4886b0f2..4422a48383d 100644
> --- a/gcc/gimple-fold.cc
> +++ b/gcc/gimple-fold.cc
> @@ -5409,6 +5409,33 @@ gimple_fold_builtin_stdarg (gimple_stmt_iterator *gsi, 
> gcall *call)
>      }
>  }
>  
> +/* Fold __builtin_call_{code_address,static_chain} builtins.  This handles
> +   only the trivial left-over cases not processed in tree-nested.cc.  */
> +
> +static bool
> +gimple_fold_builtin_call_info (gimple_stmt_iterator *gsi,
> +                            enum built_in_function fcode)
> +{
> +  gcall *stmt = as_a <gcall *>(gsi_stmt (*gsi));
> +  tree arg = gimple_call_arg (stmt, 0);
> +
> +  /* The error will be emitted in builtins.cc.  */
> +  if (TREE_CODE (arg) != ADDR_EXPR
> +      || FUNCTION_DECL != TREE_CODE (TREE_OPERAND (arg, 0)))
> +    return false;
> +
> +  /* The case with static chain is handled in tree-nested.cc.  */
> +  gcc_assert (!DECL_STATIC_CHAIN (TREE_OPERAND (arg, 0)));
> +
> +  if (fcode == BUILT_IN_CALL_STATIC_CHAIN)
> +    replace_call_with_value (gsi, null_pointer_node);
> +  else
> +    replace_call_with_value (gsi, arg);
> +
> +  return true;
> +}
> +
> +
>  /* Fold the non-target builtin at *GSI and return whether any simplification
>     was made.  */
>  
> @@ -5589,6 +5616,10 @@ gimple_fold_builtin (gimple_stmt_iterator *gsi)
>      case BUILT_IN_CONSTANT_P:
>        return gimple_fold_builtin_constant_p (gsi);
>  
> +    case BUILT_IN_CALL_CODE_ADDRESS:
> +    case BUILT_IN_CALL_STATIC_CHAIN:
> +      return gimple_fold_builtin_call_info (gsi, fcode);
> +
>      default:;
>      }
>  
> diff --git a/gcc/testsuite/gcc.dg/builtin-call-info-1.c 
> b/gcc/testsuite/gcc.dg/builtin-call-info-1.c
> new file mode 100644
> index 00000000000..ecfe3a62c9a
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/builtin-call-info-1.c
> @@ -0,0 +1,34 @@
> +/* { dg-do run } */
> +/* { dg-options "-Wtrampolines" } */
> +
> +typedef struct {
> +     int (*code)(int);
> +     void *chain;
> +} call_info_t;
> +
> +int apply(call_info_t c, int value)
> +{
> +     return __builtin_call_with_static_chain(c.code(value), c.chain);
> +}
> +
> +int foo(int x)
> +{
> +     int add(int y)
> +     {
> +             return x + y;
> +     }
> +
> +     call_info_t info = {
> +             __builtin_call_code_address(add),
> +             __builtin_call_static_chain(add)
> +     };
> +
> +     return apply(info, x);
> +}
> +
> +int main()
> +{
> +     if (4 != foo(2))
> +             __builtin_abort();
> +}
> +
> diff --git a/gcc/testsuite/gcc.dg/builtin-call-info-2.c 
> b/gcc/testsuite/gcc.dg/builtin-call-info-2.c
> new file mode 100644
> index 00000000000..acb6ad98382
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/builtin-call-info-2.c
> @@ -0,0 +1,47 @@
> +/* { dg-do run } */
> +/* { dg-options "-Wtrampolines" } */
> +
> +/* Check that we get the expected pointers in
> +   different context.  */
> +
> +int f(int x)
> +{
> +
> +     if (f != __builtin_call_code_address(f))
> +             __builtin_abort();
> +
> +     if ((void*)0 != __builtin_call_static_chain(f))
> +             __builtin_abort();
> +
> +     static int (*code)(int);
> +     static void *chain;
> +
> +     int g(int y)
> +     {
> +             if (code != __builtin_call_code_address(g))
> +                     __builtin_abort();
> +             
> +             if (chain != __builtin_call_static_chain(g))
> +                     __builtin_abort();
> +     
> +             return x + y; 
> +     }
> +
> +     code = __builtin_call_code_address(g);
> +     chain = __builtin_call_static_chain(g);
> +
> +     return g(x);
> +}
> +
> +int main()
> +{
> +     if (f != __builtin_call_code_address(f))
> +             __builtin_abort();
> +
> +     if ((void*)0 != __builtin_call_static_chain(f))
> +             __builtin_abort();
> +
> +     if (6 != f(3))
> +             __builtin_abort();
> +}
> +
> diff --git a/gcc/testsuite/gcc.dg/builtin-call-info-3.c 
> b/gcc/testsuite/gcc.dg/builtin-call-info-3.c
> new file mode 100644
> index 00000000000..70101c5fdee
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/builtin-call-info-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_call_code_address(g);  // ok
> +}
> +
> +void * bar(int n)
> +{
> +     int g(int x)
> +     {
> +             return n + x;
> +     }
> +
> +     return __builtin_call_static_chain(g);  /* { dg-warning "returns 
> address of local variable" } */
> +}
> +
> diff --git a/gcc/tree-inline.cc b/gcc/tree-inline.cc
> index 302ab8d6b7c..a3b2c71fb22 100644
> --- a/gcc/tree-inline.cc
> +++ b/gcc/tree-inline.cc
> @@ -3760,9 +3760,11 @@ 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);
> -
> +      /* Calling a function that requires a static chain directly
> +      without setting up the static chain has undefined behavior
> +      at run-time.  */
> +      if (!static_chain)
> +     static_chain = null_pointer_node;
>        setup_one_parameter (id, p, static_chain, fn, bb, &vars);
>      }
>  
> diff --git a/gcc/tree-nested.cc b/gcc/tree-nested.cc
> index cdccc51d33e..4019afb5e2c 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)
> +         && (DECL_FUNCTION_CODE (decl) == BUILT_IN_CALL_CODE_ADDRESS
> +             || DECL_FUNCTION_CODE (decl) == BUILT_IN_CALL_STATIC_CHAIN))
> +       break;
> +
>       /* Only walk call arguments, lest we generate trampolines for
>          direct calls.  */
>       unsigned long i, nargs = gimple_call_num_args (stmt);
> @@ -2994,11 +3001,41 @@ 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
> +           || DECL_FUNCTION_CODE (decl) == BUILT_IN_CALL_STATIC_CHAIN))
> +     {
> +       tree d = gimple_call_arg (stmt, 0);
> +       if (TREE_CODE (d) != ADDR_EXPR
> +           || FUNCTION_DECL != TREE_CODE (TREE_OPERAND (d, 0)))
> +         break;
> +       tree ret = null_pointer_node;
> +       if (DECL_FUNCTION_CODE (decl) == BUILT_IN_CALL_CODE_ADDRESS)
> +         {
> +           /* Return code pointer.  */
> +           ret = build_addr (TREE_OPERAND (d, 0));
> +           TREE_NO_TRAMPOLINE (ret) = 1;
> +         }
> +       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)

Reply via email to