openjdk hotspot c1compiler overview

37
OpenJDK HotSpot C1Compiler Overview version 1.0 Author nothingcosmos November 09, 2011

Upload: nothingcosmos

Post on 28-Jun-2015

2.263 views

Category:

Technology


5 download

DESCRIPTION

OpenJDK HotSpot C1Compiler Overview 20111022

TRANSCRIPT

Page 1: OpenJDK HotSpot C1Compiler Overview

OpenJDK HotSpot C1Compiler Overviewversion 1.0

Author nothingcosmos

November 09, 2011

Page 2: OpenJDK HotSpot C1Compiler Overview
Page 3: OpenJDK HotSpot C1Compiler Overview

ContentsWelcome to OpenJDK Internals's 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

参考文献 32

Indices and tables 33

Page 4: OpenJDK HotSpot C1Compiler Overview
Page 5: OpenJDK HotSpot C1Compiler Overview

Welcome to OpenJDK Internals's documentation!Contents:

OpenJDK HotSpot Client Compiler Overview

タイトルOpenJDK HotSpot Client Compiler Overview

第4回JVMソースコードリーディングの会

nothingcosmos<[email protected]>

http://nothingcosmos.blog52.fc2.com/

http://nothingcosmos.wiki.fc2.com/

自己紹介HN:nothingcosmos

元コンパイラ屋のソフトウェアエンジニア 現在は金融系SIer

趣味で、LLVMを読んだり、OpenJDKを読んだり、UNIXv6を読んだりしています。

先週は、箱根でLions'Commentary on UNIX勉強会(2011秋合宿)へ参加してました。

OpenJDK キーワード

• HotSpotコンパイラ

• C1(Client)/C2(Server)コンパイラ

• JIT

• Adaptive Compilation

• Deoptimize 脱最適化• 中間表現/中間言語

HotSpotの見どころ1一般ユーザからみたコンパイラの見どころ

• ...Scalaユーザからみたコンパイラの見どころ

• Scalaでは細かいオブジェクトをたくさん作るので、 EscapeAnalysisがあると全部レジスタやスタックに乗る

という話を以前kmizuさんに聞いた記憶が...

毎回ヒープに割り付けないので、高速

HotSpotの見どころ2私個人は、

• コンパイラ内部の中間言語構造とアーキテクチャ

• 大した最適化してないのに速いコードを吐くHotSpot Client Compiler

Welcome to OpenJDK Internals's documentation!

1

Page 6: OpenJDK HotSpot C1Compiler Overview

• 適応的コンパイル Adaptive Compilation

• JITコンパイラ/脱最適化のコントロール

• Profiling/Tracingの取得方法と活用方法• HotSpot特有の最適化技術

• EscapeAnalysis/ClassHierarchyAnalysis

• 複数アーキテクチャへの対応方法

• OpenJDK7の最適化のバグ

本日紹介するC1 Compiler

OpenJDKの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 allocation

本日紹介するC1 Compiler

2

Page 7: OpenJDK HotSpot C1Compiler Overview

• リニアスキャンというレジスタ割り付けのアルゴリズム• template-style code generator

• asmの生成はあまり頑張らない。LIRからシーケンシャルに生成

C1コンパイラの構成hotspot/src/share/vm

• c1 <-- C1コンパイラの本体

• compiler <-- コンパイラの抽象クラス

• runtime <-- JVMのruntime部分hotspot/src/share/vm/c1

C1コンパイラ 全体で36kstep

