OpenJDK Internals 1.0 documentation

On-Stack Replacement

«  JVMソースコードリーディングの会 第5回   ::   Contents   ::   OpenJDK opto macro  »

On-Stack Replacement

OSR

OSRは、ループ実行した際に、ループを内包したmethodをJITコンパイルして、実行途中からJITコンパイルしたコードに遷移する。

OSRでJITコンパイルした場合、methodの入り口に、OSR用の退避コードが挿入される。

OSRは、実現するだけであれば、非常に簡単であり、

OSREntryのframe pointerとthisポインタの位置を保存しておくだけでよいはず。

特定のレジスタに乗せておくというルール制御でも可能だろう。

トレードオフとなるところは、上記重要なアドレス以外を

いかに保存しておくかだと思う。OSRで切り替わるコードが遅くてもよいのであれば、

チェックポイントにおいて、変数はレジスタに退避しておいてよいはず。

でも性能を考えると、レジスタはフルに使いたい。この辺がトレードオフ。

レジスタをフルに使いたいなら、OSRの入れ替え対象のレジスタの意味を

フルで使いたいだけ覚えておき、退避する必要がある。

おそらくだが、ループ中にOSRのEntryを決め打ちで1ヶ所だけ作成し、

そのポイントでだけOSRを行うはず。

OSRのEntryを作成する場所の詳細はまだ不明。

たぶんループのheaderか、backedgeのどちらかのはず。

osrでJITコンパイルされたコードは、もし後で何回も呼ばれるようであれば、

再度JITコンパイルされる。再度JITコンパイルされた場合、OSR用のEntryは当然挿入されない。

OSRの参考資料

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

comment

// This code is dependent on the memory layout of the interpreter local
// array and the monitors. On all of our platforms the layout is identical
// so this code is shared. If some platform lays the their arrays out
// differently then this code could move to platform specific code or
// the code here could be modified to copy items one at a time using
// frame accessor methods and be platform independent.

OSRの調査

frame fr = thread->last_frame()

NEW_C_HEAP_ARRAY()

Copy::disjoint_words()

OSRの入り口

OnStackReplacementは、runtime/sharedRuntime.cpp::SharedRuntime::OSR_migration_begin()

OSRの出口

OSR付きでJITコンパイルされたメソッドの入り口に、migration_end()を呼び出す

OSR_migration_end

008   B1: #     B14 B2 <- BLOCK HEAD IS JUNK   Freq: 1
008     # stack bang
        PUSHL  EBP
        SUB    ESP,40   # Create frame
016     MOV    ESI,ECX
018     MOV    EBP,[ECX]        # int
01a     MOV    EBX,[ECX + #4]
01d     MOV    EAX.lo,[ESI + #8]        # long
        MOV    EAX.hi,[ESI + #8]+4
023     MOV    [ESP + #8],EAX
        MOV    [ESP + #12],EDX
02b     MOV    [ESP + #0],ECX
02e     CALL_LEAF,runtime  OSR_migration_end
        No JVM State Info

On-Stack Replacementを呼び出す前のインタプリタのコード

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

インタプリタのローカル領域を対比する。:

// Allocate temp buffer, 1 word per local & 2 per active monitor
int buf_size_words = max_locals + active_monitor_count*2;
intptr_t *buf = NEW_C_HEAP_ARRAY(intptr_t,buf_size_words);

// Copy the locals.  Order is preserved so that loading of longs works.
// Since there's no GC I can copy the oops blindly.
assert( sizeof(HeapWord)==sizeof(intptr_t), "fix this code");
Copy::disjoint_words((HeapWord*)fr.interpreter_frame_local_at(max_locals-1),
  (HeapWord*)&buf[0],
  max_locals);

c1のOSR

memo

c1 LIR Assembler
void LIR_Assembler::osr_entry() {

offsets()->set_value(CodeOffsets::OSR_Entry, code_offset());
BlockBegin* osr_entry = compilation()->hir()->osr_entry();
ValueStack* entry_state = osr_entry->state();
int number_of_locks = entry_state->locks_size();

// we jump here if osr happens with the interpreter
// state set up to continue at the beginning of the
// loop that triggered osr - in particular, we have
// the following registers setup:
//
// rcx: osr buffer
//

// build frame
ciMethod* m = compilation()->method();
__ build_frame(initial_frame_size_in_bytes());

comment

// OSR buffer is
//
// locals[nlocals-1..0]
// monitors[0..number_of_locks]
//
// locals is a direct copy of the interpreter frame so in the osr buffer
// so first slot in the local array is the last local from the interpreter
// and last slot is local[0] (receiver) from the interpreter
//
// Similarly with locks. The first lock slot in the osr buffer is the nth lock
// from the interpreter frame, the nth lock slot in the osr buffer is 0th lock
// in the interpreter frame (the method lock if a sync method)

// Initialize monitors in the compiled activation.
//   rcx: pointer to osr buffer
//
// All other registers are dead at this point and the locals will be
// copied into place by code emitted in the IR.

rcxのpointer to osr buffer以外の 全レジスタはdeadですと仮定すると。

でもそれってOSR対応メソッドのレジスタ割り付けはあほだってことですか。

«  JVMソースコードリーディングの会 第5回   ::   Contents   ::   OpenJDK opto macro  »