yhara.jp

Recent Posts

Rustと例外

Tech

LLVMと例外

Rustは言語仕様としては例外を持ちませんが、panic!やDropの実装のためにC++の例外相当の機構を利用しています。 参考:

確かに、「panic時にも確実にDropを行う」はC++のtry~finallyに似ていますね。

単に似ているだけでなく、rfcs#2945を見るとC++の例外(およびそれに互換したもの)とRustのpanicが混ざるケースも考慮されているようです。

rust_eh_personality

rustcの吐く.llを見ると、@rust_eh_personalityという関数があることがわかります。

declare noundef i32 @rust_eh_personality(i32, i32 noundef, i64, %"unwind::libunwind::_Unwind_Exception"*, %"unwind::libunwind::_Unwind_Context"*) unnamed_addr #6

unstable bookを見ると、3種類の実装がある(あった)ことがわかります。

- eh_personality: libpanic_unwind/emcc.rs (EMCC)
- eh_personality: libpanic_unwind/gcc.rs (GNU)
- eh_personality: libpanic_unwind/seh.rs (SEH)

EMCCはemscripten(wasm環境)、SEHはWindows用、GNUがそれ以外だと思われます。

現在ではrustのリポジトリにlibpanic_unwindというモジュールはなく、代わりに以下があるようです。

各スタックフレームはpersonality functionへの参照を持ちます。unwind時、スタックフレーム毎にpersonality functionが呼び出されます。このときpersonality functionは発生した例外の情報を受け取り、「unwindを続けるか止めるか」を返します。

例えばC++でいうと、発生した例外が当該フレームでcatchされている場合は「unwindを止める」、されていない場合は「unwindを続ける」になります。

「unwindを止める」、つまりこのフレームが例外を処理することが確定したあと、unwinderはcleanup phaseに入り、personality functionをもう一度(別の用途で)呼び出します。cleanup phaseでは、personality functionはどのようなcleanup処理が必要かを決定します。

catch_unwind

Rustのpanicはあまりcatchするものではありませんが、本当に必要な場合はcatch_unwindで止めることができます。catch_unwindは引数として渡されたクロージャを実行し、そこでunwindが発生した場合にそれをcatchします。

catch_unwindの実装を見ると、最終的にintrinsics::r#tryを呼ぶことがわかります。intrinsicsはRustで記述できないほど低レベルな関数で、.llを見ると以下のような実装であることがわかります。

define internal i32 @__rust_try(ptr %0, ptr %1, ptr %2) unnamed_addr #3 personality ptr @rust_eh_personality {
entry-block:
  invoke void %0(ptr %1)
          to label %then unwind label %catch

then:                                             ; preds = %entry-block
  ret i32 0 

catch:                                            ; preds = %entry-block
  %3 = landingpad { ptr, i32 }                    
          catch ptr null
  %4 = extractvalue { ptr, i32 } %3, 0
  call void %2(ptr %1, ptr %4) 
  ret i32 1
} 

Personality function

https://itanium-cxx-abi.github.io/cxx-abi/abi-eh.html を参考に、rust_eh_personality_implを読んでいきます。

        unsafe extern "C" fn rust_eh_personality_impl(
            version: c_int,
            actions: uw::_Unwind_Action,
            _exception_class: uw::_Unwind_Exception_Class,
            exception_object: *mut uw::_Unwind_Exception,
            context: *mut uw::_Unwind_Context,
        ) -> uw::_Unwind_Reason_Code {
  • version: ABIのバージョン。1固定。
  • actions: このPHがなんのために呼び出されたか。以下のビットマスク。
    • static const _Unwind_Action _UA_SEARCH_PHASE = 1; (検索フェーズ。_URC_HANDLER_FOUNDまたは_URC_CONTINUE_UNWINDを返す)
    • static const _Unwind_Action _UA_CLEANUP_PHASE = 2;(cleanupフェーズ。_URC_CONTINUE_UNWINDまたは_URC_INSTALL_CONTEXTを返す)
    • static const _Unwind_Action _UA_HANDLER_FRAME = 4;
      • 検索フェーズで「このフレームがcatchするよ」と伝えたとき、cleanupフェーズで当該フレームを処理するときにこのフラグが立つ。
    • static const _Unwind_Action _UA_FORCE_UNWIND = 8;
      • cleanupフェーズで使われる。このフラグが立っているとき、この例外をキャッチしてはいけない(longjmpやスレッドの終了時に使われる?)
  • _exception_class: 例外の型を示す値。Rustでは不使用。
  • exception_object: 例外オブジェクトへのポインタ。
  • context: unwind context
            if version != 1 {
                return uw::_URC_FATAL_PHASE1_ERROR;
            }

バージョンのチェック。

            let eh_action = match find_eh_action(context) {
                Ok(action) => action,
                Err(_) => return uw::_URC_FATAL_PHASE1_ERROR,
            };

find_eh_actionはこれ

検索フェーズ

            if actions as i32 & uw::_UA_SEARCH_PHASE as i32 != 0 {
                match eh_action {
                    EHAction::None | EHAction::Cleanup(_) => uw::_URC_CONTINUE_UNWIND,
                    EHAction::Catch(_) | EHAction::Filter(_) => uw::_URC_HANDLER_FOUND,
                    EHAction::Terminate => uw::_URC_FATAL_PHASE1_ERROR,
                }
            } 

cleanupフェーズ

                match eh_action {
                    EHAction::None => uw::_URC_CONTINUE_UNWIND,
                    // FORCE_UNWINDフラグが立っていたら何もせずunwindを続ける
                    EHAction::Filter(_) if actions as i32 & uw::_UA_FORCE_UNWIND as i32 != 0 => uw::_URC_CONTINUE_UNWIND,
                    EHAction::Cleanup(lpad) | EHAction::Catch(lpad) | EHAction::Filter(lpad) => {
                        uw::_Unwind_SetGR(
                            context,
                            UNWIND_DATA_REG.0,
                            exception_object as uintptr_t,
                        );
                        uw::_Unwind_SetGR(context, UNWIND_DATA_REG.1, 0);
                        // SetIPしてからINSTALL_CONTEXTを返すことでlandingpadに処理を進める
                        uw::_Unwind_SetIP(context, lpad);
                        uw::_URC_INSTALL_CONTEXT
                    }
                    EHAction::Terminate => uw::_URC_FATAL_PHASE2_ERROR,
                }
            }
        }

More posts

Posts

(more...)

Articles

(more...)

Category

Ads

About

About the author