diff mbox series

[RFC] Allow limited extended asm at toplevel

Message ID Zv0oPknQT0Nq4Blu@tucnak
State New
Headers show
Series [RFC] Allow limited extended asm at toplevel | expand

Commit Message

Jakub Jelinek Oct. 2, 2024, 11:02 a.m. UTC
Hi!

In the Cauldron IPA/LTO BoF we've discussed toplevel asms and it was
discussed it would be nice to tell the compiler something about what
the toplevel asm does.  Sure, I'm aware the kernel people said they
aren't willing to use something like that, but perhaps other projects
do.  And for kernel perhaps we should add some new option which allows
some dumb parsing of the toplevel asms and gather something from that
parsing.

The following patch is just a small step towards that, namely, allow
some subset of extended inline asm outside of functions.
The patch is unfinished, LTO streaming (out/in) of the ASM_EXPRs isn't
implemented, nor any cgraph/varpool changes to find out references etc.

The patch allows something like:

int a[2], b;
enum { E1, E2, E3, E4, E5 };
struct S { int a; char b; long long c; };
asm (".section blah; .quad %P0, %P1, %P2, %P3, %P4; .previous"
     : : "m" (a), "m" (b), "i" (42), "i" (E4), "i" (sizeof (struct S)));

Even for non-LTO, that could be useful e.g. for getting enumerators from
C/C++ as integers into the toplevel asm, or sizeof/offsetof etc.

The restrictions I've implemented are:
1) asm qualifiers aren't still allowed, so asm goto or asm inline can't be
   specified at toplevel, asm volatile has the volatile ignored for C++ with
   a warning and is an error in C like before
2) I see good use for mainly input operands, output maybe to make it clear
   that the inline asm may write some memory, I don't see a good use for
   clobbers, so the patch doesn't allow those (and of course labels because
   asm goto can't be specified)
3) the patch allows only constraints which don't allow registers, so
   typically "m" or "i" or other memory or immediate constraints; for
   memory, it requires that the operand is addressable and its address
   could be used in static var initializer (so that no code actually
   needs to be emitted for it), for others that they are constants usable
   in the static var initializers
4) the patch disallows + (there is no reload of the operands, so I don't
   see benefits of tying some operands together), nor % (who cares if
   something is commutative in this case), or & (again, no code is emitted
   around the asm), nor the 0-9 constraints

Right now there is no way to tell the compiler that the inline asm defines
some symbol, I wonder if we can find some unused constraint letter or
sequence or other syntax to denote that.  Note, I think we want to be
able to specify that an inline asm defines a function or variable and
be able to provide the type etc. thereof.  So
extern void foo (void);
extern int var;
asm ("%P0: ret" : : "defines" (foo));
asm ("%P0: .quad 0" : : "defines" (var));
where the exact "defines" part is TBD.

Another question is whether all targets have something like x86 P print
modifier which doesn't add any stuff around the printed expressions
(perhaps there are targets which don't do that just for %0 etc.), or
whether we want to add something that will be usable on all targets.

Thoughts on this?

2024-10-02  Jakub Jelinek  <jakub@redhat.com>

gcc/
	* output.h (insn_noperands): Declare.
	* final.cc (insn_noperands): No longer static.
	* varasm.cc (assemble_asm): Handle ASM_EXPR.
	* doc/extend.texi (Basic @code{asm}, Extended @code{asm}): Document
	that extended asm is now allowed outside of functions with certain
	restrictions.
gcc/c/
	* c-parser.cc (c_parser_asm_string_literal): Add forward declaration.
	(c_parser_asm_definition): Parse also extended asm without
	clobbers/labels.
	* c-typeck.cc (build_asm_expr): Allow extended asm outside of
	functions and check extra restrictions.
gcc/cp/
	* cp-tree.h (finish_asm_stmt): Add TOPLEV_P argument.
	* parser.cc (cp_parser_asm_definition): Parse also extended asm
	without clobbers/labels outside of functions.
	* semantics.cc (finish_asm_stmt): Add TOPLEV_P argument, if set,
	check extra restrictions for extended asm outside of functions.
	* pt.cc (tsubst_stmt): Adjust finish_asm_stmt caller.


	Jakub

Comments

Uros Bizjak Oct. 2, 2024, 11:40 a.m. UTC | #1
On Wed, Oct 2, 2024 at 1:03 PM Jakub Jelinek <jakub@redhat.com> wrote:
>
> Hi!
>
> In the Cauldron IPA/LTO BoF we've discussed toplevel asms and it was
> discussed it would be nice to tell the compiler something about what
> the toplevel asm does.  Sure, I'm aware the kernel people said they
> aren't willing to use something like that, but perhaps other projects
> do.  And for kernel perhaps we should add some new option which allows
> some dumb parsing of the toplevel asms and gather something from that
> parsing.
>
> The following patch is just a small step towards that, namely, allow
> some subset of extended inline asm outside of functions.
> The patch is unfinished, LTO streaming (out/in) of the ASM_EXPRs isn't
> implemented, nor any cgraph/varpool changes to find out references etc.
>
> The patch allows something like:
>
> int a[2], b;
> enum { E1, E2, E3, E4, E5 };
> struct S { int a; char b; long long c; };
> asm (".section blah; .quad %P0, %P1, %P2, %P3, %P4; .previous"
>      : : "m" (a), "m" (b), "i" (42), "i" (E4), "i" (sizeof (struct S)));
>
> Even for non-LTO, that could be useful e.g. for getting enumerators from
> C/C++ as integers into the toplevel asm, or sizeof/offsetof etc.
>
> The restrictions I've implemented are:
> 1) asm qualifiers aren't still allowed, so asm goto or asm inline can't be
>    specified at toplevel, asm volatile has the volatile ignored for C++ with
>    a warning and is an error in C like before
> 2) I see good use for mainly input operands, output maybe to make it clear
>    that the inline asm may write some memory, I don't see a good use for
>    clobbers, so the patch doesn't allow those (and of course labels because
>    asm goto can't be specified)
> 3) the patch allows only constraints which don't allow registers, so
>    typically "m" or "i" or other memory or immediate constraints; for
>    memory, it requires that the operand is addressable and its address
>    could be used in static var initializer (so that no code actually
>    needs to be emitted for it), for others that they are constants usable
>    in the static var initializers
> 4) the patch disallows + (there is no reload of the operands, so I don't
>    see benefits of tying some operands together), nor % (who cares if
>    something is commutative in this case), or & (again, no code is emitted
>    around the asm), nor the 0-9 constraints
>
> Right now there is no way to tell the compiler that the inline asm defines
> some symbol, I wonder if we can find some unused constraint letter or
> sequence or other syntax to denote that.  Note, I think we want to be
> able to specify that an inline asm defines a function or variable and
> be able to provide the type etc. thereof.  So
> extern void foo (void);
> extern int var;
> asm ("%P0: ret" : : "defines" (foo));
> asm ("%P0: .quad 0" : : "defines" (var));
> where the exact "defines" part is TBD.
>
> Another question is whether all targets have something like x86 P print
> modifier which doesn't add any stuff around the printed expressions
> (perhaps there are targets which don't do that just for %0 etc.), or
> whether we want to add something that will be usable on all targets.

%P is very x86 specific, perhaps you should use %c instead?

The %c modifier is described in:

6.48.2.8 Generic Operand Modifiers
..................................

The following table shows the modifiers supported by all targets and
their effects:

Modifier    Description                                        Example
---------------------------------------------------------------------------
‘c’         Require a constant operand and print the           ‘%c0’
           constant expression with no punctuation.
...

E.g.:

void bar (void);
void foo (void)
{
  asm ("%c0" :  : "i"(bar));
}

generates:

#APP
# 5 "c.c" 1
       bar
# 0 "" 2
#NO_APP

Uros.
Richard Biener Oct. 2, 2024, 11:59 a.m. UTC | #2
On Wed, 2 Oct 2024, Jakub Jelinek wrote:

> Hi!
> 
> In the Cauldron IPA/LTO BoF we've discussed toplevel asms and it was
> discussed it would be nice to tell the compiler something about what
> the toplevel asm does.  Sure, I'm aware the kernel people said they
> aren't willing to use something like that, but perhaps other projects
> do.  And for kernel perhaps we should add some new option which allows
> some dumb parsing of the toplevel asms and gather something from that
> parsing.
> 
> The following patch is just a small step towards that, namely, allow
> some subset of extended inline asm outside of functions.
> The patch is unfinished, LTO streaming (out/in) of the ASM_EXPRs isn't
> implemented, nor any cgraph/varpool changes to find out references etc.
> 
> The patch allows something like:
> 
> int a[2], b;
> enum { E1, E2, E3, E4, E5 };
> struct S { int a; char b; long long c; };
> asm (".section blah; .quad %P0, %P1, %P2, %P3, %P4; .previous"
>      : : "m" (a), "m" (b), "i" (42), "i" (E4), "i" (sizeof (struct S)));
> 
> Even for non-LTO, that could be useful e.g. for getting enumerators from
> C/C++ as integers into the toplevel asm, or sizeof/offsetof etc.
> 
> The restrictions I've implemented are:
> 1) asm qualifiers aren't still allowed, so asm goto or asm inline can't be
>    specified at toplevel, asm volatile has the volatile ignored for C++ with
>    a warning and is an error in C like before
> 2) I see good use for mainly input operands, output maybe to make it clear
>    that the inline asm may write some memory, I don't see a good use for
>    clobbers, so the patch doesn't allow those (and of course labels because
>    asm goto can't be specified)
> 3) the patch allows only constraints which don't allow registers, so
>    typically "m" or "i" or other memory or immediate constraints; for
>    memory, it requires that the operand is addressable and its address
>    could be used in static var initializer (so that no code actually
>    needs to be emitted for it), for others that they are constants usable
>    in the static var initializers
> 4) the patch disallows + (there is no reload of the operands, so I don't
>    see benefits of tying some operands together), nor % (who cares if
>    something is commutative in this case), or & (again, no code is emitted
>    around the asm), nor the 0-9 constraints
> 
> Right now there is no way to tell the compiler that the inline asm defines
> some symbol, I wonder if we can find some unused constraint letter or
> sequence or other syntax to denote that.  Note, I think we want to be
> able to specify that an inline asm defines a function or variable and
> be able to provide the type etc. thereof.  So
> extern void foo (void);
> extern int var;
> asm ("%P0: ret" : : "defines" (foo));
> asm ("%P0: .quad 0" : : "defines" (var));
> where the exact "defines" part is TBD.