top5

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向けのStubc1_Compilation.cpp <-- C1コンパイラのコントローラー Driver???c1_Compilation.hppc1_Compiler.cpp <-- C1コンパイラの本体c1_Compiler.hppc1_Defs.cpp <-- architecture依存の各種定義ファイル レジスタとかc1_Defs.hppc1_FpuStackSim.hpp <-- architecture依存のFPUStackのシミュレータの定義ファイルc1_FrameMap.cpp <-- architecture依存のFrameMapや仮想レジスタやCallingConvensionc1_FrameMap.hppc1_GraphBuilder.cpp <-- BytecodeからHIRへの変換c1_GraphBuilder.hpp 各種最適化も行う(inlining, devirtualize, canonicalizec1_IR.cpp <-- IRの定義c1_IR.hpp HIR/LIR/BB/各種helperを統合したIRという名のDescripterc1_Instruction.cpp <-- HIRの定義や、IRクラスのUtilityc1_Instruction.hppc1_InstructionPrinter.cpp <-- HIRのprinter 見やすいように情報を絞って整形して表示するc1_InstructionPrinter.hppc1_LIR.cpp <-- LIRの定義c1_LIR.hppc1_LIRAssembler.cpp <-- LIRからAsmのemitter兼helper Asmのコード生成c1_LIRAssembler.hppc1_LIRGenerator.cpp <-- HIRからLIRへの変換

C1コンパイラの構成

3

Page 8: OpenJDK HotSpot C1Compiler Overview

c1_LIRGenerator.hpp 命令選択、レジスタ割り付け、LIRレベルの最適化も行う。c1_LinearScan.cpp <-- LinearScanレジスタ割り付けc1_LinearScan.hppc1_MacroAssembler.hpp <-- architecture依存のAsm出力用マクロ(Assember向けpsuedo code)c1_Optimizer.cpp <-- HIR向け各種最適化 Eliminate (const expr|blocks|null checks)c1_Optimizer.hppc1_Runtime1.cpp <-- C1コンパイラのRuntime JVM本体のruntimeとの橋渡しc1_Runtime1.hppc1_ValueMap.cpp <-- HIR向け最適化 ValueNumberingの本体c1_ValueMap.hppc1_ValueSet.cpp <-- HIR向けADT @todoc1_ValueSet.hppc1_ValueStack.cpp <-- HIR向けADT @todoc1_ValueStack.hppc1_ValueType.cpp <-- C1コンパイラ内部のIR向け型定義c1_ValueType.hppc1_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/Shark

C1コンパイラ

-clientオプション指定時のコンパイラ

コンパイル時間が短く、メモリ使用量もそこそこ。 大した最適化をしない割にそこそこ高速に動作するコードを生成するのが特徴

JVM間の比較では、ベンチマーク結果がそこそこ高い。

C2コンパイラ

-serverオプション指定時のコンパイラ。今回は扱わない。

コンパイル時間はそこそこ長く、C1より高速に動作するコードを生成する。 また、コンパイル時のメモリも大きく消費する。

詳細は、vm/opto参照。C1とは中間言語が異なり、Idealと呼ばれる中間言語。

Sharkコンパイラ

使い方はまだちゃんと調べてない。

コンパイル前の動作

4

Page 9: OpenJDK HotSpot C1Compiler Overview

LLVMと連携してJITコンパイルを行う. JITコンパイラをC1/C2ではなく、LLVMを使うということ。JVMの制御はそのまま。

Sharkは、method単位でBytecodeをBitcodeに変換したのち、 LLVMに渡してJITコンパイルする。

LLVMにBitcodeを渡す際に何も小細工しないので、脱仮想化とかEscapeAnalysisとかさっぱり LLVMのBitcodeにMetadataを埋め込んで、 (たとえば、このcallはこのメソッドに脱仮想化候補だとか、allocaはstack/register割り付け可能だとか) LLVMのJITコンパイラ起動時、上記metadata用最適化パスをオプションで渡せば連携できるはず。

いろいろと夢広がる。 現在は、対応アーキテクチャを増やすために使っている ex) ARM PowerPC PTX CBackend

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 objectvoid 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;

JITコンパイラが呼ばれる仕組み

5

Page 10: OpenJDK HotSpot C1Compiler Overview

int c2_count = 1;

_compilers[1] = new SharkCompiler();#endif // SHARK

JITコンパイルの入出力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のカウントアップ:

JITコンパイルの入出力

6

Page 11: OpenJDK HotSpot C1Compiler Overview

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); \

JITコンパイルの入出力

7

Page 12: OpenJDK HotSpot C1Compiler Overview

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_thread__=0x806d000) 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=..., branch_bci=-1, bci=-1, 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, branch_bcp=0x0) 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, thread); (gdb)#4 0x005daced in InterpreterRuntime::frequency_counter_overflow (thread=0x806d000, branch_bcp=0x0) 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()

//詳細は"コンパイラとバーチャルマシン"っていう書籍が図入りで説明している

JITコンパイルの入出力

8

Page 13: OpenJDK HotSpot C1Compiler Overview

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 trace

gdb stack trace

// topからdownしていきます#6 0xb5fef92c in ?? () <-- template intepreter経由なのでこれ以上終えない (gdb) down#5 0x005daced in InterpreterRuntime::frequency_counter_overflow (thread=0x806d000, branch_bcp=0x0) 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, branch_bcp=0x0) 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, thread); (gdb)#3 0x004cba19 in NonTieredCompPolicy::event (this=0x807a738, method=..., inlinee=..., branch_bci=-1, bci=-1, 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_thread__=0x806d000) 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_method=..., hot_count=166, 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=..., hot_count=166, 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;

