rustcの内部関数の使い方メモ。

要件

  • rustup
  • nightlyのrustc
    • rustup が勝手に入れてくれる

準備

ツールチェイン

多分 rust-toolchain.toml を書くのが早くて楽なんじゃないか。

[toolchain]
channel = "nightly"
components = ["rust-src", "rustc-dev", "llvm-tools-preview"]

これをプロジェクトルート( Cargo.toml とかと同じ階層)に置く。そもそもnightlyでしか使えず、とても不安定なので、ツールチェインとかは年月日で固定するといいと思う。

rust-analyzer

nightly向けのインストールはする必要がある。確か rust-toolchain.toml があればツールチェインはよしなにやってくれると思う。けど一応書いておく。

rustup +nightly component add rust-analyzer

Cargo.toml に以下を追記する。

[package.metadata.rust-analyzer]
rustc_private = true

これで rustc_private に対してrust-analyzerの解析が効くようになる。

最初のプログラム

プログラムを書く。 lib.rs でも main.rs でもいいけど、一番上に以下を書く。

#![feature(rustc_private)]

このfeatureを記述することで rustc_private にアクセスできるようになる。

あとはよしなに、

extern crate rustc_borrowck;
extern crate rustc_driver;
extern crate rustc_interface;
extern crate rustc_middle

とか書いたりする。

コンパイルしてみる

とりあえずRustのプログラムをコンパイルする最初のプログラムを書きたい。

fn main() {
    let source = fs::read_to_string("main.rs").unwrap();
    let config = interface::Config {
        opts: config::Options {
            debuginfo: config::DebugInfo::Full,
            error_format: config::ErrorOutputType::Json {
                pretty: true,
                json_rendered: rustc_errors::emitter::HumanReadableErrorType::Default,
                color_config: rustc_errors::ColorConfig::Auto,
            },
            unstable_opts: config::UnstableOptions {
                polonius: config::Polonius::Legacy,
                ..Default::default()
            },
            ..Default::default()
        },
        input: config::Input::Str {
            name: FileName::Real(RealFileName::LocalPath(PathBuf::from("main.rs"))),
            input: source.to_owned(),
        },
        output_dir: None,
        output_file: None,
        file_loader: None,
        lint_caps: HashMap::default(),
        register_lints: None,
        override_queries: None,
        registry: rustc_driver::diagnostics_registry(),
        crate_cfg: vec![],
        crate_check_cfg: vec![],
        expanded_args: vec![],
        hash_untracked_state: None,
        ice_file: None,
        locale_resources: rustc_driver::DEFAULT_LOCALE_RESOURCES.to_owned(),
        make_codegen_backend: None,
        psess_created: None,
        using_internal_features: Arc::new(AtomicBool::new(true)),
    };
    interface::run_compiler(config, |compiler| {
        compiler.enter(|queries| {
            let Ok(mut gcx) = queries.global_ctxt() else {
                log::error!("error on fetching global context");
                return;
            };

            gcx.enter(|ctx| {
                for item_id in ctx.hir().items() {
                    // HIRのアイテムになんかできる
                    let item = ctx.hir().item(item_id);
                    // 他の処理...
                }
            });
        });
    });
}

オプションが多くて嫌になるけど、重要なのは input で、ここでファイル名とソースコードを突っ込める。 defaultでいいところはdefaultで。

コンパイラ内部に手を突っ込む時に、ICEとかで落ちないために using_internal_features: Arc::new(AtomicBool::new(true)) を指定していた気がする。もう忘れてしまった…… すぐにメモを取らないから。

interface::run_compiler でコンパイラが実行可能。その後にコンパイラの処理に入って、クエリからglobal context取ってきて、とか色々やる。私の場合はMIRを相手にすることが多いので、この辺はあんまりいじったことがなく、わからない。

続きは後日書く