読者です 読者をやめる 読者になる 読者になる

Rustのasm!マクロが生成するもの

Rustのasm!インラインアセンブリを挿入するための組み込みマクロである。さて、このマクロは何を生成しているのか。

実は、RustのASTには、対応する具象構文を持たない特殊な抽象構文 InlineAsm が定義されている。

pub enum ExprKind {
    Box(P<Expr>),
    ...

    /// Output of the `asm!()` macro
    InlineAsm(P<InlineAsm>),

    ...
}

asm! の実装である expand_asm の末尾 でこれが生成されているのがわかる。

具象構文を持たない抽象構文をマクロが生成できるのはなぜか。これは expand_asm の戻り値 MacEager と戻り値型 MacResult を調べるとわかる。

まず MacResult は以下のように複数のメソッドを持っている。

pub trait MacResult {
    /// Create an expression.
    fn make_expr(self: Box<Self>) -> Option<P<ast::Expr>> {
        None
    }
    /// Create zero or more items.
    fn make_items(self: Box<Self>) -> Option<SmallVector<P<ast::Item>>> {
        None
    }

    ...
}

つまり、 MacResult は文脈に応じて異なる種類のASTにキャストできる値ということになる。

MacResult を実装しているのは、 (DummyResult を除くと) ParserAnyMacroMacEager のみである。

通常の macro_rules! マクロは ParserAnyMacro を返す。 ParserAnyMacro のメンバは以下のようになっている。

pub struct ParserAnyMacro<'a> {
    parser: Parser<'a>,
    ...
}

つまりパーサーの状態そのものを持っている。このパーサーは以下のように生成されている。

...
        match TokenTree::parse(cx, lhs_tt, arg) {
            Success(named_matches) => {
                ...
                let tts = transcribe(&cx.parse_sess.span_diagnostic, Some(named_matches), rhs);
                ...
                let mut p = Parser::new(cx.parse_sess(), tts, Some(directory), false);
                ...
                return Box::new(ParserAnyMacro {
                    parser: p,
                    ...
                })
            }
            ...
        }

つまり、マクロ実引数が規則にマッチしたら、マッチ結果からトークン列を生成する。このトークン列を初期状態としてパーサーを生成している。

ParserAnyMacroMacResult以下のように実装している。

macro_rules! expansions {
    ($($kind:ident: $ty:ty [$($vec:ident, $ty_elt:ty)*], $kind_name:expr, .$make:ident,
            $(.$fold:ident)*  $(lift .$fold_elt:ident)*,
            $(.$visit:ident)*  $(lift .$visit_elt:ident)*;)*) => {
        ...
        impl<'a> MacResult for ::ext::tt::macro_rules::ParserAnyMacro<'a> {
            $(fn $make(self: Box<::ext::tt::macro_rules::ParserAnyMacro<'a>>) -> Option<$ty> {
                Some(self.make(ExpansionKind::$kind).$make())
            })*
        }
    }
}

expansions! {
    Expr: P<ast::Expr> [], "expression", .make_expr, .fold_expr, .visit_expr;
    Pat: P<ast::Pat>   [], "pattern",    .make_pat,  .fold_pat,  .visit_pat;
    ...
}

つまり、 ParserAnyMacro は、 make_* が呼び出された時点ではじめてトークン列を構文解析し、ASTを返す。

一方、 MacEager以下のように定義されている。

macro_rules! make_MacEager {
    ( $( $fld:ident: $t:ty, )* ) => {
        ...
        pub struct MacEager {
            $(
                pub $fld: Option<$t>,
            )*
        }
        ...
    }
}

make_MacEager! {
    expr: P<ast::Expr>,
    pat: P<ast::Pat>,
    ...
}

つまり、 MacEagerトークン列をもたず、構文解析済みのASTをそのまま持っている。

ここで expand_asm に戻ると、これは InlineAsm をのせた MacEager を生成しているため、具象構文を経由せずに直接 InlineAsm を生成できていることになる。

    ...
    MacEager::expr(P(ast::Expr {
        id: ast::DUMMY_NODE_ID,
        node: ast::ExprKind::InlineAsm(P(ast::InlineAsm {
            asm: asm,
            asm_str_style: asm_str_style.unwrap(),
            outputs: outputs,
            inputs: inputs,
            clobbers: clobs,
            volatile: volatile,
            alignstack: alignstack,
            dialect: dialect,
            expn_id: expn_id,
        })),
        span: sp,
        attrs: ast::ThinVec::new(),
    }))

なお、 InlineAsm を含む構文木stringify! したときは、 pprust内の処理asm!(...) の形に復元される。