Skip to content

Proposal: fix shared library linking on macOS #8180

Description

@haberman

Problem Statement

It is currently impossible to use Zig to build shared (dynamic) libraries on macOS where some symbols are left undefined. For example, consider the program:

extern fn bar() void;

export fn foo() void {
    bar();
}

On Linux we can build a shared library from this without error:

$ zig build-lib -dynamic test.zig
$ ls
libtest.so  test.zig  zig-cache

However the same program compiled with the same command line fails on macOS:

$ zig build-lib -dynamic test.zig
warning(link): unexpected LLD stderr:
Undefined symbol: zig-cache/o/c4e95092e3ee25bb54ad9a24fb2ee9a5/test.o: _bar
symbol(s) not found

error: FileNotFound

This has previously been reported in #3085 and #8016.

Motivation

This is a blocker for creating loadable extension modules for Python, Ruby, Lua, Postgres, etc. -- basically any program that loads modules via dlopen() and expects that the symbols will be resolved at load time. If you only need your module to work on Linux, then Zig will work. But if you need macOS support, there appears to be no way to make this work, and no workaround that I could find.

Background

Both macOS and Linux have command line options that control whether symbols may be undefined at link time. However the options are different and the two platforms have opposite defaults. By default, Linux is lax and macOS is strict.

Linux

Linux has two different options:

  • --no-undefined: causes the link to fail if unresolved symbols are present in the library currently being built. This is unset by default, so Linux allows undefined references by default, as we observed in the problem statement.
  • --no-allow-shlib-undefined: causes the link to fail if unresolved symbols are present in any shared libraries that we are linking against (eg. pre-existing shared libraries). This defaults to unset (ie. allowing undefined references) when you are building a shared library, but the default is the opposite (failing for undefined references) if you are building an executable. This is a somewhat odd option that I don't know a real use case for, and macOS doesn't have an analogue to this that I am aware of.

(There are also a few other flags such as --unresolved-symbols=method and -z defs, which are just different porcelain around the same underlying options, AFAICT).

More info in this StackOverflow thread

macOS

macOS has just one option for this:

  • -undefined treatment, where treatment is one of: error, warning, suppress, or dynamic_lookup. The default is error.

Existing practice for loadable modules on macOS is to prefer -undefined dynamic_lookup. For example, see: Building Python extension modules on OS X with cmake or autotools

Prior Art

zig cc has an existing option --allow-shlib-undefined. It appears to have the following semantics:

  • This flag is only available for zig cc, not zig build-lib.
  • On macOS, it adds -undefined dynamic_lookup to the link, which is just what we want. 👍👍👍
  • On ELF targets, it adds --allow-shlib-undefined to the link. This seems wrong and possibly confused, since it is not the analogue of the -undefined dynamic_lookup flag on macOS (see the "Linux" section above).

Proposed Solution

The only hard requirement is that we can somehow pass -undefined dynamic_lookup to the linker on macOS. Anything that makes this possible, even an escape hatch like -Wl,arbitrary_linker_flag would solve the problem.

That said, here is my proposed solution:

  1. Make Zig pass -undefined dynamic_lookup to the macOS linker by default for zig build-lib -dynamic. That way, anybody porting their code from Linux to macOS will find that it "just works" and no platform-specific tweaks are needed.
  2. Do the same for zig cc, and deprecate the zig cc --allow-shlib-undefined option (since this behavior is the default now).
  3. (If desired/requested): Add a zig build-lib --no-undefined option for cases where users do truly want to force the shared library to define all symbols. This option would remove -undefined dynamic_lookup on macOS and add --no-undefined on Linux.

I think the use case for (3) is somewhat rare: I personally have never used ld --no-undefined or heard of anybody using it. I'm sure there are some users for it out there, but in terms of bang for the buck this seems rather niche.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    os-macosmacOSproposalThis issue suggests language modifications. If it also has the "accepted" label then it is planned.

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions