LoginSignup
1
0

Rustのコンパイラと戯れる 〜 3日目

Last updated at Posted at 2024-04-12

はじめに

前回は、1モジュールの構造を眺めたけど、今回はクレート単位で構造を取り扱う方法を考える。

またせっかく抽出するので、RustAPIを抽出し、引数や戻り値の型から逆引きする足掛かりも考える。

方針

まず、クレート全体を周回するために、どのようにしてパラメータを渡すべきか。
これは簡単で、rustc_interface::interface::Configinputフィールドにクレートのルートモジュールを渡す。
クレートのルートモジュールはたいていsrc/lib.rsになる。

例えば、標準ライブラリのstdの収集は以下のように指定する。
ここで、sys_rootにはRustライブラリパスのプレフィックスが入っているものと想定している。

let sys_root = "...";
let root_crate = "std";

let file_path: PathBuf = [
    &sysroot, 
    "lib/rustlib/src/rust/library",
    root_crate, "src/lib.rs"
]
.iter().collect();

use rustc_interface::interface;
use rustc_session::config;

let rustc_config = interface::Config {
    input: config::Input::File(file_path),
    // 他は前回と同じ
};

上記のように指定してrustc_interface::interface::run_compiler関数を走らせると、クレート内の全ての構成要素を操作できるようになる。

APIの抽出

APIの関数識別子、引数及び戻り値の型を収集し、jsonフォーマットで出力するサンプルツールを作成した。

  • クレート名をソース上にベタ書きしてるため、stdのみ収集します。
  • 出力先も決め打ちで、カレントディレクトリのexpとなってます。

つらかったところ

型の完全名の解決

構造内で型定義を行っているノードにはrustc_hir::def_id::DefIdが振られており、この値をSrustc_middle::hir::map::Map(rustc_middle::ty::TyCtxt::mir関数で取得できる)のdef_path関数に渡すことでrustc_hir::definitions::DefPathからの値を取得することができる。

次いでDefPath::to_string_no_crate_verbose関数を呼ぶことで、コロン区切り(::)の完全名を取得することができる。

ただしDefPathの取得において幾つか制限が見受けられた。

  • self引数の場合、xxx::xxx::Traitとされてしまい、適切に完全名が取得できない。
    • DefIdを(LocalDefIdに変換した上で)TyCtxthir_node_by_def_id関数に渡して、rustc_hir::Nodeを取得し、ここから型情報を経由して、selfの実際の型解決を行なった。
  • プリミティブ型(i8とか)はDefIdが取得できない。
    • rustc_hir::def::ResPrimTyヴァリアントを経由して型名を取得できる。
  • いくつかの型(socketaddr_unなど)は型名を解決できなかった。
    • rustc_hir::PathSegmentidentフィールドの値を連結してよしとした。

コンパイルエラー表示

クレート単体でビルドするため、依存先が解決できずにコンパイルエラーが標準エラーに出力される。
出力はされるが構造の収集には全く問題がないため、ただの邪魔な存在である。

エラー表示は、rustc_errors::DiagCtxtを経由して、rustc_errors::emitter::DynEmitterで行われる。

出力を阻害する方法として、「Rust Compiler Development Guide」に記載されたサンプルのように、rustc_interface::interface::Configpsess_createdDiagCtxの初期値を渡すことで行うことができる。

やってないこと

  • 再エクスポートは拾っていない
    • std::string::Stringは含まれていない。本体はalloc::string::Stringのため。
  • std限定
    • 単なる手抜き

APIの逆引き

APIの逆引きのためにデータベースに投入する。
jsonをそのまま使ってインポートできるため、データベースとしてduckdbを選択した。

まず、以下の3つのテーブルを用意した。

  • prototype (関数名を保持するテーブル)
  • type_symbol (引数や戻り値の型を保持するテーブル)
  • type_ref (関数と型情報の関連付けを行うテーブル)
    • 引数か戻り値かを判定はenumで用意
create table prototype (
    id BIGINT not null primary key,
    symbol varchar(64) not null,
    qual_symbol varchar(256) not null
);

create table type_symbol (
    id BIGINT not null primary key,
    symbol varchar(64) not null,
    qual_symbol varchar(256) not null
);

create type type_kind as enum ('arg', 'return');

create or replace table type_ref (
    prototype_id BIGINT not null,
    type_id BIGINT not null,
    kind type_kind not null,
    constraint type_ref_pk primary key (prototype_id, type_id, kind)
);

次いで、データを投入する。

/* 関数プロトタイプを投入 */
insert into prototype
select 
    row_number() over (order by qual_symbol) as id,
    symbol, qual_symbol
from 'exp/prototype.json';

/* 型情報には重複して内容が含まれるため、一旦テンポラリなテーブルに入れる */
create or replace table temp_type as 
select 
    dense_rank() over (order by child.symbol, child.qual_symbol) as id,
    t.kind, t.parent.qual_symbol as qual_psymbol, unnest(child)
from 'exp/type.json' t
join prototype p on t.parent.qual_symbol = p.qual_symbol;

/* 重複を取り除いて型情報を投入する */
insert into type_symbol
select distinct id, symbol, qual_symbol from temp_type;

/* 関数プロトタイプと型情報を関連づける */
insert into type_ref by position
select distinct p.id as pid, t.id as tid, lower(kind)
from temp_type t
join prototype p on t.qual_psymbol = p.qual_symbol
;

jsonそのまま指定するだけなのでduckdb便利。
ネストしていても、unnest関数でカジュアルに崩せるのでduckdb便利。

例えば、以下のようなクエリを投げれば

/* 戻り値がStringな関数を列挙する */
select t.* from prototype t
where exists (
    select null from type_symbol ts1
    join type_ref ts2 on ts1.id = ts2.type_id
    where 
        ts2.prototype_id = t.id
        and ts2.kind = 'return'
        and ts1.symbol = 'String'
);
┌───────┬───────────────────┬────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│  id   │      symbol       │                                                qual_symbol                                                 │
│ int64 │      varchar      │                                                  varchar                                                   │
├───────┼───────────────────┼────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│  2539 │ into_string_lossy │ std::sys_common::wtf8::Wtf8Buf::into_string_lossy(std::sys_common::wtf8::Wtf8Buf) -> alloc::string::String │
│  2065 │ fill              │ std::panicking::FormatStringPayload::fill(std::panicking::FormatStringPayload) -> alloc::string::String    │
└───────┴───────────────────┴────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

な結果が得られる。

まとめ

DuckDBつよい (Rustどこいった・・・)。

4日目へ続く

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0