diff mbox series

[committed] d: Add `@visibility' and `@hidden' attributes.

Message ID 20220615181158.1098873-1-ibuclaw@gdcproject.org
State New
Headers show
Series [committed] d: Add `@visibility' and `@hidden' attributes. | expand

Commit Message

Iain Buclaw June 15, 2022, 6:11 p.m. UTC
Hi,

This patch adds the visibility attribute to the D compiler, and library
helpers `@visibility' and `@hidden' to the run-time library.

The `@visibility' attribute is functionality the same as
`__attribute__((visibility))', and `@hidden' is a convenience alias to
`@visibility("hidden")' defined in the `gcc.attributes' module.

As the visibility of a symbol is also indirectly controlled by the
`export' keyword, the handling of this in the code generation pass has
been improved so that conflicts will be appropriately diagnosed.

The third test for the visibility attribute has been checked for
correctness on an arm-symbianelf cross compiler.

Bootstrapped and regression tested on x86_64-linux-gnu/-m32/-mx32, and
committed to mainline.

Regards,
Iain.

---
gcc/d/ChangeLog:

	* d-attribs.cc (d_langhook_attribute_table): Add visibility.
	(insert_type_attribute): Use decl_attributes instead of
	merge_attributes.
	(insert_decl_attribute): Likewise.
	(apply_user_attributes): Do nothing when no UDAs applied.
	(d_handle_visibility_attribute): New function.
	* d-gimplify.cc (d_gimplify_binary_expr): Adjust.
	* d-tree.h (set_visibility_for_decl): Declare.
	* decl.cc (get_symbol_decl): Move setting of visibility flags to...
	(set_visibility_for_decl): ... here.  New function.
	* types.cc (TypeVisitor::visit (TypeStruct *)): Call
	set_visibility_for_decl().
	(TypeVisitor::visit (TypeClass *)): Likewise.

gcc/testsuite/ChangeLog:

	* gdc.dg/attr_visibility1.d: New test.
	* gdc.dg/attr_visibility2.d: New test.
	* gdc.dg/attr_visibility3.d: New test.

libphobos/ChangeLog:

	* libdruntime/gcc/attributes.d (visibility): Define.
	(hidden): Define.
---
 gcc/d/d-attribs.cc                      | 98 +++++++++++++++++++++----
 gcc/d/d-gimplify.cc                     |  4 +-
 gcc/d/d-tree.h                          |  1 +
 gcc/d/decl.cc                           | 54 +++++++++-----
 gcc/d/types.cc                          |  2 +
 gcc/testsuite/gdc.dg/attr_visibility1.d | 25 +++++++
 gcc/testsuite/gdc.dg/attr_visibility2.d | 26 +++++++
 gcc/testsuite/gdc.dg/attr_visibility3.d | 29 ++++++++
 libphobos/libdruntime/gcc/attributes.d  | 34 +++++++++
 9 files changed, 241 insertions(+), 32 deletions(-)
 create mode 100644 gcc/testsuite/gdc.dg/attr_visibility1.d
 create mode 100644 gcc/testsuite/gdc.dg/attr_visibility2.d
 create mode 100644 gcc/testsuite/gdc.dg/attr_visibility3.d
diff mbox series

Patch

diff --git a/gcc/d/d-attribs.cc b/gcc/d/d-attribs.cc
index b8ce30cf3c9..4c6e7a7a4bd 100644
--- a/gcc/d/d-attribs.cc
+++ b/gcc/d/d-attribs.cc
@@ -77,6 +77,7 @@  static tree d_handle_alloc_size_attribute (tree *, tree, tree, int, bool *);
 static tree d_handle_cold_attribute (tree *, tree, tree, int, bool *);
 static tree d_handle_restrict_attribute (tree *, tree, tree, int, bool *);
 static tree d_handle_used_attribute (tree *, tree, tree, int, bool *);
+static tree d_handle_visibility_attribute (tree *, tree, tree, int, bool *);
 
 /* Helper to define attribute exclusions.  */
 #define ATTR_EXCL(name, function, type, variable)	\
@@ -223,6 +224,8 @@  const attribute_spec d_langhook_attribute_table[] =
 	     d_handle_restrict_attribute, NULL),
   ATTR_SPEC ("used", 0, 0, true, false, false, false,
 	     d_handle_used_attribute, NULL),
+  ATTR_SPEC ("visibility", 1, 1, false, false, false, false,
+	     d_handle_visibility_attribute, NULL),
   ATTR_SPEC (NULL, 0, 0, false, false, false, false, NULL, NULL),
 };
 
@@ -238,10 +241,9 @@  insert_type_attribute (tree type, const char *attrname, tree value)
   if (value)
     value = tree_cons (NULL_TREE, value, NULL_TREE);
 
-  tree attribs = merge_attributes (TYPE_ATTRIBUTES (type),
-				   tree_cons (ident, value, NULL_TREE));
-
-  return build_type_attribute_variant (type, attribs);
+  decl_attributes (&type, build_tree_list (ident, value),
+		   ATTR_FLAG_TYPE_IN_PLACE);
+  return type;
 }
 
 /* Insert the decl attribute ATTRNAME with value VALUE into DECL.  */
@@ -254,10 +256,9 @@  insert_decl_attribute (tree decl, const char *attrname, tree value)
   if (value)
     value = tree_cons (NULL_TREE, value, NULL_TREE);
 
-  tree attribs = merge_attributes (DECL_ATTRIBUTES (decl),
-				   tree_cons (ident, value, NULL_TREE));
+  decl_attributes (&decl, build_tree_list (ident, value), 0);
 
-  return build_decl_attribute_variant (decl, attribs);
+  return decl;
 }
 
 /* Returns TRUE if NAME is an attribute recognized as being handled by
@@ -414,12 +415,7 @@  void
 apply_user_attributes (Dsymbol *sym, tree node)
 {
   if (!sym->userAttribDecl)
-    {
-      if (DECL_P (node) && DECL_ATTRIBUTES (node) != NULL)
-	decl_attributes (&node, DECL_ATTRIBUTES (node), 0);
-
-      return;
-    }
+    return;
 
   location_t saved_location = input_location;
   input_location = make_location_t (sym->loc);
@@ -1412,3 +1408,79 @@  d_handle_used_attribute (tree *node, tree name, tree, int, bool *no_add_attrs)
 
   return NULL_TREE;
 }
+
+/* Handle an "visibility" attribute; arguments as in
+   struct attribute_spec.handler.  */
+
+static tree
+d_handle_visibility_attribute (tree *node, tree name, tree args,
+			       int, bool *)
+{
+  /*  If this is a type, set the visibility on the type decl.  */
+  tree decl = *node;
+  if (TYPE_P (decl))
+    {
+      decl = TYPE_NAME (decl);
+      if (decl == NULL_TREE || TREE_CODE (decl) != TYPE_DECL)
+	{
+	  warning (OPT_Wattributes, "%qE attribute ignored on types", name);
+	  return NULL_TREE;
+	}
+    }
+
+  if (decl_function_context (decl) != 0 || !TREE_PUBLIC (decl))
+    {
+      warning (OPT_Wattributes, "%qE attribute ignored", name);
+      return NULL_TREE;
+    }
+
+  tree id = TREE_VALUE (args);
+  if (TREE_CODE (id) != STRING_CST)
+    {
+      error ("visibility argument not a string");
+      return NULL_TREE;
+    }
+
+  enum symbol_visibility vis;
+  if (strcmp (TREE_STRING_POINTER (id), "default") == 0)
+    vis = VISIBILITY_DEFAULT;
+  else if (strcmp (TREE_STRING_POINTER (id), "internal") == 0)
+    vis = VISIBILITY_INTERNAL;
+  else if (strcmp (TREE_STRING_POINTER (id), "hidden") == 0)
+    vis = VISIBILITY_HIDDEN;
+  else if (strcmp (TREE_STRING_POINTER (id), "protected") == 0)
+    vis = VISIBILITY_PROTECTED;
+  else
+    {
+      error ("attribute %qE argument must be one of %qs, %qs, %qs, or %qs",
+	     name, "default", "hidden", "protected", "internal");
+      vis = VISIBILITY_DEFAULT;
+    }
+
+  if (DECL_VISIBILITY_SPECIFIED (decl) && vis != DECL_VISIBILITY (decl))
+    {
+      tree attributes = (TYPE_P (*node)
+			 ? TYPE_ATTRIBUTES (*node)
+			 : DECL_ATTRIBUTES (decl));
+      if (lookup_attribute ("visibility", attributes))
+	error ("%qD redeclared with different visibility", decl);
+      else if (TARGET_DLLIMPORT_DECL_ATTRIBUTES
+	       && lookup_attribute ("dllimport", attributes))
+	error ("%qD was declared %qs which implies default visibility",
+	       decl, "export");
+      else if (TARGET_DLLIMPORT_DECL_ATTRIBUTES
+	       && lookup_attribute ("dllexport", attributes))
+	error ("%qD was declared %qs which implies default visibility",
+	       decl, "export");
+    }
+
+  DECL_VISIBILITY (decl) = vis;
+  DECL_VISIBILITY_SPECIFIED (decl) = 1;
+
+  /* Go ahead and attach the attribute to the node as well.  This is needed
+     so we can determine whether we have VISIBILITY_DEFAULT because the
+     visibility was not specified, or because it was explicitly overridden
+     from the containing scope.  */
+
+  return NULL_TREE;
+}
diff --git a/gcc/d/d-gimplify.cc b/gcc/d/d-gimplify.cc
index 36b76da3acc..33fe65c3466 100644
--- a/gcc/d/d-gimplify.cc
+++ b/gcc/d/d-gimplify.cc
@@ -235,10 +235,10 @@  d_gimplify_binary_expr (tree *expr_p)
   if (bit_field_ref (op0) || bit_field_ref (op1))
     {
       if (TREE_TYPE (op0) != TREE_TYPE (*expr_p))
-    	TREE_OPERAND (*expr_p, 0) = convert (TREE_TYPE (*expr_p), op0);
+	TREE_OPERAND (*expr_p, 0) = convert (TREE_TYPE (*expr_p), op0);
 
       if (TREE_TYPE (op1) != TREE_TYPE (*expr_p))
-    	TREE_OPERAND (*expr_p, 1) = convert (TREE_TYPE (*expr_p), op1);
+	TREE_OPERAND (*expr_p, 1) = convert (TREE_TYPE (*expr_p), op1);
 
       return GS_OK;
     }
diff --git a/gcc/d/d-tree.h b/gcc/d/d-tree.h
index 48a40a6afa4..751746395e6 100644
--- a/gcc/d/d-tree.h
+++ b/gcc/d/d-tree.h
@@ -654,6 +654,7 @@  extern tree build_artificial_decl (tree, tree, const char * = NULL);
 extern tree create_field_decl (tree, const char *, int, int);
 extern void build_type_decl (tree, Dsymbol *);
 extern void set_linkage_for_decl (tree);
+extern void set_visibility_for_decl (tree, Dsymbol *);
 
 /* In expr.cc.  */
 extern tree build_expr (Expression *, bool = false, bool = false);
diff --git a/gcc/d/decl.cc b/gcc/d/decl.cc
index 518d84c1a49..8676a1b588b 100644
--- a/gcc/d/decl.cc
+++ b/gcc/d/decl.cc
@@ -1343,27 +1343,10 @@  get_symbol_decl (Declaration *decl)
   if (decl->storage_class & STCvolatile)
     TREE_THIS_VOLATILE (decl->csym) = 1;
 
-  /* Visibility attributes are used by the debugger.  */
-  if (decl->visibility.kind == Visibility::private_)
-    TREE_PRIVATE (decl->csym) = 1;
-  else if (decl->visibility.kind == Visibility::protected_)
-    TREE_PROTECTED (decl->csym) = 1;
-
   /* Likewise, so could the deprecated attribute.  */
   if (decl->storage_class & STCdeprecated)
     TREE_DEPRECATED (decl->csym) = 1;
 
-#if TARGET_DLLIMPORT_DECL_ATTRIBUTES
-  /* Have to test for import first.  */
-  if (decl->isImportedSymbol ())
-    {
-      insert_decl_attribute (decl->csym, "dllimport");
-      DECL_DLLIMPORT_P (decl->csym) = 1;
-    }
-  else if (decl->isExport ())
-    insert_decl_attribute (decl->csym, "dllexport");
-#endif
-
   if (decl->isDataseg () || decl->isCodeseg () || decl->isThreadlocal ())
     {
       /* Set TREE_PUBLIC by default, but allow private template to override.  */
@@ -1374,6 +1357,9 @@  get_symbol_decl (Declaration *decl)
       /* The decl has not been defined -- yet.  */
       DECL_EXTERNAL (decl->csym) = 1;
 
+      /* Visibility attributes are used by the debugger.  */
+      set_visibility_for_decl (decl->csym, decl);
+
       DECL_INSTANTIATED (decl->csym) = (decl->isInstantiated () != NULL);
       set_linkage_for_decl (decl->csym);
     }
@@ -2447,3 +2433,37 @@  set_linkage_for_decl (tree decl)
   if (DECL_ARTIFICIAL (decl))
     return d_weak_linkage (decl);
 }
+
+/* NODE is a FUNCTION_DECL, VAR_DECL or RECORD_TYPE for the declaration SYM.
+   Set flags to reflect visibility that NODE will get in the object file.  */
+
+void
+set_visibility_for_decl (tree node, Dsymbol *sym)
+{
+  Visibility visibility = sym->visible ();
+  if (visibility.kind == Visibility::private_)
+    TREE_PRIVATE (node) = 1;
+  else if (visibility.kind == Visibility::protected_)
+    TREE_PROTECTED (node) = 1;
+
+  /* If the declaration was declared `export', append either the dllimport
+     or dllexport attribute.  */
+  if (TARGET_DLLIMPORT_DECL_ATTRIBUTES)
+    {
+      const char *attrname = NULL;
+
+      /* Have to test for import first.  */
+      if (sym->isImportedSymbol ())
+	attrname = "dllimport";
+      else if (sym->isExport ())
+	attrname = "dllexport";
+
+      if (attrname != NULL)
+	{
+	  if (DECL_P (node))
+	    insert_decl_attribute (node, attrname);
+	  else if (TYPE_P (node))
+	    insert_type_attribute (node, attrname);
+	}
+    }
+}
diff --git a/gcc/d/types.cc b/gcc/d/types.cc
index 0926715b7dc..b706c91560e 100644
--- a/gcc/d/types.cc
+++ b/gcc/d/types.cc
@@ -1180,6 +1180,7 @@  public:
 	/* Put out all fields.  */
 	layout_aggregate_type (t->sym, t->ctype, t->sym);
 	build_type_decl (t->ctype, t->sym);