As you are using input constraints to mark symbol uses maybe we can
use output constraints with a magic identifier (and a constraint letter
specifying 'identifier'):

asm (".globl %0; %0: ret" : "_D" (extern int foo()) : ...);

In the BOF it was noted that LTO wants to be able to rename / localize
symbols so both use and definition should be used in a way to support
this (though changing visibility is difficult - the assembler might
tie to GOT uses, and .globl is hard to replace).

Richard.

> Another question is whether all targets have something like x86 P print
> modifier which doesn't add any stuff around the printed expressions
> (perhaps there are targets which don't do that just for %0 etc.), or
> whether we want to add something that will be usable on all targets.
> 
> Thoughts on this?
> 
> 2024-10-02  Jakub Jelinek  <jakub@redhat.com>
> 
> gcc/
> 	* output.h (insn_noperands): Declare.
> 	* final.cc (insn_noperands): No longer static.
> 	* varasm.cc (assemble_asm): Handle ASM_EXPR.
> 	* doc/extend.texi (Basic @code{asm}, Extended @code{asm}): Document
> 	that extended asm is now allowed outside of functions with certain
> 	restrictions.
> gcc/c/
> 	* c-parser.cc (c_parser_asm_string_literal): Add forward declaration.
> 	(c_parser_asm_definition): Parse also extended asm without
> 	clobbers/labels.
> 	* c-typeck.cc (build_asm_expr): Allow extended asm outside of
> 	functions and check extra restrictions.
> gcc/cp/
> 	* cp-tree.h (finish_asm_stmt): Add TOPLEV_P argument.
> 	* parser.cc (cp_parser_asm_definition): Parse also extended asm
> 	without clobbers/labels outside of functions.
> 	* semantics.cc (finish_asm_stmt): Add TOPLEV_P argument, if set,
> 	check extra restrictions for extended asm outside of functions.
> 	* pt.cc (tsubst_stmt): Adjust finish_asm_stmt caller.
> 
> --- gcc/output.h.jj	2024-10-02 10:02:08.031896380 +0200
> +++ gcc/output.h	2024-10-02 11:27:13.383943702 +0200
> @@ -338,6 +338,9 @@ extern rtx_insn *current_output_insn;
>     The precise value is the insn being output, to pass to error_for_asm.  */
>  extern const rtx_insn *this_is_asm_operands;
>  
> +/* Number of operands of this insn, for an `asm' with operands.  */
> +extern unsigned int insn_noperands;
> +
>  /* Carry information from ASM_DECLARE_OBJECT_NAME
>     to ASM_FINISH_DECLARE_OBJECT.  */
>  extern int size_directive_output;
> --- gcc/final.cc.jj	2024-10-02 10:02:08.031896380 +0200
> +++ gcc/final.cc	2024-10-02 11:27:13.382943715 +0200
> @@ -149,7 +149,7 @@ extern const int length_unit_log; /* Thi
>  const rtx_insn *this_is_asm_operands;
>  
>  /* Number of operands of this insn, for an `asm' with operands.  */
> -static unsigned int insn_noperands;
> +unsigned int insn_noperands;
>  
>  /* Compare optimization flag.  */
>  
> --- gcc/varasm.cc.jj	2024-10-02 10:02:08.056896032 +0200
> +++ gcc/varasm.cc	2024-10-02 12:27:46.244456857 +0200
> @@ -62,6 +62,7 @@ along with GCC; see the file COPYING3.
>  #include "toplev.h"
>  #include "opts.h"
>  #include "asan.h"
> +#include "recog.h"
>  
>  /* The (assembler) name of the first globally-visible object output.  */
>  extern GTY(()) const char *first_global_object_name;
> @@ -1667,16 +1668,154 @@ make_decl_rtl_for_debug (tree decl)
>     for an `asm' keyword used between functions.  */
>  
>  void
> -assemble_asm (tree string)
> +assemble_asm (tree asm_str)
>  {
>    const char *p;
> -  app_enable ();
>  
> -  if (TREE_CODE (string) == ADDR_EXPR)
> -    string = TREE_OPERAND (string, 0);
> +  if (TREE_CODE (asm_str) != ASM_EXPR)
> +    {
> +      app_enable ();
> +      if (TREE_CODE (asm_str) == ADDR_EXPR)
> +	asm_str = TREE_OPERAND (asm_str, 0);
> +
> +      p = TREE_STRING_POINTER (asm_str);
> +      fprintf (asm_out_file, "%s%s\n", p[0] == '\t' ? "" : "\t", p);
> +    }
> +  else
> +    {
> +      location_t save_loc = input_location;
> +      int save_reload_completed = reload_completed;
> +      input_location = EXPR_LOCATION (asm_str);
> +      int noutputs = list_length (ASM_OUTPUTS (asm_str));
> +      int ninputs = list_length (ASM_INPUTS (asm_str));
> +      const char **constraints = NULL;
> +      int i;
> +      tree tail;
> +      bool allows_mem, allows_reg, is_inout;
> +      rtx *ops = NULL;
> +      if (noutputs + ninputs > MAX_RECOG_OPERANDS)
> +	{
> +	  error ("more than %d operands in %<asm%>", MAX_RECOG_OPERANDS);
> +	  goto done;
> +	}
> +      constraints = XALLOCAVEC (const char *, noutputs + ninputs);
> +      ops = XALLOCAVEC (rtx, noutputs + ninputs);
> +      memset (&recog_data, 0, sizeof (recog_data));
> +      recog_data.n_operands = ninputs + noutputs;
> +      recog_data.is_asm = true;
> +      reload_completed = 0;
> +      for (i = 0, tail = ASM_OUTPUTS (asm_str); tail;
> +	   ++i, tail = TREE_CHAIN (tail))
> +	{
> +	  tree output = TREE_VALUE (tail);
> +	  if (output == error_mark_node)
> +	    goto done;
> +	  constraints[i]
> +	    = TREE_STRING_POINTER (TREE_VALUE (TREE_PURPOSE (tail)));
> +	  if (!parse_output_constraint (&constraints[i], i, ninputs, noutputs,
> +					&allows_mem, &allows_reg, &is_inout))
> +	    goto done;
> +	  if (is_inout)
> +	    {
> +	      error ("%qc in output operand outside of a function", '+');
> +	      goto done;
> +	    }
> +	  if (strchr (constraints[i], '&'))
> +	    {
> +	      error ("%qc in output operand outside of a function", '&');
> +	      goto done;
> +	    }
> +	  if (strchr (constraints[i], '%'))
> +	    {
> +	      error ("%qc in output operand outside of a function", '%');
> +	      goto done;
> +	    }
> +	  output_addressed_constants (output, 0);
> +	  ops[i] = expand_expr (output, NULL_RTX, VOIDmode,
> +				EXPAND_INITIALIZER);
> +	  if (!MEM_P (ops[i]))
> +	    {
> +	      error ("output number %d not directly addressable", i);
> +	      goto done;
> +	    }
> +
> +	  recog_data.operand[i] = ops[i];
> +	  recog_data.operand_loc[i] = &ops[i];
> +	  recog_data.constraints[i] = constraints[i];
> +	  recog_data.operand_mode[i] = TYPE_MODE (TREE_TYPE (output));
> +	}
> +      for (i = 0, tail = ASM_INPUTS (asm_str); tail;
> +	   ++i, tail = TREE_CHAIN (tail))
> +	{
> +	  tree input = TREE_VALUE (tail);
> +	  if (input == error_mark_node)
> +	    goto done;
> +	  constraints[i + noutputs]
> +	    = TREE_STRING_POINTER (TREE_VALUE (TREE_PURPOSE (tail)));
> +	  if (!parse_input_constraint (&constraints[i + noutputs], i,
> +				       ninputs, noutputs, 0, constraints,
> +				       &allows_mem, &allows_reg))
> +	    goto done;
> +	  if (strchr (constraints[i], '%'))
> +	    {
> +	      error ("%qc in input operand outside of a function", '%');
> +	      goto done;
> +	    }
> +	  const char *constraint = constraints[i + noutputs];
> +	  size_t c_len = strlen (constraint);
> +	  for (size_t j = 0; j < c_len;
> +	       j += CONSTRAINT_LEN (constraint[j], constraint + j))
> +	    if (constraint[j] >= '0' && constraint[j] <= '9')
> +	      {
> +		error ("matching constraint outside of a function");
> +		goto done;
> +	      }
> +	  output_addressed_constants (input, 0);
> +	  ops[i + noutputs] = expand_expr (input, NULL_RTX, VOIDmode,
> +					   EXPAND_INITIALIZER);
> +	  if (asm_operand_ok (ops[i + noutputs], constraint, NULL) <= 0)
> +	    {
> +	      if (!allows_mem)
> +		warning (0, "%<asm%> operand %d probably does not "
> +			    "match constraints", i + noutputs);
> +	    }
> +	  recog_data.operand[i + noutputs] = ops[i + noutputs];
> +	  recog_data.operand_loc[i + noutputs] = &ops[i + noutputs];
> +	  recog_data.constraints[i + noutputs] = constraints[i + noutputs];
> +	  recog_data.operand_mode[i + noutputs]
> +	    = TYPE_MODE (TREE_TYPE (input));
> +	}
> +      if (recog_data.n_operands > 0)
> +	{
> +	  const char *p = recog_data.constraints[0];
> +	  recog_data.n_alternatives = 1;
> +	  while (*p)
> +	    recog_data.n_alternatives += (*p++ == ',');
> +	}
> +      for (i = 0; i < recog_data.n_operands; i++)
> +	recog_data.operand_type[i]
> +	  = recog_data.constraints[i][0] == '=' ? OP_OUT : OP_IN;
> +      reload_completed = 1;
> +      constrain_operands (1, ALL_ALTERNATIVES);
> +      if (which_alternative < 0)
> +	{
> +	  error ("impossible constraint in %<asm%>");
> +	  goto done;
> +	}
> +      this_is_asm_operands = make_insn_raw (gen_nop ());
> +      insn_noperands = recog_data.n_operands;
> +      if (TREE_STRING_POINTER (ASM_STRING (asm_str))[0])
> +	{
> +	  app_enable ();
> +	  output_asm_insn (TREE_STRING_POINTER (ASM_STRING (asm_str)), ops);
> +	}
> +      insn_noperands = 0;
> +      this_is_asm_operands = NULL;
>  
> -  p = TREE_STRING_POINTER (string);
> -  fprintf (asm_out_file, "%s%s\n", p[0] == '\t' ? "" : "\t", p);
> +    done:
> +      input_location = save_loc;
> +      reload_completed = save_reload_completed;
> +    }
>  }
>  
>  /* Write the address of the entity given by SYMBOL to SEC.  */
> --- gcc/doc/extend.texi.jj	2024-10-02 10:02:14.549805644 +0200
> +++ gcc/doc/extend.texi	2024-10-02 11:27:13.392943576 +0200
> @@ -10809,8 +10809,8 @@ statements.  A @dfn{basic @code{asm}} st
>  operands (@pxref{Basic Asm}), while an @dfn{extended @code{asm}}
>  statement (@pxref{Extended Asm}) includes one or more operands.  
>  The extended form is preferred for mixing C and assembly language
> -within a function, but to include assembly language at
> -top level you must use basic @code{asm}.
> +within a function and can be used at top level as well with certain
> +restrictions.
>  
>  You can also use the @code{asm} keyword to override the assembler name
>  for a C symbol, or to place a C variable in a specific register.
> @@ -10848,6 +10848,8 @@ can be used for code compiled with @opti
>  @item volatile
>  The optional @code{volatile} qualifier has no effect. 
>  All basic @code{asm} blocks are implicitly volatile.
> +Basic @code{asm} statements outside of functions may not use any
> +qualifiers.
>  
>  @item inline
>  If you use the @code{inline} qualifier, then for inlining purposes the size
> @@ -10892,25 +10894,19 @@ void function()
>  @subsubheading Remarks
>  Using extended @code{asm} (@pxref{Extended Asm}) typically produces
>  smaller, safer, and more efficient code, and in most cases it is a
> -better solution than basic @code{asm}.  However, there are two
> -situations where only basic @code{asm} can be used:
> +better solution than basic @code{asm}.  However, functions declared
> +with the @code{naked} attribute require only basic @code{asm}
> +(@pxref{Function Attributes}).
>  
> -@itemize @bullet
> -@item
> -Extended @code{asm} statements have to be inside a C
> -function, so to write inline assembly language at file scope (``top-level''),
> -outside of C functions, you must use basic @code{asm}.
> -You can use this technique to emit assembler directives,
> +Extended @code{asm} statements may be used both inside a C
> +function or at file scope (``top-level''), where
> +you can use this technique to emit assembler directives,
>  define assembly language macros that can be invoked elsewhere in the file,
>  or write entire functions in assembly language.
> -Basic @code{asm} statements outside of functions may not use any
> -qualifiers.
> -
> -@item
> -Functions declared
> -with the @code{naked} attribute also require basic @code{asm}
> -(@pxref{Function Attributes}).
> -@end itemize
> +Extended @code{asm} statements outside of functions may not use any
> +qualifiers, may not specify clobbers, may not use @code{%}, @code{+} or
> +@code{&} modifiers in constraints and can only use constraints which don%'t
> +allow using any register.
>  
>  Safely accessing C data and calling functions from basic @code{asm} is more 
>  complex than it may appear. To access C data, it is better to use extended 
> --- gcc/c/c-parser.cc.jj	2024-10-02 10:02:07.970897229 +0200
> +++ gcc/c/c-parser.cc	2024-10-02 11:27:13.388943632 +0200
> @@ -1658,6 +1658,7 @@ static struct c_arg_info *c_parser_parms
>  static struct c_arg_info *c_parser_parms_list_declarator (c_parser *, tree,
>  							  tree, bool);
>  static struct c_parm *c_parser_parameter_declaration (c_parser *, tree, bool);
> +static tree c_parser_asm_string_literal (c_parser *);
>  static tree c_parser_simple_asm_expr (c_parser *);
>  static tree c_parser_gnu_attributes (c_parser *);
>  static struct c_expr c_parser_initializer (c_parser *, tree);
> @@ -3068,7 +3069,62 @@ c_parser_declaration_or_fndef (c_parser
>  static void
>  c_parser_asm_definition (c_parser *parser)
>  {
> -  tree asm_str = c_parser_simple_asm_expr (parser);
> +  location_t asm_loc = c_parser_peek_token (parser)->location;
> +  gcc_assert (c_parser_next_token_is_keyword (parser, RID_ASM));
> +  c_parser_consume_token (parser);
> +  matching_parens parens;
> +  tree asm_str = NULL_TREE;
> +  tree outputs = NULL_TREE, inputs = NULL_TREE;
> +  if (!parens.require_open (parser))
> +    goto done;
> +  asm_str = c_parser_asm_string_literal (parser);
> +  if (c_parser_next_token_is (parser, CPP_CLOSE_PAREN))
> +    {
> +      parens.require_close (parser);
> +      goto done;
> +    }
> +  for (int section = 0; section < 2; ++section)
> +    {
> +      if (c_parser_next_token_is (parser, CPP_SCOPE))
> +	{
> +	  ++section;
> +	  if (section == 2)
> +	    {
> +	      c_parser_error (parser, "expected %<)%>");
> +	    error_close_paren:
> +	      c_parser_skip_until_found (parser, CPP_CLOSE_PAREN, NULL);
> +	      asm_str = NULL_TREE;
> +	      goto done;
> +	    }
> +	  c_parser_consume_token (parser);
> +	}
> +      else if (!c_parser_require (parser, CPP_COLON,
> +				  "expected %<:%> or %<)%>",
> +				  UNKNOWN_LOCATION, false))
> +	goto error_close_paren;
> +      if (!c_parser_next_token_is (parser, CPP_COLON)
> +	  && !c_parser_next_token_is (parser, CPP_SCOPE)
> +	  && !c_parser_next_token_is (parser, CPP_CLOSE_PAREN))
> +	{
> +	  if (section)
> +	    inputs = c_parser_asm_operands (parser);
> +	  else
> +	    outputs = c_parser_asm_operands (parser);
> +	}
> +      if (c_parser_next_token_is (parser, CPP_CLOSE_PAREN))
> +	break;
> +    }
> +
> +  if (!parens.require_close (parser))
> +    {
> +      c_parser_skip_until_found (parser, CPP_CLOSE_PAREN, NULL);
> +      asm_str = NULL_TREE;
> +    }
> +
> +  if (asm_str)
> +    asm_str = build_asm_expr (asm_loc, asm_str, outputs, inputs,
> +			      NULL_TREE, NULL_TREE, false, false);
> +done:
>    if (asm_str)
>      symtab->finalize_toplevel_asm (asm_str);
>    c_parser_skip_until_found (parser, CPP_SEMICOLON, "expected %<;%>");
> --- gcc/c/c-typeck.cc.jj	2024-10-02 10:02:07.975897160 +0200
> +++ gcc/c/c-typeck.cc	2024-10-02 11:27:13.385943674 +0200
> @@ -11621,10 +11621,37 @@ build_asm_expr (location_t loc, tree str
>  	      error_at (loc, "invalid use of void expression");
>  	      output = error_mark_node;
>  	    }
> +	  if (allows_reg && current_function_decl == NULL_TREE)
> +	    {
> +	      error_at (loc, "invalid constraint outside of a function");
> +	      output = error_mark_node;
> +	    }
>  	}
>        else
>  	output = error_mark_node;
>  
> +      if (current_function_decl == NULL_TREE && output != error_mark_node)
> +	{
> +	  if (TREE_SIDE_EFFECTS (output))
> +	    {
> +	      error_at (loc, "side-effects in output operand outside "
> +			     "of a function");
> +	      output = error_mark_node;
> +	    }
> +	  else
> +	    {
> +	      tree addr = build_unary_op (loc, ADDR_EXPR, output, false);
> +	      if (addr == error_mark_node)
> +		output = error_mark_node;
> +	      else if (!initializer_constant_valid_p (addr, TREE_TYPE (addr)))
> +		{
> +		  error_at (loc, "output operand outside of a function is not "
> +				 "constant");
> +		  output = error_mark_node;
> +		}
> +	    }
> +	}
> +
>        TREE_VALUE (tail) = output;
>      }
>  
> @@ -11664,10 +11691,37 @@ build_asm_expr (location_t loc, tree str
>  		  input = error_mark_node;
>  		}
>  	    }
> +	  if (allows_reg && current_function_decl == NULL_TREE)
> +	    {
> +	      error_at (loc, "invalid constraint outside of a function");
> +	      input = error_mark_node;
> +	    }
>  	}
>        else
>  	input = error_mark_node;
>  
> +      if (current_function_decl == NULL_TREE && input != error_mark_node)
> +	{
> +	  if (TREE_SIDE_EFFECTS (input))
> +	    {
> +	      error_at (loc, "side-effects in input operand outside "
> +			     "of a function");
> +	      input = error_mark_node;
> +	    }
> +	  else
> +	    {
> +	      tree tem = input;
> +	      if (allows_mem && lvalue_p (input))
> +		tem = build_unary_op (loc, ADDR_EXPR, input, false);
> +	      if (!initializer_constant_valid_p (tem, TREE_TYPE (tem)))
> +		{
> +		  error_at (loc, "input operand outside of a function is not "
> +				 "constant");
> +		  input = error_mark_node;
> +		}
> +	    }
> +	}
> +
>        TREE_VALUE (tail) = input;
>      }
>  
> --- gcc/cp/cp-tree.h.jj	2024-10-02 10:02:14.487806507 +0200
> +++ gcc/cp/cp-tree.h	2024-10-02 11:46:56.031506125 +0200
> @@ -7848,7 +7848,7 @@ extern tree begin_compound_stmt			(unsig
>  
>  extern void finish_compound_stmt		(tree);
>  extern tree finish_asm_stmt			(location_t, int, tree, tree,
> -						 tree, tree, tree, bool);
> +						 tree, tree, tree, bool, bool);
>  extern tree finish_label_stmt			(tree);
>  extern void finish_label_decl			(tree);
>  extern cp_expr finish_parenthesized_expr	(cp_expr);
> --- gcc/cp/parser.cc.jj	2024-10-01 09:38:57.885963653 +0200
> +++ gcc/cp/parser.cc	2024-10-02 11:48:01.417597184 +0200
> @@ -23080,7 +23080,6 @@ cp_parser_asm_definition (cp_parser* par
>       too.  Doing that means that we have to treat the `::' operator as
>       two `:' tokens.  */
>    if (cp_parser_allow_gnu_extensions_p (parser)
> -      && parser->in_function_body
>        && (cp_lexer_next_token_is (parser->lexer, CPP_COLON)
>  	  || cp_lexer_next_token_is (parser->lexer, CPP_SCOPE)))
>      {
> @@ -23134,13 +23133,15 @@ cp_parser_asm_definition (cp_parser* par
>                  invalid_inputs_p = true;
>              }
>  	}
> -      else if (cp_lexer_next_token_is (parser->lexer, CPP_SCOPE))
> +      else if (parser->in_function_body
> +	       && cp_lexer_next_token_is (parser->lexer, CPP_SCOPE))
>  	/* The clobbers are coming next.  */
>  	clobbers_p = true;
>  
>        /* Look for clobbers.  */
>        if (clobbers_p
> -	  || cp_lexer_next_token_is (parser->lexer, CPP_COLON))
> +	  || (parser->in_function_body
> +	      && cp_lexer_next_token_is (parser->lexer, CPP_COLON)))
>  	{
>  	  clobbers_p = true;
>  	  /* Consume the `:' or `::'.  */
> @@ -23186,7 +23187,8 @@ cp_parser_asm_definition (cp_parser* par
>        if (parser->in_function_body)
>  	{
>  	  asm_stmt = finish_asm_stmt (asm_loc, volatile_p, string, outputs,
> -				      inputs, clobbers, labels, inline_p);
> +				      inputs, clobbers, labels, inline_p,
> +				      false);
>  	  /* If the extended syntax was not used, mark the ASM_EXPR.  */
>  	  if (!extended_p)
>  	    {
> @@ -23197,8 +23199,11 @@ cp_parser_asm_definition (cp_parser* par
>  	      ASM_BASIC_P (temp) = 1;
>  	    }
>  	}
> -      else
> +      else if (!extended_p)
>  	symtab->finalize_toplevel_asm (string);
> +      else
> +	finish_asm_stmt (asm_loc, false, string, outputs, inputs,
> +			 NULL_TREE, NULL_TREE, false, true);
>      }
>  
>    if (std_attrs && any_nonignored_attribute_p (std_attrs))
> --- gcc/cp/semantics.cc.jj	2024-10-02 10:02:14.536805825 +0200
> +++ gcc/cp/semantics.cc	2024-10-02 12:20:43.384331967 +0200
> @@ -2134,12 +2134,13 @@ finish_compound_stmt (tree stmt)
>  /* Finish an asm-statement, whose components are a STRING, some
>     OUTPUT_OPERANDS, some INPUT_OPERANDS, some CLOBBERS and some
>     LABELS.  Also note whether the asm-statement should be
> -   considered volatile, and whether it is asm inline.  */
> +   considered volatile, and whether it is asm inline.  TOPLEV_P
> +   is true if finishing namespace scope extended asm.  */
>  
>  tree
>  finish_asm_stmt (location_t loc, int volatile_p, tree string,
>  		 tree output_operands, tree input_operands, tree clobbers,
> -		 tree labels, bool inline_p)
> +		 tree labels, bool inline_p, bool toplev_p)
>  {
>    tree r;
>    tree t;
> @@ -2213,10 +2214,45 @@ finish_asm_stmt (location_t loc, int vol
>  		 mark it addressable.  */
>  	      if (!allows_reg && !cxx_mark_addressable (*op))
>  		operand = error_mark_node;
> +	      if (allows_reg && toplev_p)
> +		{
> +		  error_at (loc, "invalid constraint outside of a function");
> +		  operand = error_mark_node;
> +		}
>  	    }
>  	  else
>  	    operand = error_mark_node;
>  
> +	  if (toplev_p && operand != error_mark_node)
> +	    {
> +	      if (TREE_SIDE_EFFECTS (operand))
> +		{
> +		  error_at (loc, "side-effects in output operand outside "
> +				 "of a function");
> +		  operand = error_mark_node;
> +		}
> +	      else
> +		{
> +		  tree addr
> +		    = cp_build_addr_expr (operand, tf_warning_or_error);
> +		  if (addr == error_mark_node)
> +		    operand = error_mark_node;
> +		  else
> +		    {
> +		      addr = maybe_constant_value (addr);
> +		      if (!initializer_constant_valid_p (addr,
> +							 TREE_TYPE (addr)))
> +			{
> +			  error_at (loc, "output operand outside of a "
> +					 "function is not constant");
> +			  operand = error_mark_node;
> +			}
> +		      else
> +			operand = build_fold_indirect_ref (addr);
> +		    }
> +		}
> +	    }
> +
>  	  TREE_VALUE (t) = operand;
>  	}
>  
> @@ -2281,10 +2317,55 @@ finish_asm_stmt (location_t loc, int vol
>  		  if (TREE_CONSTANT (constop))
>  		    operand = constop;
>  		}
> +	      if (allows_reg && toplev_p)
> +		{
> +		  error_at (loc, "invalid constraint outside of a function");
> +		  operand = error_mark_node;
> +		}
>  	    }
>  	  else
>  	    operand = error_mark_node;
>  
> +	  if (toplev_p && operand != error_mark_node)
> +	    {
> +	      if (TREE_SIDE_EFFECTS (operand))
> +		{
> +		  error_at (loc, "side-effects in input operand outside "
> +				 "of a function");
> +		  operand = error_mark_node;
> +		}
> +	      else if (allows_mem && lvalue_or_else (operand, lv_asm, tf_none))
> +		{
> +		  tree addr = cp_build_addr_expr (operand, tf_warning_or_error);
> +		  if (addr == error_mark_node)
> +		    operand = error_mark_node;
> +		  else
> +		    {
> +		      addr = maybe_constant_value (addr);
> +		      if (!initializer_constant_valid_p (addr,
> +							 TREE_TYPE (addr)))
> +			{
> +			  error_at (loc, "input operand outside of a "
> +					 "function is not constant");
> +			  operand = error_mark_node;
> +			}
> +		      else
> +			operand = build_fold_indirect_ref (addr);
> +		    }
> +		}
> +	      else
> +		{
> +		  operand = maybe_constant_value (operand);
> +		  if (!initializer_constant_valid_p (operand,
> +						     TREE_TYPE (operand)))
> +		    {
> +		      error_at (loc, "input operand outside of a "
> +				     "function is not constant");
> +		      operand = error_mark_node;
> +		    }
> +		}
> +	    }
> +
>  	  TREE_VALUE (t) = operand;
>  	}
>      }
> @@ -2294,6 +2375,11 @@ finish_asm_stmt (location_t loc, int vol
>  		  clobbers, labels);
>    ASM_VOLATILE_P (r) = volatile_p || noutputs == 0;
>    ASM_INLINE_P (r) = inline_p;
> +  if (toplev_p)
> +    {
> +      symtab->finalize_toplevel_asm (r);
> +      return r;
> +    }
>    r = maybe_cleanup_point_expr_void (r);
>    return add_stmt (r);
>  }
> --- gcc/cp/pt.cc.jj	2024-10-01 09:38:57.915963234 +0200
> +++ gcc/cp/pt.cc	2024-10-02 11:50:03.662897824 +0200
> @@ -18966,7 +18966,7 @@ tsubst_stmt (tree t, tree args, tsubst_f
>  						complain, in_decl);
>  	tmp = finish_asm_stmt (EXPR_LOCATION (t), ASM_VOLATILE_P (t), string,
>  			       outputs, inputs, clobbers, labels,
> -			       ASM_INLINE_P (t));
> +			       ASM_INLINE_P (t), false);
>  	tree asm_expr = tmp;
>  	if (TREE_CODE (asm_expr) == CLEANUP_POINT_EXPR)
>  	  asm_expr = TREE_OPERAND (asm_expr, 0);
> 
> 	Jakub
> 
>
Andi Kleen Oct. 2, 2024, 12:34 p.m. UTC | #3
Jakub Jelinek <jakub@redhat.com> writes:

> And for kernel perhaps we should add some new option which allows
> some dumb parsing of the toplevel asms and gather something from that
> parsing.

See also https://gcc.gnu.org/bugzilla/show_bug.cgi?id=107779

> The restrictions I've implemented are:
> 1) asm qualifiers aren't still allowed, so asm goto or asm inline can't be
>    specified at toplevel, asm volatile has the volatile ignored for C++ with
>    a warning and is an error in C like before
> 2) I see good use for mainly input operands, output maybe to make it clear
>    that the inline asm may write some memory, I don't see a good use for
>    clobbers, so the patch doesn't allow those (and of course labels because
>    asm goto can't be specified)

One of the main uses for this is to specify functions that may get
called by the assembler. You proposal is to specify them as input "m" ?
Seems odd.  Perhaps this needs a new syntax.

One issue that asms also often run into is that they don't like
reordering. Some way to specify attribute((no_reorder)) would be useful.

-Andi
Jakub Jelinek Oct. 2, 2024, 12:55 p.m. UTC | #4
On Wed, Oct 02, 2024 at 01:59:03PM +0200, Richard Biener wrote:
> As you are using input constraints to mark symbol uses maybe we can
> use output constraints with a magic identifier (and a constraint letter
> specifying 'identifier'):
> 
> asm (".globl %0; %0: ret" : "_D" (extern int foo()) : ...);
> 
> In the BOF it was noted that LTO wants to be able to rename / localize
> symbols so both use and definition should be used in a way to support
> this (though changing visibility is difficult - the assembler might
> tie to GOT uses, and .globl is hard to replace).

Seems we have quite a few free letters on the constraint side, I think
'-.:;[]@(){}|_`
are all currently rejected in both input and output constraints
(we only allow ISALPHA constraint chars for the target specific ones).
So, using one of those for "the inline asm defines this global symbol",
another for "the inline asm defines this local symbol", and another
"the inline asm uses this function" might be possible;
Of course it can be also just one of those special characters with
some qualifier after it, we'd just need to tweak the generated
insn_constraint_len for that.  I think the asm uses some variable can
be expressed with "m" (var) just fine.
Anyway, for function and var definitions, should the compiler be told
more information (e.g. whether it is weak or non-weak), or should one
assume that from the actually used FUNCTION_DECL/VAR_DECL?  Should the
global vs. local be also implied from it?  Though for variables, how does
one declare a static variable declaration but not definition?

And another thing is what the argument should be, what you wrote about
would be hard to parse (parsing dependent on the constraints, we usually
don't parse the constraint until the expression is parsed).
extern int foo ();
asm ("... : : "_" (foo));
probably would (but maybe just extern "C" for C++).

And we'd need to decide whether for LTO toplevel inline asm with
extended asm (as a new extension) is actually required not to rely on
the exact function/variable name at least in the non-global case and
the compiler is allowed to rename them (i.e. use %c0 etc. rather than
the actual name).

	Jakub
Alexander Monakov Oct. 2, 2024, 2:30 p.m. UTC | #5
On Wed, 2 Oct 2024, Jakub Jelinek wrote:

> Hi!
> 
> In the Cauldron IPA/LTO BoF we've discussed toplevel asms and it was
> discussed it would be nice to tell the compiler something about what
> the toplevel asm does.  Sure, I'm aware the kernel people said they
> aren't willing to use something like that, but perhaps other projects
> do.  And for kernel perhaps we should add some new option which allows
> some dumb parsing of the toplevel asms and gather something from that
> parsing.

I wonder if we should offer people a way to discover toplevel asms that
participated in the compilation. I tried implementing that the obvious
way for C (emit a warning from c_parser_asm_definition) and collected
stats on x86_64-defconfig kernel build. There are not too many unique
toplevel asms:

linux/arch/x86/include/asm/alternative.h:284:9
linux/arch/x86/include/asm/static_call.h:35:9
linux/arch/x86/include/asm/static_call.h:61:9
linux/arch/x86/kernel/alternative.c:1572:1
linux/arch/x86/kernel/callthunks.c:67:1
linux/arch/x86/kernel/kprobes/opt.c:104:1
linux/arch/x86/kernel/rethook.c:22:1
linux/arch/x86/kernel/static_call.c:44:1
linux/arch/x86/kernel/uprobes.c:313:1
linux/include/linux/export-internal.h:41:9
linux/include/linux/export.h:58:9
linux/include/linux/init.h:262:9
linux/include/linux/pci.h:2185:9
linux/include/linux/tracepoint.h:119:9

Alexander
diff mbox series

Patch

--- gcc/output.h.jj	2024-10-02 10:02:08.031896380 +0200
+++ gcc/output.h	2024-10-02 11:27:13.383943702 +0200
@@ -338,6 +338,9 @@  extern rtx_insn *current_output_insn;
    The precise value is the insn being output, to pass to error_for_asm.  */
 extern const rtx_insn *this_is_asm_operands;
 
+/* Number of operands of this insn, for an `asm' with operands.  */
+extern unsigned int insn_noperands;
+
 /* Carry information from ASM_DECLARE_OBJECT_NAME
    to ASM_FINISH_DECLARE_OBJECT.  */
 extern int size_directive_output;
--- gcc/final.cc.jj	2024-10-02 10:02:08.031896380 +0200
+++ gcc/final.cc	2024-10-02 11:27:13.382943715 +0200
@@ -149,7 +149,7 @@  extern const int length_unit_log; /* Thi
 const rtx_insn *this_is_asm_operands;
 
 /* Number of operands of this insn, for an `asm' with operands.  */
-static unsigned int insn_noperands;
+unsigned int insn_noperands;
 
 /* Compare optimization flag.  */
 
--- gcc/varasm.cc.jj	2024-10-02 10:02:08.056896032 +0200
+++ gcc/varasm.cc	2024-10-02 12:27:46.244456857 +0200
@@ -62,6 +62,7 @@  along with GCC; see the file COPYING3.
 #include "toplev.h"
 #include "opts.h"
 #include "asan.h"
+#include "recog.h"
 
 /* The (assembler) name of the first globally-visible object output.  */
 extern GTY(()) const char *first_global_object_name;
@@ -1667,16 +1668,154 @@  make_decl_rtl_for_debug (tree decl)
    for an `asm' keyword used between functions.  */
 
 void
-assemble_asm (tree string)
+assemble_asm (tree asm_str)
 {
   const char *p;
-  app_enable ();
 
-  if (TREE_CODE (string) == ADDR_EXPR)
-    string = TREE_OPERAND (string, 0);
+  if (TREE_CODE (asm_str) != ASM_EXPR)
+    {
+      app_enable ();
+      if (TREE_CODE (asm_str) == ADDR_EXPR)
+	asm_str = TREE_OPERAND (asm_str, 0);
+
+      p = TREE_STRING_POINTER (asm_str);
+      fprintf (asm_out_file, "%s%s\n", p[0] == '\t' ? "" : "\t", p);
+    }
+  else
+    {
+      location_t save_loc = input_location;
+      int save_reload_completed = reload_completed;
+      input_location = EXPR_LOCATION (asm_str);
+      int noutputs = list_length (ASM_OUTPUTS (asm_str));
+      int ninputs = list_length (ASM_INPUTS (asm_str));
+      const char **constraints = NULL;
+      int i;
+      tree tail;
+      bool allows_mem, allows_reg, is_inout;
+      rtx *ops = NULL;
+      if (noutputs + ninputs > MAX_RECOG_OPERANDS)
+	{
+	  error ("more than %d operands in %<asm%>", MAX_RECOG_OPERANDS);
+	  goto done;
+	}
+      constraints = XALLOCAVEC (const char *, noutputs + ninputs);
+      ops = XALLOCAVEC (rtx, noutputs + ninputs);
+      memset (&recog_data, 0, sizeof (recog_data));
+      recog_data.n_operands = ninputs + noutputs;
+      recog_data.is_asm = true;
+      reload_completed = 0;
+      for (i = 0, tail = ASM_OUTPUTS (asm_str); tail;
+	   ++i, tail = TREE_CHAIN (tail))
+	{
+	  tree output = TREE_VALUE (tail);
+	  if (output == error_mark_node)
+	    goto done;
+	  constraints[i]
+	    = TREE_STRING_POINTER (TREE_VALUE (TREE_PURPOSE (tail)));
+	  if (!parse_output_constraint (&constraints[i], i, ninputs, noutputs,
+					&allows_mem, &allows_reg, &is_inout))
+	    goto done;
+	  if (is_inout)
+	    {
+	      error ("%qc in output operand outside of a function", '+');
+	      goto done;
+	    }
+	  if (strchr (constraints[i], '&'))
+	    {
+	      error ("%qc in output operand outside of a function", '&');
+	      goto done;
+	    }
+	  if (strchr (constraints[i], '%'))
+	    {
+	      error ("%qc in output operand outside of a function", '%');
+	      goto done;
+	    }
+	  output_addressed_constants (output, 0);
+	  ops[i] = expand_expr (output, NULL_RTX, VOIDmode,
+				EXPAND_INITIALIZER);
+	  if (!MEM_P (ops[i]))
+	    {
+	      error ("output number %d not directly addressable", i);
+	      goto done;
+	    }
+
+	  recog_data.operand[i] = ops[i];
+	  recog_data.operand_loc[i] = &ops[i];
+	  recog_data.constraints[i] = constraints[i];
+	  recog_data.operand_mode[i] = TYPE_MODE (TREE_TYPE (output));
+	}
+      for (i = 0, tail = ASM_INPUTS (asm_str); tail;
+	   ++i, tail = TREE_CHAIN (tail))
+	{
+	  tree input = TREE_VALUE (tail);
+	  if (input == error_mark_node)
+	    goto done;
+	  constraints[i + noutputs]
+	    = TREE_STRING_POINTER (TREE_VALUE (TREE_PURPOSE (tail)));
+	  if (!parse_input_constraint (&constraints[i + noutputs], i,
+				       ninputs, noutputs, 0, constraints,
+				       &allows_mem, &allows_reg))
+	    goto done;
+	  if (strchr (constraints[i], '%'))
+	    {
+	      error ("%qc in input operand outside of a function", '%');
+	      goto done;
+	    }
+	  const char *constraint = constraints[i + noutputs];
+	  size_t c_len = strlen (constraint);
+	  for (size_t j = 0; j < c_len;
+	       j += CONSTRAINT_LEN (constraint[j], constraint + j))
+	    if (constraint[j] >= '0' && constraint[j] <= '9')
+	      {
+		error ("matching constraint outside of a function");
+		goto done;
+	      }
+	  output_addressed_constants (input, 0);
+	  ops[i + noutputs] = expand_expr (input, NULL_RTX, VOIDmode,
+					   EXPAND_INITIALIZER);
+	  if (asm_operand_ok (ops[i + noutputs], constraint, NULL) <= 0)
+	    {
+	      if (!allows_mem)
+		warning (0, "%<asm%> operand %d probably does not "
+			    "match constraints", i + noutputs);
+	    }
+	  recog_data.operand[i + noutputs] = ops[i + noutputs];
+	  recog_data.operand_loc[i + noutputs] = &ops[i + noutputs];
+	  recog_data.constraints[i + noutputs] = constraints[i + noutputs];
+	  recog_data.operand_mode[i + noutputs]
+	    = TYPE_MODE (TREE_TYPE (input));
+	}
+      if (recog_data.n_operands > 0)
+	{
+	  const char *p = recog_data.constraints[0];
+	  recog_data.n_alternatives = 1;
+	  while (*p)
+	    recog_data.n_alternatives += (*p++ == ',');
+	}
+      for (i = 0; i < recog_data.n_operands; i++)
+	recog_data.operand_type[i]
+	  = recog_data.constraints[i][0] == '=' ? OP_OUT : OP_IN;
+      reload_completed = 1;
+      constrain_operands (1, ALL_ALTERNATIVES);
+      if (which_alternative < 0)
+	{
+	  error ("impossible constraint in %<asm%>");
+	  goto done;
+	}
+      this_is_asm_operands = make_insn_raw (gen_nop ());
+      insn_noperands = recog_data.n_operands;
+      if (TREE_STRING_POINTER (ASM_STRING (asm_str))[0])
+	{
+	  app_enable ();
+	  output_asm_insn (TREE_STRING_POINTER (ASM_STRING (asm_str)), ops);
+	}
+      insn_noperands = 0;
+      this_is_asm_operands = NULL;
 
-  p = TREE_STRING_POINTER (string);
-  fprintf (asm_out_file, "%s%s\n", p[0] == '\t' ? "" : "\t", p);
+    done:
+      input_location = save_loc;
+      reload_completed = save_reload_completed;
+    }
 }
 
 /* Write the address of the entity given by SYMBOL to SEC.  */
--- gcc/doc/extend.texi.jj	2024-10-02 10:02:14.549805644 +0200
+++ gcc/doc/extend.texi	2024-10-02 11:27:13.392943576 +0200
@@ -10809,8 +10809,8 @@  statements.  A @dfn{basic @code{asm}} st
 operands (@pxref{Basic Asm}), while an @dfn{extended @code{asm}}
 statement (@pxref{Extended Asm}) includes one or more operands.  
 The extended form is preferred for mixing C and assembly language
-within a function, but to include assembly language at
-top level you must use basic @code{asm}.
+within a function and can be used at top level as well with certain
+restrictions.
 
 You can also use the @code{asm} keyword to override the assembler name
 for a C symbol, or to place a C variable in a specific register.
@@ -10848,6 +10848,8 @@  can be used for code compiled with @opti
 @item volatile
 The optional @code{volatile} qualifier has no effect. 
 All basic @code{asm} blocks are implicitly volatile.
+Basic @code{asm} statements outside of functions may not use any
+qualifiers.
 
 @item inline
 If you use the @code{inline} qualifier, then for inlining purposes the size
@@ -10892,25 +10894,19 @@  void function()
 @subsubheading Remarks
 Using extended @code{asm} (@pxref{Extended Asm}) typically produces
 smaller, safer, and more efficient code, and in most cases it is a
-better solution than basic @code{asm}.  However, there are two
-situations where only basic @code{asm} can be used:
+better solution than basic @code{asm}.  However, functions declared
+with the @code{naked} attribute require only basic @code{asm}
+(@pxref{Function Attributes}).
 
-@itemize @bullet
-@item
-Extended @code{asm} statements have to be inside a C
-function, so to write inline assembly language at file scope (``top-level''),
-outside of C functions, you must use basic @code{asm}.
-You can use this technique to emit assembler directives,
+Extended @code{asm} statements may be used both inside a C
+function or at file scope (``top-level''), where
+you can use this technique to emit assembler directives,
 define assembly language macros that can be invoked elsewhere in the file,
 or write entire functions in assembly language.
-Basic @code{asm} statements outside of functions may not use any
-qualifiers.
-
-@item
-Functions declared
-with the @code{naked} attribute also require basic @code{asm}
-(@pxref{Function Attributes}).
-@end itemize
+Extended @code{asm} statements outside of functions may not use any
+qualifiers, may not specify clobbers, may not use @code{%}, @code{+} or
+@code{&} modifiers in constraints and can only use constraints which don%'t
+allow using any register.
 
 Safely accessing C data and calling functions from basic @code{asm} is more 
 complex than it may appear. To access C data, it is better to use extended 
--- gcc/c/c-parser.cc.jj	2024-10-02 10:02:07.970897229 +0200
+++ gcc/c/c-parser.cc	2024-10-02 11:27:13.388943632 +0200
@@ -1658,6 +1658,7 @@  static struct c_arg_info *c_parser_parms
 static struct c_arg_info *c_parser_parms_list_declarator (c_parser *, tree,
 							  tree, bool);
 static struct c_parm *c_parser_parameter_declaration (c_parser *, tree, bool);
+static tree c_parser_asm_string_literal (c_parser *);
 static tree c_parser_simple_asm_expr (c_parser *);
 static tree c_parser_gnu_attributes (c_parser *);
 static struct c_expr c_parser_initializer (c_parser *, tree);
@@ -3068,7 +3069,62 @@  c_parser_declaration_or_fndef (c_parser
 static void
 c_parser_asm_definition (c_parser *parser)
 {
-  tree asm_str = c_parser_simple_asm_expr (parser);
+  location_t asm_loc = c_parser_peek_token (parser)->location;
+  gcc_assert (c_parser_next_token_is_keyword (parser, RID_ASM));
+  c_parser_consume_token (parser);
+  matching_parens parens;
+  tree asm_str = NULL_TREE;
+  tree outputs = NULL_TREE, inputs = NULL_TREE;
+  if (!parens.require_open (parser))
+    goto done;
+  asm_str = c_parser_asm_string_literal (parser);
+  if (c_parser_next_token_is (parser, CPP_CLOSE_PAREN))
+    {
+      parens.require_close (parser);
+      goto done;
+    }
+  for (int section = 0; section < 2; ++section)
+    {
+      if (c_parser_next_token_is (parser, CPP_SCOPE))
+	{
+	  ++section;
+	  if (section == 2)
+	    {
+	      c_parser_error (parser, "expected %<)%>");
+	    error_close_paren:
+	      c_parser_skip_until_found (parser, CPP_CLOSE_PAREN, NULL);
+	      asm_str = NULL_TREE;
+	      goto done;
+	    }
+	  c_parser_consume_token (parser);
+	}
+      else if (!c_parser_require (parser, CPP_COLON,
+				  "expected %<:%> or %<)%>",
+				  UNKNOWN_LOCATION, false))
+	goto error_close_paren;
+      if (!c_parser_next_token_is (parser, CPP_COLON)
+	  && !c_parser_next_token_is (parser, CPP_SCOPE)
+	  && !c_parser_next_token_is (parser, CPP_CLOSE_PAREN))
+	{
+	  if (section)
+	    inputs = c_parser_asm_operands (parser);
+	  else
+	    outputs = c_parser_asm_operands (parser);
+	}
+      if (c_parser_next_token_is (parser, CPP_CLOSE_PAREN))
+	break;
+    }
+
+  if (!parens.require_close (parser))
+    {
+      c_parser_skip_until_found (parser, CPP_CLOSE_PAREN, NULL);
+      asm_str = NULL_TREE;
+    }
+
+  if (asm_str)
+    asm_str = build_asm_expr (asm_loc, asm_str, outputs, inputs,
+			      NULL_TREE, NULL_TREE, false, false);
+done:
   if (asm_str)
     symtab->finalize_toplevel_asm (asm_str);
   c_parser_skip_until_found (parser, CPP_SEMICOLON, "expected %<;%>");
--- gcc/c/c-typeck.cc.jj	2024-10-02 10:02:07.975897160 +0200
+++ gcc/c/c-typeck.cc	2024-10-02 11:27:13.385943674 +0200
@@ -11621,10 +11621,37 @@  build_asm_expr (location_t loc, tree str
 	      error_at (loc, "invalid use of void expression");
 	      output = error_mark_node;
 	    }
+	  if (allows_reg && current_function_decl == NULL_TREE)
+	    {
+	      error_at (loc, "invalid constraint outside of a function");
+	      output = error_mark_node;
+	    }
 	}
       else
 	output = error_mark_node;
 
+      if (current_function_decl == NULL_TREE && output != error_mark_node)
+	{
+	  if (TREE_SIDE_EFFECTS (output))
+	    {
+	      error_at (loc, "side-effects in output operand outside "
+			     "of a function");
+	      output = error_mark_node;
+	    }
+	  else
+	    {
+	      tree addr = build_unary_op (loc, ADDR_EXPR, output, false);
+	      if (addr == error_mark_node)
+		output = error_mark_node;
+	      else if (!initializer_constant_valid_p (addr, TREE_TYPE (addr)))
+		{
+		  error_at (loc, "output operand outside of a function is not "
+				 "constant");
+		  output = error_mark_node;
+		}
+	    }
+	}
+
       TREE_VALUE (tail) = output;
     }
 
@@ -11664,10 +11691,37 @@  build_asm_expr (location_t loc, tree str
 		  input = error_mark_node;
 		}
 	    }
+	  if (allows_reg && current_function_decl == NULL_TREE)
+	    {
+	      error_at (loc, "invalid constraint outside of a function");
+	      input = error_mark_node;
+	    }
 	}
       else
 	input = error_mark_node;
 
+      if (current_function_decl == NULL_TREE && input != error_mark_node)
+	{
+	  if (TREE_SIDE_EFFECTS (input))
+	    {
+	      error_at (loc, "side-effects in input operand outside "
+			     "of a function");
+	      input = error_mark_node;
+	    }
+	  else
+	    {
+	      tree tem = input;
+	      if (allows_mem && lvalue_p (input))
+		tem = build_unary_op (loc, ADDR_EXPR, input, false);
+	      if (!initializer_constant_valid_p (tem, TREE_TYPE (tem)))
+		{
+		  error_at (loc, "input operand outside of a function is not "
+				 "constant");
+		  input = error_mark_node;
+		}
+	    }
+	}
+
       TREE_VALUE (tail) = input;
     }
 
--- gcc/cp/cp-tree.h.jj	2024-10-02 10:02:14.487806507 +0200
+++ gcc/cp/cp-tree.h	2024-10-02 11:46:56.031506125 +0200
@@ -7848,7 +7848,7 @@  extern tree begin_compound_stmt			(unsig
 
 extern void finish_compound_stmt		(tree);
 extern tree finish_asm_stmt			(location_t, int, tree, tree,
-						 tree, tree, tree, bool);
+						 tree, tree, tree, bool, bool);
 extern tree finish_label_stmt			(tree);
 extern void finish_label_decl			(tree);
 extern cp_expr finish_parenthesized_expr	(cp_expr);
--- gcc/cp/parser.cc.jj	2024-10-01 09:38:57.885963653 +0200
+++ gcc/cp/parser.cc	2024-10-02 11:48:01.417597184 +0200
@@ -23080,7 +23080,6 @@  cp_parser_asm_definition (cp_parser* par
      too.  Doing that means that we have to treat the `::' operator as
      two `:' tokens.  */
   if (cp_parser_allow_gnu_extensions_p (parser)
-      && parser->in_function_body
       && (cp_lexer_next_token_is (parser->lexer, CPP_COLON)
 	  || cp_lexer_next_token_is (parser->lexer, CPP_SCOPE)))
     {
@@ -23134,13 +23133,15 @@  cp_parser_asm_definition (cp_parser* par
                 invalid_inputs_p = true;
             }
 	}
-      else if (cp_lexer_next_token_is (parser->lexer, CPP_SCOPE))
+      else if (parser->in_function_body
+	       && cp_lexer_next_token_is (parser->lexer, CPP_SCOPE))
 	/* The clobbers are coming next.  */
 	clobbers_p = true;
 
       /* Look for clobbers.  */
       if (clobbers_p
-	  || cp_lexer_next_token_is (parser->lexer, CPP_COLON))
+	  || (parser->in_function_body
+	      && cp_lexer_next_token_is (parser->lexer, CPP_COLON)))
 	{
 	  clobbers_p = true;
 	  /* Consume the `:' or `::'.  */
@@ -23186,7 +23187,8 @@  cp_parser_asm_definition (cp_parser* par
       if (parser->in_function_body)
 	{
 	  asm_stmt = finish_asm_stmt (asm_loc, volatile_p, string, outputs,
-				      inputs, clobbers, labels, inline_p);
+				      inputs, clobbers, labels, inline_p,
+				      false);
 	  /* If the extended syntax was not used, mark the ASM_EXPR.  */
 	  if (!extended_p)
 	    {
@@ -23197,8 +23199,11 @@  cp_parser_asm_definition (cp_parser* par
 	      ASM_BASIC_P (temp) = 1;
 	    }
 	}
-      else
+      else if (!extended_p)
 	symtab->finalize_toplevel_asm (string);
+      else
+	finish_asm_stmt (asm_loc, false, string, outputs, inputs,
+			 NULL_TREE, NULL_TREE, false, true);
     }
 
   if (std_attrs && any_nonignored_attribute_p (std_attrs))
--- gcc/cp/semantics.cc.jj	2024-10-02 10:02:14.536805825 +0200
+++ gcc/cp/semantics.cc	2024-10-02 12:20:43.384331967 +0200
@@ -2134,12 +2134,13 @@  finish_compound_stmt (tree stmt)
 /* Finish an asm-statement, whose components are a STRING, some
    OUTPUT_OPERANDS, some INPUT_OPERANDS, some CLOBBERS and some
    LABELS.  Also note whether the asm-statement should be
-   considered volatile, and whether it is asm inline.  */
+   considered volatile, and whether it is asm inline.  TOPLEV_P
+   is true if finishing namespace scope extended asm.  */
 
 tree
 finish_asm_stmt (location_t loc, int volatile_p, tree string,
 		 tree output_operands, tree input_operands, tree clobbers,
-		 tree labels, bool inline_p)
+		 tree labels, bool inline_p, bool toplev_p)
 {
   tree r;
   tree t;
@@ -2213,10 +2214,45 @@  finish_asm_stmt (location_t loc, int vol
 		 mark it addressable.  */
 	      if (!allows_reg && !cxx_mark_addressable (*op))
 		operand = error_mark_node;
+	      if (allows_reg && toplev_p)
+		{
+		  error_at (loc, "invalid constraint outside of a function");
+		  operand = error_mark_node;
+		}
 	    }
 	  else
 	    operand = error_mark_node;
 
+	  if (toplev_p && operand != error_mark_node)
+	    {
+	      if (TREE_SIDE_EFFECTS (operand))
+		{
+		  error_at (loc, "side-effects in output operand outside "
+				 "of a function");
+		  operand = error_mark_node;
+		}
+	      else
+		{
+		  tree addr
+		    = cp_build_addr_expr (operand, tf_warning_or_error);
+		  if (addr == error_mark_node)
+		    operand = error_mark_node;
+		  else
+		    {
+		      addr = maybe_constant_value (addr);
+		      if (!initializer_constant_valid_p (addr,
+							 TREE_TYPE (addr)))
+			{
+			  error_at (loc, "output operand outside of a "
+					 "function is not constant");
+			  operand = error_mark_node;
+			}
+		      else
+			operand = build_fold_indirect_ref (addr);
+		    }
+		}
+	    }
+
 	  TREE_VALUE (t) = operand;
 	}
 
@@ -2281,10 +2317,55 @@  finish_asm_stmt (location_t loc, int vol
 		  if (TREE_CONSTANT (constop))
 		    operand = constop;
 		}
+	      if (allows_reg && toplev_p)
+		{
+		  error_at (loc, "invalid constraint outside of a function");
+		  operand = error_mark_node;
+		}
 	    }
 	  else
 	    operand = error_mark_node;
 
