Allow limited extended asm at toplevel [PR41045]

Message ID ZyXT+R87NKh7uJuZ@tucnak
State New
Headers
Series Allow limited extended asm at toplevel [PR41045] |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_gcc_build--master-arm success Build passed
linaro-tcwg-bot/tcwg_gcc_build--master-aarch64 success Build passed
linaro-tcwg-bot/tcwg_gcc_check--master-arm success Test passed
linaro-tcwg-bot/tcwg_gcc_check--master-aarch64 success Test passed

Commit Message

Jakub Jelinek Nov. 2, 2024, 7:25 a.m. UTC
  Hi!

Here is a new version of the toplevel asm PR41045 patch, not yet
with the new constraints for symbol definitions (I intend to
deal with that in a follow-up).
Compared to the last patch, this one has some testcases added
and some ICEs related to "m"/"=m" fixed; for LTO streaming I've
added a sorry for now, because I think varpool/cgraph will want to add
at least a vector of references and definitions somewhere too and that will
need to be streamed as well.

I think for the definitions in the asm we could use say
void foo (void);
extern int var;
asm ("%0: nop; ret; %1: .zero 2" : : "@" (foo), "@" (var));
or
asm ("%0: nop; ret; %1: .zero 2" : "=@" (foo), "=@" (var));
(unsure if we should pretend they are the outputs or inputs).
For uses of symbols, e.g. "i" (foo), "i" (var) works fine with
%c0 %c1, though only for non-PIC.
"m" or "=m" also works fine on x86-64/ia32 with non-PIC, doesn't work
for PIC, doesn't work on aarch64 even with non-PIC.

So, I think the main question to decide is if we want to keep existing
standard constraints work as is even in toplevel asm and add new constraints
which will allow more stuff (like "i" which will just allow SYMBOL_REFs
even with -fPIC, or "m" which will allow a mem with any address whose
address could be added to e.g. constant initializers and printed),
or whether we want to change their meaning in toplevel asm and let "i"/"s"
there really accept SYMBOL_REFs no matter if -fPIC or not, and "m" accept
the constant addresses of MEMs no matter what the backend chooses are
acceptable.  Because at toplevel, we really can't reload the addresses into
registers to make the constraint be satisfied.

Note, even the c modifier as used in %c0 etc. isn't flaw-less, it prints
the value without anything added to it only if it is CONSTANT_ADDRESS_P
(so e.g. symbol in "i"/"s"), but otherwise it just attempts to print it
as machine specific modifier, so either one unsupported, or on x86_64/ia32
requires a comparison (which will never be satisfied in toplevel).

Bootstrapped/regtested on x86_64-linux and i686-linux.

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

	PR c/41045
gcc/
	* output.h (insn_noperands): Declare.
	* final.cc (insn_noperands): No longer static.
	* varasm.cc (assemble_asm): Handle ASM_EXPR.
	* lto-streamer-out.cc (lto_output_toplevel_asms): Add sorry_at
	for non-STRING_CST toplevel asm for now.
	* 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/testsuite/
	* c-c++-common/toplevel-asm-1.c: New test.
	* c-c++-common/toplevel-asm-2.c: New test.
	* c-c++-common/toplevel-asm-3.c: New test.


	Jakub
  

Comments

Jakub Jelinek Nov. 4, 2024, 10:31 a.m. UTC | #1
On Sat, Nov 02, 2024 at 08:25:45AM +0100, Jakub Jelinek wrote:
> So, I think the main question to decide is if we want to keep existing
> standard constraints work as is even in toplevel asm and add new constraints
> which will allow more stuff (like "i" which will just allow SYMBOL_REFs
> even with -fPIC, or "m" which will allow a mem with any address whose
> address could be added to e.g. constant initializers and printed),
> or whether we want to change their meaning in toplevel asm and let "i"/"s"
> there really accept SYMBOL_REFs no matter if -fPIC or not, and "m" accept
> the constant addresses of MEMs no matter what the backend chooses are
> acceptable.  Because at toplevel, we really can't reload the addresses into
> registers to make the constraint be satisfied.

A third option because we are really almost out of usable generic constraint
letters, what remains are just some punctuation, would be a new constraint
modifier (like we have =+%&) which would change the meaning of selected
constraints, "i", "s", "m" at least, perhaps some others, making them work
similarly to the original, but with some changes to what exactly they accept
and what they reject, so "i"/"s"/"m" could be used in toplevel asm even
with -fpic, even if a target requires address of a memory reference in
registers etc.

	Jakub
  
