fix(ios,macos): add static @_cdecl references to defeat dead-strip

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(<fn> 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
This commit is contained in:
Edison Jwa
2026-06-07 23:12:07 +09:00
parent 9d8a1f8fd1
commit 80c73f34c3
2 changed files with 78 additions and 0 deletions
@@ -106,6 +106,45 @@ public func chanoraSileroVadFreeString(_ string: UnsafeMutablePointer<CChar>?) {
@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<Float>?, Int,
UnsafeMutablePointer<Float>?
) -> Int32,
to: UnsafeRawPointer.self,
)
_ = unsafeBitCast(
chanoraSileroVadLastError as @convention(c) () -> UnsafeMutablePointer<CChar>?,
to: UnsafeRawPointer.self,
)
_ = unsafeBitCast(
chanoraSileroVadFreeString as @convention(c) (UnsafeMutablePointer<CChar>?) -> Void,
to: UnsafeRawPointer.self,
)
typealias CreateFn = @convention(c) () -> UnsafeMutableRawPointer?
typealias DestroyFn = @convention(c) (UnsafeMutableRawPointer?) -> Void
typealias ResetFn = @convention(c) (UnsafeMutableRawPointer?) -> Int32
@@ -106,6 +106,45 @@ public func chanoraSileroVadFreeString(_ string: UnsafeMutablePointer<CChar>?) {
@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<Float>?, Int,
UnsafeMutablePointer<Float>?
) -> Int32,
to: UnsafeRawPointer.self,
)
_ = unsafeBitCast(
chanoraSileroVadLastError as @convention(c) () -> UnsafeMutablePointer<CChar>?,
to: UnsafeRawPointer.self,
)
_ = unsafeBitCast(
chanoraSileroVadFreeString as @convention(c) (UnsafeMutablePointer<CChar>?) -> Void,
to: UnsafeRawPointer.self,
)
typealias CreateFn = @convention(c) () -> UnsafeMutableRawPointer?
typealias DestroyFn = @convention(c) (UnsafeMutableRawPointer?) -> Void
typealias ResetFn = @convention(c) (UnsafeMutableRawPointer?) -> Int32