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