Richard Biener Nov. 18, 2024, 2:19 p.m. UTC | #2
On Sat, 2 Nov 2024, Jakub Jelinek wrote:

> Hi!
> 
> Here is a new version of the toplevel asm PR41045 patch, not yet
> with the new constraints for symbol definitions (I intend to
> deal with that in a follow-up).
> Compared to the last patch, this one has some testcases added
> and some ICEs related to "m"/"=m" fixed; for LTO streaming I've
> added a sorry for now, because I think varpool/cgraph will want to add
> at least a vector of references and definitions somewhere too and that will
> need to be streamed as well.
> 
> I think for the definitions in the asm we could use say
> void foo (void);
> extern int var;
> asm ("%0: nop; ret; %1: .zero 2" : : "@" (foo), "@" (var));
> or
> asm ("%0: nop; ret; %1: .zero 2" : "=@" (foo), "=@" (var));
> (unsure if we should pretend they are the outputs or inputs).
> For uses of symbols, e.g. "i" (foo), "i" (var) works fine with
> %c0 %c1, though only for non-PIC.
> "m" or "=m" also works fine on x86-64/ia32 with non-PIC, doesn't work
> for PIC, doesn't work on aarch64 even with non-PIC.
> 
> So, I think the main question to decide is if we want to keep existing
> standard constraints work as is even in toplevel asm and add new constraints
> which will allow more stuff (like "i" which will just allow SYMBOL_REFs
> even with -fPIC, or "m" which will allow a mem with any address whose
> address could be added to e.g. constant initializers and printed),
> or whether we want to change their meaning in toplevel asm and let "i"/"s"
> there really accept SYMBOL_REFs no matter if -fPIC or not, and "m" accept
> the constant addresses of MEMs no matter what the backend chooses are
> acceptable.  Because at toplevel, we really can't reload the addresses into
> registers to make the constraint be satisfied.
> 
> Note, even the c modifier as used in %c0 etc. isn't flaw-less, it prints
> the value without anything added to it only if it is CONSTANT_ADDRESS_P
> (so e.g. symbol in "i"/"s"), but otherwise it just attempts to print it
> as machine specific modifier, so either one unsupported, or on x86_64/ia32
> requires a comparison (which will never be satisfied in toplevel).
> 
> Bootstrapped/regtested on x86_64-linux and i686-linux.

This works for me, but I do not want to approve the FE changes.  I'll
note we should get this correct and future proof in case we need
future additions here.

Thanks,
Richard.

