diff mbox series

[RFC,C++] __builtin_source_location ()

Message ID 20191114193426.GE4650@tucnak
State New
Headers show
Series [RFC,C++] __builtin_source_location () | expand

Commit Message

Jakub Jelinek Nov. 14, 2019, 7:34 p.m. UTC
Hi!

The following WIP patch implements __builtin_source_location (),
which returns const void pointer to a std::source_location::__impl
struct that is required to contain __file, __function, __line and __column
fields, the first two with const char * type, the latter some integral type.

I don't have testcase coverage yet and the hash map to allow sharing of
VAR_DECLs with the same location is commented out both because it
doesn't compile for some reason and because hashing on location_t
is not enough, we probably need to hash on both location_t and fndecl,
as the baz case in the following shows.

Comments?

namespace std {
  struct source_location {
    struct __impl {
      const char *__file;
      const char *__function;
      unsigned int __line, __column;
    };
    const void *__ptr;
    constexpr source_location () : __ptr (nullptr) {}
    static consteval source_location
    current (const void *__p = __builtin_source_location ()) {
      source_location __ret;
      __ret.__ptr = __p;
      return __ret;
    }
    constexpr const char *file () const {
      return static_cast <const __impl *> (__ptr)->__file;
    }
    constexpr const char *function () const {
      return static_cast <const __impl *> (__ptr)->__function;
    }
    constexpr unsigned line () const {
      return static_cast <const __impl *> (__ptr)->__line;
    }
    constexpr unsigned column () const {
      return static_cast <const __impl *> (__ptr)->__column;
    }
  };
}

using namespace std;

consteval source_location
bar (const source_location x = source_location::current ())
{
  return x;
}

void
foo (const char **p, unsigned *q)
{
  constexpr source_location s = source_location::current ();
  constexpr source_location t = bar ();
  p[0] = s.file ();
  p[1] = s.function ();
  q[0] = s.line ();
  q[1] = s.column ();
  p[2] = t.file ();
  p[3] = t.function ();
  q[2] = t.line ();
  q[3] = t.column ();
  constexpr const char *r = s.file ();
}

template <int N>
constexpr source_location
baz ()
{
  return source_location::current ();
}

constexpr source_location s1 = baz <0> ();
constexpr source_location s2 = baz <1> ();
const source_location *p1 = &s1;
const source_location *p2 = &s2;


	Jakub

Comments

