d: Add `@register' attribute to compiler and library.

The `@register` attribute specifies that a local or `__gshared` variable
is to be given a register storage-class in the C sense of the term, and
will be placed into a register named `registerName`.

The variable needs to boiled down to a data type that fits the target
register.  It also cannot have either thread-local or `extern` storage.
It is an error to take the address of a register variable.

	PR d/105413

gcc/d/ChangeLog:

	* d-attribs.cc (d_handle_register_attribute): New function.
	(d_langhook_attribute_table): Add register attribute.
	* d-codegen.cc (d_mark_addressable): Error if taken address of
	register variable.
	(build_frame_type): Error if register variable has non-local
	references.
	* d-tree.h (d_mark_addressable): Add complain parameter.
	* decl.cc (get_symbol_decl): Mark register varibles DECL_REGISTER.
	Error when register variable declared thread-local or extern.
	* expr.cc (ExprVisitor::visit (IndexExp *)): Don't complain about
	marking register vectors as addressable in an ARRAY_REF.

libphobos/ChangeLog:

	* libdruntime/gcc/attributes.d (register): Define.

gcc/testsuite/ChangeLog:

	* gdc.dg/attr_register1.d: New test.
	* gdc.dg/attr_register2.d: New test.
	* gdc.dg/attr_register3.d: New test.
This commit is contained in:
Iain Buclaw 2022-06-23 18:24:07 +02:00
parent 8288cd635f
commit 91418c4208
9 changed files with 204 additions and 12 deletions

View file

@ -75,6 +75,7 @@ static tree d_handle_weak_attribute (tree *, tree, tree, int, bool *) ;
static tree d_handle_noplt_attribute (tree *, tree, tree, int, bool *) ; static tree d_handle_noplt_attribute (tree *, tree, tree, int, bool *) ;
static tree d_handle_alloc_size_attribute (tree *, tree, tree, int, bool *); 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_cold_attribute (tree *, tree, tree, int, bool *);
static tree d_handle_register_attribute (tree *, tree, tree, int, bool *);
static tree d_handle_restrict_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_used_attribute (tree *, tree, tree, int, bool *);
static tree d_handle_visibility_attribute (tree *, tree, tree, int, bool *); static tree d_handle_visibility_attribute (tree *, tree, tree, int, bool *);
@ -223,6 +224,8 @@ const attribute_spec d_langhook_attribute_table[] =
d_handle_cold_attribute, attr_cold_hot_exclusions), d_handle_cold_attribute, attr_cold_hot_exclusions),
ATTR_SPEC ("no_sanitize", 1, -1, true, false, false, false, ATTR_SPEC ("no_sanitize", 1, -1, true, false, false, false,
d_handle_no_sanitize_attribute, NULL), d_handle_no_sanitize_attribute, NULL),
ATTR_SPEC ("register", 1, 1, true, false, false, false,
d_handle_register_attribute, NULL),
ATTR_SPEC ("restrict", 0, 0, true, false, false, false, ATTR_SPEC ("restrict", 0, 0, true, false, false, false,
d_handle_restrict_attribute, NULL), d_handle_restrict_attribute, NULL),
ATTR_SPEC ("used", 0, 0, true, false, false, false, ATTR_SPEC ("used", 0, 0, true, false, false, false,
@ -1409,8 +1412,41 @@ d_handle_no_sanitize_attribute (tree *node, tree name, tree args, int,
else else
{ {
DECL_ATTRIBUTES (*node) = tree_cons (get_identifier ("no_sanitize"), DECL_ATTRIBUTES (*node) = tree_cons (get_identifier ("no_sanitize"),
build_int_cst (d_uint_type, flags), build_int_cst (d_uint_type, flags),
DECL_ATTRIBUTES (*node)); DECL_ATTRIBUTES (*node));
}
return NULL_TREE;
}
/* Handle a "register" attribute; arguments as in
struct attribute_spec.handler. */
static tree
d_handle_register_attribute (tree *node, tree name, tree args, int,
bool *no_add_attrs)
{
if (!VAR_P (*node))
{
warning (OPT_Wattributes, "%qE attribute ignored", name);
*no_add_attrs = true;
}
else if (TREE_CODE (TREE_VALUE (args)) != STRING_CST)
{
error ("%qE attribute argument not a string constant", name);
*no_add_attrs = true;
}
else if (TREE_STRING_LENGTH (TREE_VALUE (args)) == 0
|| TREE_STRING_POINTER (TREE_VALUE (args))[0] == '\0')
{
error ("register name not specified for %q+D", *node);
*no_add_attrs = true;
}
else
{
DECL_REGISTER (*node) = 1;
set_user_assembler_name (*node, TREE_STRING_POINTER (TREE_VALUE (args)));
DECL_HARD_REGISTER (*node) = 1;
} }
return NULL_TREE; return NULL_TREE;

View file

@ -697,11 +697,12 @@ build_address (tree exp)
return compound_expr (init, exp); return compound_expr (init, exp);
} }
/* Mark EXP saying that we need to be able to take the /* Mark EXP saying that we need to be able to take the address of it; it should
address of it; it should not be allocated in a register. */ not be allocated in a register. When COMPLAIN is true, issue an error if we
are marking a register variable. */
tree tree
d_mark_addressable (tree exp) d_mark_addressable (tree exp, bool complain)
{ {
switch (TREE_CODE (exp)) switch (TREE_CODE (exp))
{ {
@ -713,12 +714,22 @@ d_mark_addressable (tree exp)
d_mark_addressable (TREE_OPERAND (exp, 0)); d_mark_addressable (TREE_OPERAND (exp, 0));
break; break;
case PARM_DECL:
case VAR_DECL: case VAR_DECL:
if (complain && DECL_REGISTER (exp))
{
if (DECL_HARD_REGISTER (exp) || DECL_EXTERNAL (exp))
error ("address of explicit register variable %qD requested", exp);
else
error ("address of register variable %qD requested", exp);
}
/* Fall through. */
case PARM_DECL:
case RESULT_DECL: case RESULT_DECL:
case CONST_DECL: case CONST_DECL:
case FUNCTION_DECL: case FUNCTION_DECL:
TREE_ADDRESSABLE (exp) = 1; if (!VAR_P (exp) || !DECL_HARD_REGISTER (exp))
TREE_ADDRESSABLE (exp) = 1;
break; break;
case CONSTRUCTOR: case CONSTRUCTOR:
@ -2704,7 +2715,16 @@ build_frame_type (tree ffi, FuncDeclaration *fd)
if ((v->edtor && (v->storage_class & STCparameter)) if ((v->edtor && (v->storage_class & STCparameter))
|| v->needsScopeDtor ()) || v->needsScopeDtor ())
error_at (make_location_t (v->loc), error_at (make_location_t (v->loc),
"has scoped destruction, cannot build closure"); "variable %qs has scoped destruction, "
"cannot build closure", v->toChars ());
}
if (DECL_REGISTER (vsym))
{
/* Because the value will be in memory, not a register. */
error_at (make_location_t (v->loc),
"explicit register variable %qs cannot be used in nested "
"function", v->toChars ());
} }
} }

View file

@ -549,7 +549,7 @@ extern tree stabilize_expr (tree *);
extern tree build_target_expr (tree, tree); extern tree build_target_expr (tree, tree);
extern tree force_target_expr (tree); extern tree force_target_expr (tree);
extern tree build_address (tree); extern tree build_address (tree);
extern tree d_mark_addressable (tree); extern tree d_mark_addressable (tree, bool = true);
extern tree d_mark_used (tree); extern tree d_mark_used (tree);
extern tree d_mark_read (tree); extern tree d_mark_read (tree);
extern tree build_memcmp_call (tree, tree, tree); extern tree build_memcmp_call (tree, tree, tree);

View file

@ -670,10 +670,14 @@ public:
rest_of_decl_compilation (decl, 1, 0); rest_of_decl_compilation (decl, 1, 0);
} }
} }
else if (d->isDataseg () && !(d->storage_class & STCextern)) else if (d->isDataseg ())
{ {
tree decl = get_symbol_decl (d); tree decl = get_symbol_decl (d);
/* Only need to build the VAR_DECL for extern declarations. */
if (d->storage_class & STCextern)
return;
/* Duplicated VarDeclarations map to the same symbol. Check if this /* Duplicated VarDeclarations map to the same symbol. Check if this
is the one declaration which will be emitted. */ is the one declaration which will be emitted. */
tree ident = DECL_ASSEMBLER_NAME (decl); tree ident = DECL_ASSEMBLER_NAME (decl);
@ -1343,7 +1347,11 @@ get_symbol_decl (Declaration *decl)
if (decl->storage_class & STCvolatile) if (decl->storage_class & STCvolatile)
TREE_THIS_VOLATILE (decl->csym) = 1; TREE_THIS_VOLATILE (decl->csym) = 1;
/* Likewise, so could the deprecated attribute. */ /* Symbol was marked register. */
if (decl->storage_class & STCregister)
DECL_REGISTER (decl->csym) = 1;
/* Symbol was declared with deprecated attribute. */
if (decl->storage_class & STCdeprecated) if (decl->storage_class & STCdeprecated)
TREE_DEPRECATED (decl->csym) = 1; TREE_DEPRECATED (decl->csym) = 1;
@ -1376,6 +1384,18 @@ get_symbol_decl (Declaration *decl)
/* Apply any user attributes that may affect semantic meaning. */ /* Apply any user attributes that may affect semantic meaning. */
apply_user_attributes (decl, decl->csym); apply_user_attributes (decl, decl->csym);
/* Handle any conflicts between D language attributes and compiler-recognized
* user attributes. */
if (VAR_P (decl->csym) && DECL_HARD_REGISTER (decl->csym))
{
if (decl->storage_class & STCextern)
error_at (make_location_t (decl->loc), "explicit register variable "
"%qs declared %<extern%>", decl->toChars ());
else if (decl->isThreadlocal ())
error_at (make_location_t (decl->loc), "explicit register variable "
"%qs declared thread local", decl->toChars ());
}
/* %% Probably should be a little more intelligent about setting this. */ /* %% Probably should be a little more intelligent about setting this. */
TREE_USED (decl->csym) = 1; TREE_USED (decl->csym) = 1;
d_keep (decl->csym); d_keep (decl->csym);

View file

@ -1250,7 +1250,7 @@ public:
tree array_type = tree array_type =
build_array_type_nelts (TREE_TYPE (TREE_TYPE (array)), build_array_type_nelts (TREE_TYPE (TREE_TYPE (array)),
TYPE_VECTOR_SUBPARTS (TREE_TYPE (array))); TYPE_VECTOR_SUBPARTS (TREE_TYPE (array)));
d_mark_addressable (array); d_mark_addressable (array, false);
array = build1 (VIEW_CONVERT_EXPR, array_type, array); array = build1 (VIEW_CONVERT_EXPR, array_type, array);
} }

View file

@ -0,0 +1,55 @@
// { dg-do compile }
import gcc.attributes;
@attribute("register", null) int var1; // { dg-error "attribute argument not a string constant" }
@attribute("register", "") int var2; // { dg-error "register name not specified for .var2." }
@attribute("register", "invalid") __gshared int var3; // { dg-error "invalid register name for .var3." }
void f1(ref int r) { }
void test1()
{
@register("ref") int var6;
f1(var6); // { dg-error "address of explicit register variable .var6. requested" }
}
void f2(out int r) { }
void test2()
{
@register("out") int var7;
f2(var7); // { dg-error "address of explicit register variable .var7. requested" }
}
void f3(lazy int r) { }
void test3()
{
@register("lazy") int var8; // { dg-error "explicit register variable .var8. cannot be used in nested function" }
f3(var8);
}
void test4()
{
@register("addr") int var9;
auto ptr3 = &var9; // { dg-error "address of explicit register variable .var9. requested" }
}
ref int test5()
{
@register("refreturn") __gshared int var10; // { dg-error "invalid register name" }
return var10; // { dg-error "address of explicit register variable .var10. requested" }
}
auto test6()
{
@register("closure") int var11; // { dg-error "explicit register variable .var11. cannot be used in nested function" }
int nested()
{
return var11;
}
return &nested;
}

View file

@ -0,0 +1,11 @@
// { dg-do compile { target x86_64-*-* } }
import gcc.attributes;
@register("ebx") static int var1 = void; // { dg-error "explicit register variable .var1. declared thread local" }
@register("ebx") extern int var2; // { dg-error "explicit register variable .var2. declared .extern." }
@register("r12") __gshared int var3 = 0x2a; // { dg-error "global register variable has initial value" }
@register("r12") __gshared int[256] var4 = void; // { dg-error "data type of .var4. isn.t suitable for a register" }

View file

@ -0,0 +1,22 @@
// { dg-do compile }
// { dg-options "-Wall -O2 -fdump-tree-optimized" }
import gcc.attributes;
pragma(inline, true)
void syscall()(int val)
{
@register("4") int reg = val;
asm { "/* Some Code %0 */" :: "r" (reg); }
}
void do_syscalls()
{
for (int s = 0; s < 2; s++)
{
syscall (0);
syscall (1);
}
}
// { dg-final { scan-tree-dump-times "reg = " 4 "optimized" } }

View file

@ -301,6 +301,34 @@ auto optimize(A...)(A arguments)
assert(false, "optimize attribute argument not a string or integer constant"); assert(false, "optimize attribute argument not a string or integer constant");
} }
/**
* The `@register` attribute specifies that a local or `__gshared` variable
* is to be given a register storage-class in the C99 sense of the term, and
* will be placed into a register named `registerName`.
*
* The variable needs to boiled down to a data type that fits the target
* register. It also cannot have either thread-local or `extern` storage.
* It is an error to take the address of a register variable.
*
* Example:
* ---
* import gcc.attributes;
*
* @register("ebx") __gshared int ebx = void;
*
* void func() { @register("r10") long r10 = 0x2a; }
* ---
*/
auto register(string registerName)
{
return attribute("register", registerName);
}
auto register(A...)(A arguments)
{
assert(false, "register attribute argument not a string constant");
}
/** /**
* The `@restrict` attribute specifies that a function parameter is to be * The `@restrict` attribute specifies that a function parameter is to be
* restrict-qualified in the C99 sense of the term. The parameter needs to * restrict-qualified in the C99 sense of the term. The parameter needs to