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

(swiftc) NonCopyableとライフタイム修飾子

Sponsored · SiteGround - Reliable hosting with speed, security, and support you can count on. →
Avatar for Himeshi Himeshi
October 27, 2026

(swiftc) NonCopyableとライフタイム修飾子

わいわいswiftc #46 の発表資料です
https://iosdiscord.connpass.com/event/407319/

Avatar for Himeshi

Himeshi

October 27, 2026

More Decks by Himeshi

Other Decks in Technology

Transcript

  1. こんばんは、ひめしです • 株式会社Qoncept エンジニア • iOS, Server-side Swift, Swift on

    Android • iOSDC Japan や try! Swift などiOS開発界隈によく出没 • 交通系シミュレーションゲーム “Simutrans” の日本版開発, 運営 • 最近は隠居気味
  2. 4 メモリの管理スタイル • Stack • 手動💪 (malloc / free, new

    / delete) • Reference Counting • Gerbage Collection • 所有権 + 借用
  3. 5 所有権モデルを実現する言語機能 • SE-0390: Noncopyable structs and enums • SE-0446:

    Nonescapable Types • (No SE doc) lifetime annotation fi • SE-0519: Ref and MutableRef types for safe, rst-class references
  4. 6 理解したい ① NonCopyable struct File: ~Copyable { let fd:

    Int32 consuming func close() { /* ... */ } } func bad() { let f = File(fd: 0) let g = f f.close() // ❌ 'f' consumed more than once }
  5. 7 理解したい ② lifetime修飾子 struct BV: ~Escapable { let p:

    UnsafeRawPointer @_lifetime(borrow p) // BV は p を borrow している間しか生きられない init(_ p: UnsafeRawPointer, _ i: Int) { self.p = p; ... } } struct NE: ~Escapable { let p: UnsafeRawPointer @_lifetime(borrow self) // 返す BV は self を borrow している間だけ有効 borrowing func getBV() -> BV { BV(p, i) } } @_lifetime(copy container) func bv_get_consume(container: NE) -> BV { return container.getBV() // ❌ lifetime-dependent value escapes its scope }
  6. 8 Compilation pipeline • Swift Code • AST • Typed

    AST • Raw SIL • Canonical SIL ❌ • Lowered SIL • LLVM IR • Binary
  7. 9 NonCopyable - AST • See Swift AST Explorer •

    Suppressed Type Syntaxなるものが見える
  8. 10 SILGen • Typed AST -> Raw SIL の変換をする段階 •

    後段でフロー解析をするためにマーカーが挿入される • 例: mark_unresolved_non_copyable_value • lifetime修飾子が絡む場合はこの段階で追加の処理が走る • LifetimeDependenceInsertion • LifetimeDependenceScopeFixup
  9. 11 Mandatory Pass Pipeline • Raw SIL -> Canonical SIL

    の変換過程で走るフロー解析 • 所有権の言語機能まわりで関係するcheck • MoveOnlyChecker • DiagnoseStaticExclusivity • LifetimeDependenceDiagnostics • ConsumeOperatorCopyableAddressChecker • ConsumeOperatorCopyableValuesChecker
  10. 12 理解したい ① NonCopyable struct File: ~Copyable { let fd:

    Int32 consuming func close() { /* ... */ } } func bad() { let f = File(fd: 0) let g = f f.close() // ❌ 'f' consumed more than once }
  11. 13 NonCopyable - Raw SIL // bad() // Isolation: unspecified

    sil hidden [ossa] @$s4main3badyyF : $@convention(thin) () -> () { bb0: %0 = alloc_box ${ let File }, let, name "f" // users: %24, %1 %1 = begin_borrow [lexical] [var_decl] %0 // users: %23, %2 %2 = project_box %1, 0 // users: %17, %14, %10 %3 = metatype $@thin File.Type // user: %9 %4 = integer_literal $Builtin.IntLiteral, 0 // user: %7 %5 = metatype $@thin Int32.Type // user: %7 // function_ref Int32.init(_builtinIntegerLiteral:) %6 = function_ref @$ss5Int32V22_builtinIntegerLiteralABBI_tcfC : $@convention(method) (Builtin.IntLiteral, @thin Int32.Type) -> Int32 // user: %7 %7 = apply %6(%4, %5) : $@convention(method) (Builtin.IntLiteral, @thin Int32.Type) -> Int32 // user: %9 // function_ref File.init(fd:) %8 = function_ref @$s4main4FileV2fdACs5Int32V_tcfC : $@convention(method) (Int32, @thin File.Type) -> @owned File // user: %9 %9 = apply %8(%7, %3) : $@convention(method) (Int32, @thin File.Type) -> @owned File // user: %10 store %9 to [init] %2 // id: %10 %11 = alloc_box ${ let File }, let, name "g" // users: %22, %12 %12 = begin_borrow [lexical] [var_decl] %11 // users: %21, %13 %13 = project_box %12, 0 // user: %16 %14 = mark_unresolved_non_copyable_value [no_consume_or_assign] %2 // user: %15 %15 = load [copy] %14 // user: %16 store %15 to [init] %13 // id: %16 %17 = mark_unresolved_non_copyable_value [no_consume_or_assign] %2 // user: %18 %18 = load [copy] %17 // user: %20 // function_ref File.close() %19 = function_ref @$s4main4FileV5closeyyF : $@convention(method) (@owned File) -> () // user: %20 %20 = apply %19(%18) : $@convention(method) (@owned File) -> () end_borrow %12 // id: %21 destroy_value %11 // id: %22 end_borrow %1 // id: %23 destroy_value %0 // id: %24 %25 = tuple () // user: %26 return %25 // id: %26 } // end sil function '$s4main3badyyF'
  12. 14 NonCopyable - Raw SIL %0 = alloc_box ${ let

    File }, let, name "f" // users: %24, %1 f の格納場所の準備 %1 = begin_borrow [lexical] [var_decl] %0 // users: %23, %2 %2 が格納場所 %2 = project_box %1, 0 // users: %17, %14, %10 %3 = metatype $@thin File.Type // user: %9 %4 = integer_literal $Builtin.IntLiteral, 0 // user: %7 let f = File(fd: 0) %5 = metatype $@thin Int32.Type // user: %7 // function_ref Int32.init(_builtinIntegerLiteral:) %6 = function_ref @$ss5Int32V22_builtinIntegerLiteralABBI_tcfC : $@convention(method) (Builtin.IntLiteral, @thin Int32.Type) -> Int32 // user: %7 %7 = apply %6(%4, %5) : $@convention(method) (Builtin.IntLiteral, @thin Int32.Type) -> Int32 // user: %9 // function_ref File.init(fd:) %8 = function_ref @$s4main4FileV2fdACs5Int32V_tcfC : $@convention(method) (Int32, @thin File.Type) -> @owned File // user: %9 %9 = apply %8(%7, %3) : $@convention(method) (Int32, @thin File.Type) -> @owned File // user: %10 store %9 to [init] %2 // id: %10 %11 = alloc_box ${ let File }, let, name "g" // users: %22, %12 g の格納場所の準備 %12 = begin_borrow [lexical] [var_decl] %11 // users: %21, %13 %13 が格納場所 %13 = project_box %12, 0 // user: %16
  13. 15 NonCopyable - Raw SIL %14 = mark_unresolved_non_copyable_value [no_consume_or_assign] %2

    // user: %15 %15 = load [copy] %14 // user: %16 fからgに移動 store %15 to [init] %13 // id: %16 %17 = mark_unresolved_non_copyable_value [no_consume_or_assign] %2 // user: %18 %18 = load [copy] %17 // user: %20 // function_ref File.close() %19 = function_ref @$s4main4FileV5closeyyF : $@convention(method) (@owned File) -> () // user: %20 f.close() 呼び出し %20 = apply %19(%18) : $@convention(method) (@owned File) -> () end_borrow %12 // id: %21 destroy_value %11 // id: %22 end_borrow %1 // id: %23 destroy_value %0 // id: %24 %25 = tuple () // user: %26 return %25 // id: %26 後処理 } // end sil function '$s4main3badyyF'
  14. 16 begin̲borrow / end̲borrow • non copyable typeのborrowとは別 • 変数をbox表現する場合に、最適化で破棄されない区間を明示するためのもの

    • Box表現が必要な場合にbegin̲borrow をemitして、clean up taskとして 「end̲borrowを出力」を積んでおく。最後にそれをemitする。 https://github.com/swiftlang/swift/blob/main/docs/SIL/SIL.md#borrow-scopes
  15. 17 mark_unresolved_non_copyable_value • Noncopyableな値にアクセスすると発行されるマーカー • マーカーの結果を、以降のアクセスに使うアドレスとして返す 種類 意味 付与される代表的な場面 消費・上書きを許可。ただしコピーや同じ値

    の二重消費は禁止 自分が所有するローカル変数、consuming 引数、inout 引数など no_consume_or_assign 消費・上書きを禁止。借用による利用は可能 borrowing 引数、非消費の self、ボックスやクラスのプロパティへ の読み取りアクセス assignable_but_not_consumable 値全体の上書きは可能だが、取り出して未初 期化状態にすることは禁止 クラスのプロパティ、グローバル変数、外へ逃げるクロージャの共 有ボックスなどへの書き込みアクセス initable_but_not_consumable 未初期化領域の初期化を許可。消費・上書き は禁止 クラスのイニシャライザ内でのプロパティ初期化など consumable_and_assignable https://github.com/swiftlang/swift/blob/release/6.4.x/lib/SILGen/SILGenLValue.cpp#L1201-L1234 https://github.com/swiftlang/swift/blob/release/6.4.x/include/swift/SIL/SILInstruction.h#L9505-L9534
  16. 18 mark_unresolved_non_copyable_value • CheckKind は MandatoryAllocBoxToStack (optimizer) でも更新される // SILGen

    直後 %14 = mark_unresolved_non_copyable_value [no_consume_or_assign] %2 %15 = load [copy] %14 %17 = mark_unresolved_non_copyable_value [no_consume_or_assign] %2 %18 = load [copy] %17 Boxがlocal stack領域に変換 %0 = alloc_stack [lexical] [var_decl] $File, let, name "f", type $File %1 = mark_unresolved_non_copyable_value [consumable_and_assignable] %0 store %8 to [init] %1 // f の初期化 %11 = load [copy] %1 // let g = f store %11 to [init] %10 %13 = load [copy] %1 // f.close() %15 = apply %14(%13) : $@convention(method) (@owned File) -> ()
  17. 19 MoveOnlyChecker の直前 // f の格納領域とマーカー %0 = alloc_stack [lexical]

    [var_decl] $File, let, name "f", type $File %1 = mark_unresolved_non_copyable_value [consumable_and_assignable] %0 // f の初期化 store %8 to [init] %1 // let g = f %11 = load [copy] %1 store %11 to [init] %10 // f.close() %13 = load [copy] %1 %15 = apply %14(%13) : $@convention(method) (@owned File) -> ()
  18. 20 MoveOnlyChecker (1) • load [copy] を分類 -> 消費 •

    検査に成功するとCanonical SILとしては load [take] に変換されるもの • 消費の必要があるload [copy] をcopyInsts に登録 // f の格納領域とマーカー %0 = alloc_stack [lexical] [var_decl] $File, let, name "f", type $File %1 = mark_unresolved_non_copyable_value [consumable_and_assignable] %0 // f の初期化 store %8 to [init] %1 // let g = f %11 = load [copy] %1 store %11 to [init] %10 // f.close() %13 = load [copy] %1 %15 = apply %14(%13) : $@convention(method) (@owned File) -> () https://github.com/swiftlang/swift/blob/release/6.4.x/lib/SILOptimizer/Mandatory/MoveOnlyAddressCheckerUtils.cpp#L2392-L2626
  19. 21 MoveOnlyChecker (2) • copyInsts に登録された各消費について、変数のlivenessを整理 • ある消費について、他の消費のliveness内にいるかをチェック • %13での消費は%11での消費のlivenessの中にいる

    // f の格納領域とマーカー %0 = alloc_stack [lexical] [var_decl] $File, let, name "f", type $File %1 = mark_unresolved_non_copyable_value [consumable_and_assignable] %0 // f の初期化 store %8 to [init] %1 // let g = f %11 = load [copy] %1 store %11 to [init] %10 // f.close() %13 = load [copy] %1 %15 = apply %14(%13) : $@convention(method) (@owned File) -> () https://github.com/swiftlang/swift/blob/release/6.4.x/lib/SILOptimizer/Mandatory/MoveOnlyAddressCheckerUtils.cpp#L2931-L2999
  20. 22 ちゃんとコンパイルできるコードだと? struct File: ~Copyable { let fd: Int32 consuming

    func close() { /* ... */ } } func bad() { let f = File(fd: 0) let g = f g.close() }
  21. 23 NonCopyable - Raw SIL %11 = alloc_box ${ let

    File }, let, name "g" // users: %22, %12 g の格納場所の準備 %12 = begin_borrow [lexical] [var_decl] %11 // users: %21, %13 %13 が格納場所 %13 = project_box %12, 0 %14 = mark_unresolved_non_copyable_value [no_consume_or_assign] %2 // user: %15 %15 = load [copy] %14 // user: %16 fからgに移動 store %15 to [init] %13 // id: %16 %17 = mark_unresolved_non_copyable_value [no_consume_or_assign] %13 // user: %18 %18 = load [copy] %17 // user: %20 // function_ref File.close() %19 = function_ref @$s4main4FileV5closeyyF : $@convention(method) (@owned File) -> () // user: %20 g.close() 呼び出し %20 = apply %19(%18) : $@convention(method) (@owned File) -> ()
  22. 24 NonCopyable - Raw SIL %11 = alloc_box ${ let

    File }, let, name "g" // users: %22, %12 %12 = begin_borrow [lexical] [var_decl] %11 // users: %21, %13 %14 -> %2 を消費 %13 = project_box %12, 0 %14 = mark_unresolved_non_copyable_value [no_consume_or_assign] %2 // user: %15 %15 = load [copy] %14 // user: %16 store %15 to [init] %13 // id: %16 %17 = mark_unresolved_non_copyable_value [no_consume_or_assign] %13 // user: %18 %18 = load [copy] %17 // user: %20 // function_ref File.close() %17 -> %13 を消費 %19 = function_ref @$s4main4FileV5closeyyF : $@convention(method) (@owned File) -> () // user: %20 %20 = apply %19(%18) : $@convention(method) (@owned File) -> ()
  23. 25 MoveOnlyChecker の直前 // f の格納領域とマーカー %0 = alloc_stack [lexical]

    [var_decl] $File, let, name "f", type $File %1 = mark_unresolved_non_copyable_value [consumable_and_assignable] %0 // f の初期化 store %8 to [init] %1 // g の格納領域とマーカー %10 = alloc_stack [lexical] [var_decl] $File, let, name "g", type $File %11 = mark_unresolved_non_copyable_value [consumable_and_assignable] %10 mark̲unresolved̲non̲copyable̲value は2つあるまま // let g = f %12 = load [copy] %1 store %12 to [init] %11 // g.close() %14 = load [copy] %11 load命令がアクセスする変数も2箇所で異なる %15 = function_ref @$s4main4FileV5closeyyF: $@convention(method) (@owned File) -> () %16 = apply %15(%14): $@convention(method) (@owned File) -> ()
  24. 26 MoveOnlyCheckerによるSIL更新 • 2つの load [copy] を load [take] に変更する。

    • 検査済みの mark̲unresolved̲non̲copyable̲value を取り除く。 • 移動済みの f と g に対する、呼び出し元の destroy̲addr を取り除く。 • debug̲value undef を挿入し、その変数の値が利用できなくなったことをデ バッガに伝える。
  25. 27 所有権表現除去直前のSIL // %0 は f の格納先, %7 は g

    の格納先 %8 = load [take] %0 debug_value undef : $*File, let, name "f", expr op_deref store %8 to [init] %7 %11 = load [take] %7 debug_value undef : $*File, let, name "g", expr op_deref %13 = function_ref @$s4main4FileV5closeyyF : $@convention(method) (@owned File) -> () %14 = apply %13(%11) : $@convention(method) (@owned File) -> () // 移動済みなので、値の破棄はせず格納領域だけを解放 dealloc_stack %7 dealloc_stack %0 let g = f g.close()
  26. 28 Lowered SIL %7 = alloc_stack [lexical] [var_decl] $File, let,

    name "g", type $File // users: %11, %10, %15 %8 = load %0 // user: %10 debug_value undef : $*File, let, name "f", expr op_deref // id: %9 let g = f store %8 to %7 // id: %10 %11 = load %7 // user: %14 debug_value undef : $*File, let, name "g", expr op_deref // id: %12 g.close() // function_ref File.close() %13 = function_ref @$s4main4FileV5closeyyF : $@convention(method) (@owned File) -> () // user: %14 %14 = apply %13(%11) : $@convention(method) (@owned File) -> () dealloc_stack %7 // id: %15 dealloc_stack %0 // id: %16 %17 = tuple () // user: %18 return %17 // id: %18 } // end sil function '$s4main3badyyF'
  27. 29 OwnershipModelEliminator • 所有権つきの操作を、意味を保つ具体的な操作に変換する • Ownership SSA形式のSILをnon OSSA形式に変換する OSSA の表現

    begin_borrow end_borrow move_value copy_value destroy_value load [take] load [copy] store [init] store [assign] 変換後 結果の使用先を元の値に置き換え、命令を削除 削除 結果の使用先を元の値に置き換え、命令を削除 型に応じたコピー操作。クラス参照なら strong̲retain など 型に応じた破棄操作。クラス参照なら strong̲release など 通常の load 通常の load と、必要なコピー操作 通常の store 上書きされる旧値の破棄を含む操作
  28. 30 Lowering 小噺: 末尾再帰とARC • Nodeがclassになっているlinked listで要素の合計を求める • For loopで書くとARCが残るが、末尾再帰で書くとARCが消える

    • CopyToBorrowOptimization でload[copy] がload̲borrow になる class IntNode { let value: Int let next: IntNode? } func sum(_ node: IntNode) -> Int { guard let next = node.next else { return node.value } return sum(next) + node.value }
  29. 31 Lowering 小噺: 末尾再帰とARC sil hidden @$s10LinkedList3sumySiAA7IntNodeCF : $@convention(thin) (@guaranteed

    IntNode) -> Int { bb0(%0 : $IntNode): %2 = ref_element_addr [immutable] %0, #IntNode.next %3 = load %2 switch_enum %3, case #Optional.some!enumelt: bb1, case #Optional.none!enumelt: bb2 bb1(%5 : $IntNode): %7 = function_ref @$s10LinkedList3sumySiAA7IntNodeCF : $@convention(thin) (@guaranteed IntNode) -> Int %8 = apply %7(%5) : $@convention(thin) (@guaranteed IntNode) -> Int %9 = ref_element_addr [immutable] %0, #IntNode.value %10 = struct_element_addr %9, #Int._value %11 = load %10 %12 = struct_extract %8, #Int._value %13 = integer_literal $Builtin.Int1, -1 %14 = builtin "sadd_with_overflow_Int64"(%12, %11, %13) : $(Builtin.Int64, Builtin.Int1) %15 = tuple_extract %14, 0 %16 = tuple_extract %14, 1 cond_fail %16, "arithmetic overflow" %18 = struct $Int (%15) br bb3(%18) bb2: %20 = ref_element_addr [immutable] %0, #IntNode.value %21 = load %20 br bb3(%21) bb3(%23 : $Int): return %23 } • For loopで書くとARCが残る が、末尾再帰で書くとARCが 消える • CopyToBorrowOptimization でload[copy] がload̲borrow になる
  30. 32 理解したい ② Lifetime annotation struct BV: ~Escapable { let

    p: UnsafeRawPointer @_lifetime(borrow p) // BV は p を borrow している間しか生きられない init(_ p: UnsafeRawPointer, _ i: Int) { self.p = p; ... } } struct NE: ~Escapable { let p: UnsafeRawPointer @_lifetime(borrow self) // 返す BV は self を borrow している間だけ有効 borrowing func getBV() -> BV { BV(p, i) } } @_lifetime(copy container) func bv_get_consume(container: NE) -> BV { return container.getBV() // ❌ lifetime-dependent value escapes its scope }
  31. 34 ~Escapableの例: Ref public struct Ref<Value: ~Copyable>: Copyable & ~Escapable

    { @_lifetime(borrow target) public init(_ target: borrowing Value) public var value: Value { borrow } } func runLater(_ body: @escaping () -> Void) { ... } func captureInEscapingClosure(_ ref: borrowing Ref<Foo>) { runLater { // escaping closureは現在の関数より長く生存する可能性がある print(ref.value.integer) // コンパイルエラー } } // lifetime修飾子を指定していないため、refを呼び出し元へ持ち出せない func returnRef(_ ref: Ref<Foo>) -> Ref<Foo> { return ref // コンパイルエラー }
  32. 35 Lifetime修飾子 • ライフタイムの依存関係を明示的に指定する • ~Escapable な値を関数の戻り値に利用したり初期化時にライフタイムをイニ シャライザの引数に紐づけたりすることができる 種類 borrow

    & copy ライフタイムの指定効果 ライフタイムは指定した値を不変借用(borrowing)できる期間 ライフタイムは指定した値を可変借用(inout)できる期間 指定した値が持つライフタイム依存関係を引き継ぐ
  33. 36 不変参照Ref @_lifetime(borrow a, borrow b) // 戻り値のライフタイムはa, bを借用する期間 func

    maxFoo(_ a: borrowing Foo, _ b: borrowing Foo) -> Ref<Foo> { // Foo値そのものではなく、不変参照を返す return a.integer > b.integer ? Ref(a) : Ref(b) } • 引数aとbを借用し、aまたはbの不変参照を返す • 返される参照のライフタイムは、aおよびbを借用できる期間 • 返される参照が生きている間は、aおよびbはborrowingで借用されている
  34. 37 可変参照MutableRef @_lifetime(&foo) // 戻り値のライフタイムはfooを借用する期間 func mutableRef(of foo: inout Foo)

    -> MutableRef<Foo> { // 可変参照を返す return MutableRef(&foo) } • 引数fooを借用し、fooの可変参照を返す • 返される参照のライフタイムは、fooをinoutで借用できる期間 • 返される参照が生きている間は、fooはinoutで借用されている
  35. 39 議論中: lifetime制約を型で表現? @_lifetime(borrow spanOne, borrow spanTwo) func returnEither( spanOne:

    Span<Int>, spanTwo: Span<Int> ) -> Span<Int> func returnEither<scope a, b>( spanOne: Span<Int; scope a>, spanTwo: Span<Int; scope b> ) -> Span<Int; scope a & b> • 現行は、「他の値のlifetimeへの依存」でlifetimeを表現 • Nested lifetime (Span<Span<T>>など) をよく表現できない • 新方式では、型の一部としてlifetime制約を表現する • Rustに近い見た目になる • Optional<T> のような場合はlifetime制約はTの型の中に埋め込まれる • 実装が開始。最終的なsignatureはまだ未定 https://forums.swift.org/t/a-possible-type-based-model-for-scope-restrictions-on-escapable-types/88856 https://forums.swift.org/t/scope-restrictions/89944
  36. 40 理解したい ② Lifetime annotation struct BV: ~Escapable { let

    p: UnsafeRawPointer @_lifetime(borrow p) // BV は p を borrow している間しか生きられない init(_ p: UnsafeRawPointer, _ i: Int) { self.p = p; ... } } struct NE: ~Escapable { let p: UnsafeRawPointer @_lifetime(borrow self) // 返す BV は self を borrow している間だけ有効 borrowing func getBV() -> BV { BV(p, i) } } @_lifetime(copy container) func bv_get_consume(container: NE) -> BV { return container.getBV() // ❌ lifetime-dependent value escapes its scope }
  37. 41 Lifetime annotation struct BV: ~Escapable { let p: UnsafeRawPointer

    let i: Int @_lifetime(borrow p) init(_ p: UnsafeRawPointer, _ i: Int) { self.p = p self.i = i } } struct NE: ~Escapable { let p: UnsafeRawPointer let i: Int @_lifetime(borrow p) init(_ p: UnsafeRawPointer, _ i: Int) { self.p = p self.i = i } @_lifetime(borrow self) borrowing func getBV() -> BV { BV(p, i) } } let p: UnsafeRawPointer NE BV
  38. 42 Lifetime annotation - AST • See Swift AST Explorer

    • lifetime修飾子は、AST上ではただのattribute。
  39. 43 bv_get_consume - Raw SIL sil hidden [ossa] @$s4main14bv_get_consume9containerAA2BVVAA2NEV_tF :

    $@convention(thin) (@guaranteed NE) -> @lifetime(copy 0) @owned BV { bb0(%0 : @guaranteed $NE): NEの所有権を受け取らず引数containerを借りて使う debug_value %0, let, name "container", argno 1 %2 = function_ref @$s4main2NEV5getBVAA0D0VyF : $@convention(method) (@guaranteed NE) -> @lifetime(borrow 0) @owned BV %3 = apply %2(%0) : $@convention(method) (@guaranteed NE) -> @lifetime(borrow 0) @owned BV getBV() を呼び出し return %3 }
  40. 45 LifetimeDependenceInsertion • 関数定義から、戻り値が第0引数のlifetimeにborrowで依存 • スコープの起点を求めてreturn前にmark̲dependenceを挿入する • Yield, inout parameterなどにアクセスする場合も同様

    sil hidden [ossa] @$s4main14bv_get_consume9containerAA2BVVAA2NEV_tF : $@convention(thin) (@guaranteed NE) -> @lifetime(copy 0) @owned BV { bb0(%0 : @guaranteed $NE): debug_value %0, let, name "container", argno 1 %2 = function_ref @$s4main2NEV5getBVAA0D0VyF : $@convention(method) (@guaranteed NE) -> @lifetime(borrow 0) @owned BV 戻り値が第0引数に依存している %3 = apply %2(%0) : $@convention(method) (@guaranteed NE) -> @lifetime(borrow 0) @owned BV 追加: %3は%0(引数)に依存 %4 = mark_dependence [unresolved] %3 on %0 return %4 } https://github.com/swiftlang/swift/blob/release/6.4.x/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/LifetimeDependenceInsertion.swift#L195-L258
  41. 46 LifetimeDependenceInsertion • 関数定義から、戻り値が第0引数のlifetimeにborrowで依存 • スコープの起点を求めてreturn前にmark̲dependenceを挿入する • Yield, inout parameterなどにアクセスする場合も同様

    • ちなみに: lifetime依存がcopyの場合は挿入しない • LifetimeDependenceDefUseWalkerで追跡する • LifetimeDependenceScopeFixup, LifetimeDependenceDiagnostics の一部 https://github.com/swiftlang/swift/blob/release/6.4.x/SwiftCompilerSources/Sources/Optimizer/Utilities/LifetimeDependenceUtils.swift#L1189-L1205
  42. 47 LifetimeDependenceScopeFixup • 今回は何も起こらない • やること • end̲access やend̲borrowなどを移動する。外側のスコープを調整したり make̲dependenceの依存先を書き換えることもあるらしい。

    • 今回の関数にはend̲accessやend̲borrowがない。 • このパスが有効に働く例は後述 https://github.com/swiftlang/swift/blob/release/6.4.x/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/LifetimeDependenceScopeFixup.swift#L110-L136
  43. 48 LifetimeDependenceDiagnostics • dependent value %4 の 全ての use を

    walker で辿る → return %4 • returnedDependence() -> checkFunctionResult() を実行 • returnの場合以外にも様々なチェックのバリエーションがある(次ページ) • 戻り値が @lifetime(copy 0) @owned BV -> 分類 .inherit • 引数がすでに持っている寿命依存を戻り値に引き継ぐ (lifetime copyなので) • .inherit であるが、引数の値自体を借用する依存が導入されている • walkをabortする https://github.com/swiftlang/swift/blob/release/6.4.x/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/LifetimeDependenceDiagnostics.swift#L211
  44. 49 DiagnoseDependenceWalker 検査内容 コールバック 呼ばれる場面 処理 needWalk(for:) ある値の使用箇所を追跡しようとするとき visitedValues に登録する。初回なら

    true、追跡済みなら false を返 して重複した追跡を防ぐ。 leafUse(of:) 依存をさらに伝播しない使用に到達したとき checkInScope で、その使用位置が依存スコープ内か検査する。 追跡した値に、型依存を除く使用箇所がないとき 起点そのものが未使用なら検査を終了する。別の使用から到達した値 なら、その到達元の使用位置を checkInScope で検査する。 deadValue(_:using:) escapingDependence(on:) 依存する値が外へ逃げると分類された使用に到達したとき reportEscaping で違反を報告し、追跡を中止する。 inoutDependence(argumen 依存を持つ値が inout 引数に残ったまま、関数を終了するとき t:functionExit:) checkInoutResult で、その依存を引数経由で外へ渡してよいか検査 する。不適合なら、関数の終了位置で違反を報告する。 returnedDependence(resu 依存する値を return などで直接返すとき lt:) checkFunctionResult で戻り値の寿命契約と照合する。不適合なら 違反を報告する。 returnedDependence(addr 依存する値を、間接返却用の @out 領域へ格納するとき ess:on:) checkFunctionResult で戻り値の寿命契約と照合する。不適合な ら、その格納に関わる使用位置で違反を報告する。 yieldedDependence(resul コルーチンが依存する値・アドレスを yield するとき t:) checkYield を呼ぶ。呼び出し元のスコープに依存する場合は返却契 約と照合し、ローカルなスコープの場合は許可。不適合なら違反。 storeToYieldDependence( コルーチンから yield されたアドレスへ、依存する値を格納すると checkStoreToYield で格納先アドレスの由来・依存を遡り、格納し address:of:) き てよいか検査する。不適合なら違反を報告する。 unknownAddressUse(of:) アドレスの使用方法を共通 walker が扱えないとき reportUnknown で保守的に逃避として報告し、追跡を中止する。 https://github.com/swiftlang/swift/blob/release/6.4.x/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/LifetimeDependenceDiagnostics.swift#L532
  45. 50 LifetimeDependenceScopeFixup struct BV: ~Escapable { let p: UnsafeRawPointer let

    i: Int @_lifetime(borrow p) init(_ p: UnsafeRawPointer, _ i: Int) { self.p = p self.i = i } } struct NE: ~Escapable { let p: UnsafeRawPointer let i: Int @_lifetime(borrow p) init(_ p: UnsafeRawPointer, _ i: Int) { self.p = p self.i = i } @_lifetime(borrow self) borrowing func getBV() -> BV { BV(p, i) } } func use(_ bv: BV) -> Int { bv.i } func scope_fixup(container: NE) -> Int { var local = container let bv = local.getBV() return use(bv) }
  46. 51 scope̲ xup(container:) - Raw SIL fi sil hidden [ossa]

    @$s4main11scope_fixup9containerSiAA2NEV_tF : $@convention(thin) (@guaranteed NE) -> Int { // %0 "container" // users: %5, %1 bb0(%0 : @guaranteed $NE): debug_value %0, let, name "container", argno 1 // id: %1 %2 = alloc_box ${ var NE }, var, name "local" // users: %20, %3 %3 = begin_borrow [var_decl] %2 // users: %19, %4 %4 = project_box %3, 0 // users: %7, %6 %5 = copy_value %0 // user: %6 store %5 to [init] %4 // id: %6 %7 = begin_access [read] [unknown] %4 // users: %12, %9, %8 %8 = load [copy] %7 // users: %13, %11 end_access %7 // id: %9 // function_ref NE.getBV() %10 = function_ref @$s4main2NEV5getBVAA0D0VyF : $@convention(method) (@guaranteed NE) -> @lifetime(borrow 0) @owned BV // user: %11 %11 = apply %10(%8) : $@convention(method) (@guaranteed NE) -> @lifetime(borrow 0) @owned BV // user: %12 %12 = mark_dependence [unresolved] %11 on %7 // user: %14 destroy_value %8 // id: %13 %14 = move_value [var_decl] %12 // users: %17, %18, %15 debug_value %14, let, name "bv" // id: %15 // function_ref use(_:) %16 = function_ref @$s4main3useySiAA2BVVF : $@convention(thin) (@guaranteed BV) -> Int // user: %17 %17 = apply %16(%14) : $@convention(thin) (@guaranteed BV) -> Int // user: %21 destroy_value %14 // id: %18 end_borrow %3 // id: %19 destroy_value %2 // id: %20 return %17 // id: %21 }
  47. 52 scope̲ xup(container:) - Raw SIL • localは%4でアクセスできるbox領域として定義されている • 格納領域へのアドレスが%4,

    その領域を保持するボックスの所有参照が%2 • %7 のアクセスが、getBV() 呼び出しの前に終了している • end_accessを後に移動させなければならない local を読み出すアクセス (%4がlocalの格納領域) fi %7 = begin_access [read] [unknown] %4 // users: %12, %9, %8 %8 = load [copy] %7 // users: %13, %11 end_access %7 // id: %9 // function_ref NE.getBV() %10 = function_ref @$s4main2NEV5getBVAA0D0VyF : $@convention(method) (@guaranteed getBV() 呼び出し NE) -> @lifetime(borrow 0) @owned BV // user: %11 %11 = apply %10(%8) : $@convention(method) (@guaranteed NE) -> @lifetime(borrow 0) @owned BV // user: %12 %12 = mark_dependence [unresolved] %11 on %7 // user: %14 destroy_value %8 // id: %13
  48. 53 LifetimeDependenceScopeFixup • mark̲dependence を起点に、%12 -> %14 -> 各使用箇所 と追跡

    • bv() の呼び出しでは戻り値が寿命依存を引き継がないので追跡しない • 最後の使用は destroy_value %14 -> end_access %7 をこれより後へ移動 } %12 = mark_dependence [unresolved] %11 on %7 // user: %14 destroy_value %8 // id: %13 %14 = move_value [var_decl] %12 // users: %17, %18, %15 debug_value %14, let, name "bv" // id: %15 leafUse // function_ref use(_:) %16 = function_ref @$s4main3useySiAA2BVVF : $@convention(thin) (@guaranteed BV) -> Int // user: %17 visitAppliedUse %17 = apply %16(%14) : $@convention(thin) (@guaranteed BV) -> Int // user: %21 BVの使用位置を記録 destroy_value %14 // id: %18 leafUse end_borrow %3 // id: %19 destroy_value %2 // id: %20 return %17 // id: %21 https://github.com/swiftlang/swift/blob/release/6.4.x/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/LifetimeDependenceScopeFixup.swift#L699-L730
  49. 54 LifetimeDependenceScopeFixup後 local を読み出すアクセス (%4がlocalの格納領域) %7 = begin_access [read] [unknown]

    %4 %8 = load [copy] %7 %9 = function_ref @$s4main2NEV5getBVAA0D0VyF : $@convention(method) (@guaranteed NE) -> @lifetime(borrow 0) @owned BV %10 = apply %9(%8) : $@convention(method) (@guaranteed NE) -> @lifetime(borrow 0) @owned BV %11 = mark_dependence [unresolved] %10 on %7 getBV() 呼び出し destroy_value %8 %13 = move_value [var_decl] %11 use(bv) 呼び出し debug_value %13, let, name "bv" %15 = function_ref @$s4main3useySiAA2BVVF : $@convention(thin) (@guaranteed BV) -> Int %16 = apply %15(%13) : $@convention(thin) (@guaranteed BV) -> Int destroy_value %13 end_access %7 end_borrow %3 %7の破棄がここに移動 destroy_value %2 return %16