Skip to content

Export the generated and core wrapper symbols as public for GCC 16.2 - #30

Merged
nichollsh merged 1 commit into
mainfrom
egp/export-c-binding-symbols-gcc16
Sep 8, 2026
Merged

nichollsh merged 1 commit into
mainfrom
egp/export-c-binding-symbols-gcc16

Conversation

@egpbos

@egpbos egpbos commented Sep 8, 2026

Copy link
Copy Markdown
Member

PR Summary

Sci/Tech Reviewer: @nichollsh
Code Reviewer:

Follow-up to #29. That PR fixed the first symptom of the GCC 16.2 symbol
visibility regression, but 125 of the 128 C binding symbols are still hidden,
so AGNI remains unable to load the library.

The compiler behaviour

GCC 16.2.0 (GCC PR126872) gives a PRIVATE module procedure bound to C hidden
ELF visibility, even though its binding label gives it external linkage. As a
result dlsym() can no longer resolve it. Any wrapper module that relies on
the binding label alone, without naming the procedure in a PUBLIC statement,
stops being reachable from Julia.

Why #29 is not sufficient on its own

real_kind_bytes is the first symbol the Julia layer resolves: it is the RealK
precision probe at julia/src/SOCRATES.jl:15, executed while the module is
being loaded. So it is the first thing to fail and the natural thing to fix.
The same bare PRIVATE appears in two further places, however, so fixing only
UTILITIES_CF moves the failure one step later rather than removing it.

Counting PS_ symbols in julia/lib/libSOCRATES_C.so, built with GCC 16.2.0
on macOS arm64:

SOCRATES exported (T) hidden (t) first symbol that fails
2603.8, before #29 2 126 PS_real_kind_bytes
2603.9, current main 3 125 PS_create_StrDim
this branch 128 0 none

What each line does

julia/src/GenFortranWrappers.jl writes private into the header of every
module it generates. That hides 108 symbols across the 24 generated wrappers,
including PS_create_StrDim, which is the next symbol AGNI resolves after the
precision probe, via julia/gen/StrDim_JL.jl:9.

julia/src/SOCRATES_C.f90 carries its own bare private, which hides the 20
radiative transfer entry points: PS_radiance_calc, PS_read_spectrum,
PS_set_spectrum, PS_diff_planck_source and the PS_allocate_* and
PS_deallocate_* families. These are the calls that do the actual work, so
even once structure construction succeeded, nothing would run.

Both kinds of module exist solely to expose their bind(C) procedures. Nothing
in them is intended to be internal, so PUBLIC is the appropriate default, and
it matches the treatment #29 gave UTILITIES_CF.

version goes from 2603.9 to 2603.10, mirroring what #29 did. Happy to drop
that hunk if it interferes with the release flow.

Downstream failures this fixes

Against current main, AGNI fails to precompile and then raises at runtime:

juliacall.JuliaError: could not load symbol "PS_create_StrDim":
dlsym(0x2a4fc9bb0, PS_create_StrDim): symbol not found
  @ AGNI.atmosphere ~/AGNI/src/state/atmosphere.jl:756   (atmosphere.setup!)
  @ ~/socrates/julia/gen/StrDim_JL.jl:9 [inlined]

These PROTEUS tests fail against main and pass on this branch:

  • tests/integration/test_integration_agni_transparent_limit.py::test_transparent_greygas_olr_equals_blackbody_emission
  • tests/integration/test_integration_agni_transparent_limit.py::test_transparent_greygas_olr_scales_with_surface_emissivity
  • tests/integration/test_integration_agni_transparent_limit.py::test_transparent_banded_olr_recovers_blackbody_emission
  • tests/integration/test_integration_agni_greygas_interior.py::test_agni_greygas_dummy_interior_coupling

The first three assert an analytic result, that outgoing longwave radiation
equals the surface blackbody emission in the transparent limit. They therefore
confirm that radiative transfer returns correct values, not merely that the
library loads.

PROTEUS pins SOCRATES by commit SHA and is still on 2603.8, so it will need its
pin bumped past this change to pick the fix up.

Code Quality Checklist

  • I have performed a self-review of my own code
  • My code follows the project's style guidelines
  • Comments have been included that aid undertanding and enhance the
    readability of the code
  • My changes generate no new warnings

The change is one keyword in each of two locations, so no new comments were
warranted. The wrapper build reports the same pre-existing warnings as before
it (unused variables in Utilities_CF.f90, a maybe-uninitialized in
SOCRATES_C.f90) and no new ones.

Testing

  • If shared files have been modified, I have run the UM and LFRic Apps rose
    stem suites
  • If any tests fail (rose-stem or CI) the reason is understood and
    acceptable (eg. kgo changes)
  • I have added tests to cover new functionality as appropriate (eg. system
    tests, unit tests, etc.)