JITコンパイルする際のしきい値

9

Page 14: OpenJDK HotSpot C1Compiler Overview

InterpreterProfileLimit = ((CompileThreshold * InterpreterProfilePercentage) / 100)<< number_of_noncount_bits;

// When methodData is collected, the backward branch limit is compared against a // methodData counter, rather than an InvocationCounter. In the former case, we // don't 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 - InterpreterProfilePercentage)) / 100; } else { InterpreterBackwardBranchLimit = ((CompileThreshold * OnStackReplacePercentage) / 100) << number_of_noncount_bits; }

ちなみに、clientの場合

InterpreterInvocationLimit = 12000

InterpreterBackwardBranchLimit = 111960

serverの場合

InterpreterInvocationLimit = 80000

InterpreterBackwardBranchLimit = 10700

clientコンパイラのしきい値って、メソッド呼び出しが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()

C1コンパイラの内部構造

10

Page 15: OpenJDK HotSpot C1Compiler Overview

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 handler

Bytecodeから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);// iterateBytecodes::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;}

BytecodeからHIRへの変換

11

Page 16: OpenJDK HotSpot C1Compiler Overview

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の変換部

if_icmpXXの変換

12

Page 17: OpenJDK HotSpot C1Compiler Overview

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 we'd 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展開を試行する。

invokeの変換

13

Page 18: OpenJDK HotSpot C1Compiler Overview

また、invokevirtualやinvokeinterfaceのdevirtual化(invokespecialとみなす)を行い、 積極的にinline展開を試行する

invoke

switch (code) {case Bytecodes::_nop : /* nothing to do */ break;...case Bytecodes::_invokevirtual : // fall throughcase Bytecodes::_invokespecial : // fall throughcase Bytecodes::_invokestatic : // fall throughcase Bytecodes::_invokedynamic : // fall throughcase Bytecodes::_invokeinterface: invoke(code); break;

GraphBuilder::invoke(Bytecodes::Code)

ciMethod* cha_monomorphic_targetciMethod* 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)

dependencydependencyは、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" receiver

dependency

14

Page 19: OpenJDK HotSpot C1Compiler Overview

// 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 {

dependency

15

Page 20: OpenJDK HotSpot C1Compiler Overview

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:14491449 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:14641464 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:14491449 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:14641464 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:20632063 if (deps.spot_check_dependency_at(changes) != NULL) {#3 0x005b6030 in instanceKlass::mark_dependent_nmethods (this=0xb212bde0, changes=...)

dependency

16

Page 21: OpenJDK HotSpot C1Compiler Overview

at /home/elise/language/openjdk6/hotspot/src/share/vm/oops/instanceKlass.cpp:14061406 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:641641 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:11821182 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:17271727 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:15061506 add_to_hierarchy(k, CHECK); // No exception, but can block#8 0x00823df1 in SystemDictionary::resolve_from_stream (class_name=..., class_loader=..., protection_domain=..., st=0xfd0980, verify=true, __the_thread__=0x806cc00) at /home/elise/language/openjdk6/hotspot/src/share/vm/classfile/systemDictionary.cpp:11381138 define_instance_class(k, THREAD);#9 0x0064c2d9 in jvm_define_class_common (env=0x806cd3c, name=0xfd0eac "C", loader=0xfd0fac, buf=0xa1c16508 "\312\376\272\276", len=316, pd=0xfd0f98, source=0xfd0aac "file:/home/elise/language/java/sample6/", verify=1 '\001', __the_thread__=0x806cc00)at /home/elise/language/openjdk6/hotspot/src/share/vm/prims/jvm.cpp:864864 CHECK_NULL);#10 0x0064c7d6 in JVM_DefineClassWithSource (env=0x806cd3c, name=0xfd0eac "C", loader=0xfd0fac, buf=0xa1c16508 "\312\376\272\276", len=316, pd=0xfd0f98, source=0xfd0aac "file:/home/elise/language/java/sample6/")at /home/elise/language/openjdk6/hotspot/src/share/vm/prims/jvm.cpp:884884 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, name=0xfd0fa8, data=0xfd0fa4, offset=0, length=316, pd=0xfd0f98, source=0xfd0f94) at ../../../src/share/native/java/lang/ClassLoader.c:151151 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(),

dependency

17

Page 22: OpenJDK HotSpot C1Compiler Overview

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

Page 23: OpenJDK HotSpot C1Compiler Overview

product(bool, C1ProfileCalls, true, \ "Profile calls when generating code for updating MDOs") \ \// deadproduct(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();

HIR から LIR への変換

19

Page 24: OpenJDK HotSpot C1Compiler Overview

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 blockfalse blocktrue_instructionfalse_instruction

a = (b > c) ? b : c;

BB: if ... then BBn false BBmBBn: const n goto BBsBBm: const m goto BBsBBs: 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 B74B73: <-- delete v188 = goto B74 <-- delete

C1コンパイラのHIR最適化

20

Page 25: OpenJDK HotSpot C1Compiler Overview

B72: <-- delete v187 = goto B74 <-- deleteB74: i189 = [i179,i180] <-- replace v190 = goto B70B70: 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);

GlobalValueNumbering

21

Page 26: OpenJDK HotSpot C1Compiler Overview

if (instr->hash() != 0) { Value f = current_map()->find_insert(instr); <-- ここがキモ if (f != instr) { assert(!f->has_subst(), "can't have a substitution"); instr->set_subst(f); subst_count++; } } }

// remember value map for successors set_value_map_of(block, current_map());}

EscapeAnalysisc1コンパイラからは呼ばれません!!!

wimmerの資料によるとclientからも呼ばれるようだが、昔のJDKもしくはSun JDKだけなのかもしれない。

Serverだとbreakを確認できた。

Sharkだとifdef切ってるけど、多分動かないんだろうと推測

C1コンパイラのLIR最適化

EdgeMoveOptimizerc1_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 みたいな処理

ControlFlowOptimizerc1_LiearScan.cpp::ControlFlowOptimize::optimize(BlockList* code)

ControlFlowOptimizer optimizer = ControlFlowOptimizer();

// push the OSR entry block to the end so that we're not jumping over it.

EscapeAnalysis

22

Page 27: OpenJDK HotSpot C1Compiler Overview

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 3138 1 i20 i15 * i1941 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_01: iconst_1

sample code factIf

23

Page 28: OpenJDK HotSpot C1Compiler Overview

2: if_icmple 175: iload_06: iload_07: iconst_18: isub9: invokestatic #3; //Method factIf:(I)I12: imul13: istore_114: goto 1917: iconst_118: istore_119: iload_120: ireturn

factIf HIR:

static jint Fact.factIf(jint)B9: i4 = method parameter v32 = std entry B0B0: i5 = 1 v6 = if i4 <= i5 then B2 else B1B1: i7 = 1 i8 = i4 - i7 v15 = if i8 <= i7 then B7 else B6B7: i21 = 1 v22 = goto B8B6: i16 = 1 i17 = i8 - i16 i18 = invokestatic(i17) Fact.factIf(I)I i19 = i8 * i18 v20 = goto B8B8: i23 = [i19,i21] v24 = goto B4B4: i25 = i4 * i23 v26 = goto B3B2: i27 = 1 v28 = goto B3B3: i29 = [i25,i27] i30 = ireturn i29

factIf HIR CFG

factIf HIR DFG

sample code factIf

24

Page 29: OpenJDK HotSpot C1Compiler Overview

factIf Optimized HIR

static jint Fact.factIf(jint)B9: i4 = method parameter v32 = std entry B0B0: i5 = 1 v6 = if i4 <= i5 then B2 else B1B1: // deleted i7 = 1 i8 = i4 - i7 v15 = if i8 <= i7 then B7 else B6B7: // deleted i21 = 1 v22 = goto B8B6: // deleted i16 = 1 i17 = i8 - i5 // replaced i17 = i8 - i16 i18 = invokestatic(i17) Fact.factIf(I)I i19 = i8 * i18 v20 = goto B8B8: i23 = [i19,i5] // replaced i23 = [i19,i21] // deleted v24 = goto B4 // deleted B4: i25 = i4 * i23 v26 = goto B3B2: // deleted i27 = 1 v28 = goto B3B3: 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 B0label B0 cmp R41 1 branch LE B2 branch AL B1label B1 move R41 R42

sample code factIf

25

Page 30: OpenJDK HotSpot C1Compiler Overview

sub R42 1 R42 cmp R42 1 branch LE B7 branch AL B6label 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 B8label B7 move 1 R46 branch AL B8label B8 move R46 R47 mul R47 R41 move R47 R48 branch AL B3label B2 move 1 B48 branch AL B3label B3 move R48 eax return eax

sample code factIf

26

Page 31: OpenJDK HotSpot C1Compiler Overview

B9: std_entry move ecx R41 branch AL B0

B0: cmp R41 1 branch LE B2 branch AL B1

