Upgrade to Pro — share decks privately, control downloads, hide ads and more …

コンパイル結果を観察してSwift の効率的なエラー処理 メカニズムを紐解く

コンパイル結果を観察してSwift の効率的なエラー処理 メカニズムを紐解く

Swift言語におけるエラー処理のメカニズムは、他のプログラミング言語と共通する命名や概念を持ちながら、その実装と性能の特性において独自のアプローチを取っています。本トークでは、Swiftのエラー処理の仕組みを具体的なコード例とそのコンパイル結果を通じて解説し、他の言語との比較を行います。

発表では、まずシンプルな例を通じて、エラー処理付きの関数がどのようにコンパイルされるかを示します。特に、throwキーワードの周辺で行われる処理、つまり通常の戻り値処理に加えたエラーオブジェクトの伝達、およびtry周辺でのエラー検知と分岐処理に焦点を当てます。

そしてコンパイル結果の観察から、Swiftが如何にして他言語と類似の構文を提供しながら、実行時の負荷を軽減しているかを明らかにします。比較対象としてJavaを挙げて、類似の構文を提供しながらもその実装方法と実行時の性能に違いがある点を簡潔に解説します。

このトークはさまざまなエラー処理方法を一通り学んだSwift中級者へ特にお勧めできるものです。
このトークを通じてtryを利用したエラー処理の性質をより深く理解し、さまざまあるエラー処理方法の中から状況に応じた適切な方法を選択できるようになると考えています。

ryu raptor

August 23, 2024
Tweet

Other Decks in Programming

