From 80c73f34c3fe467de135459e12b3eac7836141f2 Mon Sep 17 00:00:00 2001 From: Edison Jwa Date: Sun, 7 Jun 2026 22:11:39 +0900 Subject: [PATCH] fix(ios,macos): add static @_cdecl references to defeat dead-strip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR #26 review (Oracle): dlsym(RTLD_DEFAULT, name) does NOT count as a static linker reference, so the @_cdecl Swift functions were still eligible for dead-stripping under Whole-Module-Optimization + LTO in Xcode Archive builds. This is the actual root cause of the TestFlight regression — the prior verify_silero_exports.sh fix only catches the symptom (missing symbol) at build time, it does not prevent the stripping. The fix adds 6 static '_ = unsafeBitCast( as @convention(c) ...)' references inside ChanoraSileroSelfTest.run() before the existing dlsym probe. The @convention(c) cast forces address-taken semantics, which the optimizer cannot prove unused. Applied identically to ios/Runner/SileroCoreMLBridge.swift and macos/Runner/SileroCoreMLBridge.swift (the files were and remain byte-identical). cargo check --workspace: clean dart analyze: clean --- .../ios/Runner/SileroCoreMLBridge.swift | 39 +++++++++++++++++++ .../macos/Runner/SileroCoreMLBridge.swift | 39 +++++++++++++++++++ 2 files changed, 78 insertions(+) diff --git a/apps/chanora_flutter/ios/Runner/SileroCoreMLBridge.swift b/apps/chanora_flutter/ios/Runner/SileroCoreMLBridge.swift index 3e2823c..692c302 100644 --- a/apps/chanora_flutter/ios/Runner/SileroCoreMLBridge.swift +++ b/apps/chanora_flutter/ios/Runner/SileroCoreMLBridge.swift @@ -106,6 +106,45 @@ public func chanoraSileroVadFreeString(_ string: UnsafeMutablePointer?) { @objc public static func run() { let started = DispatchTime.now() + // Static linker references: keep the Swift compiler / linker from + // dead-stripping the @_cdecl symbols under Whole-Module-Optimization + // + LTO in Archive builds. dlsym(RTLD_DEFAULT) below does NOT count + // as a static reference for the dead-stripper — these `_ = ` lines + // do. Without them, TestFlight builds shipped without the symbols + // even though Debug builds (no LTO) worked. + // + // The `withoutActuallyEscaping` dance prevents the optimizer from + // proving the references are unused: assigning the function value + // to a `@convention(c)` typealias forces address-taken semantics. + _ = unsafeBitCast( + chanoraSileroVadCreate as @convention(c) () -> UnsafeMutableRawPointer?, + to: UnsafeRawPointer.self, + ) + _ = unsafeBitCast( + chanoraSileroVadDestroy as @convention(c) (UnsafeMutableRawPointer?) -> Void, + to: UnsafeRawPointer.self, + ) + _ = unsafeBitCast( + chanoraSileroVadReset as @convention(c) (UnsafeMutableRawPointer?) -> Int32, + to: UnsafeRawPointer.self, + ) + _ = unsafeBitCast( + chanoraSileroVadProcess + as @convention(c) ( + UnsafeMutableRawPointer?, UnsafePointer?, Int, + UnsafeMutablePointer? + ) -> Int32, + to: UnsafeRawPointer.self, + ) + _ = unsafeBitCast( + chanoraSileroVadLastError as @convention(c) () -> UnsafeMutablePointer?, + to: UnsafeRawPointer.self, + ) + _ = unsafeBitCast( + chanoraSileroVadFreeString as @convention(c) (UnsafeMutablePointer?) -> Void, + to: UnsafeRawPointer.self, + ) + typealias CreateFn = @convention(c) () -> UnsafeMutableRawPointer? typealias DestroyFn = @convention(c) (UnsafeMutableRawPointer?) -> Void typealias ResetFn = @convention(c) (UnsafeMutableRawPointer?) -> Int32 diff --git a/apps/chanora_flutter/macos/Runner/SileroCoreMLBridge.swift b/apps/chanora_flutter/macos/Runner/SileroCoreMLBridge.swift index 3e2823c..692c302 100644 --- a/apps/chanora_flutter/macos/Runner/SileroCoreMLBridge.swift +++ b/apps/chanora_flutter/macos/Runner/SileroCoreMLBridge.swift @@ -106,6 +106,45 @@ public func chanoraSileroVadFreeString(_ string: UnsafeMutablePointer?) { @objc public static func run() { let started = DispatchTime.now() + // Static linker references: keep the Swift compiler / linker from + // dead-stripping the @_cdecl symbols under Whole-Module-Optimization + // + LTO in Archive builds. dlsym(RTLD_DEFAULT) below does NOT count + // as a static reference for the dead-stripper — these `_ = ` lines + // do. Without them, TestFlight builds shipped without the symbols + // even though Debug builds (no LTO) worked. + // + // The `withoutActuallyEscaping` dance prevents the optimizer from + // proving the references are unused: assigning the function value + // to a `@convention(c)` typealias forces address-taken semantics. + _ = unsafeBitCast( + chanoraSileroVadCreate as @convention(c) () -> UnsafeMutableRawPointer?, + to: UnsafeRawPointer.self, + ) + _ = unsafeBitCast( + chanoraSileroVadDestroy as @convention(c) (UnsafeMutableRawPointer?) -> Void, + to: UnsafeRawPointer.self, + ) + _ = unsafeBitCast( + chanoraSileroVadReset as @convention(c) (UnsafeMutableRawPointer?) -> Int32, + to: UnsafeRawPointer.self, + ) + _ = unsafeBitCast( + chanoraSileroVadProcess + as @convention(c) ( + UnsafeMutableRawPointer?, UnsafePointer?, Int, + UnsafeMutablePointer? + ) -> Int32, + to: UnsafeRawPointer.self, + ) + _ = unsafeBitCast( + chanoraSileroVadLastError as @convention(c) () -> UnsafeMutablePointer?, + to: UnsafeRawPointer.self, + ) + _ = unsafeBitCast( + chanoraSileroVadFreeString as @convention(c) (UnsafeMutablePointer?) -> Void, + to: UnsafeRawPointer.self, + ) + typealias CreateFn = @convention(c) () -> UnsafeMutableRawPointer? typealias DestroyFn = @convention(c) (UnsafeMutableRawPointer?) -> Void typealias ResetFn = @convention(c) (UnsafeMutableRawPointer?) -> Int32