fix(ios): zero-initialize TfLiteCoreMlDelegateOptions - #196
Conversation
TfLiteCoreMlDelegateOptions is a plain C struct with no ...OptionsDefault()
helper, and it was declared without any initialization - so enabled_devices,
coreml_version, max_delegated_partitions and min_nodes_per_partition were
stack garbage. Depending on what values happened to be on the stack, this
poisons the delegate's graph partitioning and can crash (EXC_BAD_ACCESS)
inside the first TfLiteInterpreterInvoke.
Zero values are the documented defaults (ANE-only devices, newest CoreML
version, unlimited partitions), so '= {}' is the correct initialization.
Same fix as margelo#165, re-rolled against the Nitro codebase per maintainer
feedback there.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Hey @jslok, thanks for the PR. What bug does this PR intend to fix though? Do you know of any github issue? Asking so I can repro and understand the situation better |
|
Hi @DaveyEke, fair question. There's no dedicated issue; the history is #165, which fixed the same line in the codebase before the Nitro migration and was closed as out of sync, with an invitation to reopen it against the current code. This PR is that reroll: the Nitro migration carried the uninitialized declaration into TfliteHelpers.cpp. To set expectations honestly: this is an uninitialized memory bug (undefined behavior), so there's no deterministic repro to hand you. delegateOptions contains whatever happens to be on the stack when getCoreMLDelegate() runs. On most stacks the garbage is benign and everything works, which is why nobody files an issue, but all four fields directly control delegate behavior:
The easiest way to observe it is not a crash but the values themselves: breakpoint (or log) the struct fields right before TfLiteCoreMlDelegateCreate. They're arbitrary, not the documented defaults. In our production app we've seen nondeterministic first inference EXC_BAD_ACCESS inside TfLiteInterpreterInvoke with the CoreML delegate on some device/OS combos. I can't prove uninitialized partitioning params are the cause, since UB doesn't leave that kind of receipt, but they're exactly the class of input that can produce it, and initializing to zero is unambiguously correct regardless: per coreml_delegate.h, zero is the documented default for every field, so = {} just makes the code do what it already assumes it's doing. |
What
getCoreMLDelegate()declaresTfLiteCoreMlDelegateOptions delegateOptions;without initialization. It's a plain C struct with no...OptionsDefault()helper, so all four fields (enabled_devices,coreml_version,max_delegated_partitions,min_nodes_per_partition) are indeterminate stack garbage.Depending on what happens to be on the stack, this poisons the delegate's graph partitioning — we've seen it produce
EXC_BAD_ACCESSinside the firstTfLiteInterpreterInvokeon some device/OS combinations, and it can also silently change which devices the delegate enables.Fix
= {}. Zero values are the documented defaults (ANE-only devices, newest CoreML version, unlimited partitions).History
This is the same bug #165 fixed — that PR was closed because it targeted the pre-Nitro
TensorflowPlugin.cppand you invited a re-roll against the current codebase ("if this is still relevant in the latest version, we can open another PR and get it merged 👍"). It is still relevant: the Nitro migration carried the uninitialized declaration intocpp/TfliteHelpers.cpp.We've been shipping this as a patch-package fix in production.
🤖 Generated with Claude Code