From 1bc2fccd0a2e69cf7f6772db76a910bc22d1d9c3 Mon Sep 17 00:00:00 2001 From: Edison Jwa Date: Sun, 7 Jun 2026 23:08:18 +0900 Subject: [PATCH] fix(ios,macos): add -u force-undefined linker flags for @_cdecl symbols Oracle re-review on PR #26 flagged that the Swift-side `_ = unsafeBitCast(fn as @convention(c) ...)` static references in ChanoraSileroSelfTest.run() are not a robust anti-dead-strip guarantee under WMO + LTO. The optimizer can prove the discarded result has no side effects and eliminate the address-taken reference. The load-bearing fix is a second linker flag per symbol: -u _sym forces the symbol as undefined at link time, preventing the object that defines it from being dropped and stopping -dead_strip from removing the definition. -exported_symbol _sym was already present; re-exports the symbol in the binary's dynamic symbol table so the Rust framework's dlsym(RTLD_DEFAULT) can find it. This flag alone does NOT prevent dead- strip; it only controls the export list applied AFTER dead-strip. Both flags now appear per symbol on both iOS and macOS Release xcconfigs. The Swift-side static references stay as defense-in-depth but are no longer the load-bearing guarantee. --- .../ios/Flutter/Release.xcconfig | 21 +++++++++++++++---- .../macos/Flutter/Flutter-Release.xcconfig | 10 ++++----- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/apps/chanora_flutter/ios/Flutter/Release.xcconfig b/apps/chanora_flutter/ios/Flutter/Release.xcconfig index f752e6a..ad1f188 100644 --- a/apps/chanora_flutter/ios/Flutter/Release.xcconfig +++ b/apps/chanora_flutter/ios/Flutter/Release.xcconfig @@ -2,10 +2,23 @@ #include "Generated.xcconfig" // Force the linker to retain Swift @_cdecl symbols that the chanora_bridge -// Rust framework resolves at runtime via dlsym(RTLD_DEFAULT). Without these -// flags, Xcode Archive's -dead_strip removes them because no Swift caller -// exists, and CoreML VAD silently falls back to WebRTC on TestFlight/App Store. -OTHER_LDFLAGS = $(inherited) -Xlinker -exported_symbol -Xlinker _chanora_silero_vad_create -Xlinker -exported_symbol -Xlinker _chanora_silero_vad_destroy -Xlinker -exported_symbol -Xlinker _chanora_silero_vad_reset -Xlinker -exported_symbol -Xlinker _chanora_silero_vad_process -Xlinker -exported_symbol -Xlinker _chanora_silero_vad_last_error -Xlinker -exported_symbol -Xlinker _chanora_silero_vad_free_string +// Rust framework resolves at runtime via dlsym(RTLD_DEFAULT). Two flags per +// symbol, intentionally redundant: +// +// -u _sym marks the symbol as force-undefined at link time, +// which keeps the object that defines it from being +// dropped and prevents dead-strip from removing the +// definition. This is the load-bearing flag. +// -exported_symbol _sym re-exports the symbol in the final binary's +// dynamic symbol table so dlsym(RTLD_DEFAULT) can +// find it from the Rust framework at runtime. +// +// Without -u, Xcode Archive's -dead_strip (WMO + LTO) can remove the +// symbol before the export list is applied, and CoreML VAD silently falls +// back to WebRTC on TestFlight / App Store. The Swift-side static +// `unsafeBitCast` references in SileroCoreMLBridge.swift are belt-and- +// suspenders defense-in-depth, NOT the primary guarantee. +OTHER_LDFLAGS = $(inherited) -Xlinker -u -Xlinker _chanora_silero_vad_create -Xlinker -u -Xlinker _chanora_silero_vad_destroy -Xlinker -u -Xlinker _chanora_silero_vad_reset -Xlinker -u -Xlinker _chanora_silero_vad_process -Xlinker -u -Xlinker _chanora_silero_vad_last_error -Xlinker -u -Xlinker _chanora_silero_vad_free_string -Xlinker -exported_symbol -Xlinker _chanora_silero_vad_create -Xlinker -exported_symbol -Xlinker _chanora_silero_vad_destroy -Xlinker -exported_symbol -Xlinker _chanora_silero_vad_reset -Xlinker -exported_symbol -Xlinker _chanora_silero_vad_process -Xlinker -exported_symbol -Xlinker _chanora_silero_vad_last_error -Xlinker -exported_symbol -Xlinker _chanora_silero_vad_free_string // `STRIP_STYLE = all` (Xcode default for archive installs) runs `strip` without // `-x`, which removes even the global @_cdecl symbols the linker exported above diff --git a/apps/chanora_flutter/macos/Flutter/Flutter-Release.xcconfig b/apps/chanora_flutter/macos/Flutter/Flutter-Release.xcconfig index 856b483..6cef814 100644 --- a/apps/chanora_flutter/macos/Flutter/Flutter-Release.xcconfig +++ b/apps/chanora_flutter/macos/Flutter/Flutter-Release.xcconfig @@ -1,11 +1,11 @@ #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" -// macOS Release defaults to DEAD_CODE_STRIPPING = YES. Without these flags the -// Swift @_cdecl symbols below would be stripped because no Swift caller exists; -// the chanora_bridge Rust framework resolves them at runtime via -// dlsym(RTLD_DEFAULT) and would silently fall back to WebRTC VAD. -OTHER_LDFLAGS = $(inherited) -Xlinker -exported_symbol -Xlinker _chanora_silero_vad_create -Xlinker -exported_symbol -Xlinker _chanora_silero_vad_destroy -Xlinker -exported_symbol -Xlinker _chanora_silero_vad_reset -Xlinker -exported_symbol -Xlinker _chanora_silero_vad_process -Xlinker -exported_symbol -Xlinker _chanora_silero_vad_last_error -Xlinker -exported_symbol -Xlinker _chanora_silero_vad_free_string +// macOS Release defaults to DEAD_CODE_STRIPPING = YES. See +// ios/Flutter/Release.xcconfig for the full rationale; -u is the +// load-bearing flag, -exported_symbol re-exports for dlsym, both +// are intentionally present per @_cdecl symbol. +OTHER_LDFLAGS = $(inherited) -Xlinker -u -Xlinker _chanora_silero_vad_create -Xlinker -u -Xlinker _chanora_silero_vad_destroy -Xlinker -u -Xlinker _chanora_silero_vad_reset -Xlinker -u -Xlinker _chanora_silero_vad_process -Xlinker -u -Xlinker _chanora_silero_vad_last_error -Xlinker -u -Xlinker _chanora_silero_vad_free_string -Xlinker -exported_symbol -Xlinker _chanora_silero_vad_create -Xlinker -exported_symbol -Xlinker _chanora_silero_vad_destroy -Xlinker -exported_symbol -Xlinker _chanora_silero_vad_reset -Xlinker -exported_symbol -Xlinker _chanora_silero_vad_process -Xlinker -exported_symbol -Xlinker _chanora_silero_vad_last_error -Xlinker -exported_symbol -Xlinker _chanora_silero_vad_free_string // See ios/Flutter/Release.xcconfig for the STRIP_STYLE rationale. STRIP_STYLE = non-global