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