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