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を相手にすることが多いので、この辺はあんまりいじったことがなく、わからない。
続きは後日書く