https://gcc.gnu.org/g:a919711b47501f146e0c274793896efa877b3b27
commit r17-1826-ga919711b47501f146e0c274793896efa877b3b27 Author: Martin Uecker <[email protected]> Date: Thu Jan 1 16:01:31 2026 +0100 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/ChangeLog: * doc/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: --- gcc/builtins.cc | 14 ++++++ gcc/builtins.def | 4 ++ gcc/doc/extend.texi | 73 ++++++++++++++++++++++++++++-- gcc/gimple-fold.cc | 31 +++++++++++++ gcc/testsuite/gcc.dg/builtin-call-info-1.c | 34 ++++++++++++++ gcc/testsuite/gcc.dg/builtin-call-info-2.c | 47 +++++++++++++++++++ gcc/testsuite/gcc.dg/builtin-call-info-3.c | 24 ++++++++++ gcc/tree-inline.cc | 8 ++-- gcc/tree-nested.cc | 41 ++++++++++++++++- 9 files changed, 267 insertions(+), 9 deletions(-) diff --git a/gcc/builtins.cc b/gcc/builtins.cc index 52f3ee029d0b..59ac630e30c7 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 e39c3427d77d..4adeb440ed9d 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 2e8ec8a0af2c..0ff097d75846 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 fab4886b0f2e..4422a48383d5 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 000000000000..ecfe3a62c9a4 --- /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 000000000000..acb6ad983824 --- /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 000000000000..70101c5fdee9 --- /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 0610af5f3f05..8162b1f5051a 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 2a57a84e1208..78ee580465c6 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" @@ -2886,6 +2887,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); @@ -2998,11 +3005,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)) {
