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
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy