Skip to content

[native_toolchain_c] LinkerOptions.treeshake fails to link on macOS and iOS if a symbol to keep is not defined #3737

Open

Description

@mosuem

Note

This issue was generated by an AI coding agent (Jetski) on behalf of @mosuem.

LinkerOptions.treeshake(symbolsToKeep: ...) passes -Wl,-u,_<symbol> for every symbol to keep on macOS and iOS. ld64 documents -u symbol_name as "specifies that symbol symbol_name must be defined for the link to succeed", so if one of the symbols isn't defined by the input archives, the link fails with an undefined-symbols error.

Tree-shaking can easily ask for symbols that a library doesn't define, for example when bindings cover functions that a particular build or target leaves out. #3726 fixes the equivalent problem on Windows (LNK2001 from the generated module-definition file) by reading the symbol table of the input archives and only keeping the symbols they define. Linux and Android don't have the problem: for shared libraries, -u of a symbol that nothing defines is a no-op there.

Doing the same for macOS and iOS needs a reader for the BSD archive symbol table, which differs from the COFF/GNU one that #3726 reads: the first member is named __.SYMDEF or __.SYMDEF SORTED (or __.SYMDEF_64/__.SYMDEF_64 SORTED for 64-bit tables), usually with a BSD #1/<length> extended name stored at the start of the member data, followed by a little-endian ranlib table of (string offset, member offset) pairs and a string table. Mach-O symbol names carry a leading underscore, so _<symbol> is what to look up.

Until then, packages that tree-shake on macOS and iOS have to filter symbolsToKeep themselves.

Activity

  1. added a commit that references this issue on Oct 9, 2026
    53811ed
  2. dcharkes commented on Oct 12, 2026

    @dcharkes
    Collaborator

    Tree-shaking can easily ask for symbols that a library doesn't define

    This is a result of:

    for example when bindings cover functions that a particular build or target leaves out

    only when TFA cannot prove that you will never call it at runtime (at runtime you would fail to lookup the symbol!)

    A workaround could be add

    if (Platform.isXxx) throw UnreachableError();

    in the code that wraps the call, or structure your code in another way so that TFA can see the native calls are unreachable.

    In your specific use case, which symbols are retained but shouldn't? How is your code structured? Any patterns we can observe but TFA can't?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions