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

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

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
Avatar for rikusouda rikusouda
September 20, 2025
220

 末尾再帰なら安心でしょ?って信じてた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