Jonathan Wakely Nov. 14, 2019, 10:15 p.m. UTC | #1
On 14/11/19 20:34 +0100, Jakub Jelinek wrote:
>Hi!
>
>The following WIP patch implements __builtin_source_location (),
>which returns const void pointer to a std::source_location::__impl
>struct that is required to contain __file, __function, __line and __column
>fields, the first two with const char * type, the latter some integral type.
>
>I don't have testcase coverage yet and the hash map to allow sharing of
>VAR_DECLs with the same location is commented out both because it
>doesn't compile for some reason and because hashing on location_t
>is not enough, we probably need to hash on both location_t and fndecl,
>as the baz case in the following shows.
>
>Comments?
>
>namespace std {
>  struct source_location {
>    struct __impl {

Will this work if the library type is actually in an inline namespace,
such as std::__8::source_location::__impl (as for
--enable-symvers=gnu-versioned-namespace) or
std::__v1::source_location::__impl (as it probably would be in
libc++).

If I'm reading the patch correctly, it would work fine, because
qualified lookup for std::source_location would find that name even if
it's really in some inline namespace.


>      const char *__file;
>      const char *__function;
>      unsigned int __line, __column;
>    };
>    const void *__ptr;

If the magic type the compiler generates is declared in the header,
then this member might as well be 'const __impl*'.

>    constexpr source_location () : __ptr (nullptr) {}
>    static consteval source_location
>    current (const void *__p = __builtin_source_location ()) {
>      source_location __ret;
>      __ret.__ptr = __p;
>      return __ret;
>    }
>    constexpr const char *file () const {
>      return static_cast <const __impl *> (__ptr)->__file;

Not really relevant to your patch, but I'll say it here for the
benefit of others reading these mails ...

On IRC I suggested that the default constructor should set the __ptr
member to null, and these member functions should check for null, e.g.

  if (__ptr) [[likely]]
    return __ptr->__function;
  else
    return "";

The alternative is for the default constructor to call
__builtin_source_location() or refer to some static object in the
runtime library, but both options waste space. Adding a [[likely]]
branch to the accessors wastes no space and should only penalise users
who are misusing source_location by trying to get meaningful values
out of default constructed objects. If that's a bit slower I don't
care.
Jakub Jelinek Nov. 14, 2019, 10:39 p.m. UTC | #2
On Thu, Nov 14, 2019 at 10:15:21PM +0000, Jonathan Wakely wrote:
> > namespace std {
> >  struct source_location {
> >    struct __impl {
> 
> Will this work if the library type is actually in an inline namespace,
> such as std::__8::source_location::__impl (as for
> --enable-symvers=gnu-versioned-namespace) or
> std::__v1::source_location::__impl (as it probably would be in
> libc++).
> 
> If I'm reading the patch correctly, it would work fine, because
> qualified lookup for std::source_location would find that name even if
> it's really in some inline namespace.

I'd say so, but the unfinished testsuite coverage would need to cover it of
course.

> >      const char *__file;
> >      const char *__function;
> >      unsigned int __line, __column;
> >    };
> >    const void *__ptr;
> 
> If the magic type the compiler generates is declared in the header,
> then this member might as well be 'const __impl*'.

Yes, with the static_cast on the __builtin_source_location ()
result sure.  I can't easily make it return const std::source_location::__impl*
though, because the initialization of the builtins happens early, before
<source_location> is parsed.
And as it is a nested class, I think I can't predeclare it in the compiler.
If it would be std::__source_location_impl instead of
std::source_location::__impl, perhaps I could pretend there is
namespace std { struct __source_location_impl; }
but I bet that wouldn't work well with the inline namespaces.
So, is const void * return from the builtin ok?

	Jakub
Jonathan Wakely Nov. 15, 2019, 12:32 p.m. UTC | #3
On 14/11/19 23:39 +0100, Jakub Jelinek wrote:
>On Thu, Nov 14, 2019 at 10:15:21PM +0000, Jonathan Wakely wrote:
>> > namespace std {
>> >  struct source_location {
>> >    struct __impl {
>>
>> Will this work if the library type is actually in an inline namespace,
>> such as std::__8::source_location::__impl (as for
>> --enable-symvers=gnu-versioned-namespace) or
>> std::__v1::source_location::__impl (as it probably would be in
>> libc++).
>>
>> If I'm reading the patch correctly, it would work fine, because
>> qualified lookup for std::source_location would find that name even if
>> it's really in some inline namespace.
>
>I'd say so, but the unfinished testsuite coverage would need to cover it of
>course.
>
>> >      const char *__file;
>> >      const char *__function;
>> >      unsigned int __line, __column;
>> >    };
>> >    const void *__ptr;
>>
>> If the magic type the compiler generates is declared in the header,
>> then this member might as well be 'const __impl*'.
>
>Yes, with the static_cast on the __builtin_source_location ()
>result sure.  I can't easily make it return const std::source_location::__impl*
>though, because the initialization of the builtins happens early, before
><source_location> is parsed.
>And as it is a nested class, I think I can't predeclare it in the compiler.
>If it would be std::__source_location_impl instead of
>std::source_location::__impl, perhaps I could pretend there is
>namespace std { struct __source_location_impl; }
>but I bet that wouldn't work well with the inline namespaces.
>So, is const void * return from the builtin ok?

Yes, that seems fine. The library can just do the cast once and store
the result, instead of casting it on every use.
diff mbox series

Patch

--- gcc/cp/tree.c.jj	2019-11-13 10:54:45.437045793 +0100
+++ gcc/cp/tree.c	2019-11-14 18:11:42.391213117 +0100
@@ -445,7 +445,9 @@  builtin_valid_in_constant_expr_p (const_
   if (DECL_BUILT_IN_CLASS (decl) != BUILT_IN_NORMAL)
     {
       if (fndecl_built_in_p (decl, CP_BUILT_IN_IS_CONSTANT_EVALUATED,
-			   BUILT_IN_FRONTEND))
+			     BUILT_IN_FRONTEND)
+	  || fndecl_built_in_p (decl, CP_BUILT_IN_SOURCE_LOCATION,
+				BUILT_IN_FRONTEND))
 	return true;
       /* Not a built-in.  */
       return false;
--- gcc/cp/constexpr.c.jj	2019-11-13 10:54:45.426045960 +0100
+++ gcc/cp/constexpr.c	2019-11-14 18:26:40.691581038 +0100
@@ -1238,6 +1238,9 @@  cxx_eval_builtin_function_call (const co
       return boolean_true_node;
     }
 
+  if (fndecl_built_in_p (fun, CP_BUILT_IN_SOURCE_LOCATION, BUILT_IN_FRONTEND))
+    return fold_builtin_source_location (EXPR_LOCATION (t));
+
   /* Be permissive for arguments to built-ins; __builtin_constant_p should
      return constant false for a non-constant argument.  */
   constexpr_ctx new_ctx = *ctx;
--- gcc/cp/name-lookup.c.jj	2019-11-13 10:54:45.495044911 +0100
+++ gcc/cp/name-lookup.c	2019-11-14 18:38:30.765804391 +0100
@@ -5747,6 +5747,8 @@  get_std_name_hint (const char *name)
     {"shared_lock", "<shared_mutex>", cxx14},
     {"shared_mutex", "<shared_mutex>", cxx17},
     {"shared_timed_mutex", "<shared_mutex>", cxx14},
+    /* <source_location>.  */
+    {"source_location", "<source_location>", cxx2a},
     /* <sstream>.  */
     {"basic_stringbuf", "<sstream>", cxx98},
     {"basic_istringstream", "<sstream>", cxx98},
--- gcc/cp/cp-gimplify.c.jj	2019-11-06 08:58:38.036473709 +0100
+++ gcc/cp/cp-gimplify.c	2019-11-14 20:22:32.905068438 +0100
@@ -35,6 +35,9 @@  along with GCC; see the file COPYING3.
 #include "attribs.h"
 #include "asan.h"
 #include "gcc-rich-location.h"
+#include "output.h"
+#include "file-prefix-map.h"
+#include "cgraph.h"
 
 /* Forward declarations.  */
 
@@ -896,8 +899,12 @@  cp_gimplify_expr (tree *expr_p, gimple_s
 	  tree decl = cp_get_callee_fndecl_nofold (*expr_p);
 	  if (decl
 	      && fndecl_built_in_p (decl, CP_BUILT_IN_IS_CONSTANT_EVALUATED,
-				  BUILT_IN_FRONTEND))
+				    BUILT_IN_FRONTEND))
 	    *expr_p = boolean_false_node;
+	  else if (decl
+		   && fndecl_built_in_p (decl, CP_BUILT_IN_SOURCE_LOCATION,
+					 BUILT_IN_FRONTEND))
+	    *expr_p = fold_builtin_source_location (EXPR_LOCATION (*expr_p));
 	}
       break;
 
@@ -2641,9 +2648,17 @@  cp_fold (tree x)
 	/* Defer folding __builtin_is_constant_evaluated.  */
 	if (callee
 	    && fndecl_built_in_p (callee, CP_BUILT_IN_IS_CONSTANT_EVALUATED,
-				BUILT_IN_FRONTEND))
+				  BUILT_IN_FRONTEND))
 	  break;
 
+	if (callee
+	    && fndecl_built_in_p (callee, CP_BUILT_IN_SOURCE_LOCATION,
+				  BUILT_IN_FRONTEND))
+	  {
+	    x = fold_builtin_source_location (EXPR_LOCATION (x));
+	    break;
+	  }
+
 	x = copy_node (x);
 
 	m = call_expr_nargs (x);
@@ -2868,4 +2883,172 @@  process_stmt_hotness_attribute (tree std
   return std_attrs;
 }
 
+/* Helper of fold_builtin_source_location, return the
+   std::source_location::__impl type after performing verification
+   on it.  */
+
+static tree
+get_source_location_impl (location_t loc)
+{
+  tree name = get_identifier ("source_location");
+  tree decl = lookup_qualified_name (std_node, name);
+  if (TREE_CODE (decl) != TYPE_DECL)
+    {
+      auto_diagnostic_group d;
+      if (decl == error_mark_node || TREE_CODE (decl) == TREE_LIST)
+	qualified_name_lookup_error (std_node, name, decl, loc);
+      else
+	error ("%<std::%D%> is not a type", decl);
+      return error_mark_node;
+    }
+  name = get_identifier ("__impl");
+  tree type = TREE_TYPE (decl);
+  decl = lookup_qualified_name (type, name);
+  if (TREE_CODE (decl) != TYPE_DECL)
+    {
+      auto_diagnostic_group d;
+      if (decl == error_mark_node || TREE_CODE (decl) == TREE_LIST)
+	qualified_name_lookup_error (type, name, decl, loc);
+      else
+	error ("%<std::source_location::%D%> is not a type", decl);
+      return error_mark_node;
+    }
+  type = TREE_TYPE (decl);
+  if (TREE_CODE (type) != RECORD_TYPE)
+    {
+      error ("%<std::source_location::%D%> is not a class type", decl);
+      return error_mark_node;
+    }
+
+  int cnt = 0;
+  for (tree field = TYPE_FIELDS (type);
+       (field = next_initializable_field (field)) != NULL_TREE;
+       field = DECL_CHAIN (field))
+    {
+      if (DECL_NAME (field) != NULL_TREE)
+	{
+	  const char *n = IDENTIFIER_POINTER (DECL_NAME (field));
+	  if (strcmp (n, "__file") == 0 || strcmp (n, "__function") == 0)
+	    {
+	      if (TREE_TYPE (field) != const_string_type_node)
+		{
+		  error ("%<std::source_location::__impl::%D%> does not "
+			 "have %<const char *%> type", field);
+		  return error_mark_node;
+		}
+	      cnt++;
+	      continue;
+	    }
+	  else if (strcmp (n, "__line") == 0 || strcmp (n, "__column") == 0)
+	    {
+	      if (TREE_CODE (TREE_TYPE (field)) != INTEGER_TYPE)
+		{
+		  error ("%<std::source_location::__impl::%D%> does not "
+			 "have integral type", field);
+		  return error_mark_node;
+		}
+	      cnt++;
+	      continue;
+	    }
+	}
+      cnt = 0;
+      break;
+    }
+  if (cnt != 4)
+    {
+      error ("%<std::source_location::__impl%> does not contain only "
+	     "non-static data members %<__file%>, %<__function%>, "
+	     "%<__line%> and %<__column%>");
+      return error_mark_node;
+    }
+  return build_qualified_type (type, TYPE_QUAL_CONST);
+}
+
+/* static GTY(()) hash_map <location_hash, tree> *source_location_table; */
+static GTY(()) unsigned int source_location_id;
+
+/* Fold __builtin_source_location () call.  LOC is the location
+   of the call.  */
+
+tree
+fold_builtin_source_location (location_t loc)
+{
+  if (source_location_impl == NULL_TREE)
+    {
+      auto_diagnostic_group d;
+      source_location_impl = get_source_location_impl (loc);
+      if (source_location_impl == error_mark_node)
+	inform (loc, "evaluating %qs", "__builtin_source_location");
+    }
+  if (source_location_impl == error_mark_node)
+    return build_zero_cst (const_ptr_type_node);
+//  if (source_location_table)
+//    hash_map <location_hash, tree>::create_ggc (64);
+  loc = LOCATION_LOCUS (loc);
+//  tree *varp = source_location_table->get (loc);
+  tree var;
+//  if (*varp)
+//    var = *varp;
+//  else
+    {
+      char tmp_name[32];
+      ASM_GENERATE_INTERNAL_LABEL (tmp_name, "Lsrc_loc", source_location_id++);
+      var = build_decl (loc, VAR_DECL, get_identifier (tmp_name),
+			source_location_impl);
+      TREE_STATIC (var) = 1;
+      TREE_PUBLIC (var) = 0;
+      DECL_ARTIFICIAL (var) = 1;
+      DECL_IGNORED_P (var) = 1;
+      DECL_EXTERNAL (var) = 0;
+      DECL_DECLARED_CONSTEXPR_P (var) = 1;
+      DECL_INITIALIZED_BY_CONSTANT_EXPRESSION_P (var) = 1;
+      layout_decl (var, 0);
+
+      vec<constructor_elt, va_gc> *v = NULL;
+      vec_alloc (v, 4);
+      for (tree field = TYPE_FIELDS (source_location_impl);
+	   (field = next_initializable_field (field)) != NULL_TREE;
+	   field = DECL_CHAIN (field))
+	{
+	  const char *n = IDENTIFIER_POINTER (DECL_NAME (field));
+	  tree val = NULL_TREE;
+	  if (strcmp (n, "__file") == 0)
+	    {
+	      if (const char *fname = LOCATION_FILE (loc))
+		{
+		  fname = remap_macro_filename (fname);
+		  val = build_string_literal (strlen (fname) + 1, fname);
+		}
+	      else
+		val = build_string_literal (1, "");
+	    }
+	  else if (strcmp (n, "__function") == 0)
+	    {
+	      const char *name = "";
+
+	      if (current_function_decl)
+		name = cxx_printable_name (current_function_decl, 0);
+
+	      val = build_string_literal (strlen (name) + 1, name);
+	    }
+	  else if (strcmp (n, "__line") == 0)
+	    val = build_int_cst (TREE_TYPE (field), LOCATION_LINE (loc));
+	  else if (strcmp (n, "__column") == 0)
+	    val = build_int_cst (TREE_TYPE (field), LOCATION_COLUMN (loc));
+	  else
+	    gcc_unreachable ();
+	  CONSTRUCTOR_APPEND_ELT (v, field, val);
+	}
+
+      tree ctor = build_constructor (source_location_impl, v);
+      TREE_CONSTANT (ctor) = 1;
+      TREE_STATIC (ctor) = 1;
+      DECL_INITIAL (var) = ctor;
+      varpool_node::finalize_decl (var);
+//    source_location_table->put (loc, var);
+    }
+
+  return build_fold_addr_expr_with_type_loc (loc, var, const_ptr_type_node);
+}
+
 #include "gt-cp-cp-gimplify.h"
--- gcc/cp/cp-tree.h.jj	2019-11-13 10:54:45.495044911 +0100
+++ gcc/cp/cp-tree.h	2019-11-14 18:44:36.632251612 +0100
@@ -204,6 +204,8 @@  enum cp_tree_index
 
     CPTI_ANY_TARG,
 
+    CPTI_SOURCE_LOCATION_IMPL,
+
     CPTI_MAX
 };
 
@@ -356,6 +358,9 @@  extern GTY(()) tree cp_global_trees[CPTI
 /* A node which matches any template argument.  */
 #define any_targ_node			cp_global_trees[CPTI_ANY_TARG]
 
+/* std::source_location::__impl class.  */
+#define source_location_impl		cp_global_trees[CPTI_SOURCE_LOCATION_IMPL]
+
 /* Node to indicate default access. This must be distinct from the
    access nodes in tree.h.  */
 
@@ -6175,6 +6180,7 @@  struct GTY((chain_next ("%h.next"))) tin
 enum cp_built_in_function {
   CP_BUILT_IN_IS_CONSTANT_EVALUATED,
   CP_BUILT_IN_INTEGER_PACK,
+  CP_BUILT_IN_SOURCE_LOCATION,
   CP_BUILT_IN_LAST
 };
 
@@ -7723,6 +7729,7 @@  extern void clear_fold_cache			(void);
 extern tree lookup_hotness_attribute		(tree);
 extern tree process_stmt_hotness_attribute	(tree, location_t);
 extern bool simple_empty_class_p		(tree, tree, tree_code);
+extern tree fold_builtin_source_location	(location_t);
 
 /* in name-lookup.c */
 extern tree strip_using_decl                    (tree);
--- gcc/cp/decl.c.jj	2019-11-12 09:09:33.598815768 +0100
+++ gcc/cp/decl.c	2019-11-14 18:10:35.918221482 +0100
@@ -4290,6 +4290,12 @@  cxx_init_decl_processing (void)
 			    BUILT_IN_FRONTEND, NULL, NULL_TREE);
   set_call_expr_flags (decl, ECF_CONST | ECF_NOTHROW | ECF_LEAF);
 
+  tree cptr_ftype = build_function_type_list (const_ptr_type_node, NULL_TREE);
+  decl = add_builtin_function ("__builtin_source_location",
+			       cptr_ftype, CP_BUILT_IN_SOURCE_LOCATION,
+			       BUILT_IN_FRONTEND, NULL, NULL_TREE);
+  set_call_expr_flags (decl, ECF_CONST | ECF_NOTHROW | ECF_LEAF);
+
   integer_two_node = build_int_cst (NULL_TREE, 2);
 
   /* Guess at the initial static decls size.  */