The two source changes are confined to the julia/ wrapper layer, which the UM
and LFRic Apps builds do not compile, so rose stem does not exercise them. No
test was added here because the behaviour is covered by the PROTEUS AGNI
coupling tests listed above; a SOCRATES-side equivalent would need a Julia test
harness this repository does not currently have.

Verification performed, all on macOS arm64 with GCC 16.2.0, netCDF-Fortran
4.6.4, Julia 1.12.7:

  1. ./configure && ./build_code at this branch head, completing with
    All compiled OK.
  2. julia generate_wrappers.jl followed by make in julia/lib, then
    nm libSOCRATES_C.so, giving 128 exported PS_ symbols and none local.
  3. The four PROTEUS AGNI tests above, run against this tree via RAD_DIR:
    4 passed.
  4. The same four tests against a build of current main, reproduced by reverting
    only these two source hunks: 4 failed, at PS_create_StrDim.

trac.log

Not applicable; this change was not tested through trac. The measurements above
stand in for it.

Security Considerations

  • I have reviewed my changes for potential security issues
  • Sensitive data is properly handled (if applicable)
  • Authentication and authorisation are properly implemented (if applicable)

The change widens Fortran module accessibility for procedures that already
carried C binding labels and were already intended to be called externally. It
exposes no new functionality.

Performance Impact

  • Performance of the code has been considered and, if applicable, suitable
    performance measurements have been conducted

Symbol visibility only. No effect on generated code paths or numerics.

AI Assistance and Attribution

  • Some of the content of this change has been produced with the assistance
    of Claude Code (Claude Opus) and I have followed the
    Simulation Systems AI policy
    (including attribution labels)

The diagnosis, the two source changes and the verification runs above were
produced with Claude Code and reviewed by the author.

Documentation

  • Where appropriate I have updated documentation related to this change and
    confirmed that it builds correctly

No documented interface changes; the affected symbols were already part of the
intended Julia-facing API.

GCC 16.2.0 (PR126872) gives a PRIVATE module procedure bound to C hidden
ELF visibility even though its binding label gives it external linkage,
so dlsym() can no longer find it. #29 fixed real_kind_bytes in
UTILITIES_CF, the first symbol the Julia wrapper resolves, but the same
bare PRIVATE remains in two other places and hides the other 125 entry
points, so the failure moves one step later instead of going away.

GenFortranWrappers.jl writes PRIVATE into every module it generates,
hiding 108 symbols across the 24 generated wrappers, PS_create_StrDim
among them. SOCRATES_C.f90 carries its own bare PRIVATE, hiding the 20
radiative transfer entry points including PS_radiance_calc and
PS_read_spectrum. Both kinds of module exist only to expose their
bind(C) procedures, so PUBLIC is the appropriate default.

Built with GCC 16.2.0 on macOS arm64: libSOCRATES_C.so exports all 128
PS_ symbols with none left local, where main exports 3 of 128.
@nichollsh

nichollsh commented Sep 8, 2026

Copy link
Copy Markdown
Member

Thanks for this, @egpbos. I thought that my last change to SOCRATES had resolved the problem - apparently it did not - but what you describe here makes sense to me.

Have you tested these SOCRATES updates by running AGNI (standalone) and AGNI+PROTEUS (coupled) locally? You mention above that the tests pass, but I just want to be sure that using the model 'as intended' also passes with this patch.

Bumping to 2603.10 is fine.

@egpbos

egpbos commented Sep 8, 2026

Copy link
Copy Markdown
Member Author

Apologies for the mega ultra long Claude PR summary, I clearly need to tune my chatty bot much better still 😅 I think my Ponytail actually got lost in my laptop migration... Anyway.

Yes, AGNI on my laptop (in a Pixi env btw, which is what I'm testing currently; seems to work) fails tests without these two fixes and passes with. My pixi-testing session initially had three fixes, but indeed you already caught one of them 😄 The setup was done on a clean PROTEUS clone of a few days ago.

@nichollsh

Copy link
Copy Markdown
Member

That's fantastic, thanks for clarifying. I am happy to merge this PR, then.

I will then delete the SOCRATES cache from both PROTEUS and AGNI repos. I will then also re-run the AGNI install_and_test action on its main branch, to be certain this is now working there.

Could you then make a PR on the PROTEUS side to update the SOCRATES hash in its pyproject.toml file?

@nichollsh
nichollsh merged commit c329658 into main Sep 8, 2026
1 check passed
@egpbos

egpbos commented Sep 9, 2026

Copy link
Copy Markdown
Member Author

Could you then make a PR on the PROTEUS side to update the SOCRATES hash in its pyproject.toml file?

Done: FormingWorlds/PROTEUS#863 However, it's blocking on AGNI pins, so we need to resolve them both.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants