rustc Internal

はてブロから引き続きrustcの内部構造について。

ちなみに今の所全体像は全く見えていないのでこの文章はほぼ勘ですw

はてブロに書いたように、まずはここを参考にしてコードリーディングをしていきます。最終的な目標としてはコンパイラに何らかのhookをかけることとします。

driver.rs

rustcのコードはかなり階層的に別れていて初見だとわけわからないのですが、全体を統括しているcrateがlibrustc_driverで、まずはdriver.rsから始めましょう。

まずそれっぽそうなのが332行目のpub struct CompileController<'a>PhaseController<'a>型の要素に適当なクロージャ等をぶっこむことで途中でコンパイルを止めたりできそうです。実際PhaseController型は

pub struct PhaseController<'a> {
    pub stop: Compilation,
    // If true then the compiler will try to run the callback even if the phase
    // ends with an error. Note that this is not always possible.
    pub run_callback_on_error: bool,
    pub callback: Box<Fn(&mut CompileState) + 'a>,
}

impl<'a> PhaseController<'a> {
    pub fn basic() -> PhaseController<'a> {
        PhaseController {
            stop: Compilation::Continue,
            run_callback_on_error: false,
            callback: box |_| {},
        }
    }
}

こんな感じの実装です。おそらく、このcallback中でCompileState構造体を勝手に変更してしまえばいろいろできそうです。なおCompileState型は

pub struct CompileState<'a, 'tcx: 'a> {
    pub input: &'a Input,
    pub session: &'tcx Session,
    pub krate: Option<ast::Crate>,
    pub registry: Option<Registry<'a>>,
    pub cstore: Option<&'tcx CStore>,
    pub crate_name: Option<&'a str>,
    pub output_filenames: Option<&'a OutputFilenames>,
    pub out_dir: Option<&'a Path>,
    pub out_file: Option<&'a Path>,
    pub arenas: Option<&'tcx AllArenas<'tcx>>,
    pub expanded_crate: Option<&'a ast::Crate>,
    pub hir_crate: Option<&'a hir::Crate>,
    pub hir_map: Option<&'a hir_map::Map<'tcx>>,
    pub resolutions: Option<&'a Resolutions>,
    pub analysis: Option<&'a ty::CrateAnalysis>,
    pub tcx: Option<TyCtxt<'a, 'tcx, 'tcx>>,
}

だそうです。たしかにいじれそうな形をしています。

次の疑問は、どこでCompileControllerを設定するかです。CompileControllerを引数に取るpub fnとしてはphase_1_parse_input, phase_3_parse_inputなどもありますが、これらを呼び出しているpub fn compile_inputがそれっぽいと思います。

lib.rs

そうしたら、pub fn compile_inputをどこで呼び出しているか見てみましょう。とりあえずlibrustc_driver中で眺めてみると、lib.rs内で呼ばれています(ちなみに余談ですがもしソース分割のためだけにcompile_inputpub fnにしているのだとしたらpub(crate)にしてほしいかなーと思いました。まあまだ全体像をつかめていないので単なる感想なのですが…)。呼んでいるのはfn run_compiler_implで、第二引数callbacks: &mut CompilerCalls<'a>,のmethodを呼ぶことでCompileControllerを作っています。

// 538行目
let control = callbacks.build_controller(&sess, &matches);

driver::compile_input(trans,
                      &sess,
                      &cstore,
                      &input_file_path,
                      &input,
                      &odir,
                      &ofile,
                      Some(plugins),
                      &control)

run_compiler_impl自体はpubでないですが、直上のpub fn run_compilerで呼ばれています。callbacksもここから引き継いでいますね。そしてrun_compilerはコード末尾にくっついているmain関数で呼ばれています。main???という感じですが、さらに気になるのはコマンドライン引数をそのままrun_compilerに渡しているところです。ということでrun_compiler_impl中でどうやってこの引数を評価しているかをまず見てみましょう。

run_compiler_impl中では、渡された引数をそのままhandle_options関数に渡しています。handle_options関数では、どうやら確かにrustcのオプションを解析しているみたいです。ですので、lib.rsのmain関数は、おそらくrustcのエントリポイントそれ自身のようです。

ということで、また戻ってきて、main中でCompilerCallsをどうしているかです。これは

pub fn main() {
    init_rustc_env_logger();
    let result = run(|| {
        let args = env::args_os().enumerate()
            .map(|(i, arg)| arg.into_string().unwrap_or_else(|arg| {
                early_error(ErrorOutputType::default(),
                            &format!("Argument {} is not valid Unicode: {:?}", i, arg))
            }))
            .collect::<Vec<_>>();
        run_compiler(&args,
                     &mut RustcDefaultCalls,
                     None,
                     None)
    });
    process::exit(result as i32);
}

コードを見ればわかるように、RustcDefaultCalls構造体の(空の)インスタンスを使用しています。RustcDefaultCalls構造体に対するCompilerCalls traitの実装も、実際にlib.rsの799行目から行われています。

結論

とりあえずこれを見た限り、一番簡単にコンパイラをカスタマイズするには上のmain関数をコピペし、RustcDefaultCallsに代わる、CompilerCalls traitを実装した構造体を渡してやればよい気がします。

次回、実践。

Comments

comments powered by Disqus