Transcript

  1. 4XJGUͷΤϥʔॲཧ ͓͞Β͍ func someFunc() throws { throw SomeError() } do

    { try someFunc() } catch { print(error) } UISPXTΤϥʔΛૹग़͢Δؔ਺ͷϚʔΫ UISPXΤϥʔΛૹग़͢Δ EPΤϥʔͷՄೳੑ͕͋ΔॲཧϒϩοΫ USZΤϥʔΛૹग़͢ΔࣜͷϚʔΫ DBUDIΤϥʔॲཧͷϒϩοΫ
  2. 4XJGUͷΤϥʔॲཧ͸ಛघ υΩϡϝϯτʹ΋ͦ͏ॻ͔Ε͍ͯΔ Error handling in Swift resembles exception handling in

    other languages, with the use of the try, catch and throw keywords. Unlike exception handling in many languages — including Objective-C — error handling in Swift doesn’t involve unwinding the call stack, a process that can be computationally expensive. As such, the performance characteristics of a throw statement are comparable to those of a return statement. IUUQTEPDTTXJGUPSHTXJGUCPPLEPDVNFOUBUJPOUIFTXJGUQSPHSBNNJOHMBOHVBHFFSSPSIBOEMJOH 4XJGUͰͷΤϥʔॲཧ͸USZ΍DBUDI UISPXͷར༻ͳͲଞͷݴޠͱࣅ͍ͯΔ͕ɺ 0CKFDUJWF$ΛؚΉଞͷݴޠͱҧͬͯ தུ ܭࢉྔͷଟ͍ॲཧΛߦΘͳ͍ɻ ͦͷͨΊUISPXจͷੑೳಛੑ͸SFUVSOจͷͦΕʹඖఢ͢Δɻ
  3. αϯϓϧίʔυ struct MyError: Error {} func randomlyThrows() throws -> Int

    { if Double.random(in: 0..<1) < 0.5 { throw MyError() } else { return 0 } } func main() -> Int { do { try randomlyThrows() } catch { return -1 } return 0 }
  4. *3·ͰίϯύΠϧ͢Δ 4*-ͱ*3ͱBTN w 4XJGUͷίʔυ͸ҎԼͷॱͰίϯύΠϧ͞ΕΔ ‎4XJGUதؒݴޠ 4*-  ‎--7.தؒදݱ *3 

    ‎ωΠςΟϒίʔυ BTN  w 4*-ͷஈ֊Ͱ͸Τϥʔ͸·ͩந৅Խ͞Εͨ·· 㾎*3Ͱ͸͡ΊͯͦͷϕʔϧͷཪଆΛ೷͚Δ --7.ɿ4XJGUίϯύΠϥͷόοΫΤϯυίϯύΠϥ BTNɿ"4TF.CMZΞηϯϒϦݴޠ
  5. %swift.error = type opaque %TSd = type <{ double }>

    %TSnySdG = type <{ %TSd, %TSd }> %TSn = type <{}> %TSn.0 = type <{}> %swift.metadata_response = type { %swift.type*, i64 } %swift.vwtable = type { i8*, i8*, i8*, i8*, i8*, i8*, i8*, i8*, i64, i64, i32, i32 } %swift.tuple_type = type { %swift.type, i64, i8*, [0 x %swift.tuple_element_type] } %swift.tuple_element_type = type { %swift.type*, i32 } @"$sSdN" = external global %swift.type, align 8 @"$sSdSLsWP" = external global i8*, align 8 @".str.39.Range requires lowerBound <= upperBound" = private unnamed_addr constant [40 x i8] c"Range requires lowerBound <= upperBound\00" @".str.39.Swift/arm64e-apple-macos.swiftinterface" = private unnamed_addr constant [40 x i8] c"Swift/arm64e-apple- macos.swiftinterface\00" @".str.11.Fatal error" = private unnamed_addr constant [12 x i8] c"Fatal error\00" @"$sS2dSBsWL" = linkonce_odr hidden global i8** null, align 8 @"$sSdSBsMc" = external global %swift.protocol_conformance_descriptor, align 4 @"$ss6UInt64VABs17FixedWidthIntegersWL" = linkonce_odr hidden global i8** null, align 8 @"$ss6UInt64Vs17FixedWidthIntegersMc" = external global %swift.protocol_conformance_descriptor, align 4 @"$ss6UInt64VN" = external global %swift.type, align 8 @"$s19ErrorHandlingSample02MyA0VACs0A0AAWL" = linkonce_odr hidden global i8** null, align 8 @"$ss5ErrorMp" = external global %swift.protocol, align 4 @"got.$ss5ErrorMp" = private unnamed_addr constant %swift.protocol* @"$ss5ErrorMp" @"$ss5ErrorP7_domainSSvgTq" = external global %swift.method_descriptor, align 4 @"got.$ss5ErrorP7_domainSSvgTq" = private unnamed_addr constant %swift.method_descriptor* @"$ss5ErrorP7_domainSSvgTq" @"$ss5ErrorP5_codeSivgTq" = external global %swift.method_descriptor, align 4 @"got.$ss5ErrorP5_codeSivgTq" = private unnamed_addr constant %swift.method_descriptor* @"$ss5ErrorP5_codeSivgTq" @"$ss5ErrorP9_userInfoyXlSgvgTq" = external global %swift.method_descriptor, align 4 @"got.$ss5ErrorP9_userInfoyXlSgvgTq" = private unnamed_addr constant %swift.method_descriptor* @"$ss5ErrorP9_userInfoyXlSgvgTq" @"$ss5ErrorP19_getEmbeddedNSErroryXlSgyFTq" = external global %swift.method_descriptor, align 4 @"got.$ss5ErrorP19_getEmbeddedNSErroryXlSgyFTq" = private unnamed_addr constant %swift.method_descriptor* @"$ss5ErrorP19_getEmbeddedNSErroryXlSgyFTq" @"$s19ErrorHandlingSample02MyA0Vs0A0AAMcMK" = internal global [16 x i8*] zeroinitializer @"$s19ErrorHandlingSample02MyA0Vs0A0AAMc" = hidden constant { i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i16, i32, i32 } { i32 add (i32 trunc (i64 sub (i64 ptrtoint (%swift.protocol** @"got.$ss5ErrorMp" to i64), i64 ptrtoint ({ i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i16, i16, i32, i32 }* @"$s19ErrorHandlingSample02MyA0Vs0A0AAMc" to i64)) i32), i32 1), i32 trunc (i64 sub (i64 ptrtoint (<{ i32, i32, i32, i32, i32, i32, i32 }>* @"$s19ErrorHandlingSample02MyA0VMn" to
  6. *3Λൈਮ ॏཁͳ෦෼͸͜͜UISPX 46: ; preds = %23 call swiftcc void

    @"$s19ErrorHandlingSample02MyA0VACycfC"() %47 = call i8** @"$s19ErrorHandlingSample02MyA0VACs0A0AAWl"() #10 %48 = call swiftcc { %swift.error*, %swift.opaque* } @swift_allocError(%swift.type* bitcast (i64* getelementptr inbounds (<{ i8*, i8**, i64, <{ i32, i32, i32, i32, i32, i32, i32 }>* }>, <{ i8*, i8**, i64, <{ i32, i32, i32, i32, i32, i32, i32 }>* }>* @"$s19ErrorHandlingSample02MyA0VMf", i32 0, i32 2) to %swift.type*), i8** %47, %swift.opaque* null, i1 false) #6 %49 = extractvalue { %swift.error*, %swift.opaque* } %48, 0 %50 = extractvalue { %swift.error*, %swift.opaque* } %48, 1 %51 = bitcast %swift.opaque* %50 to %T19ErrorHandlingSample02MyA0V* store %swift.error* %49, %swift.error** %1, align 8 call swiftcc void @swift_willThrow(i8* swiftself undef, %swift.error** noalias nocapture readonly swifterror dereferenceable(8) %1) #6 store %swift.error* null, %swift.error** %1, align 8 store %swift.error* %49, %swift.error** %1, align 8 ret i64 undef func randomlyThrows() throws -> Int { if Double.random(in: 0..<1) < 0.5 { throw MyError() } else { return 0 } }
  7. *3Λൈਮ ॏཁͳ෦෼͸͜͜UISPX func randomlyThrows() throws -> Int { if Double.random(in:

    0..<1) < 0.5 { throw MyError() } else { return 0 } } let throwingError: any Error = MyError() STORE_REGISTER(nil, SWIFT_ERROR_REGISTER) STORE_REGISTER(throwingError, SWIFT_ERROR_REGISTER) return UNDEFINED STORE_REGISTER(value, registerId): Ϩδελʹ஋Λ୅ೖ ٖࣅ4XJGUίʔυ ٖࣅؔ਺ ٖࣅఆ਺ SWIFT_ERROR_REGISTER: SwiftͰࢦఆ͞Ε͍ͯΔΤϥʔ༻Ϩδελ UNDEFINED: ࢦఆͷܕͰ͸͋Δ͕ɺ஋͕ෆఆͳ΋ͷ ΤϥʔΛࢦఆͷϨδελʹ୅ೖ ͠ɺద౰ͳ஋ΛSFUVSO͍ͯ͠Δ ͚ͩʂ
  8. *3Λൈਮ ॏཁͳ෦෼͸͜͜USZ entry: %swifterror = alloca swifterror %swift.error*, align 8

    store %swift.error* null, %swift.error** %swifterror, align 8 %error.debug = alloca %swift.error*, align 8 %0 = bitcast %swift.error** %error.debug to i8* call void @llvm.memset.p0i8.i64(i8* align 8 %0, i8 0, i64 8, i1 false) %1 = call swiftcc i64 @"$s19ErrorHandlingSample14randomlyThrowsSiyKF"(%swift.refcounte d* swiftself undef, %swift.error** noalias nocapture swifterror dereferenceable(8) %swifterror) %2 = load %swift.error*, %swift.error** %swifterror, align 8 %3 = icmp ne %swift.error* %2, null br i1 %3, label %8, label %4 4: ; preds = %entry %5 = phi i64 [ %1, %entry ] br label %6 8: ; preds = %entry %9 = phi %swift.error* [ %2, %entry ] store %swift.error* null, %swift.error** %swifterror, align 8 %10 = call %swift.error* @swift_errorRetain(%swift.error* %9) #6 store %swift.error* %9, %swift.error** %error.debug, align 8 call void @swift_errorRelease(%swift.error* %9) #6 call void @swift_errorRelease(%swift.error* %9) #6 br label %6 func main() -> Int { do { try randomlyThrows() } catch { return -1 } return 0 }
  9. *3Λൈਮ ॏཁͳ෦෼͸͜͜USZ func main() -> Int { do { try

    randomlyThrows() } catch { return -1 } return 0 } _ = randomlyThrows() let thrownError = LOAD_REGISTER(SWIFT_ERROR_REGISTER) if let thrownError { return -1 } return 0 ٖࣅ4XJGUίʔυ LOAD_REGISTER(registerId): Ϩδελͷ஋Λऔಘ ٖࣅؔ਺ ࢦఆͷϨδελʹΤϥʔ͕ೖͬ ͍ͯͨΒΤϥʔॲཧΛ࣮ߦ
  10. ଞͷݴޠ͸Ͳ͏ͩΖ͏ʁ +BWBͱൺֱ IUUQTEPDTPSBDMFDPNKBWBTFUVUPSJBMFTTFOUJBMFYDFQUJPOTEF fi OJUJPOIUNM When an error occurs within

    a method, the method creates an object and hands it off to the runtime system. … The runtime system searches the call stack for a method that contains a block of code that can handle the exception. … When an appropriate handler is found, the runtime system passes the exception to the handler. Τϥʔ͕ൃੜ͢ΔͱϥϯλΠϜʹΤϥʔΦϒδΣΫτ͕౉͞ΕΔɻ தུ ϥϯ λΠϜ͸ίʔϧελοΫΛḪΓɺΤϥʔॲཧͰ͖Δద੾ͳϒϩοΫΛݕࡧ͠ɺ தུ ݟ͔ͭΕ͹ͦͷϒϩοΫʹྫ֎ ΤϥʔΦϒδΣΫτ Λ౉͢ɻ DBUDIՄೳͳϒϩοΫΛݕࡧ͢ΔϥϯλΠϜΦʔόʔϔου͕͋Δʂ