> 2024-11-02  Jakub Jelinek  <jakub@redhat.com>
> 
> 	PR c/41045
> gcc/
> 	* output.h (insn_noperands): Declare.
> 	* final.cc (insn_noperands): No longer static.
> 	* varasm.cc (assemble_asm): Handle ASM_EXPR.
> 	* lto-streamer-out.cc (lto_output_toplevel_asms): Add sorry_at
> 	for non-STRING_CST toplevel asm for now.
> 	* 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/testsuite/
> 	* c-c++-common/toplevel-asm-1.c: New test.
> 	* c-c++-common/toplevel-asm-2.c: New test.
> 	* c-c++-common/toplevel-asm-3.c: New test.
> 
> --- gcc/output.h.jj	2024-10-02 13:30:11.006426823 +0200
> +++ gcc/output.h	2024-11-01 14:59:28.563541848 +0100
> @@ -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-24 18:53:38.780079517 +0200
> +++ gcc/final.cc	2024-11-01 14:59:28.980535887 +0100
> @@ -150,7 +150,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-29 13:51:43.247898084 +0100
> +++ gcc/varasm.cc	2024-11-01 16:46:23.998237127 +0100
> @@ -62,6 +62,8 @@ along with GCC; see the file COPYING3.
>  #include "toplev.h"
>  #include "opts.h"
>  #include "asan.h"
> +#include "recog.h"
> +#include "gimple-expr.h"
>  
>  /* The (assembler) name of the first globally-visible object output.  */
>  extern GTY(()) const char *first_global_object_name;
> @@ -1667,16 +1669,167 @@ 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;
> +      int save_cse_not_expected = cse_not_expected;
> +      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;
> +      cse_not_expected = 1;
> +      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);
> +	  if (!is_gimple_addressable (output))
> +	    {
> +	      error ("output number %d not directly addressable", i);
> +	      goto done;
> +	    }
> +	  ops[i] = expand_expr (build_fold_addr_expr (output), NULL_RTX,
> +				VOIDmode, EXPAND_INITIALIZER);
> +	  ops[i] = gen_rtx_MEM (TYPE_MODE (TREE_TYPE (output)), ops[i]);
> +
> +	  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);
> +	  if (allows_mem && is_gimple_addressable (input))
> +	    {
> +	      ops[i + noutputs]
> +		= expand_expr (build_fold_addr_expr (input), NULL_RTX,
> +			       VOIDmode, EXPAND_INITIALIZER);
> +	      ops[i + noutputs] = gen_rtx_MEM (TYPE_MODE (TREE_TYPE (input)),
> +					       ops[i + noutputs]);
> +	    }
> +	  else
> +	    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;
> +      cse_not_expected = save_cse_not_expected;
> +    }
>  }
>  
>  /* Write the address of the entity given by SYMBOL to SEC.  */
> --- gcc/lto-streamer-out.cc.jj	2024-10-25 10:00:29.489767556 +0200
> +++ gcc/lto-streamer-out.cc	2024-11-01 17:35:28.700127715 +0100
> @@ -2543,6 +2543,13 @@ lto_output_toplevel_asms (void)
>  
>    for (can = symtab->first_asm_symbol (); can; can = can->next)
>      {
> +      if (TREE_CODE (can->asm_str) != STRING_CST)
> +	{
> +	  sorry_at (EXPR_LOCATION (can->asm_str),
> +		    "LTO streaming of toplevel extended %<asm%> "
> +		    "unimplemented");
> +	  continue;
> +	}
>        streamer_write_string_cst (ob, ob->main_stream, can->asm_str);
>        streamer_write_hwi (ob, can->order);
>      }
> --- gcc/doc/extend.texi.jj	2024-11-01 11:49:46.722734014 +0100
> +++ gcc/doc/extend.texi	2024-11-01 14:59:28.985535816 +0100
> @@ -10839,8 +10839,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.
> @@ -10878,6 +10878,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
> @@ -10922,25 +10924,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-31 21:17:06.861021154 +0100
> +++ gcc/c/c-parser.cc	2024-11-01 14:59:29.000535601 +0100
> @@ -1660,6 +1660,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);
> @@ -3071,7 +3072,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-31 21:17:06.909020481 +0100
> +++ gcc/c/c-typeck.cc	2024-11-01 14:59:29.021535301 +0100
> @@ -12171,10 +12171,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;
>      }
>  
> @@ -12214,10 +12241,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-25 10:00:29.409768701 +0200
> +++ gcc/cp/cp-tree.h	2024-11-01 14:59:29.023535272 +0100
> @@ -7854,7 +7854,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-25 10:00:29.427768443 +0200
> +++ gcc/cp/parser.cc	2024-11-01 14:59:29.036535086 +0100
> @@ -23112,7 +23112,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)))
>      {
> @@ -23166,13 +23165,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 `::'.  */
> @@ -23218,7 +23219,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)
>  	    {
> @@ -23229,8 +23231,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-29 13:51:33.257037334 +0100
> +++ gcc/cp/semantics.cc	2024-11-01 14:59:29.039535043 +0100
> @@ -2137,12 +2137,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;
> @@ -2216,10 +2217,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;
>  	}
>  
> @@ -2284,10 +2320,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;
>  	}
>      }
> @@ -2297,6 +2378,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-11-01 11:57:23.900128987 +0100
> +++ gcc/cp/pt.cc	2024-11-01 14:59:29.047534929 +0100
> @@ -18982,7 +18982,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);
> --- gcc/testsuite/c-c++-common/toplevel-asm-1.c.jj	2024-11-01 15:09:46.209708353 +0100
> +++ gcc/testsuite/c-c++-common/toplevel-asm-1.c	2024-11-01 19:25:47.512567789 +0100
> @@ -0,0 +1,25 @@
> +/* PR c/41045 */
> +/* { dg-do compile } */
> +/* { dg-options "-O0" } */
> +/* { dg-additional-options "-fno-pie" { target pie } } */
> +
> +struct S { char a; long long b; int c; };
> +enum E { E0, E1 = sizeof (struct S) + 15 };
> +int v[42];
> +void foo (void) {}
> +
> +asm ("# %0 %1 %2 %c3 %c4 %5 %% %="
> +     :: "i" (sizeof (struct S)),
> +	"i" (__builtin_offsetof (struct S, c)),
> +	"i" (E1),
> +	"s" (foo),
> +	"i" (v),
> +/* Not all targets can satisfy "m" even in non-pic code.  */
> +#if !defined(__i386__) && !defined(__x86_64__)
> +	"s" (v));
> +#else
> +	"m" (v));
> +asm ("# %0 %1"
> +     : "=m" (v[16])
> +     : "m" (v[41]));
> +#endif
> --- gcc/testsuite/c-c++-common/toplevel-asm-2.c.jj	2024-11-01 17:33:35.173750585 +0100
> +++ gcc/testsuite/c-c++-common/toplevel-asm-2.c	2024-11-01 17:52:37.657418058 +0100
> @@ -0,0 +1,21 @@
> +/* PR c/41045 */
> +/* { dg-do compile } */
> +/* { dg-options "-O0" } */
> +/* { dg-additional-options "-fno-pie" { target pie } } */
> +
> +int v[42], w[42], x;
> +void l1 (void);
> +
> +asm ("# %0" : "=m" (32));		/* { dg-error "lvalue required in 'asm' statement" } */
> +asm ("# %0" : "=m" (v) : "0" (v));	/* { dg-warning "matching constraint does not allow a register" } */
> +asm ("# %0" : : "m" (v), "0" (v));	/* { dg-error "matching constraint references invalid operand number" } */
> +asm ("# %0" :: "i" (0) : "cc");		/* { dg-error "expected '\\\)' before ':' token" } */
> +asm ("# %0" : : "i" (0) :: l1);		/* { dg-error "expected '\\\)' before '::?' token" } */
> +asm ("# %0" : "=r" (x));		/* { dg-error "invalid constraint outside of a function" } */
> +asm ("# %0" : "=m" (x++));		/* { dg-error "lvalue required in 'asm' statement" } */
> +asm ("# %0" : "=m" (v[x]));		/* { dg-error "output operand outside of a function is not constant" } */
> +asm ("# %0" :: "r" (x));		/* { dg-error "invalid constraint outside of a function" } */
> +asm ("# %0" : : "m" (x++));		/* { dg-error "side-effects in input operand outside of a function" } */
> +asm ("# %0" : : "m" (v[x]));		/* { dg-error "input operand outside of a function is not constant" } */
> +asm ("# %0" : : "i" (v[x]));		/* { dg-error "input operand outside of a function is not constant" } */
> +asm ("# %0" : : "i" (x++));		/* { dg-error "side-effects in input operand outside of a function" } */
> --- gcc/testsuite/c-c++-common/toplevel-asm-3.c.jj	2024-11-01 17:45:33.482482325 +0100
> +++ gcc/testsuite/c-c++-common/toplevel-asm-3.c	2024-11-01 17:53:46.399435004 +0100
> @@ -0,0 +1,11 @@
> +/* PR c/41045 */
> +/* { dg-do compile } */
> +/* { dg-options "-O0" } */
> +/* { dg-additional-options "-fno-pie" { target pie } } */
> +
> +int v[42], w[42], x;
> +
> +asm ("# %0" : "+m" (v));		/* { dg-error "'\\\+' in output operand outside of a function" } */
> +asm ("# %0" : "=&m" (v));		/* { dg-error "'&' in output operand outside of a function" } */
> +asm ("# %0, %1" : "=%m" (v), "=m" (w));	/* { dg-error "'%' in output operand outside of a function" } */
> +asm ("# %0, %1" : : "%m" (v), "m" (w));	/* { dg-error "'%' in input operand outside of a function" } */
> 
> 	Jakub
> 
>
  
Joseph Myers Nov. 21, 2024, 9:32 p.m. UTC | #3
On Sat, 2 Nov 2024, Jakub Jelinek wrote:

> +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.

Just ' in Texinfo, not %'.

> @@ -3071,7 +3072,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;

The syntax comment above this function needs updating.

The C front-end changes are OK with that fix.
  

Patch

--- gcc/output.h.jj	2024-10-02 13:30:11.006426823 +0200
+++ gcc/output.h	2024-11-01 14:59:28.563541848 +0100
@@ -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-24 18:53:38.780079517 +0200
+++ gcc/final.cc	2024-11-01 14:59:28.980535887 +0100
@@ -150,7 +150,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-29 13:51:43.247898084 +0100
+++ gcc/varasm.cc	2024-11-01 16:46:23.998237127 +0100
@@ -62,6 +62,8 @@  along with GCC; see the file COPYING3.
 #include "toplev.h"
 #include "opts.h"
 #include "asan.h"
+#include "recog.h"
+#include "gimple-expr.h"
 
 /* The (assembler) name of the first globally-visible object output.  */
 extern GTY(()) const char *first_global_object_name;
@@ -1667,16 +1669,167 @@  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;
+      int save_cse_not_expected = cse_not_expected;
+      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;
+      cse_not_expected = 1;
+      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);
+	  if (!is_gimple_addressable (output))
+	    {
+	      error ("output number %d not directly addressable", i);
+	      goto done;
+	    }
+	  ops[i] = expand_expr (build_fold_addr_expr (output), NULL_RTX,
+				VOIDmode, EXPAND_INITIALIZER);
+	  ops[i] = gen_rtx_MEM (TYPE_MODE (TREE_TYPE (output)), ops[i]);
+
+	  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);
+	  if (allows_mem && is_gimple_addressable (input))
+	    {
+	      ops[i + noutputs]
+		= expand_expr (build_fold_addr_expr (input), NULL_RTX,
+			       VOIDmode, EXPAND_INITIALIZER);
+	      ops[i + noutputs] = gen_rtx_MEM (TYPE_MODE (TREE_TYPE (input)),
+					       ops[i + noutputs]);
+	    }
+	  else
+	    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;
+      cse_not_expected = save_cse_not_expected;
+    }
 }
 
 /* Write the address of the entity given by SYMBOL to SEC.  */
--- gcc/lto-streamer-out.cc.jj	2024-10-25 10:00:29.489767556 +0200
+++ gcc/lto-streamer-out.cc	2024-11-01 17:35:28.700127715 +0100
@@ -2543,6 +2543,13 @@  lto_output_toplevel_asms (void)
 
   for (can = symtab->first_asm_symbol (); can; can = can->next)
     {
+      if (TREE_CODE (can->asm_str) != STRING_CST)
+	{
+	  sorry_at (EXPR_LOCATION (can->asm_str),
+		    "LTO streaming of toplevel extended %<asm%> "
+		    "unimplemented");
+	  continue;
+	}
       streamer_write_string_cst (ob, ob->main_stream, can->asm_str);
       streamer_write_hwi (ob, can->order);
     }
--- gcc/doc/extend.texi.jj	2024-11-01 11:49:46.722734014 +0100
+++ gcc/doc/extend.texi	2024-11-01 14:59:28.985535816 +0100
@@ -10839,8 +10839,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.
@@ -10878,6 +10878,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
@@ -10922,25 +10924,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-31 21:17:06.861021154 +0100
+++ gcc/c/c-parser.cc	2024-11-01 14:59:29.000535601 +0100
@@ -1660,6 +1660,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);
@@ -3071,7 +3072,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-31 21:17:06.909020481 +0100
+++ gcc/c/c-typeck.cc	2024-11-01 14:59:29.021535301 +0100
@@ -12171,10 +12171,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;
     }
 
@@ -12214,10 +12241,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-25 10:00:29.409768701 +0200
+++ gcc/cp/cp-tree.h	2024-11-01 14:59:29.023535272 +0100
@@ -7854,7 +7854,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-25 10:00:29.427768443 +0200
+++ gcc/cp/parser.cc	2024-11-01 14:59:29.036535086 +0100
@@ -23112,7 +23112,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)))
     {
@@ -23166,13 +23165,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 `::'.  */
@@ -23218,7 +23219,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)
 	    {
@@ -23229,8 +23231,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-29 13:51:33.257037334 +0100
+++ gcc/cp/semantics.cc	2024-11-01 14:59:29.039535043 +0100
@@ -2137,12 +2137,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;
@@ -2216,10 +2217,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;
 	}
 
