Any comments on this patch?
Best
Martin
Am Samstag, dem 18.04.2026 um 12:44 +0200 schrieb Martin Uecker:
> This is how this patch could look like with a single builtin that returns
> an anonymous structure. I named it __builtin_call_info which might be a bit
> nicer in combination with __builtin_call_with_static_chain. I also now fold
> this in gimple-fold.cc for the trivial case where there is no static
> chain.
>
> Bootstrapped and regression tested on x86_64. No AI was used.
>
> Best,
> Martin
>
>
>
>
>
> c: Built-in to access code pointer and static chain of nested function.
>
> This patch adds a new built-in, __builtin_call_info, 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 feature 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-in.
> * builtin-types.def: Add new call info type.
> * tree.h: Add new call info type.
> * tree-core.h: Add new call info type.
> * tree.cc (build_common_tree_nodes): Build new call info type
> node.
> * builtins.cc (expand_builtin): Mark new case as unreachable.
> (is_simple_builtin): Add new built-in.
> * gimple-fold.cc (gimple_fold_builtin_call_info): New function.
> (gimple_fold_builtin) Expand new built-in for non-nested
> functions.
> * tree-nested.cc (convert_tramp_reference_stmt): Ingore new
> built-in.
> (convert_gimple_call): Expand built-in for nested functions.
> * tree-inline.cc (initialize_inlined_parameters): Replace
> assertion with error.
>
> gcc/doc/ChangeLog:
> * extend.texi: Document new built-in.
>
> 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/builtin-types.def b/gcc/builtin-types.def
> index ab0cbc65cfc..1ca220d1ce1 100644
> --- a/gcc/builtin-types.def
> +++ b/gcc/builtin-types.def
> @@ -209,6 +209,8 @@ DEF_PRIMITIVE_TYPE (BT_DFLOAT64X, (dfloat64x_type_node
> DEF_PRIMITIVE_TYPE (BT_VALIST_REF, va_list_ref_type_node)
> DEF_PRIMITIVE_TYPE (BT_VALIST_ARG, va_list_arg_type_node)
>
> +DEF_PRIMITIVE_TYPE (BT_CALL_INFO, call_info_type_node)
> +
> DEF_PRIMITIVE_TYPE (BT_I1, builtin_type_for_size (BITS_PER_UNIT*1, 1))
> DEF_PRIMITIVE_TYPE (BT_I2, builtin_type_for_size (BITS_PER_UNIT*2, 1))
> DEF_PRIMITIVE_TYPE (BT_I4, builtin_type_for_size (BITS_PER_UNIT*4, 1))
> @@ -425,6 +427,7 @@ DEF_FUNCTION_TYPE_1 (BT_FN_UINT16_UINT32, BT_UINT16,
> BT_UINT32)
> DEF_FUNCTION_TYPE_1 (BT_FN_UINT32_UINT16, BT_UINT32, BT_UINT16)
> DEF_FUNCTION_TYPE_1 (BT_FN_INT_FENV_T_PTR, BT_INT, BT_FENV_T_PTR)
> DEF_FUNCTION_TYPE_1 (BT_FN_INT_CONST_FENV_T_PTR, BT_INT, BT_CONST_FENV_T_PTR)
> +DEF_FUNCTION_TYPE_1 (BT_FN_CALL_INFO_PTR, BT_CALL_INFO, BT_PTR)
>
> DEF_POINTER_TYPE (BT_PTR_FN_VOID_PTR, BT_FN_VOID_PTR)
>
> diff --git a/gcc/builtins.cc b/gcc/builtins.cc
> index 692e20088c2..efef8afce69 100644
> --- a/gcc/builtins.cc
> +++ b/gcc/builtins.cc
> @@ -8093,6 +8093,9 @@ 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_INFO:
> + gcc_unreachable ();
> +
> case BUILT_IN_SAVEREGS:
> return expand_builtin_saveregs ();
>
> @@ -12311,6 +12314,7 @@ is_simple_builtin (tree decl)
> case BUILT_IN_STACK_SAVE:
> case BUILT_IN_STACK_RESTORE:
> case BUILT_IN_DWARF_CFA:
> + case BUILT_IN_CALL_INFO:
> /* 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..1d5bff152e3 100644
> --- a/gcc/builtins.def
> +++ b/gcc/builtins.def
> @@ -1164,6 +1164,9 @@ 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_INFO, "call_info", BT_FN_CALL_INFO_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..2ddcd1f30d6 100644
> --- a/gcc/doc/extend.texi
> +++ b/gcc/doc/extend.texi
> @@ -16610,6 +16610,19 @@ This builtin can be used to call Go closures from C.
>
> @enddefbuiltin
>
> +@defbuiltin{@var{type} __builtin_call_info (@var{pointer_exp})}
> +
> +The @var{pointer_exp} expression must designate a function.
> +The result is a structure with two members of pointer type named @code{code}
> +and @code{chain}. @code{code} holds 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. @code{chain} is the chain pointer that that is
> +needed to call the function call in its current context, or a null pointer
> +if none is needed.
> +
> +@enddefbuiltin
> +
> @node Return Address
> @section Getting the Return or Frame Address of a Function
>
> diff --git a/gcc/gimple-fold.cc b/gcc/gimple-fold.cc
> index e9019e2c7bd..d8eceb080be 100644
> --- a/gcc/gimple-fold.cc
> +++ b/gcc/gimple-fold.cc
> @@ -5354,6 +5354,41 @@ gimple_fold_builtin_stdarg (gimple_stmt_iterator *gsi,
> gcall *call)
> }
> }
>
> +/* Fold __builtin_call_info builtin. 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)
> +{
> + 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)))
> + {
> + error_at (gimple_location (stmt),
> + "argument to %<__builtin_call_info%> must be a function");
> + return false;
> + }
> +
> + /* The case with static chain is handled in tree-nested.cc. */
> + gcc_assert (!DECL_STATIC_CHAIN (TREE_OPERAND (arg, 0)));
> +
> + tree fields = TYPE_FIELDS (call_info_type_node);
> + tree ret = create_tmp_var (call_info_type_node);
> + tree cref2 = build3 (COMPONENT_REF, TREE_TYPE (fields),
> + ret, fields, NULL_TREE);
> + tree cref1 = build3 (COMPONENT_REF, TREE_TYPE (TREE_CHAIN (fields)),
> + ret, TREE_CHAIN (fields), NULL_TREE);
> + gimple *g = gimple_build_assign (cref1, null_pointer_node);
> + gsi_insert_before (gsi, g, GSI_SAME_STMT);
> + g = gimple_build_assign (cref2, arg);
> + gsi_insert_before (gsi, g, GSI_SAME_STMT);
> +
> + replace_call_with_value (gsi, ret);
> + return true;
> +}
> +
> /* Fold the non-target builtin at *GSI and return whether any simplification
> was made. */
>
> @@ -5530,6 +5565,9 @@ gimple_fold_builtin (gimple_stmt_iterator *gsi)
> case BUILT_IN_CONSTANT_P:
> return gimple_fold_builtin_constant_p (gsi);
>
> + case BUILT_IN_CALL_INFO:
> + return gimple_fold_builtin_call_info (gsi);
> +
> 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..24ac8b1ccf2
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/builtin-call-info-1.c
> @@ -0,0 +1,26 @@
> +/* { dg-do run } */
> +/* { dg-options "-Wtrampolines" } */
> +
> +typedef typeof(__builtin_call_info(nullptr)) call_info_t;
> +
> +int apply(call_info_t c, int value)
> +{
> + return __builtin_call_with_static_chain(((int(*)(int))c.code)(value),
> c.chain);
> +}
> +
> +int foo(int x)
> +{
> + int add(int y)
> + {
> + return x + y;
> + }
> +
> + return apply(__builtin_call_info(add), 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..0ec8d865971
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/builtin-call-info-2.c
> @@ -0,0 +1,49 @@
> +/* { dg-do run } */
> +/* { dg-options "-Wtrampolines" } */
> +
> +/* Check that we get the expected pointers in
> + different context. */
> +
> +int f(int x)
> +{
> + static typeof(__builtin_call_info(f)) c;
> + c = __builtin_call_info(f);
> +
> + if (f != c.code)
> + __builtin_abort();
> +
> + if ((void*)0 != c.chain)
> + __builtin_abort();
> +
> + int g(int y)
> + {
> + auto c2 = __builtin_call_info(g);
> +
> + if (c.code != c2.code)
> + __builtin_abort();
> +
> + if (c.chain != c2.chain)
> + __builtin_abort();
> +
> + return x + y;
> + }
> +
> + c = __builtin_call_info(g);
> +
> + return g(x);
> +}
> +
> +int main()
> +{
> + auto c = __builtin_call_info(f);
> +
> + if (f != c.code)
> + __builtin_abort();
> +
> + if ((void*)0 != c.chain)
> + __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..5d78892fefb
> --- /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_info(g).code; // ok
> +}
> +
> +void * bar(int n)
> +{
> + int g(int x)
> + {
> + return n + x;
> + }
> +
> + return __builtin_call_info(g).chain; /* { dg-warning "returns
> address of local variable" } */
> +}
> +
> diff --git a/gcc/tree-core.h b/gcc/tree-core.h
> index 07e9318f5e8..0a3740b060a 100644
> --- a/gcc/tree-core.h
> +++ b/gcc/tree-core.h
> @@ -822,6 +822,7 @@ enum tree_index : unsigned {
> TI_FEXCEPT_T_PTR_TYPE,
> TI_CONST_FEXCEPT_T_PTR_TYPE,
> TI_POINTER_SIZED_TYPE,
> + TI_CALL_INFO_TYPE,
>
> TI_DFLOAT32_TYPE,
> TI_DFLOAT64_TYPE,
> 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..0b0affe9a44 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,11 @@ 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_INFO == 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 +3000,52 @@ 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_INFO)
> + {
> + tree d = gimple_call_arg (stmt, 0);
> + tree ret1 = null_pointer_node;
> + tree ret2 = 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_info%> "
> + "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));
> + ret1 = get_static_chain (info, target_context, &wi->gsi);
> + }
> + /* Return code pointer. */
> + ret2 = build_addr (TREE_OPERAND (d, 0));
> + TREE_NO_TRAMPOLINE (ret2) = 1;
> + }
> + tree fields = TYPE_FIELDS (call_info_type_node);
> + tree ret = create_tmp_var_for (info, call_info_type_node, NULL);
> + tree cref2 = build3 (COMPONENT_REF, TREE_TYPE (fields),
> + ret, fields, NULL_TREE);
> + tree cref1 = build3 (COMPONENT_REF, TREE_TYPE (TREE_CHAIN (fields)),
> + ret, TREE_CHAIN (fields), NULL_TREE);
> + gimple *assign = gimple_build_assign (cref1, ret1);
> + gsi_insert_before (gsi, assign, GSI_SAME_STMT);
> + assign = gimple_build_assign (cref2, ret2);
> + gsi_insert_before (gsi, assign, GSI_SAME_STMT);
> + 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))
> {
> diff --git a/gcc/tree.cc b/gcc/tree.cc
> index d0e745e8d28..38d0590a8c7 100644
> --- a/gcc/tree.cc
> +++ b/gcc/tree.cc
> @@ -9984,6 +9984,18 @@ build_common_tree_nodes (bool signed_char)
> va_list_type_node = t;
> }
>
> + /* Create the call info type. */
> + call_info_type_node = make_node (RECORD_TYPE);
> + tree code_field = build_decl (BUILTINS_LOCATION, FIELD_DECL,
> + get_identifier ("code"), ptr_type_node);
> + DECL_FIELD_CONTEXT (code_field) = call_info_type_node;
> + tree data_field = build_decl (BUILTINS_LOCATION, FIELD_DECL,
> + get_identifier ("chain"), ptr_type_node);
> + DECL_FIELD_CONTEXT (data_field) = call_info_type_node;
> + TREE_CHAIN (code_field) = data_field;
> + TYPE_FIELDS (call_info_type_node) = code_field;
> + layout_type (call_info_type_node);
> +
> /* SCEV analyzer global shared trees. */
> chrec_dont_know = make_node (SCEV_NOT_KNOWN);
> TREE_TYPE (chrec_dont_know) = void_type_node;
> diff --git a/gcc/tree.h b/gcc/tree.h
> index 19bc67718d1..c63898f672c 100644
> --- a/gcc/tree.h
> +++ b/gcc/tree.h
> @@ -4665,6 +4665,7 @@ tree_strip_any_location_wrapper (tree exp)
> #define fexcept_t_ptr_type_node
> global_trees[TI_FEXCEPT_T_PTR_TYPE]
> #define const_fexcept_t_ptr_type_node
> global_trees[TI_CONST_FEXCEPT_T_PTR_TYPE]
> #define pointer_sized_int_node
> global_trees[TI_POINTER_SIZED_TYPE]
> +#define call_info_type_node global_trees[TI_CALL_INFO_TYPE]
>
> #define boolean_type_node global_trees[TI_BOOLEAN_TYPE]
> #define boolean_false_node global_trees[TI_BOOLEAN_FALSE]