+	set_visibility_for_decl (t->ctype, t->sym);
 	apply_user_attributes (t->sym, t->ctype);
 	finish_aggregate_type (structsize, alignsize, t->ctype);
       }
@@ -1224,6 +1225,7 @@  public:
     /* Put out all fields, including from each base class.  */
     layout_aggregate_type (t->sym, basetype, t->sym);
     build_type_decl (basetype, t->sym);
+    set_visibility_for_decl (basetype, t->sym);
     apply_user_attributes (t->sym, basetype);
     finish_aggregate_type (t->sym->structsize, t->sym->alignsize, basetype);
 
diff --git a/gcc/testsuite/gdc.dg/attr_visibility1.d b/gcc/testsuite/gdc.dg/attr_visibility1.d
new file mode 100644
index 00000000000..a7ed4065605
--- /dev/null
+++ b/gcc/testsuite/gdc.dg/attr_visibility1.d
@@ -0,0 +1,25 @@ 
+// { dg-do compile }
+// { dg-require-visibility "" }
+
+import gcc.attributes;
+
+void nested()
+{
+    @attribute("visibility", "default")
+    struct nested_struct { } // { dg-warning ".visibility. attribute ignored" }
+
+    @attribute("visibility", "default")
+    void nested_func() { } // { dg-warning ".visibility. attribute ignored" }
+}
+
+@attribute("visibility", 123)
+int not_a_string(); // { dg-error "visibility argument not a string" }
+
+@attribute("visibility", "invalid argument")
+int invalid_argument(); // { dg-error ".visibility. argument must be one of" }
+
+@attribute("visibility", "default")
+int redeclared_visibility();
+
+@attribute("visibility", "internal")
+int redeclared_visibility(); // { dg-error "redeclared with different visibility" }
diff --git a/gcc/testsuite/gdc.dg/attr_visibility2.d b/gcc/testsuite/gdc.dg/attr_visibility2.d
new file mode 100644
index 00000000000..a3398822647
--- /dev/null
+++ b/gcc/testsuite/gdc.dg/attr_visibility2.d
@@ -0,0 +1,26 @@ 
+// { dg-do compile }
+// { dg-require-visibility "" }
+
+module attr_visibility2;
+
+import gcc.attributes;
+
+// { dg-final { scan-hidden "_D16attr_visibility25func1FZv" } }
+
+@hidden void func1() { }
+
+// { dg-final { scan-hidden "_D16attr_visibility25func2FZv" } }
+
+@hidden void func2();
+
+void func2() { }
+
+// { dg-final { scan-hidden "_D16attr_visibility25func3FZv" } }
+
+void func3();
+
+@hidden void func3() { }
+
+// { dg-final { scan-hidden "_D16attr_visibility210globalvar1i" } }
+
+@hidden __gshared int globalvar1 = 5;
diff --git a/gcc/testsuite/gdc.dg/attr_visibility3.d b/gcc/testsuite/gdc.dg/attr_visibility3.d
new file mode 100644
index 00000000000..32984287d89
--- /dev/null
+++ b/gcc/testsuite/gdc.dg/attr_visibility3.d
@@ -0,0 +1,29 @@ 
+// { dg-do compile }
+// { dg-require-visibility "" }
+// { dg-require-dll "" }
+
+import gcc.attributes;
+
+@visibility("hidden")
+export void func1(); // { dg-error ".func1. was declared .export." }
+
+@visibility("hidden")
+export void func2() { } // { dg-error ".func2. was declared .export." }
+
+@visibility("default")
+export void func3();
+
+@visibility("default")
+export void func4() { };
+
+@visibility("hidden")
+export struct type1 { } // { dg-error ".type1. was declared .export." }
+
+@visibility("default")
+export struct type2 { }
+
+@visibility("hidden")
+export class type3 { } // { dg-error ".type3. was declared .export." }
+
+@visibility("default")
+export class type4 { }
diff --git a/libphobos/libdruntime/gcc/attributes.d b/libphobos/libdruntime/gcc/attributes.d
index 09f684cd44e..ca066cef822 100644
--- a/libphobos/libdruntime/gcc/attributes.d
+++ b/libphobos/libdruntime/gcc/attributes.d
@@ -424,6 +424,30 @@  auto target_clones(A...)(A arguments)
  */
 enum used = attribute("used");
 