@@ -2284,10 +2320,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;
 	}
     }
@@ -2297,6 +2378,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-11-01 11:57:23.900128987 +0100
+++ gcc/cp/pt.cc	2024-11-01 14:59:29.047534929 +0100
@@ -18982,7 +18982,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);
--- gcc/testsuite/c-c++-common/toplevel-asm-1.c.jj	2024-11-01 15:09:46.209708353 +0100
+++ gcc/testsuite/c-c++-common/toplevel-asm-1.c	2024-11-01 19:25:47.512567789 +0100
@@ -0,0 +1,25 @@ 
+/* PR c/41045 */
+/* { dg-do compile } */
+/* { dg-options "-O0" } */
+/* { dg-additional-options "-fno-pie" { target pie } } */
+
+struct S { char a; long long b; int c; };
+enum E { E0, E1 = sizeof (struct S) + 15 };
+int v[42];
+void foo (void) {}
+
+asm ("# %0 %1 %2 %c3 %c4 %5 %% %="
+     :: "i" (sizeof (struct S)),
+	"i" (__builtin_offsetof (struct S, c)),
+	"i" (E1),
+	"s" (foo),
+	"i" (v),
+/* Not all targets can satisfy "m" even in non-pic code.  */
+#if !defined(__i386__) && !defined(__x86_64__)
+	"s" (v));
+#else
+	"m" (v));
+asm ("# %0 %1"
+     : "=m" (v[16])
+     : "m" (v[41]));
+#endif
--- gcc/testsuite/c-c++-common/toplevel-asm-2.c.jj	2024-11-01 17:33:35.173750585 +0100
+++ gcc/testsuite/c-c++-common/toplevel-asm-2.c	2024-11-01 17:52:37.657418058 +0100
@@ -0,0 +1,21 @@ 
+/* PR c/41045 */
+/* { dg-do compile } */
+/* { dg-options "-O0" } */
+/* { dg-additional-options "-fno-pie" { target pie } } */
+
+int v[42], w[42], x;
+void l1 (void);
+
+asm ("# %0" : "=m" (32));		/* { dg-error "lvalue required in 'asm' statement" } */
+asm ("# %0" : "=m" (v) : "0" (v));	/* { dg-warning "matching constraint does not allow a register" } */
+asm ("# %0" : : "m" (v), "0" (v));	/* { dg-error "matching constraint references invalid operand number" } */
+asm ("# %0" :: "i" (0) : "cc");		/* { dg-error "expected '\\\)' before ':' token" } */
+asm ("# %0" : : "i" (0) :: l1);		/* { dg-error "expected '\\\)' before '::?' token" } */
+asm ("# %0" : "=r" (x));		/* { dg-error "invalid constraint outside of a function" } */
+asm ("# %0" : "=m" (x++));		/* { dg-error "lvalue required in 'asm' statement" } */
+asm ("# %0" : "=m" (v[x]));		/* { dg-error "output operand outside of a function is not constant" } */
+asm ("# %0" :: "r" (x));		/* { dg-error "invalid constraint outside of a function" } */
+asm ("# %0" : : "m" (x++));		/* { dg-error "side-effects in input operand outside of a function" } */
+asm ("# %0" : : "m" (v[x]));		/* { dg-error "input operand outside of a function is not constant" } */
+asm ("# %0" : : "i" (v[x]));		/* { dg-error "input operand outside of a function is not constant" } */
+asm ("# %0" : : "i" (x++));		/* { dg-error "side-effects in input operand outside of a function" } */
--- gcc/testsuite/c-c++-common/toplevel-asm-3.c.jj	2024-11-01 17:45:33.482482325 +0100
+++ gcc/testsuite/c-c++-common/toplevel-asm-3.c	2024-11-01 17:53:46.399435004 +0100
@@ -0,0 +1,11 @@ 
+/* PR c/41045 */
+/* { dg-do compile } */
+/* { dg-options "-O0" } */
+/* { dg-additional-options "-fno-pie" { target pie } } */
+
+int v[42], w[42], x;
+
+asm ("# %0" : "+m" (v));		/* { dg-error "'\\\+' in output operand outside of a function" } */
+asm ("# %0" : "=&m" (v));		/* { dg-error "'&' in output operand outside of a function" } */
+asm ("# %0, %1" : "=%m" (v), "=m" (w));	/* { dg-error "'%' in output operand outside of a function" } */
+asm ("# %0, %1" : : "%m" (v), "m" (w));	/* { dg-error "'%' in input operand outside of a function" } */