]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_builtin_macros/src/llvm_asm.rs
Auto merge of #86808 - fee1-dead:constify-1, r=oli-obk
[rust.git] / compiler / rustc_builtin_macros / src / llvm_asm.rs
1 // Llvm-style inline assembly support.
2 //
3 use State::*;
4
5 use rustc_ast as ast;
6 use rustc_ast::ptr::P;
7 use rustc_ast::token::{self, Token};
8 use rustc_ast::tokenstream::{self, TokenStream};
9 use rustc_ast::LlvmAsmDialect;
10 use rustc_errors::{struct_span_err, DiagnosticBuilder, PResult};
11 use rustc_expand::base::*;
12 use rustc_parse::parser::Parser;
13 use rustc_span::symbol::{kw, sym, Symbol};
14 use rustc_span::Span;
15
16 enum State {
17     Asm,
18     Outputs,
19     Inputs,
20     Clobbers,
21     Options,
22     StateNone,
23 }
24
25 impl State {
26     fn next(&self) -> State {
27         match *self {
28             Asm => Outputs,
29             Outputs => Inputs,
30             Inputs => Clobbers,
31             Clobbers => Options,
32             Options => StateNone,
33             StateNone => StateNone,
34         }
35     }
36 }
37
38 const OPTIONS: &[Symbol] = &[sym::volatile, sym::alignstack, sym::intel];
39
40 pub fn expand_llvm_asm<'cx>(
41     cx: &'cx mut ExtCtxt<'_>,
42     sp: Span,
43     tts: TokenStream,
44 ) -> Box<dyn MacResult + 'cx> {
45     let mut inline_asm = match parse_inline_asm(cx, sp, tts) {
46         Ok(Some(inline_asm)) => inline_asm,
47         Ok(None) => return DummyResult::any(sp),
48         Err(mut err) => {
49             err.emit();
50             return DummyResult::any(sp);
51         }
52     };
53
54     // If there are no outputs, the inline assembly is executed just for its side effects,
55     // so ensure that it is volatile
56     if inline_asm.outputs.is_empty() {
57         inline_asm.volatile = true;
58     }
59
60     MacEager::expr(P(ast::Expr {
61         id: ast::DUMMY_NODE_ID,
62         kind: ast::ExprKind::LlvmInlineAsm(P(inline_asm)),
63         span: cx.with_def_site_ctxt(sp),
64         attrs: ast::AttrVec::new(),
65         tokens: None,
66     }))
67 }
68
69 fn parse_asm_str<'a>(p: &mut Parser<'a>) -> PResult<'a, Symbol> {
70     match p.parse_str_lit() {
71         Ok(str_lit) => Ok(str_lit.symbol_unescaped),
72         Err(opt_lit) => {
73             let span = opt_lit.map_or(p.token.span, |lit| lit.span);
74             let mut err = p.sess.span_diagnostic.struct_span_err(span, "expected string literal");
75             err.span_label(span, "not a string literal");
76             Err(err)
77         }
78     }
79 }
80
81 fn parse_inline_asm<'a>(
82     cx: &mut ExtCtxt<'a>,
83     sp: Span,
84     tts: TokenStream,
85 ) -> Result<Option<ast::LlvmInlineAsm>, DiagnosticBuilder<'a>> {
86     // Split the tts before the first colon, to avoid `llvm_asm!("x": y)`  being
87     // parsed as `llvm_asm!(z)` with `z = "x": y` which is type ascription.
88     let first_colon = tts
89         .trees()
90         .position(|tt| {
91             matches!(
92                 tt,
93                 tokenstream::TokenTree::Token(Token { kind: token::Colon | token::ModSep, .. })
94             )
95         })
96         .unwrap_or(tts.len());
97     let mut p = cx.new_parser_from_tts(tts.trees().skip(first_colon).collect());
98     let mut asm = kw::Empty;
99     let mut asm_str_style = None;
100     let mut outputs = Vec::new();
101     let mut inputs = Vec::new();
102     let mut clobs = Vec::new();
103     let mut volatile = false;
104     let mut alignstack = false;
105     let mut dialect = LlvmAsmDialect::Att;
106
107     let mut state = Asm;
108
109     'statement: loop {
110         match state {
111             Asm => {
112                 if asm_str_style.is_some() {
113                     // If we already have a string with instructions,
114                     // ending up in Asm state again is an error.
115                     return Err(struct_span_err!(
116                         cx.sess.parse_sess.span_diagnostic,
117                         sp,
118                         E0660,
119                         "malformed inline assembly"
120                     ));
121                 }
122                 // Nested parser, stop before the first colon (see above).
123                 let mut p2 = cx.new_parser_from_tts(tts.trees().take(first_colon).collect());
124
125                 if p2.token == token::Eof {
126                     let mut err =
127                         cx.struct_span_err(sp, "macro requires a string literal as an argument");
128                     err.span_label(sp, "string literal required");
129                     return Err(err);
130                 }
131
132                 let expr = p2.parse_expr()?;
133                 let (s, style) =
134                     match expr_to_string(cx, expr, "inline assembly must be a string literal") {
135                         Some((s, st)) => (s, st),
136                         None => return Ok(None),
137                     };
138
139                 // This is most likely malformed.
140                 if p2.token != token::Eof {
141                     let mut extra_tts = p2.parse_all_token_trees()?;
142                     extra_tts.extend(tts.trees().skip(first_colon));
143                     p = cx.new_parser_from_tts(extra_tts.into_iter().collect());
144                 }
145
146                 asm = s;
147                 asm_str_style = Some(style);
148             }
149             Outputs => {
150                 while p.token != token::Eof && p.token != token::Colon && p.token != token::ModSep {
151                     if !outputs.is_empty() {
152                         p.eat(&token::Comma);
153                     }
154
155                     let constraint = parse_asm_str(&mut p)?;
156
157                     let span = p.prev_token.span;
158
159                     p.expect(&token::OpenDelim(token::Paren))?;
160                     let expr = p.parse_expr()?;
161                     p.expect(&token::CloseDelim(token::Paren))?;
162
163                     // Expands a read+write operand into two operands.
164                     //
165                     // Use '+' modifier when you want the same expression
166                     // to be both an input and an output at the same time.
167                     // It's the opposite of '=&' which means that the memory
168                     // cannot be shared with any other operand (usually when
169                     // a register is clobbered early.)
170                     let constraint_str = constraint.as_str();
171                     let mut ch = constraint_str.chars();
172                     let output = match ch.next() {
173                         Some('=') => None,
174                         Some('+') => Some(Symbol::intern(&format!("={}", ch.as_str()))),
175                         _ => {
176                             struct_span_err!(
177                                 cx.sess.parse_sess.span_diagnostic,
178                                 span,
179                                 E0661,
180                                 "output operand constraint lacks '=' or '+'"
181                             )
182                             .emit();
183                             None
184                         }
185                     };
186
187                     let is_rw = output.is_some();
188                     let is_indirect = constraint_str.contains('*');
189                     outputs.push(ast::LlvmInlineAsmOutput {
190                         constraint: output.unwrap_or(constraint),
191                         expr,
192                         is_rw,
193                         is_indirect,
194                     });
195                 }
196             }
197             Inputs => {
198                 while p.token != token::Eof && p.token != token::Colon && p.token != token::ModSep {
199                     if !inputs.is_empty() {
200                         p.eat(&token::Comma);
201                     }
202
203                     let constraint = parse_asm_str(&mut p)?;
204
205                     if constraint.as_str().starts_with('=') {
206                         struct_span_err!(
207                             cx.sess.parse_sess.span_diagnostic,
208                             p.prev_token.span,
209                             E0662,
210                             "input operand constraint contains '='"
211                         )
212                         .emit();
213                     } else if constraint.as_str().starts_with('+') {
214                         struct_span_err!(
215                             cx.sess.parse_sess.span_diagnostic,
216                             p.prev_token.span,
217                             E0663,
218                             "input operand constraint contains '+'"
219                         )
220                         .emit();
221                     }
222
223                     p.expect(&token::OpenDelim(token::Paren))?;
224                     let input = p.parse_expr()?;
225                     p.expect(&token::CloseDelim(token::Paren))?;
226
227                     inputs.push((constraint, input));
228                 }
229             }
230             Clobbers => {
231                 while p.token != token::Eof && p.token != token::Colon && p.token != token::ModSep {
232                     if !clobs.is_empty() {
233                         p.eat(&token::Comma);
234                     }
235
236                     let s = parse_asm_str(&mut p)?;
237
238                     if OPTIONS.iter().any(|&opt| s == opt) {
239                         cx.span_warn(p.prev_token.span, "expected a clobber, found an option");
240                     } else if s.as_str().starts_with('{') || s.as_str().ends_with('}') {
241                         struct_span_err!(
242                             cx.sess.parse_sess.span_diagnostic,
243                             p.prev_token.span,
244                             E0664,
245                             "clobber should not be surrounded by braces"
246                         )
247                         .emit();
248                     }
249
250                     clobs.push(s);
251                 }
252             }
253             Options => {
254                 let option = parse_asm_str(&mut p)?;
255
256                 if option == sym::volatile {
257                     // Indicates that the inline assembly has side effects
258                     // and must not be optimized out along with its outputs.
259                     volatile = true;
260                 } else if option == sym::alignstack {
261                     alignstack = true;
262                 } else if option == sym::intel {
263                     dialect = LlvmAsmDialect::Intel;
264                 } else {
265                     cx.span_warn(p.prev_token.span, "unrecognized option");
266                 }
267
268                 if p.token == token::Comma {
269                     p.eat(&token::Comma);
270                 }
271             }
272             StateNone => (),
273         }
274
275         loop {
276             // MOD_SEP is a double colon '::' without space in between.
277             // When encountered, the state must be advanced twice.
278             match (&p.token.kind, state.next(), state.next().next()) {
279                 (&token::Colon, StateNone, _) | (&token::ModSep, _, StateNone) => {
280                     p.bump();
281                     break 'statement;
282                 }
283                 (&token::Colon, st, _) | (&token::ModSep, _, st) => {
284                     p.bump();
285                     state = st;
286                 }
287                 (&token::Eof, ..) => break 'statement,
288                 _ => break,
289             }
290         }
291     }
292
293     Ok(Some(ast::LlvmInlineAsm {
294         asm,
295         asm_str_style: asm_str_style.unwrap(),
296         outputs,
297         inputs,
298         clobbers: clobs,
299         volatile,
300         alignstack,
301         dialect,
302     }))
303 }