+/**
+ * The `@visibility` attribute affects the linkage of the declaration to which
+ * it is attached. It can be applied to variables, types, and functions.
+ *
+ * There are four supported visibility_type values: `default`, `hidden`,
+ * `protected`, or `internal` visibility.
+ *
+ * Example:
+ * ---
+ * import gcc.attributes;
+ *
+ * @visibility("protected") void func() {  }
+ * ---
+ */
+auto visibility(string visibilityName)
+{
+    return attribute("visibility", visibilityName);
+}
+
+auto visibility(A...)(A arguments)
+{
+    assert(false, "visibility attribute argument not a string constant");
+}
+
 /**
  * The `@weak` attribute causes a declaration of an external symbol to be
  * emitted as a weak symbol rather than a global.  This is primarily useful in
@@ -542,6 +566,16 @@  enum dynamicCompileEmit = false;
  */
 enum fastmath = optimize("Ofast");
 
+/**
+ * Sets the visibility of a function or global variable to "hidden".
+ * Such symbols aren't directly accessible from outside the DSO
+ * (executable or DLL/.so/.dylib) and are resolved inside the DSO
+ * during linking. If unreferenced within the DSO, the linker can
+ * strip a hidden symbol.
+ * An `export` visibility overrides this attribute.
+ */
+enum hidden = visibility("hidden");
+
 /**
  * Adds GCC's "naked" attribute to a function, disabling function prologue /
  * epilogue emission.