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

末尾再帰なら安心でしょ?って信じてたSwiftコードが落ちた夜

Avatar for rikusouda rikusouda
September 20, 2025
120

 末尾再帰なら安心でしょ?って信じてたSwiftコードが落ちた夜

Avatar for rikusouda

rikusouda

September 20, 2025
Tweet

Transcript

  1. func sum( _ n: Int, _ accumulator: Int = 0

    ) -> Int { if n == 0 { return accumulator } else { return sum(n - 1, accumulator + n) } }
  2. func sum( _ n: Int, _ accumulator: Int = 0

    ) -> Int { if n == 0 { return accumulator } else { return sum(n - 1, accumulator + n) } } ຤ඌͰࣗ෼ࣗ਎Λݺͼग़͢ Oͷ਺͚ͩ ॲཧΛ܁Γฦ͢
  3. ࣮ߦ݁Ռ • σόοάϏϧυ • Ҿ਺100,000 -> Ϋϥογϡ💥 • ϦϦʔεϏϧυ •

    Ҿ਺1,000,000 -> ໰୊ͳ͠🎉 ←࠷దԽ͕༗ޮͦ͏
  4. define hidden swiftcc i64 @"$s4Sum14sum1yS2i_SitF"(i64 %0, i64 %1) local_unnamed_addr #1

    { entry: %2 = icmp eq i64 %0, 0 br i1 %2, label %tailrecurse._crit_edge, label %.lr.ph .lr.ph: ; preds = %entry, %tailrecurse %.tr15 = phi i64 [ %9, %tailrecurse ], [ %1, %entry ] %.tr4 = phi i64 [ %4, %tailrecurse ], [ %0, %entry ] %3 = tail call { i64, i1 } @llvm.sadd.with.overflow.i64(i64 %.tr4, i64 -1) %4 = extractvalue { i64, i1 } %3, 0 %5 = extractvalue { i64, i1 } %3, 1 br i1 %5, label %11, label %6, !prof !17 6: ; preds = %.lr.ph %7 = tail call { i64, i1 } @llvm.sadd.with.overflow.i64(i64 %.tr15, i64 %.tr4) %8 = extractvalue { i64, i1 } %7, 1 br i1 %8, label %12, label %tailrecurse, !prof !17 tailrecurse: ; preds = %6 %9 = extractvalue { i64, i1 } %7, 0 %10 = icmp eq i64 %4, 0 br i1 %10, label %tailrecurse._crit_edge, label %.lr.ph tailrecurse._crit_edge: ; preds = %tailrecurse, %entry %.tr1.lcssa = phi i64 [ %1, %entry ], [ %9, %tailrecurse ] ret i64 %.tr1.lcssa 11: ; preds = %.lr.ph tail call void asm sideeffect "", "n"(i32 0) #4 tail call void @llvm.trap() unreachable 12: ; preds = %6 tail call void asm sideeffect "", "n"(i32 1) #4 tail call void @llvm.trap() unreachable }
  5. define hidden swiftcc i64 @"$s4Sum14sum1yS2i_SitF"(i64 %0, i64 %1) local_unnamed_addr #1

    { entry: %2 = icmp eq i64 %0, 0 br i1 %2, label %tailrecurse._crit_edge, label %.lr.ph .lr.ph: ; preds = %entry, %tailrecurse %.tr15 = phi i64 [ %9, %tailrecurse ], [ %1, %entry ] %.tr4 = phi i64 [ %4, %tailrecurse ], [ %0, %entry ] %3 = tail call { i64, i1 } @llvm.sadd.with.overflow.i64(i64 %.tr4, i64 -1) %4 = extractvalue { i64, i1 } %3, 0 %5 = extractvalue { i64, i1 } %3, 1 br i1 %5, label %11, label %6, !prof !17 6: ; preds = %.lr.ph %7 = tail call { i64, i1 } @llvm.sadd.with.overflow.i64(i64 %.tr15, i64 %.tr4) %8 = extractvalue { i64, i1 } %7, 1 br i1 %8, label %12, label %tailrecurse, !prof !17 tailrecurse: ; preds = %6 %9 = extractvalue { i64, i1 } %7, 0 %10 = icmp eq i64 %4, 0 br i1 %10, label %tailrecurse._crit_edge, label %.lr.ph tailrecurse._crit_edge: ; preds = %tailrecurse, %entry %.tr1.lcssa = phi i64 [ %1, %entry ], [ %9, %tailrecurse ] ret i64 %.tr1.lcssa 11: ; preds = %.lr.ph tail call void asm sideeffect "", "n"(i32 0) #4 tail call void @llvm.trap() unreachable 12: ; preds = %6 tail call void asm sideeffect "", "n"(i32 1) #4 tail call void @llvm.trap() unreachable }
  6. func sum(_ n: Int) -> Int { if n ==

    0 { return 0 } else { return n + sum(n - 1) } }
  7. func sum(_ n: Int) -> Int { if n ==

    0 { return 0 } else { return n + sum(n - 1) } } SFUVSOจʹԋࢉؚ͕·ΕΔ
  8. define hidden swiftcc i64 @"$s4Sum34sum3yS2iF"(i64 %0) local_unnamed_addr #1 { entry:

    %1 = icmp eq i64 %0, 0 br i1 %1, label %11, label %2 2: ; preds = %entry %3 = tail call { i64, i1 } @llvm.sadd.with.overflow.i64(i64 %0, i64 -1) %4 = extractvalue { i64, i1 } %3, 1 br i1 %4, label %13, label %5, !prof !17 5: ; preds = %2 %6 = extractvalue { i64, i1 } %3, 0 %7 = tail call swiftcc i64 @"$s4Sum34sum3yS2iF"(i64 %6) %8 = tail call { i64, i1 } @llvm.sadd.with.overflow.i64(i64 %0, i64 %7) %9 = extractvalue { i64, i1 } %8, 0 %10 = extractvalue { i64, i1 } %8, 1 br i1 %10, label %14, label %11, !prof !17 11: ; preds = %5, %entry %12 = phi i64 [ 0, %entry ], [ %9, %5 ] ret i64 %12 13: ; preds = %2 tail call void asm sideeffect "", "n"(i32 0) #4 tail call void @llvm.trap() unreachable 14: ; preds = %5 tail call void asm sideeffect "", "n"(i32 1) #4 tail call void @llvm.trap() unreachable }
  9. func sum(_ n: Int, _ acc: Int = 0) async

    -> Int { if n == 0 { return acc } else { let added = await add(acc, n) return await sum(n - 1, added) } } func add(_ a: Int, _ b: Int) async -> Int { await Task { a + b }.value }
  10. func sum(_ n: Int, _ acc: Int = 0) async

    -> Int { if n == 0 { return acc } else { let added = await add(acc, n) return await sum(n - 1, added) } } func add(_ a: Int, _ b: Int) async -> Int { await Task { a + b }.value } ඇಉظͰ࠶ؼ ඇಉظॲཧͷݺग़͠
  11. define internal swifttailcc void @"$s4Sum24sum2yS2i_SitYaFTY0_"(ptr swiftasync %0) #1 { entryresume.0:

    %async.ctx.frameptr = getelementptr inbounds i8, ptr %0, i64 16 %.reload.addr28 = getelementptr inbounds i8, ptr %0, i64 40 %.reload29 = load ptr, ptr %.reload.addr28, align 8 %.reload.addr23 = getelementptr inbounds i8, ptr %0, i64 32 %.reload24 = load i64, ptr %.reload.addr23, align 8 %.reload.addr20 = getelementptr inbounds i8, ptr %0, i64 24 %.reload21 = load i64, ptr %.reload.addr20, align 8 %1 = tail call swiftcc %swift.metadata_response @"$sScPMa"(i64 0) #13 %2 = extractvalue %swift.metadata_response %1, 0 %3 = getelementptr inbounds ptr, ptr %2, i64 -1 %.valueWitnesses1 = load ptr, ptr %3, align 8, !invariant.load !17, !dereferenceable !18 %4 = getelementptr inbounds ptr, ptr %.valueWitnesses1, i64 7 %StoreEnumTagSinglePayload = load ptr, ptr %4, align 8, !invariant.load !17 tail call void %StoreEnumTagSinglePayload(ptr noalias %.reload29, i32 1, i32 1, ptr %2) #6 %5 = tail call noalias ptr @swift_allocObject(ptr nonnull getelementptr inbounds (%swift.full_boxmetadata, ptr @metadata, i64 0, i32 2), i64 48, i64 7) #6 %6 = getelementptr inbounds <{ %swift.refcounted, %TScA_pSg, %TSi, %TSi }>, ptr %5, i64 0, i32 1 %7 = getelementptr inbounds <{ %swift.refcounted, %TScA_pSg, %TSi, %TSi }>, ptr %5, i64 0, i32 2 tail call void @llvm.memset.p0.i64(ptr noundef nonnull align 8 dereferenceable(16) %6, i8 0, i64 16, i1 false) store i64 %.reload24, ptr %7, align 8 %8 = getelementptr inbounds <{ %swift.refcounted, %TScA_pSg, %TSi, %TSi }>, ptr %5, i64 0, i32 3 store i64 %.reload21, ptr %8, align 8 %9 = tail call swiftcc ptr @"$sScTss5NeverORs_rlE8priority9operationScTyxABGScPSg_xyYaYAcntcfCSi_Tgm5"(ptr noalias %.reload29, ptr nonnull @"$s4Sum23add33_E6F2F9B028D281319C2D944EF4ECECE0LLyS2i_SitYaFSiyYacfU_TATu", ptr %5) %.spill.addr30 = getelementptr inbounds i8, ptr %0, i64 48 store ptr %9, ptr %.spill.addr30, align 8 tail call void @llvm.lifetime.start.p0(i64 8, ptr nonnull %async.ctx.frameptr) %10 = load i32, ptr getelementptr inbounds (%swift.async_func_pointer, ptr @"$sScTss5NeverORs_rlE5valuexvgTu", i64 0, i32 1), align 8 %11 = zext i32 %10 to i64 %12 = tail call swiftcc ptr @swift_task_alloc(i64 %11) #4 %.spill.addr33 = getelementptr inbounds i8, ptr %0, i64 56 store ptr %12, ptr %.spill.addr33, align 8 tail call void @llvm.lifetime.start.p0(i64 -1, ptr %12) store ptr %0, ptr %12, align 8 %13 = getelementptr inbounds <{ ptr, ptr }>, ptr %12, i64 0, i32 1 store ptr @"$s4Sum24sum2yS2i_SitYaFTQ1_", ptr %13, align 8 musttail call swifttailcc void @"$sScTss5NeverORs_rlE5valuexvg"(ptr noalias nonnull %async.ctx.frameptr, ptr nonnull swiftasync %12, ptr %9, ptr nonnull @"$sSiN") #6 ret void }
  12. func sum(_ n: Int) -> Int { return recursionSum(Array(1...n)) }

    func recursionSum(_ arr: [Int], _ acc: Int = 0) -> Int { if arr.isEmpty { return acc } let first = arr.first ?? 0 let rest = Array(arr.dropFirst()) return recursionSum(rest, first + acc) }
  13. func sum(_ n: Int) -> Int { return recursionSum(Array(1...n)) }

    func recursionSum(_ arr: [Int], _ acc: Int = 0) -> Int { if arr.isEmpty { return acc } let first = arr.first ?? 0 let rest = Array(arr.dropFirst()) return recursionSum(rest, first + acc) } "SSBZͷར༻
  14. define hidden swiftcc i64 @"$s4Sum615mapRecursionSum_11accumulatorSiSaySiG_SitF"(ptr %0, i64 %1) #0 {

    entry: %arr.debug = alloca ptr, align 8 call void @llvm.memset.p0.i64(ptr align 8 %arr.debug, i8 0, i64 8, i1 false) %accumulator.debug = alloca i64, align 8 call void @llvm.memset.p0.i64(ptr align 8 %accumulator.debug, i8 0, i64 8, i1 false) %2 = alloca %TSa, align 8 %3 = alloca %TSi, align 8 %4 = alloca %TSiSg, align 8 %5 = alloca %TSa, align 8 %6 = alloca %TSiSg, align 8 %first.debug = alloca i64, align 8 call void @llvm.memset.p0.i64(ptr align 8 %first.debug, i8 0, i64 8, i1 false) %7 = alloca %Ts10ArraySliceV, align 8 %8 = alloca %TSa, align 8 %9 = alloca %Ts10ArraySliceV, align 8 %rest.debug = alloca ptr, align 8 call void @llvm.memset.p0.i64(ptr align 8 %rest.debug, i8 0, i64 8, i1 false) store ptr %0, ptr %arr.debug, align 8 store i64 %1, ptr %accumulator.debug, align 8 %10 = call ptr @swift_bridgeObjectRetain(ptr returned %0) #9 call void @llvm.lifetime.start.p0(i64 8, ptr %2) %._buffer = getelementptr inbounds %TSa, ptr %2, i32 0, i32 0 %._buffer._storage = getelementptr inbounds %Ts12_ArrayBufferV, ptr %._buffer, i32 0, i32 0 %._buffer._storage.rawValue = getelementptr inbounds %Ts14_BridgeStorageV, ptr %._buffer._storage, i32 0, i32 0 store ptr %0, ptr %._buffer._storage.rawValue, align 8 %11 = call ptr @__swift_instantiateConcreteTypeFromMangledName(ptr @"$sSaySiGMD") #8 %12 = call ptr @"$sSaySiGSayxGSlsWl"() #14 %13 = call swiftcc i1 @"$sSlsE7isEmptySbvg"(ptr %11, ptr %12, ptr noalias nocapture swiftself %2) %14 = call ptr @"$sSaySiGWOh"(ptr %2) call void @llvm.lifetime.end.p0(i64 8, ptr %2) br i1 %13, label %15, label %16 15: ; preds = %entry br label %48 16: ; preds = %entry call void @llvm.lifetime.start.p0(i64 8, ptr %3) %17 = call ptr @swift_bridgeObjectRetain(ptr returned %0) #9 call void @llvm.lifetime.start.p0(i64 9, ptr %4) call void @llvm.lifetime.start.p0(i64 8, ptr %5) %._buffer1 = getelementptr inbounds %TSa, ptr %5, i32 0, i32 0 %._buffer1._storage = getelementptr inbounds %Ts12_ArrayBufferV, ptr %._buffer1, i32 0, i32 0 %._buffer1._storage.rawValue = getelementptr inbounds %Ts14_BridgeStorageV, ptr %._buffer1._storage, i32 0, i32 0 store ptr %0, ptr %._buffer1._storage.rawValue, align 8 call swiftcc void @"$sSlsE5first7ElementQzSgvg"(ptr noalias nocapture sret(%swift.opaque) %4, ptr %11, ptr %12, ptr noalias nocapture swiftself %5) %18 = call ptr @"$sSaySiGWOh"(ptr %5) call void @llvm.lifetime.end.p0(i64 8, ptr %5) %19 = load i64, ptr %4, align 8 %20 = getelementptr inbounds %TSiSg, ptr %4, i32 0, i32 1 %21 = load i1, ptr %20, align 8 call void @llvm.lifetime.start.p0(i64 9, ptr %6) store i64 %19, ptr %6, align 8 %22 = getelementptr inbounds %TSiSg, ptr %6, i32 0, i32 1 store i1 %21, ptr %22, align 8 %23 = load i64, ptr %6, align 8 %24 = getelementptr inbounds %TSiSg, ptr %6, i32 0, i32 1 %25 = load i1, ptr %24, align 8 br i1 %25, label %28, label %26 26: ; preds = %16 %._value6 = getelementptr inbounds %TSi, ptr %6, i32 0, i32 0 %27 = load i64, ptr %._value6, align 8 %._value7 = getelementptr inbounds %TSi, ptr %3, i32 0, i32 0 store i64 %27, ptr %._value7, align 8 call void @llvm.lifetime.end.p0(i64 9, ptr %6) br label %29 28: ; preds = %16 %._value = getelementptr inbounds %TSi, ptr %3, i32 0, i32 0 store i64 0, ptr %._value, align 8 call void @llvm.lifetime.end.p0(i64 9, ptr %6) br label %29 29: ; preds = %26, %28 call void @llvm.lifetime.end.p0(i64 9, ptr %4) %._value2 = getelementptr inbounds %TSi, ptr %3, i32 0, i32 0 %30 = load i64, ptr %._value2, align 8 store i64 %30, ptr %first.debug, align 8 call void @llvm.lifetime.end.p0(i64 8, ptr %3) call void @llvm.lifetime.start.p0(i64 32, ptr %7) %31 = call ptr @swift_bridgeObjectRetain(ptr returned %0) #9 call void @llvm.lifetime.start.p0(i64 8, ptr %8) %._buffer3 = getelementptr inbounds %TSa, ptr %8, i32 0, i32 0 %._buffer3._storage = getelementptr inbounds %Ts12_ArrayBufferV, ptr %._buffer3, i32 0, i32 0 %._buffer3._storage.rawValue = getelementptr inbounds %Ts14_BridgeStorageV, ptr %._buffer3._storage, i32 0, i32 0 store ptr %0, ptr %._buffer3._storage.rawValue, align 8 %32 = call swiftcc i64 @"$sSlsE9dropFirsty11SubSequenceQzSiFfA_"(ptr %11, ptr %12) call swiftcc void @"$sSlsE9dropFirsty11SubSequenceQzSiF"(ptr noalias nocapture sret(%swift.opaque) %7, i64 %32, ptr %11, ptr %12, ptr noalias nocapture swiftself %8) call void @llvm.lifetime.end.p0(i64 8, ptr %8) %._buffer4 = getelementptr inbounds %Ts10ArraySliceV, ptr %7, i32 0, i32 0 %._buffer4.owner = getelementptr inbounds %Ts12_SliceBufferV, ptr %._buffer4, i32 0, i32 0 %33 = getelementptr inbounds %AnyObject, ptr %._buffer4.owner, i32 0, i32 0 %34 = load ptr, ptr %33, align 8 %._buffer4.subscriptBaseAddress = getelementptr inbounds %Ts12_SliceBufferV, ptr %._buffer4, i32 0, i32 1 %._buffer4.subscriptBaseAddress._rawValue = getelementptr inbounds %TSp, ptr %._buffer4.subscriptBaseAddress, i32 0, i32 0 %35 = load ptr, ptr %._buffer4.subscriptBaseAddress._rawValue, align 8 %._buffer4.startIndex = getelementptr inbounds %Ts12_SliceBufferV, ptr %._buffer4, i32 0, i32 2 %._buffer4.startIndex._value = getelementptr inbounds %TSi, ptr %._buffer4.startIndex, i32 0, i32 0 %36 = load i64, ptr %._buffer4.startIndex._value, align 8 %._buffer4.endIndexAndFlags = getelementptr inbounds %Ts12_SliceBufferV, ptr %._buffer4, i32 0, i32 3 %._buffer4.endIndexAndFlags._value = getelementptr inbounds %TSu, ptr %._buffer4.endIndexAndFlags, i32 0, i32 0 %37 = load i64, ptr %._buffer4.endIndexAndFlags._value, align 8 call void @llvm.lifetime.start.p0(i64 32, ptr %9) %._buffer5 = getelementptr inbounds %Ts10ArraySliceV, ptr %9, i32 0, i32 0 %._buffer5.owner = getelementptr inbounds %Ts12_SliceBufferV, ptr %._buffer5, i32 0, i32 0 %38 = getelementptr inbounds %AnyObject, ptr %._buffer5.owner, i32 0, i32 0 store ptr %34, ptr %38, align 8 %._buffer5.subscriptBaseAddress = getelementptr inbounds %Ts12_SliceBufferV, ptr %._buffer5, i32 0, i32 1 %._buffer5.subscriptBaseAddress._rawValue = getelementptr inbounds %TSp, ptr %._buffer5.subscriptBaseAddress, i32 0, i32 0 store ptr %35, ptr %._buffer5.subscriptBaseAddress._rawValue, align 8 %._buffer5.startIndex = getelementptr inbounds %Ts12_SliceBufferV, ptr %._buffer5, i32 0, i32 2 %._buffer5.startIndex._value = getelementptr inbounds %TSi, ptr %._buffer5.startIndex, i32 0, i32 0 store i64 %36, ptr %._buffer5.startIndex._value, align 8 %._buffer5.endIndexAndFlags = getelementptr inbounds %Ts12_SliceBufferV, ptr %._buffer5, i32 0, i32 3 %._buffer5.endIndexAndFlags._value = getelementptr inbounds %TSu, ptr %._buffer5.endIndexAndFlags, i32 0, i32 0 store i64 %37, ptr %._buffer5.endIndexAndFlags._value, align 8 %39 = call ptr @__swift_instantiateConcreteTypeFromMangledName(ptr @"$ss10ArraySliceVySiGMD") #8 %40 = call ptr @"$ss10ArraySliceVySiGAByxGSTsWl"() #14 %41 = call swiftcc ptr @"$sSaySayxGqd__c7ElementQyd__RszSTRd__lufC"(ptr noalias nocapture %9, ptr @"$sSiN", ptr %39, ptr %40) call void @llvm.lifetime.end.p0(i64 32, ptr %9) call void @llvm.lifetime.end.p0(i64 32, ptr %7) store ptr %41, ptr %rest.debug, align 8 %42 = call { i64, i1 } @llvm.sadd.with.overflow.i64(i64 %30, i64 %1) %43 = extractvalue { i64, i1 } %42, 0 %44 = extractvalue { i64, i1 } %42, 1 %45 = call i1 @llvm.expect.i1(i1 %44, i1 false) br i1 %45, label %50, label %46 46: ; preds = %29 %47 = call swiftcc i64 @"$s4Sum615mapRecursionSum_11accumulatorSiSaySiG_SitF"(ptr %41, i64 %43) call void @swift_bridgeObjectRelease(ptr %41) #9 br label %48 48: ; preds = %15, %46 %49 = phi i64 [ %47, %46 ], [ %1, %15 ] ret i64 %49 50: ; preds = %29 call void @llvm.trap() unreachable } ಡΊͳ͍ɺɺ
  15. ࢀߟࢿྉ • Swiftʹ͓͚Δ຤ඌ࠶ؼͱCompilerʹΑΔ࠷దԽΛ୳Δ(ϒϩά) • https://engineering.mercari.com/blog/entry/2019-12-16-081047/ • LLVM Language Reference Manual

    • https://llvm.org/docs/LangRef.html • ʲSwiftʳasync/await͸ͲͷΑ͏ʹͯ͠ಈ͍͍ͯΔͷ͔LLVMͷϨϕϧͰௐ΂ͯΈΔ(Qiita) • https://qiita.com/TokyoYoshida/items/c0a02bfa0197b703518f • Proposal: Tail Call Optimization keyword/attribute (Swift forums) • https://forums.swift.org/t/proposal-tail-call-optimization-keyword-attribute/181 • Stack explosion or tail recursion? (Swift forums) • https://forums.swift.org/t/stack-explosion-or-tail-recursion/25636