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