Hi Paul,

thanks for reporting this issue.

None of us on the GNU APL side currently have a macOS machine, so we
can't reproduce this directly -- but one detail in your report already
points somewhere fairly specific, and I'd like to help narrow it down
rather than leave you guessing.

"apl --v" segfaulting is the important clue: that just prints a version
string and exits, before argument parsing even reaches the interactive
line editor. So this can't be the stdin/iostream code you suspected --
it has to be crashing in C++ static initialization, i.e. before main()'s
first line even runs.

src/static_Objects.cc exists specifically because of this class of
problem: its own header comment cites ISO C++ 3.6.2 (the
static-initialization-order rule) and it deliberately sequences several
global objects that depend on each other (Workspace::the_workspace,
Macro::init() and all Macro definitions, etc.) by relying on the one
ordering guarantee the standard does give: within a single translation
unit, construction happens top-to-bottom. What that file can't control
is its order *relative to* dozens of other global singletons defined in
Quad_*.cc/Bif_*.cc across other files -- that has only ever worked by
whatever order the linker happened to place object files in. Recent
Apple linkers (the one introduced around Xcode 15, moving toward being
the default) are known to order and dead-strip differently than the
classic ld64. If Xcode 27 continues that, it's quite plausible some
Quad_XX::fun constructor is now running before Workspace::the_workspace
exists, touching state that isn't there yet -- segfaulting the same way
on every invocation, exactly as you're seeing, with the banner in the
normal case suggesting some but not all of that chain completes first.

That's our leading hypothesis, not a confirmed diagnosis -- I'd like an
actual backtrace before anyone changes any code. I've put together a
small script that captures exactly that (a real lldb backtrace for the
"apl --v" crash, plus two backtrace samples of the interactive hang a
few seconds apart, plus the toolchain/linker details needed to check the
theory above) and packages it into one tarball to attach here. Save it
as macos_diag.sh next to your built apl binary (in src/), chmod +x it,
and run ./macos_diag.sh from there:

----------------------------------------------------------------------
#!/bin/bash
#
# macos_diag.sh -- collect diagnostic evidence for "fails to build/run on
# macOS 27 + Xcode 27" ([email protected], Paul Rockwell, 2026-09-24): apl --v
# segfaults immediately (before any interactive input is even possible), and
# a normal interactive start prints the welcome banner, a couple of garbage
# characters, then hangs instead of prompting.
#
# We (GNU APL upstream) have no macOS machine to reproduce this on. This
# script exists to gather, in one pass, what we would otherwise have to ask
# for one command at a time: a real crash backtrace (not just "it
# segfaults"), the same for the hang, and enough toolchain/build detail to
# tell whether this is a static-initialization-order issue newly exposed by
# a different linker. That is our leading hypothesis: src/static_Objects.cc
# exists specifically to work around undefined *cross-translation-unit*
# static-init order (its own header comment cites ISO C++ 3.6.2), by
# consolidating every order-sensitive global (Workspace::the_workspace,
# Macro::init(), etc.) into one file and relying on the standard's
# within-one-TU top-to-bottom guarantee. Dozens of *other* files
# (Quad_*.cc, Bif_*.cc) each define their own global singleton whose
# construction order *relative to* that chain has never actually been
# guaranteed by the standard -- only by whatever order the old linker
# happened to place object files in. Recent Apple linkers (the one
# introduced around Xcode 15, on track to become fully default) are known to
# order/dead-strip differently than the classic ld64; if that is what
# happened here, some Quad_XX::fun constructor is now plausibly running
# before Workspace::the_workspace exists, touching state that isn't there
# yet -- segfaulting identically and immediately on every invocation,
# exactly as reported (even the simplest possible flag crashes; the banner
# still printing on a normal start suggests some, but not all, of that
# chain completed before things went wrong).
#
# Usage (from the built src/ directory; ./apl must already exist):
#   ./macos_diag.sh
#
# Produces macos_diag_<hostname>.tar.gz in the current directory -- attach
# that (or paste its contents) in a reply on [email protected].

