Fix and enable building FreeBASIC for Darwin / Apple Silicon - #479
Open
agorangetek wants to merge 9 commits into
Open
agorangetek wants to merge 9 commits into
agorangetek wants to merge 9 commits into
Conversation
FB's OpenGL support is implemented in the X11 gfx driver. Without
ENABLE_XQUARTZ that driver is compiled out, but gfx_opengl.c is still
built and calls fb_hGL_GetProcAddress(), which only the driver defines.
libfbgfx.a therefore ends up with an undefined symbol and every link
fails:
Undefined symbols for architecture arm64:
"_fb_hGL_GetProcAddress", referenced from:
_fb_GfxGetGLProcAddress in libfbgfxmt.a[31](gfx_opengl.o)
gfx_opengl.c already provides a stub for exactly this situation; define
DISABLE_OPENGL alongside DISABLE_X11 so it is used.
rtlErrorCheck() and rtlErrorThrow() only have a resume label to hand to
the error throw when -ex/-exx enabled RESUME support. Without it the
label arguments are NULL, fb_ErrorThrowAt() can never return a usable
jump target, and `fb_ErrorThrow(); goto *result;` is equivalent to a
plain call.
Emitting the call also keeps the generated C compilable by clang, which
rejects `goto *ptr;` in a function that contains no address-of-label
expression ("indirect goto in function with no address-of-label
expressions").
Code built with -ex/-exx is unaffected: there the label addresses are
taken anyway (&&label), so the indirect jump is still emitted.
DATA items are emitted as a packed { short, void* } table, 10 bytes per
entry, so the embedded string/link pointers sit at 2-byte offsets.
ELF linkers accept that, but Mach-O's ld64 refuses to relocate a
pointer that is not pointer-aligned and fails the link:
ld: pointer not aligned in '_label$N'+0x16
Give the descriptor the natural pointer alignment when targeting
Darwin, and make fb_data.h use the matching, non-packed layout. Other
targets keep the packed 10-byte layout so their ABI is unchanged.
AArch64 Darwin uses a plain pointer-like va_list (sizeof(va_list) == 8), not the AAPCS64 __va_list_tag struct used by Linux/BSD aarch64. The compiler assumed the struct, so cva_start()/cva_arg() walked the wrong memory and any program using cva_list crashed immediately.
Mach-O has no __start_/__stop_ section boundary symbols and limits section names to 16 characters, so the ELF-style section scan cannot work there. Guard it for Darwin and fall back to the single version record (the report is then simply empty) instead of emitting an invalid section attribute and failing to build.
The Darwin link command was built for a GNU ld: it adds crt1.o/crti.o/ crtbegin.o/crtend.o/crtn.o, `-macosx_version_min 10.4`, `--eh-frame-hdr`, `--export-dynamic` and `-lgcc`, and uses `-shared -h<name>` for shared libraries. Modern ld64 rejects --eh-frame-hdr outright, the crt objects cannot be linked that way on macOS, and libgcc does not exist unless a GCC toolchain happens to be installed. Invoke the C compiler driver (clang) for linking Darwin targets instead: it supplies the startup object and libSystem itself. Drop the options it does not accept, use -dynamiclib/-install_name for -dylib, and add -arch arm64 so the architecture is explicit. Executables and dynamic libraries now link with the system toolchain alone.
Xfuncproto.h defines NeedWidePrototypes to 1 unless the application
defines NARROWPROTO, which makes Xlib declare XGetKeyboardMapping()'s
first_keycode parameter as unsigned int instead of KeyCode (unsigned
char). FB's XGETKEYBOARDMAPPING typedef hard-codes KeyCode, so passing
XGetKeyboardMapping to fb_hInitX11KeycodeToScancodeTb() no longer
matches the parameter type:
gfx_x11.c:588:68: error: incompatible function pointer types
passing 'KeySym *(Display *, unsigned int, int, int *)' to
parameter of type 'XGETKEYBOARDMAPPING' (aka
'unsigned long *(*)(struct _XDisplay *, unsigned char, int, int *)')
GCC only warns about the mismatch, which is why this went unnoticed, but
clang (and GCC 14+, where it is an error too) rejects it -- so the X11
rtlib/gfxlib2 code cannot be built by clang at all. That makes the
XQuartz/X11 graphics path unbuildable on Darwin, where clang is the
system compiler.
Mirror Xlib's own conditional in the typedef so it matches either way.
uname -m reports "arm64" on Apple Silicon, which matched the arm% pattern and was normalized to "arm" -- the 32-bit family. A plain "make" on macOS therefore resolved FBTARGET to darwin-arm instead of darwin-aarch64, so objects and libraries landed in directories that do not match the ones fbc looks for at run time (lib/freebasic/darwin-aarch64). Normalize aarch64/arm64 first, then let the remaining 32-bit spellings match armv%/arm. Verified that arm-linux-gnueabihf and armv7-linux-gnueabihf still resolve to linux-arm, and that arm64-apple-darwin resolves to darwin-aarch64. Based on the corresponding change in freebasic#470 by @metaneutrons.
This was referenced Sep 18, 2026
4 tasks
metaneutrons
added a commit
to metaneutrons/freebasic-ng
that referenced
this pull request
Sep 19, 2026
## Summary Seven defects on the Darwin and clang paths, derived from reviewing upstream freebasic/fbc#479 against this tree. The assembly backends keep their current behaviour throughout. - **DATA descriptors are not linkable on Darwin.** The descriptor is emitted packed, which puts its pointer at offset 2 and every 10 bytes after that. ld64 requires a pointer relocation to be pointer-aligned and rejects that, fatally on arm64, so any program containing a `DATA` statement fails to link on Apple silicon. Darwin now uses the natural layout on both the compiler and the runtime side. - **`fbc -e` cannot be compiled by clang.** The C backend emits the jump to the error handler as a computed goto, which clang rejects in a function that takes no label address — every procedure containing an error check is such a function when RESUME support is off. This hit macOS and, as the new regression showed, the Windows aarch64 host as well, whose CLANGARM64 toolchain ships clang under the gcc name. The former workaround, one unused address-taken label per procedure, is worse than the error it hid: with only one possible destination both clang and gcc fold the jump into a direct branch to that label, so `-gen clang -e -O 2` loops forever instead of reaching the handler. The C backend now emits the resume labels even without `-ex`, which is the shape `-ex` already produces. - **`fbc -dylib` cannot link on macOS.** The shared-library path passed the GNU spellings `-shared`, `-h<soname>` and `--export-dynamic` to the clang driver, which rejects `-h<soname>`. Darwin now uses `-dynamiclib` with `-install_name` and `-Wl,-export_dynamic`. - **Darwin shared libraries were named `.so`.** Mach-O uses `.dylib`; `.so` is the loadable-bundle extension, and `fb_DylibLoad()` tries the `.dylib` name first on Darwin. - **OpenGL was enabled without an X11 driver.** `gfx_driver_opengl_x11.c` is the only unix translation unit defining `fb_hGL_GetProcAddress()`, while `gfx_opengl.c` calls it unconditionally, so libfbgfx kept an undefined symbol that surfaced at user link time. macOS without XQuartz hits this by default because the SDK supplies `OpenGL/gl.h`. The graphics library also decided about X11 independently of the runtime, which made a machine with XQuartz installed build an X11 driver against a runtime built without X11. - **`XGetKeyboardMapping` prototype mismatch.** Xlib declares `first_keycode` as `KeyCode` or `unsigned int` depending on `NeedWidePrototypes`; the runtime typedef hard-coded the narrow form. Clang treats the mismatch as an error, GCC only warns. - **The compiler regressions could not run on Windows.** MSYS Python reads a `C:/...` argument as a relative POSIX path, so the adapter was looked up below the build directory. Registration now goes through one helper that hands Windows relative paths and plain tool names, the way the legacy suites already do, and every regression runs with the build directory as its working directory. Three portable regressions are added: `compiler.data-statement`, `compiler.error-check-handler` (built with `-e`) and `compiler.dylib-load`, which builds a library with `-dylib` and loads it back through `DyLibLoad()`. `scripts/run-compiler-regression.py` gained `--fbc-flag` and `--library-source`, resolves a tool name through PATH and also accepts `.exe` names. ## Verification - [x] CMake build completed on the affected host (Linux x86_64, seed and self-hosted builds; CI covers the Darwin and Windows hosts). - [x] `fbc --version` or a focused compiler/runtime test passed — the three new regressions plus the full legacy fbcunit suite, green both with `-g -exx` (2302 tests, ~1.15M asserts) and without it. - [x] Copyright and licence notices remain intact. - [x] No generated binaries or unrelated changes are included. Evidence collected while fixing these: - The DATA layout was checked against the object file: with the packed descriptor `clang -target arm64-apple-macos11` emits `ARM64_RELOC_UNSIGNED` at offsets 2, 0xc and 0x16, and at 8, 0x18 and 0x28 after the change. - The error path was checked at `-O 0`, `-O 2` and `-O 3` with `-gen clang`; the previous compiler hangs at `-O 2` and `-O 3`, this one reaches the handler at all three. - The Darwin link lines were checked by cross-targeting with a stub toolchain: `-arch x86_64 -o libfoo.dylib -dynamiclib -install_name libfoo.dylib -Wl,-export_dynamic ...`. `Self-host Darwin ARM64` runs the three regressions on Apple silicon, so the DATA layout, `-e` under clang and the `-dynamiclib` link line are now measured on the target rather than derived. Unrelated defect found along the way and filed separately: #159.
Two defects on the -dylib path, both found while cross-checking metaneutrons'
freebasic-ng#160, which reviewed this branch against their fork:
- Shared libraries were named .so on every unix target, including Darwin.
Mach-O uses .dylib -- .so is the loadable bundle extension. fb_DylibLoad()
looks for the .dylib name first on Darwin, so it worked, but only by falling
through to the .so entry in its candidate list, and the file was named wrongly
for every other tool on the platform. Darwin now has its own case in the
output naming, leaving the ELF targets on .so.
- The link line skipped --export-dynamic on Darwin entirely rather than
translating it. The clang driver wants it spelled -Wl,-export_dynamic.
Symbols resolved without it -- Mach-O exports global symbols by default --
but leaving it out was a silent difference from every other unix target, and
-export therefore did nothing on Darwin.
Verified by building a library and loading it back:
fbc -dylib foo.bas -> libfoo.dylib, install name "libfoo"
linking: clang ... -dynamiclib -install_name "libfoo" -Wl,-export_dynamic
DyLibLoad("foo") -> handle, DyLibSymbol(handle, "FOO_ADD") -> 5
agorangetek
force-pushed
the
darwin-arm64-support
branch
from
September 19, 2026 09:04
80742d9 to
13794ed
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fix and enable building FreeBASIC for Darwin / Apple Silicon
Upstream publishes no macOS binaries of any version (the SourceForge
Binaries-*folders only ever contain DOS, Windows, Linux, FreeBSD andRaspberry Pi packages), so
fbccan currently only be obtained on macOS bybootstrapping it by hand — and that fails, because the Darwin support has
bit-rotted against modern
ld64and against arm64. This branch makes a nativedarwin-aarch64build work end to end.I bootstrapped from the official
FreeBASIC-1.10.1-source-bootstrappackage(its
linux-aarch64compiler C sources are the closest CPU family; they areself-contained C with no
#includes) plus alibfbbuilt natively from the100%-C runtime sources, and then built current
masterwith it. Getting thereneeded the fixes below.
Commits
darwin: define DISABLE_OPENGL when X11/XQuartz is unavailable—gfx_opengl.cstill callsfb_hGL_GetProcAddress(), which only the X11driver defines; without XQuartz
libfbgfx.ahas an undefined symbol andevery link fails. The stub for this already exists, it just was not
selected.
rtl-error: don't emit an indirect jump when RESUME support is off—rtlErrorCheck()/rtlErrorThrow()pass a resume label only when-ex/-exxenabled RESUME support; without it
fb_ErrorThrowAt()can never return ausable jump target, so the
goto *resultis dead. Emitting a plain callinstead is equivalent and additionally keeps the generated C acceptable to
clang, which rejects
goto *ptrin a function with no address-of-labelexpression.
-ex/-exxcode is unchanged.darwin: align DATA descriptors to the pointer size— DATA tables are{ short, void* }packed, so the embedded pointers are 2-byte aligned.ELF tolerates that,
ld64does not:ld: pointer not aligned in '_label$N'+0x16, a hard error on arm64. Darwin now gets the naturallyaligned descriptor (16 bytes); other targets keep the packed layout.
darwin: use the correct arm64 va_list ABI— Apple's arm64va_listis a plain pointer (
sizeof(va_list) == 8), not the AAPCS64__va_list_tagstruct;cva_start()/cva_arg()read garbage and anycva_listprogram crashed.profile_cycles: handle Mach-O sections— Mach-O has no__start_/__stop_symbols and 16-character section names, so the ELFsection scan cannot work; guard it for Darwin and degrade to an empty
report instead of failing to build.
darwin: link through the C compiler driver instead of driving ld64—the Darwin link line was a GNU
ldline (crt1.o/crti.o/crtbegin.o,-macosx_version_min 10.4,--eh-frame-hdr,-lgcc,-shared -h<name>).ld64rejects--eh-frame-hdr, the crt objects are not linkable that way onmacOS, and
libgccdoes not exist unless a GCC toolchain is installed.Darwin now links via
clang, which brings in the startup object andlibSystem;-dylibmaps to-dynamiclib -install_name, and-arch arm64is passed explicitly.
darwin: name shared libraries .dylib, and export their symbols—-dylibnamed its output.soon every unix target, including Darwin, whereMach-O calls it
.dylib;.sois the loadable-bundle extension. It workedonly because
fb_DylibLoad()falls through to the.soentry in itscandidate list. The link line also dropped
--export-dynamicfor Darwinrather than translating it, so
-exportdid nothing there; it is now-Wl,-export_dynamic.Validation
Built
master(5714d10) natively fordarwin-aarch64on macOS 27 / Apple M5with the Xcode 27 toolchain, then ran the upstream fbcunit suite
(
make unit-tests, 2283 cases,-exx -mt):tests/unix-tty— which builds a library with-dyliband loads it backthrough
fb_DylibLoad()— could not be built on macOS at all before thisbranch (
tester.cxxneeds C++17, which Apple clang does not default to, andcompares the Linux-only
termios.c_line), so nothing exercised that path onDarwin. It builds now, and its two dylibload testees pass on Apple silicon:
The whole suite passes with both C backends (Apple clang and GCC 16); the
two failing assertions are the same in both and are the only remaining issue I
know of:
which looks like a constant-folding vs. runtime-
libmULP difference ratherthan an arm64 regression — I could not check it on other platforms, so please
treat it as unverified.
fbcalso rebuilds itself with Apple clang alone (no GCC anywhere), andrebuilds itself with GCC; both binaries behave identically on the suite.
Reproducing
Relationship to other pull requests
makefilechange that normalizesarm64toaarch64is taken fromFix macOS arm64 (Apple Silicon) linker support #470 by @metaneutrons — thanks. It is the piece of that PR which makes a
plain
makework on Apple Silicon; without ituname -m's "arm64" isnormalized to the 32-bit
armand everything lands indarwin-arm.between them: it does not touch the
crt1.olookup, and on current macOSthat lookup cannot resolve (
clang --sysroot=<sdk> -print-file-name=crt1.oreturns the bare name, so
hFindLib()reportserror 23: File not found, crt1.o), leaving ld64 with no startup object:Undefined symbols for architecture arm64: "_main". It also predates theDATAdescriptor alignment problem. Both are fixed here.makefile,src/compiler/fbc.basandsrc/rtlib/profile_cycles.cas well, so it will conflict with this branch.It is the better long-term answer for macOS graphics (no XQuartz needed);
this branch only makes the existing X11/XQuartz path build and work. Happy
to rebase on whichever lands first.