Skip to content

Fix @cfunction closure error on ARM64/Apple Silicon#99

Merged
amartinhuertas merged 1 commit into
mainfrom
fix-cfunction-arm64-aarch64
May 27, 2026
Merged

Fix @cfunction closure error on ARM64/Apple Silicon#99
amartinhuertas merged 1 commit into
mainfrom
fix-cfunction-arm64-aarch64

Conversation

@santiagobadia

Copy link
Copy Markdown
Member

Problem

On ARM64 (Apple Silicon / aarch64), @cfunction($closure, ...) cannot create thunk-based C function pointers at runtime. macOS enforces strict W^X memory protection, so the JIT cannot allocate writable+executable trampolines for closure-capturing callbacks. Any attempt to build a distributed p4est mesh fails with:

ERROR: cfunction: closures are not supported on this platform

This makes OctreeDistributedDiscreteModel (and anything that calls generate_face_labeling) completely unusable on Apple Silicon, regardless of Julia version.

Root cause

generate_face_labeling defined four p4est iterator callbacks (jcorner_callback, jedge_callback, jface_callback, jcell_callback) as local functions that captured variables from the enclosing scope. They were registered with @cfunction($f, ...) (dollar-sign = closure thunk), which fails on ARM64.

Fix

  • Introduce a mutable struct FaceLabelingCallbackData that bundles all captured state (topology face tables, entity arrays, coarse-mesh labeling data, iterator mode).
  • Lift the four callbacks to module-level named functions (_jcorner_callback_impl, _jedge_callback_impl, _jface_callback_impl, _jcell_callback_impl) that recover the struct via unsafe_pointer_to_objref(user_data).
  • Register them with @cfunction(f, ...) without $, which creates a static function pointer — fully supported on all platforms including ARM64.
  • Use GC.@preserve to pin the struct while C holds a raw pointer to it.
  • The iterator_mode previously passed as a separate Ref to p4est_iterate is now a field of the struct, updated between calls.

No behaviour change on x86-64. Only UniformlyRefinedForestOfOctreesDiscreteModels.jl is modified.

Testing

Tested on Apple Silicon (arm64-apple-darwin, Julia 1.10) with 4 MPI ranks on a 2D cubed-sphere mesh — previously crashed at mesh construction, now completes successfully.

Existing behaviour on x86-64 is unchanged (static cfunctions work identically on that platform).

On aarch64 (Apple Silicon), `@cfunction(\$closure, ...)` cannot
create thunk-based C function pointers at runtime because macOS
enforces strict W^X memory protection, preventing the JIT from
allocating writable+executable trampolines.

The four p4est iterator callbacks defined inside generate_face_labeling
(corner, edge, face, cell) captured local Julia variables as closures
and were registered via `@cfunction(\$f, ...)`. This caused the error:

    cfunction: closures are not supported on this platform

Fix: bundle all captured state into a module-level mutable struct
`FaceLabelingCallbackData` and pass it through the `user_data` pointer
that p4est provides to every callback invocation. The four callbacks
are lifted to module-level named functions and registered with the
non-closure form `@cfunction(f, ...)`, which produces a static function
pointer compatible with ARM64.

The `GC.@preserve` block around the p4est_iterate calls ensures the
struct is not collected or moved while C holds a raw pointer to it.

Tested on Apple Silicon (arm64-apple-darwin, Julia 1.10) with 4 MPI
ranks on a 2D cubed-sphere mesh.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@amartinhuertas amartinhuertas merged commit c224dfa into main May 27, 2026
2 checks passed
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