set -u

MAX_WIDTH=79
rep() { local c=$1 n=$2 s="" i; for ((i = 0; i < n; i++)); do s+=$c; done; printf '%s' "$s"; }
wrap_text() {
   local text=$1 maxlen=$2
   WRAP_LINES=()
   local line="" word
   for word in $text; do
      if [[ -z "$line" ]]; then line=$word
      elif (( ${#line} + 1 + ${#word} <= maxlen )); then line+=" $word"
      else WRAP_LINES+=("$line"); line=$word
      fi
   done
   WRAP_LINES+=("$line")
}
box() {
   local hc=$1 tl=$2 tr=$3 bl=$4 br=$5 side=$6 label=$7 text=$8
   local interior=$(( MAX_WIDTH - 2 )); local maxlen=$(( interior - 2 ))
   (( maxlen < 10 )) && maxlen=10
   wrap_text "$text" "$maxlen"
   local width=${#label} wl w
   for wl in "${WRAP_LINES[@]}"; do w=$(( ${#wl} + 2 )); (( w > width )) && width=$w; done
   local lead=4 tail=$(( width - lead - ${#label} ))
   (( tail < 0 )) && { lead=0; tail=$(( width - ${#label} )); }
   echo
   printf '%s%s%s%s%s\n' "$tl" "$(rep "$hc" $lead)" "$label" "$(rep "$hc" $tail)" "$tr"
   for wl in "${WRAP_LINES[@]}"; do
      local content=" $wl "
      while (( ${#content} < width )); do content+=' '; done
      printf '%s%s%s\n' "$side" "$content" "$side"
   done
   printf '%s%s%s\n' "$bl" "$(rep "$hc" $width)" "$br"
}
STEP=0
step() { (( ++STEP )); box '═' '╔' '╗' '╚' '╝' '║' " Step #$STEP " "$*"; }
note() { box '─' '┌' '┐' '└' '┘' '│' " $1 " "$2"; }

if [[ ! -x ./apl ]]; then
   echo "macos_diag.sh: must be run from the src/ directory (need ./apl" \
        "here, already built)" >&2
   exit 1
fi

HOST_TAG=$(hostname 2>/dev/null || uname -n 2>/dev/null)
HOST_TAG=${HOST_TAG//[^A-Za-z0-9._-]/_}
[[ -z "$HOST_TAG" ]] && HOST_TAG=unknown-host

OUTDIR=$(mktemp -d)
TARBALL=$(pwd)/macos_diag_$HOST_TAG.tar.gz

step "collect toolchain and environment info"
{
   echo "=== sw_vers ==="; sw_vers 2>&1
   echo; echo "=== uname -a ==="; uname -a 2>&1
   echo; echo "=== arch ==="; arch 2>&1
   echo; echo "=== xcodebuild -version ==="; xcodebuild -version 2>&1
   echo; echo "=== xcode-select -p ==="; xcode-select -p 2>&1
   echo; echo "=== clang --version ==="; clang --version 2>&1
   echo; echo "=== ld -version_details (or -v) ==="
   ld -version_details 2>&1 || ld -v 2>&1
   echo; echo "=== otool -L ./apl (linked libraries) ==="; otool -L ./apl 2>&1
   echo; echo "=== file ./apl ==="; file ./apl 2>&1
   echo; echo "=== ./apl --cfg ==="; ./apl --cfg 2>&1
} > "$OUTDIR/environment.txt" 2>&1
note "wrote" "$OUTDIR/environment.txt"

step "capture a real backtrace for 'apl --v' (reported to segfault" \
     "immediately)"
if command -v lldb >/dev/null 2>&1; then
   lldb -b \
        -o "run --v" \
        -o "bt all" \
        -o "quit" \
        -- ./apl > "$OUTDIR/crash_apl_dash_v.txt" 2>&1
   note "wrote" "$OUTDIR/crash_apl_dash_v.txt (look for the first frame" \
                "below the signal line -- if it's a global constructor" \
                "rather than anything in main(), that confirms the" \
                "static-init-order hypothesis above)"
else
   echo "lldb not found -- install Xcode Command Line Tools" \
        > "$OUTDIR/crash_apl_dash_v.txt"
   note "lldb missing" "recorded a note instead; please install Xcode" \
                        "Command Line Tools (xcode-select --install) and" \
                        "re-run this script"
fi

step "capture two backtrace samples of the interactive hang (banner +" \
     "garbage characters, then no prompt)"
# A real pty is needed here, not a plain pipe: GNU APL's raw-mode terminal
# setup (LineInput.cc, tcsetattr) takes a different code path when stdin
# isn't a tty at all, which could easily fail to reproduce the reported
# hang. BSD/macOS 'script' allocates one for us.
HANG_LOG="$OUTDIR/hang_session.txt"
script -q /dev/null ./apl > "$HANG_LOG" 2>&1 &
SCRIPT_PID=$!
sleep 3
APL_PID=$(pgrep -n -f '(^|/)apl$' 2>/dev/null)

if [[ -z "$APL_PID" ]]; then
   note "could not find the apl process" "it may have exited (crashed?)" \
        "instead of hanging this time -- see $HANG_LOG for whatever it" \
        "printed before that"
else
   {
      echo "=== sample 1, $(date '+%H:%M:%S') ==="
      lldb -b -p "$APL_PID" -o "bt all" -o "detach" -o "quit" 2>&1
      echo
   } > "$OUTDIR/hang_backtrace.txt"
   sleep 5
   {
      echo "=== sample 2, $(date '+%H:%M:%S') ==="
      lldb -b -p "$APL_PID" -o "bt all" -o "detach" -o "quit" 2>&1
   } >> "$OUTDIR/hang_backtrace.txt"
   note "wrote" "$OUTDIR/hang_backtrace.txt (two samples 5s apart -- the" \
                "same location both times means a genuinely stuck" \
                "wait/loop, not just something slow)"
fi
kill -KILL "$APL_PID" 2>/dev/null
kill -KILL "$SCRIPT_PID" 2>/dev/null
wait 2>/dev/null

step "package everything for emailing"
tar -czf "$TARBALL" -C "$OUTDIR" .
rm -rf "$OUTDIR"
note "wrote" "$TARBALL"

echo
echo "Done. Please attach $TARBALL (or paste its contents) in a reply on" \
     "[email protected]. Thank you for helping track this down -- none of us" \      "on the GNU APL side currently have a macOS machine to reproduce it on."
----------------------------------------------------------------------

If lldb refuses to attach for the hang capture (System Integrity
Protection sometimes blocks debugger attach to processes outside
Xcode), that's fine -- send whatever the script did produce along with
a note that it happened, and we'll go from there.

Thanks again for digging into this and for offering to help others hit
the same wall -- much appreciated.

Best regards,
Jürgen


On 9/24/26 22:36, Paul Rockwell wrote:
A head up to anyone trying to build GNU APL on macOS 27 Golden Gate with Xcode 
27 or Command Line tools for Xcode 27.

It doesn't work.

I'm trying to get a handle on what's going on, but evidently Apple made some 
change to Xcode and/or the C++ environment that is breaking GNU APL. Everything 
works fine under macOS 26. And if you try to run GNU APL that you built on 
macOS 26 after upgrading to macOS 27, it still works.

Even something as simple as trying to display help via "apl --v" results in a 
seg fault.

Trying to bring up APL normally prints the welcome banner, displays a couple of 
garbage characters, and then hangs instead of prompting for input.

I'm suspecting that something in the way GNU APL is dealing with std input and 
output (or the C++ iostream llibraries) s incompatible with something that 
Apple changed in macOS 27 and/or the clang C++ environment that's present in 
the compilation system in Xcode 27.

Any thoughts on how to debug this would be appreciated.  I suspedt that Jürgen 
doesn't use macOS so us macOS users are probably going to have to help figure 
this out. If anyone else runs into this on macOS, please reach out and perhaps 
we can collaborate on getting to the bottom of what's wrong.

- Paul


Reply via email to