Skip to main content

hir_def/expr_store/lower/
asm.rs

1//! Lowering of inline assembly.
2use hir_expand::name::Name;
3use intern::Symbol;
4use rustc_hash::{FxHashMap, FxHashSet};
5use syntax::{
6    AstNode, AstPtr, AstToken, T,
7    ast::{self, HasName, IsString},
8};
9use tt::TextRange;
10
11use crate::{
12    expr_store::lower::{ExprCollector, FxIndexSet},
13    hir::{AsmOperand, AsmOptions, Expr, ExprId, InlineAsm, InlineAsmKind, InlineAsmRegOrRegClass},
14};
15
16impl ExprCollector<'_> {
17    pub(super) fn lower_inline_asm(
18        &mut self,
19        asm: ast::AsmExpr,
20        syntax_ptr: AstPtr<ast::Expr>,
21    ) -> ExprId {
22        let mut clobber_abis = FxIndexSet::default();
23        let mut operands = vec![];
24        let mut options = AsmOptions::empty();
25
26        let mut named_pos: FxHashMap<usize, Symbol> = Default::default();
27        let mut named_args: FxHashMap<Symbol, usize> = Default::default();
28        let mut reg_args: FxHashSet<usize> = Default::default();
29        for piece in asm.asm_pieces() {
30            if !self.check_cfg(&piece) {
31                continue;
32            }
33
34            let slot = operands.len();
35            let mut lower_reg = |reg: Option<ast::AsmRegSpec>| {
36                let reg = reg?;
37                if let Some(string) = reg.string_token() {
38                    reg_args.insert(slot);
39                    Some(InlineAsmRegOrRegClass::Reg(Symbol::intern(string.text())))
40                } else {
41                    reg.name_ref().map(|name_ref| {
42                        InlineAsmRegOrRegClass::RegClass(Symbol::intern(name_ref.text()))
43                    })
44                }
45            };
46
47            let op = match piece {
48                ast::AsmPiece::AsmClobberAbi(clobber_abi) => {
49                    if let Some(abi_name) = clobber_abi.string_token() {
50                        clobber_abis.insert(Symbol::intern(abi_name.text()));
51                    }
52                    continue;
53                }
54                ast::AsmPiece::AsmOptions(opt) => {
55                    opt.asm_options().for_each(|opt| {
56                        options |= match opt.syntax().first_token().map_or(T![$], |it| it.kind()) {
57                            T![att_syntax] => AsmOptions::ATT_SYNTAX,
58                            T![may_unwind] => AsmOptions::MAY_UNWIND,
59                            T![nomem] => AsmOptions::NOMEM,
60                            T![noreturn] => AsmOptions::NORETURN,
61                            T![nostack] => AsmOptions::NOSTACK,
62                            T![preserves_flags] => AsmOptions::PRESERVES_FLAGS,
63                            T![pure] => AsmOptions::PURE,
64                            T![raw] => AsmOptions::RAW,
65                            T![readonly] => AsmOptions::READONLY,
66                            _ => return,
67                        }
68                    });
69                    continue;
70                }
71                ast::AsmPiece::AsmOperandNamed(op) => {
72                    let name = op.name().map(|name| Symbol::intern(name.text()));
73                    if let Some(name) = &name {
74                        named_args.insert(name.clone(), slot);
75                        named_pos.insert(slot, name.clone());
76                    }
77                    let Some(op) = op.asm_operand() else { continue };
78                    (
79                        name.map(Name::new_symbol_root),
80                        match op {
81                            ast::AsmOperand::AsmRegOperand(op) => {
82                                let Some(dir_spec) = op.asm_dir_spec() else {
83                                    continue;
84                                };
85                                let Some(reg) = lower_reg(op.asm_reg_spec()) else {
86                                    continue;
87                                };
88                                if dir_spec.in_token().is_some() {
89                                    let expr = self.collect_expr_opt(
90                                        op.asm_operand_expr().and_then(|it| it.in_expr()),
91                                    );
92                                    AsmOperand::In { reg, expr }
93                                } else if dir_spec.out_token().is_some() {
94                                    let expr = op
95                                        .asm_operand_expr()
96                                        .and_then(|it| it.in_expr())
97                                        .filter(|it| !matches!(it, ast::Expr::UnderscoreExpr(_)))
98                                        .map(|expr| self.collect_expr(expr));
99                                    AsmOperand::Out { reg, expr, late: false }
100                                } else if dir_spec.lateout_token().is_some() {
101                                    let expr = op
102                                        .asm_operand_expr()
103                                        .and_then(|it| it.in_expr())
104                                        .filter(|it| !matches!(it, ast::Expr::UnderscoreExpr(_)))
105                                        .map(|expr| self.collect_expr(expr));
106
107                                    AsmOperand::Out { reg, expr, late: true }
108                                } else if dir_spec.inout_token().is_some() {
109                                    let Some(op_expr) = op.asm_operand_expr() else { continue };
110                                    let in_expr = self.collect_expr_opt(op_expr.in_expr());
111                                    match op_expr.fat_arrow_token().is_some() {
112                                        true => {
113                                            let out_expr = op_expr
114                                                .out_expr()
115                                                .filter(|it| {
116                                                    !matches!(it, ast::Expr::UnderscoreExpr(_))
117                                                })
118                                                .map(|expr| self.collect_expr(expr));
119
120                                            AsmOperand::SplitInOut {
121                                                reg,
122                                                in_expr,
123                                                out_expr,
124                                                late: false,
125                                            }
126                                        }
127                                        false => {
128                                            AsmOperand::InOut { reg, expr: in_expr, late: false }
129                                        }
130                                    }
131                                } else if dir_spec.inlateout_token().is_some() {
132                                    let Some(op_expr) = op.asm_operand_expr() else { continue };
133                                    let in_expr = self.collect_expr_opt(op_expr.in_expr());
134                                    match op_expr.fat_arrow_token().is_some() {
135                                        true => {
136                                            let out_expr = op_expr
137                                                .out_expr()
138                                                .filter(|it| {
139                                                    !matches!(it, ast::Expr::UnderscoreExpr(_))
140                                                })
141                                                .map(|expr| self.collect_expr(expr));
142
143                                            AsmOperand::SplitInOut {
144                                                reg,
145                                                in_expr,
146                                                out_expr,
147                                                late: true,
148                                            }
149                                        }
150                                        false => {
151                                            AsmOperand::InOut { reg, expr: in_expr, late: true }
152                                        }
153                                    }
154                                } else {
155                                    continue;
156                                }
157                            }
158                            ast::AsmOperand::AsmLabel(l) => {
159                                AsmOperand::Label(self.collect_block_opt(l.block_expr()))
160                            }
161                            ast::AsmOperand::AsmConst(c) => {
162                                AsmOperand::Const(self.collect_expr_opt(c.expr()))
163                            }
164                            ast::AsmOperand::AsmSym(s) => {
165                                let Some(path) = s.path().and_then(|p| {
166                                    self.lower_path(
167                                        p,
168                                        &mut ExprCollector::impl_trait_error_allocator,
169                                    )
170                                }) else {
171                                    continue;
172                                };
173                                AsmOperand::Sym(path)
174                            }
175                        },
176                    )
177                }
178            };
179            operands.push(op);
180        }
181
182        let mut mappings = vec![];
183        let mut curarg = 0;
184        if !options.contains(AsmOptions::RAW) {
185            // Don't treat raw asm as a format string.
186            asm.template()
187                .enumerate()
188                .filter_map(|(idx, it)| Some((idx, it.clone(), self.expand_macros_to_string(it)?)))
189                .for_each(|(idx, expr, (s, is_direct_literal))| {
190                    mappings.resize_with(idx + 1, Vec::default);
191                    let Ok(text) = s.value() else {
192                        return;
193                    };
194                    let mappings = &mut mappings[idx];
195                    let template_snippet = match expr {
196                        ast::Expr::Literal(literal) => match literal.kind() {
197                            ast::LiteralKind::String(s) => Some(s.text().to_owned()),
198                            _ => None,
199                        },
200                        _ => None,
201                    };
202                    let str_style = match s.quote_offsets() {
203                        Some(offsets) => {
204                            let raw = usize::from(offsets.quotes.0.len()) - 1;
205                            // subtract 1 for the `r` prefix
206                            (raw != 0).then(|| raw - 1)
207                        }
208                        None => None,
209                    };
210
211                    let mut parser = rustc_parse_format::Parser::new(
212                        &text,
213                        str_style,
214                        template_snippet,
215                        false,
216                        rustc_parse_format::ParseMode::InlineAsm,
217                    );
218                    parser.curarg = curarg;
219
220                    let mut unverified_pieces = Vec::new();
221                    while let Some(piece) = parser.next() {
222                        if !parser.errors.is_empty() {
223                            break;
224                        } else {
225                            unverified_pieces.push(piece);
226                        }
227                    }
228
229                    curarg = parser.curarg;
230
231                    let to_span = |inner_span: std::ops::Range<usize>| {
232                        is_direct_literal.then(|| {
233                            TextRange::new(
234                                inner_span.start.try_into().unwrap(),
235                                inner_span.end.try_into().unwrap(),
236                            )
237                        })
238                    };
239                    for piece in unverified_pieces {
240                        match piece {
241                            rustc_parse_format::Piece::Lit(_) => {}
242                            rustc_parse_format::Piece::NextArgument(arg) => {
243                                // let span = arg_spans.next();
244
245                                let (operand_idx, _name) = match arg.position {
246                                    rustc_parse_format::ArgumentIs(idx)
247                                    | rustc_parse_format::ArgumentImplicitlyIs(idx) => {
248                                        if idx >= operands.len()
249                                            || named_pos.contains_key(&idx)
250                                            || reg_args.contains(&idx)
251                                        {
252                                            (None, None)
253                                        } else {
254                                            (Some(idx), None)
255                                        }
256                                    }
257                                    rustc_parse_format::ArgumentNamed(name) => {
258                                        let name = Symbol::intern(name);
259                                        (
260                                            named_args.get(&name).copied(),
261                                            Some(Name::new_symbol_root(name)),
262                                        )
263                                    }
264                                };
265
266                                if let Some(operand_idx) = operand_idx
267                                    && let Some(position_span) = to_span(arg.position_span)
268                                {
269                                    mappings.push((position_span, operand_idx));
270                                }
271                            }
272                        }
273                    }
274                })
275        };
276
277        let kind = if asm.global_asm_token().is_some() {
278            InlineAsmKind::GlobalAsm
279        } else if asm.naked_asm_token().is_some() {
280            InlineAsmKind::NakedAsm
281        } else {
282            InlineAsmKind::Asm
283        };
284
285        let idx = self.alloc_expr(
286            Expr::InlineAsm(InlineAsm { operands: operands.into_boxed_slice(), options, kind }),
287            syntax_ptr,
288        );
289        self.store
290            .template_map
291            .get_or_insert_with(Default::default)
292            .asm_to_captures
293            .insert(idx, mappings);
294        idx
295    }
296}