+	  if (toplev_p && operand != error_mark_node)
+	    {
+	      if (TREE_SIDE_EFFECTS (operand))
+		{
+		  error_at (loc, "side-effects in input operand outside "
+				 "of a function");
+		  operand = error_mark_node;
+		}
+	      else if (allows_mem && lvalue_or_else (operand, lv_asm, tf_none))
+		{
+		  tree addr = cp_build_addr_expr (operand, tf_warning_or_error);
+		  if (addr == error_mark_node)
+		    operand = error_mark_node;
+		  else
+		    {
+		      addr = maybe_constant_value (addr);
+		      if (!initializer_constant_valid_p (addr,
+							 TREE_TYPE (addr)))
+			{
+			  error_at (loc, "input operand outside of a "
+					 "function is not constant");
+			  operand = error_mark_node;
+			}
+		      else
+			operand = build_fold_indirect_ref (addr);
+		    }
+		}
+	      else
+		{
+		  operand = maybe_constant_value (operand);
+		  if (!initializer_constant_valid_p (operand,
+						     TREE_TYPE (operand)))
+		    {
+		      error_at (loc, "input operand outside of a "
+				     "function is not constant");
+		      operand = error_mark_node;
+		    }
+		}
+	    }
+
 	  TREE_VALUE (t) = operand;
 	}
     }
@@ -2294,6 +2375,11 @@  finish_asm_stmt (location_t loc, int vol
 		  clobbers, labels);
   ASM_VOLATILE_P (r) = volatile_p || noutputs == 0;
   ASM_INLINE_P (r) = inline_p;
+  if (toplev_p)
+    {
+      symtab->finalize_toplevel_asm (r);
+      return r;
+    }
   r = maybe_cleanup_point_expr_void (r);
   return add_stmt (r);
 }
--- gcc/cp/pt.cc.jj	2024-10-01 09:38:57.915963234 +0200
+++ gcc/cp/pt.cc	2024-10-02 11:50:03.662897824 +0200
@@ -18966,7 +18966,7 @@  tsubst_stmt (tree t, tree args, tsubst_f
 						complain, in_decl);
 	tmp = finish_asm_stmt (EXPR_LOCATION (t), ASM_VOLATILE_P (t), string,
 			       outputs, inputs, clobbers, labels,
-			       ASM_INLINE_P (t));
+			       ASM_INLINE_P (t), false);
 	tree asm_expr = tmp;
 	if (TREE_CODE (asm_expr) == CLEANUP_POINT_EXPR)
 	  asm_expr = TREE_OPERAND (asm_expr, 0);