markdown · 2913 bytes Raw Blame History

Sprint 18.5: HELLO LIBRARY MILESTONE (Dylib)

Prerequisites

Sprint 18 — executable path works end-to-end.

Goals

Validate MH_DYLIB output end-to-end. afs-ld emits a dylib that dlopen/dlsym can load and a minimal C or Fortran harness can call into. Proves that every dylib-specific decision in Sprints 10–17 is actually correct.

Deliverables

1. Staging fixture

tests/corpus/hello_library/:

  • foo.f90: module exporting a single foo_add(a, b) -> c interoperable procedure.
  • foo.o: assembled object.
  • caller.c: int main() { void *h = dlopen("./libfoo.dylib", RTLD_NOW); int (*f)(int,int) = dlsym(h, "foo_add"); printf("%d\n", f(2, 3)); }.
  • Expected runtime output: 5.
afs-ld -dylib foo.o libarmfortas_rt.a \
  -lSystem -syslibroot "$(xcrun --show-sdk-path)" \
  -install_name @rpath/libfoo.dylib \
  -compatibility_version 1.0 -current_version 1.0.0 \
  -no_uuid -platform_version macos 11.0 14.0 \
  -o libfoo.dylib

3. Validation checklist

  • file libfoo.dylibMach-O 64-bit dynamically linked shared library arm64.
  • otool -lV libfoo.dylib shows LC_ID_DYLIB with install-name @rpath/libfoo.dylib, current_version = 1.0.0, compat_version = 1.0.0. No __PAGEZERO, no LC_MAIN.
  • Export trie contains _foo_add (Fortran name-mangled per armfortas convention, or bind(C) if used).
  • dlopen("./libfoo.dylib", RTLD_NOW) returns non-null.
  • dlsym(h, "foo_add") returns the function address.
  • Calling foo_add(2, 3) returns 5.

4. Differential

Link the same inputs with ld -dylib and afs-ld. Compare load commands, export trie contents, indirect symbol table. Tolerated diffs same as Sprint 18.

5. -rpath interaction

caller is linked against libfoo.dylib with @rpath indirection. Sprint 19 will wire the full -rpath CLI; this sprint validates that an install-name of @rpath/... is correctly emitted and that the binary's DYLD_PRINT_LIBRARIES=1 output shows dyld resolving @rpath via the LC_RPATH entries of the caller.

6. dladdr/backtrace in the dylib

When foo_add calls into libarmfortas_rt, backtrace_symbols() should return readable names — proves the symbol table partitioning for a dylib is correct and the Sprint 17 unwind info is wired into dyld's unwinder.

Testing Strategy

  • tests/hello_library.rs: builds and dlopens the dylib, calls foo_add, asserts the return.
  • tests/hello_library_nm.rs: runs nm -D on the dylib, asserts _foo_add appears as external.
  • Differential harness with ld -dylib on the same inputs.

Definition of Done

  • libfoo.dylib loads via dlopen and exports _foo_add.
  • Calling the exported function returns the expected value.
  • Differential parity with ld on the staging fixture.
  • otool -lV shows correct dylib-specific load commands with no __PAGEZERO or LC_MAIN.
  • Post-sprint audit passes.
View source
1 # Sprint 18.5: HELLO LIBRARY MILESTONE (Dylib)
2
3 ## Prerequisites
4 Sprint 18 — executable path works end-to-end.
5
6 ## Goals
7 Validate `MH_DYLIB` output end-to-end. afs-ld emits a dylib that `dlopen`/`dlsym` can load and a minimal C or Fortran harness can call into. Proves that every dylib-specific decision in Sprints 10–17 is actually correct.
8
9 ## Deliverables
10
11 ### 1. Staging fixture
12 `tests/corpus/hello_library/`:
13 - `foo.f90`: module exporting a single `foo_add(a, b) -> c` interoperable procedure.
14 - `foo.o`: assembled object.
15 - `caller.c`: `int main() { void *h = dlopen("./libfoo.dylib", RTLD_NOW); int (*f)(int,int) = dlsym(h, "foo_add"); printf("%d\n", f(2, 3)); }`.
16 - Expected runtime output: `5`.
17
18 ### 2. Dylib link invocation
19 ```
20 afs-ld -dylib foo.o libarmfortas_rt.a \
21 -lSystem -syslibroot "$(xcrun --show-sdk-path)" \
22 -install_name @rpath/libfoo.dylib \
23 -compatibility_version 1.0 -current_version 1.0.0 \
24 -no_uuid -platform_version macos 11.0 14.0 \
25 -o libfoo.dylib
26 ```
27
28 ### 3. Validation checklist
29 - `file libfoo.dylib``Mach-O 64-bit dynamically linked shared library arm64`.
30 - `otool -lV libfoo.dylib` shows `LC_ID_DYLIB` with install-name `@rpath/libfoo.dylib`, `current_version = 1.0.0`, `compat_version = 1.0.0`. No `__PAGEZERO`, no `LC_MAIN`.
31 - Export trie contains `_foo_add` (Fortran name-mangled per armfortas convention, or `bind(C)` if used).
32 - `dlopen("./libfoo.dylib", RTLD_NOW)` returns non-null.
33 - `dlsym(h, "foo_add")` returns the function address.
34 - Calling `foo_add(2, 3)` returns `5`.
35
36 ### 4. Differential
37 Link the same inputs with `ld -dylib` and afs-ld. Compare load commands, export trie contents, indirect symbol table. Tolerated diffs same as Sprint 18.
38
39 ### 5. `-rpath` interaction
40 `caller` is linked against `libfoo.dylib` with `@rpath` indirection. Sprint 19 will wire the full `-rpath` CLI; this sprint validates that an install-name of `@rpath/...` is correctly emitted and that the binary's `DYLD_PRINT_LIBRARIES=1` output shows dyld resolving `@rpath` via the `LC_RPATH` entries of the caller.
41
42 ### 6. `dladdr`/`backtrace` in the dylib
43 When `foo_add` calls into `libarmfortas_rt`, `backtrace_symbols()` should return readable names — proves the symbol table partitioning for a dylib is correct and the Sprint 17 unwind info is wired into dyld's unwinder.
44
45 ## Testing Strategy
46 - `tests/hello_library.rs`: builds and `dlopen`s the dylib, calls `foo_add`, asserts the return.
47 - `tests/hello_library_nm.rs`: runs `nm -D` on the dylib, asserts `_foo_add` appears as external.
48 - Differential harness with `ld -dylib` on the same inputs.
49
50 ## Definition of Done
51 - `libfoo.dylib` loads via `dlopen` and exports `_foo_add`.
52 - Calling the exported function returns the expected value.
53 - Differential parity with `ld` on the staging fixture.
54 - `otool -lV` shows correct dylib-specific load commands with no `__PAGEZERO` or `LC_MAIN`.
55 - Post-sprint audit passes.