• Share
  • Email
  • Embed
  • Like
  • Save
  • Private Content
OpenJDK HotSpot C1Compiler Overview
 

OpenJDK HotSpot C1Compiler Overview

on

  • 1,975 views

OpenJDK HotSpot C1Compiler Overview 20111022

OpenJDK HotSpot C1Compiler Overview 20111022

Statistics

Views

Total Views
1,975
Views on SlideShare
1,955
Embed Views
20

Actions

Likes
1
Downloads
23
Comments
1

3 Embeds 20

http://paper.li 16
http://www.slashdocs.com 2
http://www.docshut.com 2

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel

11 of 1 previous next

  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
  • pdf版は見難いので、下記のURLのgithub pageでhtml版を公開しています。
    http://nothingcosmos.github.com/OpenJDKOverview/
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

    OpenJDK HotSpot C1Compiler Overview OpenJDK HotSpot C1Compiler Overview Document Transcript

    • OpenJDK HotSpot C1Compiler Overview version 1.0 Author nothingcosmos November 09, 2011
    • ContentsWelcome to OpenJDK Internalss documentation! 1 OpenJDK HotSpot Client Compiler Overview 1 タイトル 1 自己紹介 1 OpenJDK キーワード 1 HotSpotの見どころ1 1 HotSpotの見どころ2 1 本日紹介するC1 Compiler 2 C1コンパイラの構成 3 コンパイル前の動作 4 JITコンパイラ 4 JITコンパイラが呼ばれる仕組み 5 JITコンパイルの入出力 6 JITコンパイルする条件 6 JITコンパイルする際のしきい値 9 C1コンパイラの内部構造 10 大まかなコンパイルの概要 10 コンパイラ全体の制御 10 BytecodeからHIRへの変換 11 if_icmpXXの変換 12 invokeの変換 13 dependency 14 プロファイラ 18 HIR から LIR への変換 19 C1コンパイラのHIR最適化 20 eliminate_conditional_expressions 20 GlobalValueNumbering 21 EscapeAnalysis 22 C1コンパイラのLIR最適化 22 EdgeMoveOptimizer 22 ControlFlowOptimizer 22 sample code factIf 23 sample code factFor 28 参考文献 32Indices and tables 33
    • Welcome to OpenJDK Internalss documentation! Welcome to OpenJDK Internalss documentation! Contents: OpenJDK HotSpot Client Compiler Overview タイトル OpenJDK HotSpot Client Compiler Overview 第4回JVMソースコードリーディングの会 nothingcosmos<nothingcosmos@gmail.com> http://nothingcosmos.blog52.fc2.com/ http://nothingcosmos.wiki.fc2.com/ 自己紹介 HN:nothingcosmos 元コンパイラ屋のソフトウェアエンジニア 現在は金融系SIer 趣味で、LLVMを読んだり、OpenJDKを読んだり、UNIXv6を読んだりしています。 先週は、箱根でLionsCommentary on UNIX勉強会(2011秋合宿)へ参加してました。 OpenJDK キーワード • HotSpotコンパイラ • C1(Client)/C2(Server)コンパイラ • JIT • Adaptive Compilation • Deoptimize 脱最適化 • 中間表現/中間言語 HotSpotの見どころ1 一般ユーザからみたコンパイラの見どころ • ... Scalaユーザからみたコンパイラの見どころ • Scalaでは細かいオブジェクトをたくさん作るので、 EscapeAnalysisがあると全部レジスタやスタックに乗る という話を以前kmizuさんに聞いた記憶が... 毎回ヒープに割り付けないので、高速 HotSpotの見どころ2 私個人は、 • コンパイラ内部の中間言語構造とアーキテクチャ • 大した最適化してないのに速いコードを吐くHotSpot Client Compiler1
    • 本日紹介するC1 Compiler • 適応的コンパイル Adaptive Compilation • JITコンパイラ/脱最適化のコントロール • Profiling/Tracingの取得方法と活用方法 • HotSpot特有の最適化技術 • EscapeAnalysis/ClassHierarchyAnalysis • 複数アーキテクチャへの対応方法 • OpenJDK7の最適化のバグ本日紹介するC1 CompilerOpenJDKのHotSpot Glossary of Termsより抜粋 Fast, lightly optimizing bytecode compiler. Performs some value numbering, inlining, and class analysis. Uses a simple CFG-oriented SSA "high" IR, a machine-oriented "low" IR, a linear scan register allocation, and a template-style code generator. • ValueNumbering • 値番号付けというSSA形式を利用した最適化のアルゴリズムの名前. 冗長な式を削除する • inlining • インライン展開 • class analysis • CHA(ClassHierachyAnalysis). クラス解析 仮想関数呼出の呼び出し先を特定する際に活躍する • CFG-oriented SSA • CFG ControlFlowGraph • SSA StaticSingleAssignment form • IR • Intermediate Representation コンパイラ独自の中間表現 • linear scan register allocation2
    • C1コンパイラの構成 • リニアスキャンというレジスタ割り付けのアルゴリズム • template-style code generator • asmの生成はあまり頑張らない。LIRからシーケンシャルに生成C1コンパイラの構成hotspot/src/share/vm • c1 <-- C1コンパイラの本体 • compiler <-- コンパイラの抽象クラス • runtime <-- JVMのruntime部分hotspot/src/share/vm/c1C1コンパイラ 全体で36ksteptop5 c1_LinerScan 7700step LinerScanでレジスタ割り付け c1_LIR 4300step LIR(Low-Level IRの定義) c1_GraphBuilder 4200step BytecodeからHIRへの変換 c1_LIRGenerator 3500step HIRからLIRへの変換 c1_Instruction 3300step HIR(High-Level IR)の定義vm/c1/* c1_CFGPrinter.cpp <-- -XX:+PrintCFGToFile オプションを指定時、 c1_CFGPrinter.hpp 中間表現のHIRやLIRをxmlで出力。c1visualizerで解析する c1_Canonicalizer.cpp <-- HIRへ変換する際に正規化する c1_Canonicalizer.hpp c1_GraphBuilderから呼ばれる c1_CodeStubs.hpp <-- LIRやAssemblerで挿入される、JVMのciXX/runtime向けのStub c1_Compilation.cpp <-- C1コンパイラのコントローラー Driver??? c1_Compilation.hpp c1_Compiler.cpp <-- C1コンパイラの本体 c1_Compiler.hpp c1_Defs.cpp <-- architecture依存の各種定義ファイル レジスタとか c1_Defs.hpp c1_FpuStackSim.hpp <-- architecture依存のFPUStackのシミュレータの定義ファイル c1_FrameMap.cpp <-- architecture依存のFrameMapや仮想レジスタやCallingConvension c1_FrameMap.hpp c1_GraphBuilder.cpp <-- BytecodeからHIRへの変換 c1_GraphBuilder.hpp 各種最適化も行う(inlining, devirtualize, canonicalize c1_IR.cpp <-- IRの定義 c1_IR.hpp HIR/LIR/BB/各種helperを統合したIRという名のDescripter c1_Instruction.cpp <-- HIRの定義や、IRクラスのUtility c1_Instruction.hpp c1_InstructionPrinter.cpp <-- HIRのprinter 見やすいように情報を絞って整形して表示する c1_InstructionPrinter.hpp c1_LIR.cpp <-- LIRの定義 c1_LIR.hpp c1_LIRAssembler.cpp <-- LIRからAsmのemitter兼helper Asmのコード生成 c1_LIRAssembler.hpp c1_LIRGenerator.cpp <-- HIRからLIRへの変換3
    • コンパイル前の動作 c1_LIRGenerator.hpp 命令選択、レジスタ割り付け、LIRレベルの最適化も行う。 c1_LinearScan.cpp <-- LinearScanレジスタ割り付け c1_LinearScan.hpp c1_MacroAssembler.hpp <-- architecture依存のAsm出力用マクロ(Assember向けpsuedo code) c1_Optimizer.cpp <-- HIR向け各種最適化 Eliminate (const expr|blocks|null checks) c1_Optimizer.hpp c1_Runtime1.cpp <-- C1コンパイラのRuntime JVM本体のruntimeとの橋渡し c1_Runtime1.hpp c1_ValueMap.cpp <-- HIR向け最適化 ValueNumberingの本体 c1_ValueMap.hpp c1_ValueSet.cpp <-- HIR向けADT @todo c1_ValueSet.hpp c1_ValueStack.cpp <-- HIR向けADT @todo c1_ValueStack.hpp c1_ValueType.cpp <-- C1コンパイラ内部のIR向け型定義 c1_ValueType.hpp c1_globals.cpp <-- C1コンパイラ向けのオプション定義 c1_globals.hpp※ architecture依存と書いたものは、hotspot/src/cpu/XXX/vm の下に本体がいる。※ architectureは、x86_32/x86_64 sparc zero があるコンパイル前の動作JITコンパイラJVMは、最初bytecodeをclassloaderが呼び出した後、インタプリタ実行を行う。インタプリタ実行中にプロファイルを行い、条件を満たしたらJITコンパイルするJITコンパイルは、コンパイラの抽象クラス経由で操作する。 コンパイラクラスは3種類ある.C1/C2/SharkC1コンパイラ -clientオプション指定時のコンパイラ コンパイル時間が短く、メモリ使用量もそこそこ。 大した最適化をしない割にそこそこ高速に動作するコード を生成するのが特徴 JVM間の比較では、ベンチマーク結果がそこそこ高い。C2コンパイラ -serverオプション指定時のコンパイラ。今回は扱わない。 コンパイル時間はそこそこ長く、C1より高速に動作するコードを生成する。 また、コンパイル時のメモリも 大きく消費する。 詳細は、vm/opto参照。C1とは中間言語が異なり、Idealと呼ばれる中間言語。Sharkコンパイラ 使い方はまだちゃんと調べてない。4
    • JITコンパイラが呼ばれる仕組み LLVMと連携してJITコンパイルを行う. JITコンパイラをC1/C2ではなく、LLVMを使うということ。JVMの制 御はそのまま。 Sharkは、method単位でBytecodeをBitcodeに変換したのち、 LLVMに渡してJITコンパイルする。 LLVMにBitcodeを渡す際に何も小細工しないので、脱仮想化とかEscapeAnalysisとかさっぱり LLVMのBitcod eにMetadataを埋め込んで、 (たとえば、このcallはこのメソッドに脱仮想化候補だとか、allocaはstack/regi ster割り付け可能だとか) LLVMのJITコンパイラ起動時、上記metadata用最適化パスをオプションで渡せば連 携できるはず。 いろいろと夢広がる。 現在は、対応アーキテクチャを増やすために使っている ex) ARM PowerPC PTX CBac kend LLVM 3.0 のReleaseNoteから、Sharkの連携やIcedTeaとの連携のことがかかれているので、 興味があるかた はLLVMのページへJITコンパイラが呼ばれる仕組みcompileBrokerがJITコンパイラを生成し、メソッド単位でコンパイルするcompiler::compile_method() compileBroker compiler/abstructCompiler compile_method(ciEnv*, ciMethod*, int entry_bci)条件を満たしたときにJITコンパイラを生成し、メソッド単位でJITコンパイルを行う。 • 条件を満たしたときに ... vm/runtime/compilationPolicy • JITコンパイラを生成 ... vm/compiler/compileBrokerJITコンパイラは、JVMがメモリを確保して、別スレッドでコンパイルブローカーに処理を移譲する。JVMTIを使うので、スレッドが切れていて、処理が追いにくい。また、スレッド並列で、インタプリタと並行してJITコンパイルは走るが、-Xbatchオプションを指定すると、JITコンパイル中にインタプリタ実行を停止することができる。compileBroker::compilation_init() // ------------------------------------------------------------------ // CompileBroker::compilation_init // // Initialize the Compilation object void CompileBroker::compilation_init() { _last_method_compiled[0] = 0; #ifndef SHARK // Set the interface to the current compiler(s). int c1_count = CompilationPolicy::policy()->compiler_count(CompLevel_simple); int c2_count = CompilationPolicy::policy()->compiler_count(CompLevel_full_optimization); #ifdef COMPILER1 if (c1_count > 0) { _compilers[0] = new Compiler(); } #endif // COMPILER1 #ifdef COMPILER2 if (c2_count > 0) { _compilers[1] = new C2Compiler(); } #endif // COMPILER2 #else // SHARK int c1_count = 0;5
    • JITコンパイルの入出力 int c2_count = 1; _compilers[1] = new SharkCompiler(); #endif // SHARKJITコンパイルの入出力JVMのJITコンパイラは、ciMethodクラスが入力compiler::compile_method() compiler/abstructCompiler compile_method(ciEnv*, ciMethod*, int entry_bci)JITコンパイラの出力の形式は複数存在する。 • method->codeの書き換え • もし書き換え対象のメソッドを今実行中だったら。。。JITコンパイルの入り口のメソッドCompileBroker::compile_method_base() void CompileBroker::compile_method_base(methodHandle method, int osr_bci, int comp_level, methodHandle hot_method, int hot_count, const char* comment, TRAPS) {JITコンパイルする条件JVMのインタプリタ実行中にprofileを行い、 下記に示すカウンタをカウントアップする。 • invocation count • メソッドの呼び出し回数をカウント • backward branch count • ループの実行回数をカウントinvocation countのカウントアップ:6
    • JITコンパイルの入出力 bytecodeInterpreter.cpp::BytecodeInterpreter::run() case method_entry: { THREAD->set_do_not_unlock(); // count invocations assert(initialized, "Interpreter not initialized"); if (_compiling) { if (ProfileInterpreter) { METHOD->increment_interpreter_invocation_count(); } INCR_INVOCATION_COUNT; if (INVOCATION_COUNT->reached_InvocationLimit()) { CALL_VM((void)InterpreterRuntime::frequency_counter_overflow(THREAD, NULL), handle_exception); // We no longer retry on a counter overflow // istate->set_msg(retry_method); // THREAD->clr_do_not_unlock(); // return; } SAFEPOINT; } if ((istate->_stack_base - istate->_stack_limit) != istate->method()->max_stack() + 1) { // initialize os::breakpoint(); }//memo frequency_counter_overflowでJITコンパイラを呼ぶはずbackward branch countのカウントアップ CASE(_goto): { int16_t offset = (int16_t)Bytes::get_Java_u2(pc + 1); address branch_pc = pc; UPDATE_PC(offset); DO_BACKEDGE_CHECKS(offset, branch_pc); CONTINUE; } CASE(_goto_w): { int32_t offset = Bytes::get_Java_u4(pc + 1); address branch_pc = pc; UPDATE_PC(offset); DO_BACKEDGE_CHECKS(offset, branch_pc); CONTINUE; } #define DO_BACKEDGE_CHECKS(skip, branch_pc) if ((skip) <= 0) { if (UseLoopCounter) { bool do_OSR = UseOnStackReplacement; BACKEDGE_COUNT->increment(); if (do_OSR) do_OSR = BACKEDGE_COUNT->reached_InvocationLimit(); if (do_OSR) { nmethod* osr_nmethod; OSR_REQUEST(osr_nmethod, branch_pc); if (osr_nmethod != NULL && osr_nmethod->osr_entry_bci() != InvalidOSREntryBci) { intptr_t* buf = SharedRuntime::OSR_migration_begin(THREAD); 7
    • JITコンパイルの入出力 istate->set_msg(do_osr); istate->set_osr_buf((address)buf); istate->set_osr_entry(osr_nmethod->osr_entry()); return; } } } /* UseCompiler ... */ INCR_INVOCATION_COUNT; SAFEPOINT; }インタプリタがprofileのカウンタを更新する様子gdb stack trace Breakpoint 5, NonTieredCompPolicy::reset_counter_for_invocation_event (this=0x807a738, m=...) at /home/elise/language/openjdk6/hotspot/src/share/vm/runtime/compilationPolicy.cpp:189 189 m->invocation_counter()->set_carry(); (gdb) up #1 0x004cbdd5 in SimpleCompPolicy::method_invocation_event (this=0x807a738, m=..., __the_th at /home/elise/language/openjdk6/hotspot/src/share/vm/runtime/compilationPolicy.cpp:394 394 reset_counter_for_invocation_event(m); (gdb) #2 0x004cba19 in NonTieredCompPolicy::event (this=0x807a738, method=..., inlinee=..., branc comp_level=CompLevel_none, __the_thread__=0x806d000) at /home/elise/language/openjdk6/hotspot/src/share/vm/runtime/compilationPolicy.cpp:323 323 method_invocation_event(method, CHECK_NULL); (gdb) #3 0x005dafd2 in InterpreterRuntime::frequency_counter_overflow_inner (thread=0x806d000, br at /home/elise/language/openjdk6/hotspot/src/share/vm/interpreter/interpreterRuntime.cpp:854 854 nmethod* osr_nm = CompilationPolicy::policy()->event(method, method, branch_bci, bci, CompLevel_none, (gdb) #4 0x005daced in InterpreterRuntime::frequency_counter_overflow (thread=0x806d000, branch_b at /home/elise/language/openjdk6/hotspot/src/share/vm/interpreter/interpreterRuntime.cpp:826 826 nmethod* nm = frequency_counter_overflow_inner(thread, branch_bcp); (gdb) #5 0xb5fef92c in ?? () (gdb)//interpreterのgoto命令実行時にカウントアップ//memo OSR_REQESTマクロの中で、frequency_counter_overflow()を呼び出しJITコンパイラが呼ばれるのは、2つのケース対象のメソッドの呼び出し回数が規定回数以上になった場合 • 通常のJITコンパイル。メソッド単位でJITコンパイルする。 次回呼ばれた際にインタプリタではなく、JITコンパイルしたコードを実行する対象のループのバックエッジの通過回数が規定回数以上になった場合 • 現在実行中のメソッドをJITコンパイルする。 現在実行中のメソッドなので、インタプリタからJITしたコードへ遷移するのが難しい インタプリタ実行中からJITしたコードへ遷移する技術をOnStackReplacementと呼ぶ。 おもにsafepointを設けて(分岐の前や、分岐の集合地点) インタプリタ実行中のFrameとJITコンパイルしたコードのFrameを記録、計算し、 遷移できるようにテーブルを作成するはず//OnStackReplacementは、runtime/sharedRuntime.cpp::SharedRuntime::OSR_migration_begin()//詳細は"コンパイラとバーチャルマシン"っていう書籍が図入りで説明している8
    • JITコンパイルする際のしきい値JITコンパイルする際のしきい値JITコンパイルのしきい値は、clientコンパイラの場合、2000回, serverコンパイラの場合、15000回のはず。JITコンパイルのしきい値は、CompLevel で計算方法が異なるらしいCompLevel_simple or CompLevel_full_optimization or CompLevel_limited_profile or CompLevel_full_profileオプション: • -XX:CompileThreshold=xxxデフォルト: • Tier3CompileThreshold 2000 • Tier4CompileThreshold 15000compile_methodが呼ばれた際のstack tracegdb stack trace // topからdownしていきます #6 0xb5fef92c in ?? () <-- template intepreter経由なのでこれ以上終えない (gdb) down #5 0x005daced in InterpreterRuntime::frequency_counter_overflow (thread=0x806d000, branch_b at /home/elise/language/openjdk6/hotspot/src/share/vm/interpreter/interpreterRuntime.cpp:826 826 nmethod* nm = frequency_counter_overflow_inner(thread, branch_bcp); (gdb) #4 0x005dafd2 in InterpreterRuntime::frequency_counter_overflow_inner (thread=0x806d000, br at /home/elise/language/openjdk6/hotspot/src/share/vm/interpreter/interpreterRuntime.cpp:854 854 nmethod* osr_nm = CompilationPolicy::policy()->event(method, method, branch_bci, bci, CompLevel_none, (gdb) #3 0x004cba19 in NonTieredCompPolicy::event (this=0x807a738, method=..., inlinee=..., branc comp_level=CompLevel_none, __the_thread__=0x806d000) at /home/elise/language/openjdk6/hotspot/src/share/vm/runtime/compilationPolicy.cpp:323 323 method_invocation_event(method, CHECK_NULL); (gdb) #2 0x004cbe50 in SimpleCompPolicy::method_invocation_event (this=0x807a738, m=..., __the_th at /home/elise/language/openjdk6/hotspot/src/share/vm/runtime/compilationPolicy.cpp:402 402 m, hot_count, comment, CHECK); (gdb) #1 0x004cf34e in CompileBroker::compile_method (method=..., osr_bci=-1, comp_level=1, hot_m comment=0x94b7f6 "count", __the_thread__=0x806d000) at /home/elise/language/openjdk6/hotspot/src/share/vm/compiler/compileBroker.cpp:1084 1084 compile_method_base(method, osr_bci, comp_level, hot_method, hot_count, comment, CHECK_0); (gdb) #0 CompileBroker::compile_method_base (method=..., osr_bci=-1, comp_level=1, hot_method=... comment=0x94b7f6 "count", __the_thread__=0x806d000) at /home/elise/language/openjdk6/hotspot/src/share/vm/compiler/compileBroker.cpp:840 840 if (!_initialized ) {InterpreterInvocationLimitとInterpreterBackwardBranchLimitの設定 void InvocationCounter::reinitialize(bool delay_overflow) { // define states guarantee((int)number_of_states <= (int)state_limit, "adjust number_of_state_bits"); def(wait_for_nothing, 0, do_nothing); if (delay_overflow) { def(wait_for_compile, 0, do_decay); } else { def(wait_for_compile, 0, dummy_invocation_counter_overflow); } InterpreterInvocationLimit = CompileThreshold << number_of_noncount_bits;9
    • C1コンパイラの内部構造 InterpreterProfileLimit = ((CompileThreshold * InterpreterProfilePercentage) / 100)<< number_of_noncount_bit // When methodData is collected, the backward branch limit is compared against a // methodData counter, rather than an InvocationCounter. In the former case, we // dont need the shift by number_of_noncount_bits, but we do need to adjust // the factor by which we scale the threshold. if (ProfileInterpreter) { InterpreterBackwardBranchLimit = (CompileThreshold * (OnStackReplacePercentage - InterpreterProfilePercen } else { InterpreterBackwardBranchLimit = ((CompileThreshold * OnStackReplacePercentage) / 100) << number_of_non }ちなみに、clientの場合InterpreterInvocationLimit = 12000InterpreterBackwardBranchLimit = 111960serverの場合InterpreterInvocationLimit = 80000InterpreterBackwardBranchLimit = 10700clientコンパイラのしきい値って、メソッド呼び出しが1500回で、OnStackReplacementが14000回じゃないの?C1コンパイラの内部構造大まかなコンパイルの概要method単位で、BytecodeからHIRへの変換HIRからLIRへの変換LIRからMachine codeへの変換コンパイラ全体の制御c1_Compiler.cpp コンパイルの入り口 // 入力はciMethond* method <-- bytecode method単位 void Compiler::compile_method(ciEnv* env, ciMethod* method, int entry_bci) compile_method()c1_Compilation.cpp コンパイラの全体制御 Compilation::compile_method() method()->break_at_execute() compile_java_method() install_code(frame_size) Compilation::compile_java_method() build_hir() _hir = new IR() _hir->optimize() _hir->split_critical_edges() _hir->compute_code() GlobalValueNumbering gvn(_hir) _hir->compute_use_counts()10
    • BytecodeからHIRへの変換 FrameMap() emit_lir() LIRGenerator gen() hir()->iterate_linear_scan_order() LinearScan allocator = new LinearScan() allocator->do_linear_scan() compute_local_live_sets() compute_global_live_sets() build_intervals() allocate_registers() resolve_data_flow() propagate_spill_slots() eliminate_spill_moves() assign_reg_num() allocate_fpu_stack() EdgeMoveOptimizer::optimize(ir()->code()) ControlFlowOptimizer::optimize(ir()->code()) emit_code_body() setup_code_buffer() _masm = new C1_MacroAssembler() LIR_Asssembler lir_asm() lir_asm.emit_code() emit_code_epilog() generate code for deopt handlerBytecodeからHIRへの変換BytecodeからHIRへの変換は、大体1Bytecodeにつき、1HIRに変換するIR() IR()->IRScope()->XHandlers() IRScope() _requires_phi_function IR()->IRScope()->IRScope() _start = GraphBuilder gm() constructor GraphBuilder() GraphBuilder::iterate_all_blocks() GraphBuilder::iterate_bytecodes_for_block(int bci)Bytecodeの各命令ごとに処理をわけているところGraphBuilder::iterate_bytecodes_for_block(int bci): _skip_block = false; assert(state() != NULL, "ValueStack missing!"); ciBytecodeStream s(method()); s.reset_to_bci(bci); int prev_bci = bci; scope_data()->set_stream(&s); // iterate Bytecodes::Code code = Bytecodes::_illegal; bool push_exception = false; if (block()->is_set(BlockBegin::exception_entry_flag) && block()->next() == NULL) { // first thing in the exception entry block should be the exception object. push_exception = true; }11
    • if_icmpXXの変換 while (!bailed_out() && last()->as_BlockEnd() == NULL && (code = stream()->next()) != ciBytecodeStream::EOBC() && (block_at(s.cur_bci()) == NULL || block_at(s.cur_bci()) == block())) { assert(state()->kind() == ValueStack::Parsing, "invalid state kind"); // Check for active jsr during OSR compilation if (compilation()->is_osr_compile() && scope()->is_top_scope() && parsing_jsr() && s.cur_bci() == compilation()->osr_bci()) { bailout("OSR not supported while a jsr is active"); } if (push_exception) { apush(append(new ExceptionObject())); push_exception = false; } // handle bytecode switch (code) { case Bytecodes::_nop : /* nothing to do */ break; case Bytecodes::_aconst_null : apush(append(new Constant(objectNull ))); break; case Bytecodes::_iconst_m1 : ipush(append(new Constant(new IntConstant (-1)))); break; case Bytecodes::_iconst_0 : ipush(append(new Constant(intZero ))); break; case Bytecodes::_iconst_1 : ipush(append(new Constant(intOne ))); break; case Bytecodes::_iconst_2 : ipush(append(new Constant(new IntConstant ( 2)))); break; case Bytecodes::_iconst_3 : ipush(append(new Constant(new IntConstant ( 3)))); break; case Bytecodes::_iconst_4 : ipush(append(new Constant(new IntConstant ( 4)))); break; case Bytecodes::_iconst_5 : ipush(append(new Constant(new IntConstant ( 5)))); break; ... case Bytecodes::_invokevirtual : // fall through case Bytecodes::_invokespecial : // fall through case Bytecodes::_invokestatic : // fall through case Bytecodes::_invokedynamic : // fall through case Bytecodes::_invokeinterface: invoke(code); break;invoke命令を変換時にdevirtualize/inline展開する • @todo codeを追う • @todo devirtualizeの仕組み • @todo is_profile_callの仕組みif_icmpXXの変換if_icmpXXの変換が分かりやすいBytecodeのiterate case Bytecodes::_if_icmpeq : if_same(intType , If::eql); break; case Bytecodes::_if_icmpne : if_same(intType , If::neq); break; case Bytecodes::_if_icmplt : if_same(intType , If::lss); break; case Bytecodes::_if_icmpge : if_same(intType , If::geq); break; case Bytecodes::_if_icmpgt : if_same(intType , If::gtr); break; case Bytecodes::_if_icmple : if_same(intType , If::leq); break; case Bytecodes::_if_acmpeq : if_same(objectType, If::eql); break; case Bytecodes::_if_acmpne : if_same(objectType, If::neq); break;ifの変換部12
    • invokeの変換 void GraphBuilder::if_same(ValueType* type, If::Condition cond) { ValueStack* state_before = copy_state_before(); Value y = pop(type); Value x = pop(type); if_node(x, cond, y, state_before); } void GraphBuilder::if_node(Value x, If::Condition cond, Value y, ValueStack* state_before) { BlockBegin* tsux = block_at(stream()->get_dest()); BlockBegin* fsux = block_at(stream()->next_bci()); bool is_bb = tsux->bci() < stream()->cur_bci() || fsux->bci() < stream()->cur_bci(); Instruction *i = append(new If(x, cond, false, y, tsux, fsux, is_bb ? state_before : NULL, is_bb)); if (is_profiling()) { If* if_node = i->as_If(); if (if_node != NULL) { // Note that wed collect profile data in this method if we wanted it. compilation()->set_would_profile(true); // At level 2 we need the proper bci to count backedges if_node->set_profiled_bci(bci()); if (profile_branches()) { // Successors can be rotated by the canonicalizer, check for this case. if_node->set_profiled_method(method()); if_node->set_should_profile(true); if (if_node->tsux() == fsux) { if_node->set_swapped(true); } } return; } // Check if this If was reduced to Goto. Goto *goto_node = i->as_Goto(); if (goto_node != NULL) { compilation()->set_would_profile(true); if (profile_branches()) { goto_node->set_profiled_method(method()); goto_node->set_profiled_bci(bci()); goto_node->set_should_profile(true); // Find out which successor is used. if (goto_node->default_sux() == tsux) { goto_node->set_direction(Goto::taken); } else if (goto_node->default_sux() == fsux) { goto_node->set_direction(Goto::not_taken); } else { ShouldNotReachHere(); } } return; } } }invokeの変換BytecodeのinvokeXXXは、invoke()メソッドで処理する。invokeを処理する際に、呼び出し対象が一意に定まるか判定し、もし定まる場合は、inline展開を試行する。13
    • dependency また、invokevirtualやinvokeinterfaceのdevirtual化(invokespecialとみなす)を行い、 積極的にinline展開を試行 する invoke switch (code) { case Bytecodes::_nop : /* nothing to do */ break; ... case Bytecodes::_invokevirtual : // fall through case Bytecodes::_invokespecial : // fall through case Bytecodes::_invokestatic : // fall through case Bytecodes::_invokedynamic : // fall through case Bytecodes::_invokeinterface: invoke(code); break; GraphBuilder::invoke(Bytecodes::Code) ciMethod* cha_monomorphic_target ciMethod* exact_target if (!target->is_static()) { type_is_exact exact_target = target->resolve_invoke(calling_klass, receiver_klass); code = invokespecial invokevirtual || invokeinterfaceの場合 cha_monomorphic_target = target->find_monomorphic_target(calling_klass, callee_holder, actual_recv); invokeinterface && singleton? CheckCast* c = new CheckCast(klass, receiver, copy_state_for_exception()) set_incompatible_class_change_check() cha_monomorphic_targetがabstractだった場合、 NULL cha_monomorphic_targetが見つかった場合 dependency_recorder()->assert_unique_concrete_method(actual_recv, cha_monomorphic_target) code = invokespecial もし上記処理で一意に分かったら try_inline(inline_target, ); try_inline_full() <-- 200stepの関数なので、あまり見たくない 最終的には、iterate_bytecode_ みたいなのを呼び出す is_profiling() target_klass = cha_monomorphic_target->holder() || exact_garget->holder() profile_call(recv, target_klass) dependency dependencyは、JVM上での制約をチェックし、違反した場合イベントを起動してくれるイベントハンドラみたい なもの よく脱仮想化する際に使用し、もし脱仮想化の条件が崩れた場合、脱最適化するようにイベントを登録する 脱仮想化の条件が崩れる例として、newで新しいクラスを作成した時や、classloader、redefine void GraphBuilder::invoke(Bytecodes::Code code) if (cha_monomorphic_target != NULL) { if (!(target->is_final_method())) { // If we inlined because CHA revealed only a single target method, // then we are dependent on that target method not getting overridden // by dynamic class loading. Be sure to test the "static" receiver14
    • dependency // dest_method here, as opposed to the actual receiver, which may // falsely lead us to believe that the receiver is final or private. dependency_recorder()->assert_unique_concrete_method(actual_recv, cha_monomorphic_target); } code = Bytecodes::_invokespecial; } GraphBuilder::call_register_finalizer() ciInstanceKlass* ik = compilation()->method()->holder(); //finalだったら、一意 if (ik->is_final()) { exact_type = ik; //クラス階層解析を使用するかつサブクラスを持ってない } else if (UseCHA && !(ik->has_subklass() || ik->is_interface())) { // test class is leaf class compilation()->dependency_recorder()->assert_leaf_type(ik); exact_type = ik; } else { declared_type = ik; } Dependencyを試す場合のオプション -XX:+TraceDependencies -XX:+VerifyDependencies dependencyの制約にひっかかり、deoptimizeするサンプルプログラム deoptimize sample interface getter { public int num(); public int get(); } class Bgetter implements getter { public int num() { return 1; } public int get() { int sum = 0; for (int i=0; i<100; i++) { sum += num(); } return sum; } } class Cgetter implements getter { public int num() { return 2; } public int get() { int sum = 0; for (int i=0; i<100; i++) { sum += num(); } return sum; } } public class iftest {15
    • dependency static final long LEN=100000000; public static void main(String args[]) { getter f = new B(); long sum=0; for( long i=0; i<LEN; i++ ) { sum += f.get(); } // getter f2 = new C(); //devirtualize System.out.println(sum); } } getter f2のコメントを外すと、new C()された際にdependencyが反応し、deoptimizeが走る log Failed dependency of type unique_concrete_method context = *getter method = {method} get ()I in B witness = *getter code: 9434 1% nmethod iftest::main @ 13 (58 bytes) Marked for deoptimization context = getter dependee = C context supers = 1, interfaces = 1 Compiled (c1) 9434 1% nmethod iftest::main @ 13 (58 bytes) total in heap [0xb5891388,0xb5891acc] = 1860 relocation [0xb5891458,0xb5891500] = 168 main code [0xb5891500,0xb58917c0] = 704 stub code [0xb58917c0,0xb589180c] = 76 oops [0xb589180c,0xb5891818] = 12 scopes data [0xb5891818,0xb58918ec] = 212 scopes pcs [0xb58918ec,0xb5891aac] = 448 dependencies [0xb5891aac,0xb5891ab0] = 4 nul chk table [0xb5891ab0,0xb5891acc] = 28 Dependencies: Dependency of type unique_concrete_method context = *getter method = {method} get ()I in B [nmethod<=klass]getter checking (true) 9434 1% nmethod iftest::main @ 13 (58 bytes) depdnecyのcheck処理が呼ばれた際のstack trace Breakpoint 4, Dependencies::DepStream::check_dependency_impl (this=0xfd05c8, changes=0xfd06f8) at /home/elise/language/openjdk6/hotspot/src/share/vm/code/dependencies.cpp:1449 1449 if (TraceDependencies) { #1 0x00530b7e in Dependencies::DepStream::spot_check_dependency_at (this=0xfd05c8, changes= at /home/elise/language/openjdk6/hotspot/src/share/vm/code/dependencies.cpp:1464 1464 return check_dependency_impl(&changes); #0 Dependencies::DepStream::check_dependency_impl (this=0xfd05c8, changes=0xfd06f8) at /home/elise/language/openjdk6/hotspot/src/share/vm/code/dependencies.cpp:1449 1449 if (TraceDependencies) { #1 0x00530b7e in Dependencies::DepStream::spot_check_dependency_at (this=0xfd05c8, changes= at /home/elise/language/openjdk6/hotspot/src/share/vm/code/dependencies.cpp:1464 1464 return check_dependency_impl(&changes); #2 0x007573ae in nmethod::check_dependency_on (this=0xb60d4388, changes=...) at /home/elise/language/openjdk6/hotspot/src/share/vm/code/nmethod.cpp:2063 2063 if (deps.spot_check_dependency_at(changes) != NULL) { #3 0x005b6030 in instanceKlass::mark_dependent_nmethods (this=0xb212bde0, changes=...)16
    • dependency at /home/elise/language/openjdk6/hotspot/src/share/vm/oops/instanceKlass.cpp:1406 1406 if (nm->is_alive() && !nm->is_marked_for_deoptimization() && nm->check_dependency_on(changes)) { #4 0x004b0f83 in CodeCache::mark_for_deoptimization (changes=...) at /home/elise/language/openjdk6/hotspot/src/share/vm/code/codeCache.cpp:641 641 number_of_marked_CodeBlobs += instanceKlass::cast(d)->mark_dependent_nmethods(changes); #5 0x00866302 in Universe::flush_dependents_on (dependee=...) at /home/elise/language/openjdk6/hotspot/src/share/vm/memory/universe.cpp:1182 1182 if (CodeCache::mark_for_deoptimization(changes) > 0) { #6 0x00825729 in SystemDictionary::add_to_hierarchy (k=..., __the_thread__=0x806cc00) at /home/elise/language/openjdk6/hotspot/src/share/vm/classfile/systemDictionary.cpp:1727 1727 Universe::flush_dependents_on(k); #7 0x00824e09 in SystemDictionary::define_instance_class (k=..., __the_thread__=0x806cc00) at /home/elise/language/openjdk6/hotspot/src/share/vm/classfile/systemDictionary.cpp:1506 1506 add_to_hierarchy(k, CHECK); // No exception, but can block #8 0x00823df1 in SystemDictionary::resolve_from_stream (class_name=..., class_loader=..., p verify=true, __the_thread__=0x806cc00) at /home/elise/language/openjdk6/hotspot/src/share/vm/classfile/ 1138 define_instance_class(k, THREAD); #9 0x0064c2d9 in jvm_define_class_common (env=0x806cd3c, name=0xfd0eac "C", loader=0xfd0fac len=316, pd=0xfd0f98, source=0xfd0aac "file:/home/elise/language/java/sample6/", verify=1 001, __the_t at /home/elise/language/openjdk6/hotspot/src/share/vm/prims/jvm.cpp:864 864 CHECK_NULL); #10 0x0064c7d6 in JVM_DefineClassWithSource (env=0x806cd3c, name=0xfd0eac "C", loader=0xfd0f len=316, pd=0xfd0f98, source=0xfd0aac "file:/home/elise/language/java/sample6/") at /home/elise/language/openjdk6/hotspot/src/share/vm/prims/jvm.cpp:884 884 return jvm_define_class_common(env, name, loader, buf, len, pd, source, true, THREAD); #11 0x00ff7942 in Java_java_lang_ClassLoader_defineClass1 (env=0x806cd3c, loader=0xfd0fac, n length=316, pd=0xfd0f98, source=0xfd0f94) at ../../../src/share/native/java/lang/ClassLoader.c:151 151 result = JVM_DefineClassWithSource(env, utfName, loader, body, length, pd, utfSource); @todo DepStream::check_dependency_impl() code/dependencies.cpp invokenの際は、unique_concreate_method CHAで再度チェックしている klassOop Dependencies::DepStream::check_dependency_impl(DepChange* changes) { assert_locked_or_safepoint(Compile_lock); klassOop witness = NULL; switch (type()) { case evol_method: witness = check_evol_method(method_argument(0)); break; case leaf_type: witness = check_leaf_type(context_type()); break; case abstract_with_unique_concrete_subtype: witness = check_abstract_with_unique_concrete_subtype(context_type(), type_argument(1), changes); break; case abstract_with_no_concrete_subtype: witness = check_abstract_with_no_concrete_subtype(context_type(), changes); break; case concrete_with_no_concrete_subtype: witness = check_concrete_with_no_concrete_subtype(context_type(), changes); break; case unique_concrete_method: witness = check_unique_concrete_method(context_type(),17
    • プロファイラ method_argument(1), changes); break; case abstract_with_exclusive_concrete_subtypes_2: witness = check_abstract_with_exclusive_concrete_subtypes(context_type(), type_argument(1), type_argument(2), changes); break; case exclusive_concrete_methods_2: witness = check_exclusive_concrete_methods(context_type(), method_argument(1), method_argument(2), changes); break; case no_finalizable_subclasses: witness = check_has_no_finalizable_subclasses(context_type(), changes); break; default: witness = NULL; ShouldNotReachHere(); break; }deoptimizeも、thread並列で行うが、実際にコードを置換する際には全体をmutexで止めるdependenceからDeoptimizeを呼び出す場所 // Flushes compiled methods dependent on dependee. void Universe::flush_dependents_on(instanceKlassHandle dependee) { assert_lock_strong(Compile_lock); if (CodeCache::number_of_nmethods_with_dependencies() == 0) return; // CodeCache can only be updated by a thread_in_VM and they will all be // stopped dring the safepoint so CodeCache will be safe to update without // holding the CodeCache_lock. DepChange changes(dependee); // Compute the dependent nmethods if (CodeCache::mark_for_deoptimization(changes) > 0) { //<----- koko // At least one nmethod has been marked for deoptimization VM_Deoptimize op; VMThread::execute(&op); } }dependenciesのルールから、VM_deoptimizeがキックされ、 dependenciesに引っかかった要素をdeoptimizeするdeoptimizeも、2種類あり、mutexで止めた際に、実行中でないなら、oopsのcodeを書き換える抱け。もし実行中だったら、frameを書き換えて、JITコンパイルしたコードからintepreter実行に切り替えるプロファイラC1コンパイラでは、プロファイルした情報を活用する部分と、C1コンパイラが生成したコードにプロファイルする命令を埋め込む部分がある。プロファイル系のオプション18
    • HIR から LIR への変換 product(bool, C1ProfileCalls, true, "Profile calls when generating code for updating MDOs") // dead product(bool, C1ProfileVirtualCalls, true, "Profile virtual calls when generating code for updating MDOs") product(bool, C1ProfileInlinedCalls, true, "Profile inlined calls when generating code for updating MDOs") product(bool, C1ProfileBranches, true, "Profile branches when generating code for updating MDOs") product(bool, C1ProfileCheckcasts, true, "Profile checkcasts when generating code for updating MDOs") 主にプロファイルはインタプリタが行っているが、C1コンパイラがJITコンパイルしたコードにも埋め込み可能になっている。JITコンパイルしたコードに埋め込む場合、2回目、3回目のJITコンパイルが行われるはず複数回のJITコンパイルの条件は不明。。プロファイルの様子は、TemplateIntepreterの解説に期待HIR から LIR への変換HIRからLIRへの変換はLIRGeneratorが行う。visitorでHIRを走査し、HIRに対して複数のLIRへ分解するLIRは仮想レジスタを無限に持つことを仮定し、レジスタ割り付けで実レジスタを割り振るvoid LIRGenerator::block_do(BlockBegin* block) block_do_prolog(block); __ branch_destination(block->label()); <-- label設定 set_block(block); for (Instruction* instr = block; instr != NULL; instr = instr->next()) { if (instr->is_pinned()) do_root(instr); } set_block(NULL); block_do_epilog(block);void LIRGenerator::do_IfOp(IfOp* x) // Code for : x->x() {x->cond()} x->y() ? x->tval() : x->fval() LIRItem left(x->x(), this); LIRItem right(x->y(), this); left.load_item(); if (can_inline_as_constant(right.value())) { right.dont_load_item(); } else { right.load_item(); } LIRItem t_val(x->tval(), this); LIRItem f_val(x->fval(), this); t_val.dont_load_item(); f_val.dont_load_item();19
    • C1コンパイラのHIR最適化 LIR_Opr reg = rlock_result(x); __ cmp(lir_cond(x->cond()), left.result(), right.result()); __ cmove(lir_cond(x->cond()), t_val.result(), f_val.result(), reg, as_BasicType(x->x()->type())); }invokeの処理なんかは複雑で面白いかもしれないC1コンパイラのHIR最適化HIR Optimizationは、BytecodeからHIRへ変換終わった後に行うc1_Compilation.cpp::build_hir() _hir->optimize(); IR::optimize() opt.eliminate_conditional_expressions(); opt.eliminate_blocks(); opt.eliminate_null_checks();eliminate_conditional_expressions@todo 時間があればコードを追うことCE_Eliminator::block_do()が本体 ブロックの終端のifを取得 ifはint型かobject型か判定 true block false block true_instruction false_instruction a = (b > c) ? b : c; BB: if ... then BBn false BBm BBn: const n goto BBs BBm: const m goto BBs BBs: phi [][]CEE Java: public static int CETest(int ret, int n,int m) { ret += (n < m) ? n : m; return ret; }CEE HIR B62: i179 = i103 - i76 i180 = i178 - i151 v186 = if i179 > i180 then B73 else B72 <-- replace i186 = ifop (i179 > i180) ixx, iyy; <-- add goto B74 B73: <-- delete v188 = goto B74 <-- delete20
    • GlobalValueNumbering B72: <-- delete v187 = goto B74 <-- delete B74: i189 = [i179,i180] <-- replace v190 = goto B70 B70: i191 = i151 + i189 GlobalValueNumbering @todo 時間があればコードを追うこと c1_ValueMap.hpp::GlobalValueNumbering(IR* ir) ShortLoopOptimizer short_loop_optimizer(this); for (int i = 1; i < num_blocks; i++) { BlockBegin* block = blocks->at(i); ... if (num_preds == 1) { // nothing to do here } else if (block->is_set(BlockBegin::linear_scan_loop_header_flag)) { // block has incoming backward branches -> try to optimize short loops if (!short_loop_optimizer.process(block)) { <-- ループは特別に処理 // loop is too complicated, so kill all memory loads because there might be // stores to them in the loop current_map()->kill_memory(); } } else { // only incoming forward branches that are already processed for (int j = 0; j < num_preds; j++) { BlockBegin* pred = block->pred_at(j); ValueMap* pred_map = value_map_of(pred); if (pred_map != NULL) { // propagate killed values of the predecessor to this block current_map()->kill_map(value_map_of(pred)); } else { // kill all memory loads because predecessor not yet processed // (this can happen with non-natural loops and OSR-compiles) current_map()->kill_memory(); } } } if (block->is_set(BlockBegin::exception_entry_flag)) { current_map()->kill_exception(); } TRACE_VALUE_NUMBERING(tty->print("value map before processing block: "); current_map()->print()); // visit all instructions of this block for (Value instr = block->next(); instr != NULL; instr = instr->next()) { assert(!instr->has_subst(), "substitution already set"); // check if instruction kills any values instr->visit(this);21
    • EscapeAnalysis if (instr->hash() != 0) { Value f = current_map()->find_insert(instr); <-- ここがキモ if (f != instr) { assert(!f->has_subst(), "cant have a substitution"); instr->set_subst(f); subst_count++; } } } // remember value map for successors set_value_map_of(block, current_map()); } EscapeAnalysis c1コンパイラからは呼ばれません!!! wimmerの資料によるとclientからも呼ばれるようだが、昔のJDKもしくはSun JDKだけなのかもしれない。 Serverだとbreakを確認できた。 Sharkだとifdef切ってるけど、多分動かないんだろうと推測 C1コンパイラのLIR最適化 EdgeMoveOptimizer c1_LinearScan.cpp::EdgeMoveOptimizer::optimize(BlockList* code) EdgeMoveOptimizer optimizer = EdgeMoveOptimizer(); // ignore the first block in the list (index 0 is not processed) for (int i = code->length() - 1; i >= 1; i--) { BlockBegin* block = code->at(i); if (block->number_of_preds() > 1 && !block->is_set(BlockBegin::exception_entry_flag)) { optimizer.optimize_moves_at_block_end(block); } if (block->number_of_sux() == 2) { optimizer.optimize_moves_at_block_begin(block); } } preds > 1 --> ブロックへのjumpが複数ある場合 CFGの合流ブロックとか、loopのheaderとか code sink みたいな処理 sux == 2 --> ブロックからのjumpが複数ある場合 分岐とか、loopのback_edgeとか code hoist みたいな処理 ControlFlowOptimizer c1_LiearScan.cpp::ControlFlowOptimize::optimize(BlockList* code) ControlFlowOptimizer optimizer = ControlFlowOptimizer(); // push the OSR entry block to the end so that were not jumping over it.22
    • sample code factIf BlockBegin* osr_entry = code->at(0)->end()->as_Base()->osr_entry(); if (osr_entry) { int index = osr_entry->linear_scan_number(); assert(code->at(index) == osr_entry, "wrong index"); code->remove_at(index); code->append(osr_entry); } optimizer.reorder_short_loops(code); optimizer.delete_empty_blocks(code); optimizer.delete_unnecessary_jumps(code); optimizer.delete_jumps_to_return(code); reorder_short_loops() 下記のようなケースをpost jumpっぽくする end_block B4 (V) [35, 50] -> B3 dom B3 sux: B3 pred: B3 __bci__use__tid____instr____________________________________ 35 1 i19 31 38 1 i20 i15 * i19 41 2 i21 1 . 41 1 i22 i16 + i21 . 44 1 i23 a11[i16] (C) . 45 1 i24 i20 + i23 . 47 1 i26 i17 + i21 . 50 0 27 goto B3 (safepoint) header_block B3 (LHbV) [28, 32] -> B5 B4 dom B1 sux: B5 B4 pred: B1 B4 __bci__use__tid____instr____________________________________ . 32 0 18 if i17 >= i12 then B5 else B4 delete_unnecessary_jumps() last_branchが次のブロックへの飛び先だったらdelete cmp branch1 branch2の場合、branch1が次のブロックへのjumpだったら、 branch1を書き換えて、condの 条件を反転 branch2を削除 sample code factIf 入力ソースコードfactIf public static int factIf(int n) { int p; if (n > 1) { p = n * factIf(n - 1); } else { p = 1; } return p; } factIf Bytecode public static int factIf(int): Code: 0: iload_0 1: iconst_123
    • sample code factIf 2: if_icmple 17 5: iload_0 6: iload_0 7: iconst_1 8: isub 9: invokestatic #3; //Method factIf:(I)I 12: imul 13: istore_1 14: goto 19 17: iconst_1 18: istore_1 19: iload_1 20: ireturn factIf HIR: static jint Fact.factIf(jint) B9: i4 = method parameter v32 = std entry B0 B0: i5 = 1 v6 = if i4 <= i5 then B2 else B1 B1: i7 = 1 i8 = i4 - i7 v15 = if i8 <= i7 then B7 else B6 B7: i21 = 1 v22 = goto B8 B6: i16 = 1 i17 = i8 - i16 i18 = invokestatic(i17) Fact.factIf(I)I i19 = i8 * i18 v20 = goto B8 B8: i23 = [i19,i21] v24 = goto B4 B4: i25 = i4 * i23 v26 = goto B3 B2: i27 = 1 v28 = goto B3 B3: i29 = [i25,i27] i30 = ireturn i29 factIf HIR CFG factIf HIR DFG24
    • sample code factIf factIf Optimized HIR static jint Fact.factIf(jint) B9: i4 = method parameter v32 = std entry B0 B0: i5 = 1 v6 = if i4 <= i5 then B2 else B1 B1: // deleted i7 = 1 i8 = i4 - i7 v15 = if i8 <= i7 then B7 else B6 B7: // deleted i21 = 1 v22 = goto B8 B6: // deleted i16 = 1 i17 = i8 - i5 // replaced i17 = i8 - i16 i18 = invokestatic(i17) Fact.factIf(I)I i19 = i8 * i18 v20 = goto B8 B8: i23 = [i19,i5] // replaced i23 = [i19,i21] // deleted v24 = goto B4 // deleted B4: i25 = i4 * i23 v26 = goto B3 B2: // deleted i27 = 1 v28 = goto B3 B3: i29 = [i25,i5] // replaced i29 = [i25,i27] i30 = ireturn i29 基本的には、HIRからLIRへシーケンシャルに変換する BBの入り口処理とかは遣るけどさ factIf translate HIR to LIR # # # # label B9 std_entry move ecx R41 branch AL B0 label B0 cmp R41 1 branch LE B2 branch AL B1 label B1 move R41 R4225
    • sample code factIf sub R42 1 R42 cmp R42 1 branch LE B7 branch AL B6 label B6 move R42 R43 sub R43 1 R43 move R43 ecx static call [static jint Fact.factIf(jint)] result eax bci:9 move eax R44 move R44 R45 mul R45 R42 R45 move R45 R46 branch AL B8 label B7 move 1 R46 branch AL B8 label B8 move R46 R47 mul R47 R41 move R47 R48 branch AL B3 label B2 move 1 B48 branch AL B3 label B3 move R48 eax return eax26
    • sample code factIf B9: std_entry move ecx R41 branch AL B0 B0: cmp R41 1 branch LE B2 branch AL B1 B1: move R41 R42 sub R42 1 R42 cmp R42 1 branch LE B7 branch AL B6 B6: move R42 R43 sub R43 1 R43 move R43 ecx B7: static call [static jint Fact.factIf(jint)] result eax bci:9 move 1 R46 move eax R44 branch AL B8 move R44 R45 mul R45 R42 R45 move R45 R46 branch AL B8 B8: B2: move R46 R47 move 1 B48 mul R47 R41 branch AL B3 move R47 R48 branch AL B3 B3: move R48 eax return eax LinearScanレジスタ割り付け27
    • sample code factFor LIRでも色々冗長な命令を削る Before Code Generation # # # # # label B9 std_entry // deleted move ecx R41 // deleted branch AL B0 label B0 cmp ecx 1 // replaced cmp R41 1 branch LE B2 // deleted branch AL B1 label B1 move ecx esi // replaced move R41 R42 sub esi 1 esi // replaced sub R42 1 R42 cmp esi 1 // replaced cmp R42 1 move ecx stack:2 // add branch LE B7 // deleted branch AL B6 label B6 move esi edi // replaced move R42 R43 sub edi 1 edi // replaced sub R43 1 R43 move edi ecx // replaced move R43 ecx move esi stack:1 // add static call [static jint Fact.factIf(jint)] result eax bci:9 move stack:1 esi // add // deleted move eax R44 // deleted move R44 R45 mul eax esi eax // replaced mul R45 R42 R45 // deleted move R45 R46 branch AL B8 label B7 move 1 eax // replaced move 1 R46 // deleted branch AL B8 label B8 move stack:2 ecx // add // deleted move R46 R47 mul eax ecx eax // replaced mul R47 R41 // deleted move R47 R48 // deleted branch AL B3 return eax // add label B2 move 1 eax // replaced move 1 B48 // deleted branch AL B3 label B3 // deleted move R48 eax return eax sample code factFor 入力ソースコードfactFor public static int factFor(int n) { int p = 1; for (int i = 1; i <= n; i++) {28
    • sample code factFor p = p * i; } return p; } factFor Bytecode public static int factFor(int); Code: 0: iconst_1 1: istore_1 2: iconst_1 3: istore_2 4: iload_2 5: iload_0 6: if_icmpgt 19 9: iload_1 10: iload_2 11: imul 12: istore_1 13: iinc 2, 1 16: goto 4 19: iload_1 20: ireturn factFor HIR static jint Fact.factFor(jint) B4: i4 = method parameter v17 = std entry B0 B0: i5 = 1 v7 = goto B1 B1: i8 = [i5, i11] i9 = [i5, i13] v10 = if i9 > i4 then B3 else B2 B2: i11 = i8 * i9 i12 = 1 i13 = i9 + i12 v14 = goto B1 B3: i15 = ireturn i8 factFor HIR CFG factFor HIR DFG factFor Optimized HIR29
    • sample code factFor static jint Fact.factFor(jint) B4: i4 = method parameter v17 = std entry B0 B0: i5 = 1 v7 = goto B1 B1: i8 = [i5, i11] i9 = [i5, i13] v10 = if i9 > i4 then B3 else B2 B2: i11 = i8 * i9 // deleted i12 = 1 i13 = i9 + i5 // replaced i13 = i9 + i12 v14 = goto B1 B3: i15 = ireturn i8 factFor translate HIR to LIR # # # # label B4 std_entry move ecx R41 branch AL B0 label B0 move 1 R43 move 1 R42 branch AL B1 label B1 cmp R43 R41 branch GT B3 branch AL B2 label B2 move R42 R44 mul R44 R43 R44 move R43 R45 add R45 1 R45 safepoint bci:16 move R45 R43 move R44 R42 branch AL B1 label B3 move R42 eax return eax30
    • sample code factFor B4 std_entry move ecx R41 branch AL B0 B0 move 1 R43 move 1 R42 branch AL B1 B1 cmp R43 R41 branch GT B3 branch AL B2 B2 move R42 R44 mul R44 R43 R44 B3 move R43 R45 move R42 eax add R45 1 R45 return eax safepoint bci:16 move R45 R43 move R44 R42 branch AL B1 LenearScanレジスタ割り付け Before Code Generation # # # # label B4 std_entry // deleted move ecx R41 // deleted branch AL B0 label B0 move 1 eax // replaced move 1 R43 move 1 esi // replaced move 1 R42 branch AL B131
    • 参考文献 label B2 // deleted move R42 R44 mul esi eax esi // replaced mul R44 R43 R44 // deleted move R43 R45 add eax 1 eax // replaced add R45 1 R45 safepoint bci:16 // deleted move R45 R43 // deleted move R44 R42 // deleted branch AL B1 label B1 cmp eax ecx // replaced cmp R43 R41 // deleted branch GT B3 branch LE B2 // replaced branch AL B2 label B3 move esi eax // replaced move R42 eax return eax参考文献SSA Form for the Java HotSpot™ Client Compiler Christian Wimmer April 2009 Sun Microsystems, Inc. Institute for System Software Johannes Kepler University Linz, Austria Department of Computer Science University of California, IrvineLinear Scan Register Allocation Christian Wimmer Linear Scan Register Allocation for the Java HotSpot™ Client Compiler A thesis submitted in partial satisfaction of the requirements for the degree of Master of Science (Diplom-Ingenieur)Design of the Java HotSpotTM Client Compiler for Java 6 Design of the Java HotSpotTM Client Compiler for Java 6 THOMAS KOTZMANN, CHRISTIAN WIMMER and HANSPETER MO¨ SSENBO¨ CK Johannes Kepler University Linz and THOMAS RODRIGUEZ, KENNETH RUSSELL, and DAVID COX Sun Microsystems, Inc.Escape Analysis in the Context of Dynamic Compilation and Deoptimization Thomas Kotzmann Escape Analysis in the Context of Dynamic Compilation and Deoptimization A PhD thesis submitted in partial satisfaction of the requirements for the degree of Doctor of Technical Sciences Institute for System Software Johannes Kepler University Linz32
    • Indices and tables Indices and tables • genindex • modindex • search33