B2: move 1 B48 branch AL B3

B1: move R41 R42 sub R42 1 R42 cmp R42 1 branch LE B7 branch AL B6

B3: move R48 eax return eax

B7: move 1 R46 branch AL B8

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

B8: move R46 R47 mul R47 R41 move R47 R48 branch AL B3

LinearScanレジスタ割り付け

sample code factIf

27

Page 32: OpenJDK HotSpot C1Compiler Overview

LIRでも色々冗長な命令を削る

Before Code Generation

# # # # #label B9 std_entry // deleted move ecx R41 // deleted branch AL B0label B0 cmp ecx 1 // replaced cmp R41 1 branch LE B2 // deleted branch AL B1label 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 B6label 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 B8label B7 move 1 eax // replaced move 1 R46 // deleted branch AL B8label 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 // addlabel B2 move 1 eax // replaced move 1 B48 // deleted branch AL B3label 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++) {

sample code factFor

28

Page 33: OpenJDK HotSpot C1Compiler Overview

p = p * i; } return p;}

factFor Bytecode

public static int factFor(int);Code:0: iconst_11: istore_12: iconst_13: istore_24: iload_25: iload_06: if_icmpgt 199: iload_110: iload_211: imul12: istore_113: iinc 2, 116: goto 419: iload_120: ireturn

factFor HIR

static jint Fact.factFor(jint)B4: i4 = method parameter v17 = std entry B0B0: i5 = 1 v7 = goto B1B1: i8 = [i5, i11] i9 = [i5, i13] v10 = if i9 > i4 then B3 else B2B2: i11 = i8 * i9 i12 = 1 i13 = i9 + i12 v14 = goto B1B3: i15 = ireturn i8

factFor HIR CFG

factFor HIR DFG

factFor Optimized HIR

sample code factFor

29

Page 34: OpenJDK HotSpot C1Compiler Overview

static jint Fact.factFor(jint)B4: i4 = method parameter v17 = std entry B0B0: i5 = 1 v7 = goto B1B1: i8 = [i5, i11] i9 = [i5, i13] v10 = if i9 > i4 then B3 else B2B2: i11 = i8 * i9 // deleted i12 = 1 i13 = i9 + i5 // replaced i13 = i9 + i12 v14 = goto B1B3: i15 = ireturn i8

factFor translate HIR to LIR

# # # #label B4 std_entry move ecx R41 branch AL B0label B0 move 1 R43 move 1 R42 branch AL B1label B1 cmp R43 R41 branch GT B3 branch AL B2label 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 B1label B3 move R42 eax return eax

sample code factFor

30

Page 35: OpenJDK HotSpot C1Compiler Overview

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

B3 move R42 eax return eax

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

LenearScanレジスタ割り付け

Before Code Generation

# # # #label B4 std_entry // deleted move ecx R41 // deleted branch AL B0label B0 move 1 eax // replaced move 1 R43 move 1 esi // replaced move 1 R42 branch AL B1

sample code factFor

31

Page 36: OpenJDK HotSpot C1Compiler Overview

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 B2label B3 move esi eax // replaced move R42 eax return eax

参考文献SSA Form for the Java HotSpot™ Client Compiler

Christian WimmerApril 2009Sun Microsystems, Inc.Institute for System SoftwareJohannes Kepler University Linz, AustriaDepartment of Computer ScienceUniversity of California, Irvine

Linear Scan Register Allocation

Christian WimmerLinear Scan Register Allocationfor the Java HotSpot™ Client CompilerA thesis submitted in partial satisfaction ofthe requirements for the degree ofMaster of Science(Diplom-Ingenieur)

Design of the Java HotSpotTM Client Compiler for Java 6

Design of the Java HotSpotTM ClientCompiler for Java 6THOMAS KOTZMANN, CHRISTIAN WIMMERand HANSPETER MO¨ SSENBO¨ CKJohannes Kepler University LinzandTHOMAS RODRIGUEZ, KENNETH RUSSELL, and DAVID COXSun Microsystems, Inc.

Escape Analysis in the Context of Dynamic Compilation and Deoptimization

Thomas KotzmannEscape Analysis in the Context ofDynamic Compilation and DeoptimizationA PhD thesissubmitted in partial satisfaction of the requirements for the degree ofDoctor of Technical SciencesInstitute for System SoftwareJohannes Kepler University Linz

参考文献

32

Page 37: OpenJDK HotSpot C1Compiler Overview

Indices and tables• genindex

• modindex

• search

Indices and tables

33