Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions .github/workflows/macos.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# SPDX-FileCopyrightText: 2026 Aoto
# SPDX-License-Identifier: MIT

# Native macOS arm64 build + tests: Release and Debug, ASan, shared. Apple
# clang on the runner; no container. The reactor, fuzzing and TSan stay
# Linux-only.
name: macos

on:
push:
branches: [master]
paths:
- 'src/**'
- 'include/**'
- 'tests/**'
- 'examples/**'
- 'utils/**'
- 'cmake/**'
- '**/CMakeLists.txt'
- '.github/workflows/macos.yml'
pull_request:

permissions:
contents: read

concurrency:
group: "${{ github.workflow }}-${{ github.ref }}"
cancel-in-progress: true

jobs:
build-test:
runs-on: macos-15
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
include:
- name: release
args: "-t -e"
- name: debug
args: "-t -e -d"
- name: asan
args: "-t -e --asan"
- name: shared
args: "-t -e --shared"
name: "macos arm64 (${{ matrix.name }})"
steps:
- uses: actions/checkout@v4
- name: Build and run tests
run: ./utils/make.sh ${{ matrix.args }}
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ change the API.

## [Unreleased]

### Added

- macOS support, Apple Silicon (arm64) only: growable stacks recycle with
`MADV_FREE_REUSABLE`, the assembly builds through the Mach-O branch of
`asm_defs.inc`, and a native `macos-15` CI job covers Release, Debug, ASan
and the shared `libcfiber.dylib`. The reactor, fuzzing and ThreadSanitizer
stay Linux-only; x86_64 macOS is not claimed.

### Changed

- The hosted assembly files take their object-format bracketing (symbol
Expand Down
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
[![x86_64](https://github.com/aotodev/cfiber/actions/workflows/x86_64.yml/badge.svg?branch=master)](https://github.com/aotodev/cfiber/actions/workflows/x86_64.yml)
[![aarch64](https://github.com/aotodev/cfiber/actions/workflows/aarch64.yml/badge.svg?branch=master)](https://github.com/aotodev/cfiber/actions/workflows/aarch64.yml)
[![macos](https://github.com/aotodev/cfiber/actions/workflows/macos.yml/badge.svg?branch=master)](https://github.com/aotodev/cfiber/actions/workflows/macos.yml)
[![cortex-m0](https://github.com/aotodev/cfiber/actions/workflows/cortex-m0.yml/badge.svg?branch=master)](https://github.com/aotodev/cfiber/actions/workflows/cortex-m0.yml)
[![cortex-m3](https://github.com/aotodev/cfiber/actions/workflows/cortex-m3.yml/badge.svg?branch=master)](https://github.com/aotodev/cfiber/actions/workflows/cortex-m3.yml)
[![cortex-m4](https://github.com/aotodev/cfiber/actions/workflows/cortex-m4.yml/badge.svg?branch=master)](https://github.com/aotodev/cfiber/actions/workflows/cortex-m4.yml)
Expand Down Expand Up @@ -39,11 +40,13 @@ cooperative scheduler and the allocators to back them. Two targets, one API:
| ------------------- | -------------- | ------------------------------------ |
| x86_64 Linux | System V AMD64 | Native |
| AArch64 Linux | AAPCS64 | `qemu-aarch64` user-mode emulation |
| macOS arm64 | AAPCS64 (Apple)| Native (GitHub `macos-15` runner) |
| ARM Cortex-M0 / M0+ | AAPCS Thumb-1 | `qemu-system-arm -M microbit` |
| ARM Cortex-M3 | AAPCS Thumb-2 | `qemu-system-arm -M mps2-an385` |
| ARM Cortex-M4 / M7 | AAPCS Thumb-2 | `qemu-system-arm -M mps2-an386/an500`|

No other host is supported. The reactor and the fuzz targets are Linux only.
No other host is supported (x86_64 macOS is not claimed). The reactor, the
fuzz targets and ThreadSanitizer are Linux only.

## Why fibers

Expand Down Expand Up @@ -117,8 +120,9 @@ Cross-compilation, the full option table and integration notes are in

Every push runs the CI matrix above: x86_64 under AddressSanitizer,
UndefinedBehaviorSanitizer and the stack sanitizer, AArch64 under `qemu-user`,
Cortex-M0/M3/M4/M7 under `qemu-system-arm`, the reactor under ThreadSanitizer,
and libFuzzer harnesses for the allocators, the scheduler and the reactor.
macOS arm64 natively (Release, Debug, ASan, shared), Cortex-M0/M3/M4/M7 under
`qemu-system-arm`, the reactor under ThreadSanitizer, and libFuzzer harnesses
for the allocators, the scheduler and the reactor.
Per-architecture tests check the callee-saved set across a context switch.

This is a young library. Treat the above as what is exercised today, not as a
Expand Down
7 changes: 6 additions & 1 deletion docs/building.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
used (`constexpr`, `nullptr`, `#elifdef`) and are unverified.
- For ARM cross-builds: `arm-none-eabi-gcc` and `qemu-system-arm`
- For AArch64 cross-builds: `aarch64-linux-gnu-gcc` and `qemu-user`
- On macOS (arm64 only): Apple Clang 17 or newer (Xcode 16.3+) for C23
`constexpr`; on an older Xcode, `brew install llvm` and point `CC` at it.
Native builds need no coreutils; `timeout` is only used for QEMU runs.

Select a compiler at configure time with `CC=clang cmake ...` or
`CC=clang ./utils/make.sh ...`.
Expand Down Expand Up @@ -80,7 +83,9 @@ library and everything linked against it must be built with the same
The default is a static archive. Shared builds export only the documented API
(everything declared with `CFIBER_EXPORT` in the public headers) and hide
everything else via `-fvisibility=hidden`. Shared builds carry the major
version as `SOVERSION`.
version as `SOVERSION`; on macOS this yields `libcfiber.dylib` with the same
versioned names, and a consumer run against a staged install needs
`DYLD_LIBRARY_PATH` where Linux uses `LD_LIBRARY_PATH`.

## API and ABI

Expand Down
8 changes: 6 additions & 2 deletions docs/memory.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,10 @@ and writable across its whole extent, and the kernel commits a physical page the
first time the fiber's stack pointer reaches it. `MAP_NORESERVE` keeps the
untouched remainder from counting against commit accounting under the default
overcommit heuristic; with `vm.overcommit_memory=2` the whole extent is charged
at `mmap` time. An idle fiber costs
at `mmap` time. On Darwin there is no overcommit toggle, `MAP_NORESERVE` is
accepted and ignored, `MAP_STACK` does not exist (the flag compiles out), and
pages are 16 KiB, so the guard page and the growth granularity are four times
the common x86-64 size. An idle fiber costs
the pages it has actually touched, not the pages it might touch, which is what
makes tens of thousands of reactor fibers with a 64 KB ceiling practical.

Expand All @@ -94,7 +97,8 @@ happened to sit below.

`cfiber_growable_stack_allocator_t` pools these. It keeps up to `cache_capacity` stacks
for reuse and can pre-allocate `initial_cached` of them. A stack returned to the
pool is recycled with `MADV_DONTNEED` over its grown pages, keeping the top page
pool is recycled with `MADV_DONTNEED` (`MADV_FREE_REUSABLE` on Darwin, where
`MADV_DONTNEED` keeps pages resident) over its grown pages, keeping the top page
warm: that drops the physical memory while leaving the mapping and the guard
page in place, so reuse costs no `mmap` and the next fiber starts from a small
resident footprint. `cfiber_growable_stack_allocator_destroy()` returns `-1` if stacks
Expand Down
11 changes: 11 additions & 0 deletions src/cfiber/stack/growable_stack.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@
#include <sys/mman.h>
#include <unistd.h>

/* Darwin has no MAP_STACK; dropping it is harmless (MAP_NORESERVE exists
* there and is ignored). */
#ifndef MAP_STACK
#define MAP_STACK 0
#endif

/* 0 if the page size cannot be queried; callers fail with errno set. */
static size_t page_size(void) {
const long ps = sysconf(_SC_PAGESIZE);
Expand Down Expand Up @@ -80,5 +86,10 @@ void cfiber_growable_stack_recycle(cfiber_stack_t* const stack) {
}

const size_t length = cfiber_stack_usable_size(stack) - page;
#ifdef __APPLE__
/* MADV_DONTNEED keeps pages resident on Darwin. */
(void)madvise(stack->usable_base, length, MADV_FREE_REUSABLE);
#else
(void)madvise(stack->usable_base, length, MADV_DONTNEED);
#endif
}
20 changes: 11 additions & 9 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ include(arm_executable)
add_library(cfiber_test STATIC test/test.c)
target_include_directories(cfiber_test PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
# cfiber: the framework follows the library's hosted/freestanding split
# (CFIBER_FREESTANDING) for its fork-based death helper.
# (CFIBER_FREESTANDING) for its fork-based death helper. Test targets get
# cfiber through this PUBLIC link; naming it again makes Apple's ld warn
# about a duplicate library.
target_link_libraries(cfiber_test PUBLIC cfiber project_flags)

# add_test() can only run the binary on the build host, so register CTest cases
Expand Down Expand Up @@ -41,7 +43,7 @@ endif()
target_include_directories(${_cfiber_test_name} PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
${PROJECT_SOURCE_DIR}/src)
target_link_libraries(${_cfiber_test_name} PRIVATE cfiber cfiber_test project_flags m) # m: fenv
target_link_libraries(${_cfiber_test_name} PRIVATE cfiber_test project_flags m) # m: fenv

if(_cfiber_register_ctest)
add_test(NAME context_switch COMMAND ${_cfiber_test_name})
Expand All @@ -53,7 +55,7 @@ endif()
if(_cfiber_arch STREQUAL "arm")
add_arm_executable(test_cortex_syscalls cortex/test_syscalls.c)
target_include_directories(test_cortex_syscalls PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(test_cortex_syscalls PRIVATE cfiber cfiber_test project_flags)
target_link_libraries(test_cortex_syscalls PRIVATE cfiber_test project_flags)
endif()

# ======================================================================================
Expand All @@ -62,7 +64,7 @@ endif()
if(NOT CFIBER_FREESTANDING)
add_executable(test_memory_allocators memory/test_multislab_alloc.c)
target_include_directories(test_memory_allocators PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(test_memory_allocators PRIVATE cfiber cfiber_test project_flags)
target_link_libraries(test_memory_allocators PRIVATE cfiber_test project_flags)

if(_cfiber_register_ctest)
add_test(NAME memory_allocators COMMAND test_memory_allocators)
Expand All @@ -75,7 +77,7 @@ endif()
if(NOT CFIBER_FREESTANDING)
add_executable(test_scheduler scheduler/test_scheduler.c)
target_include_directories(test_scheduler PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(test_scheduler PRIVATE cfiber cfiber_test project_flags)
target_link_libraries(test_scheduler PRIVATE cfiber_test project_flags)

if(_cfiber_register_ctest)
add_test(NAME scheduler COMMAND test_scheduler)
Expand All @@ -88,13 +90,13 @@ endif()
if(NOT CFIBER_FREESTANDING)
add_executable(test_fixed_size_stack_allocator stack/test_fixed_size_stack_allocator.c)
target_include_directories(test_fixed_size_stack_allocator PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(test_fixed_size_stack_allocator PRIVATE cfiber cfiber_test project_flags)
target_link_libraries(test_fixed_size_stack_allocator PRIVATE cfiber_test project_flags)

add_executable(test_growable_stack stack/test_growable_stack.c)
target_include_directories(test_growable_stack PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
# sigaction, sigsetjmp: hidden under strict -std=c23
target_compile_definitions(test_growable_stack PRIVATE _POSIX_C_SOURCE=200809L)
target_link_libraries(test_growable_stack PRIVATE cfiber cfiber_test project_flags)
target_link_libraries(test_growable_stack PRIVATE cfiber_test project_flags)

if(_cfiber_register_ctest)
add_test(NAME fixed_size_stack_allocator COMMAND test_fixed_size_stack_allocator)
Expand Down Expand Up @@ -144,7 +146,7 @@ if(NOT CFIBER_FREESTANDING)
add_executable(test_death death/test_death.c)
target_include_directories(test_death PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_compile_definitions(test_death PRIVATE _GNU_SOURCE)
target_link_libraries(test_death PRIVATE cfiber cfiber_test project_flags)
target_link_libraries(test_death PRIVATE cfiber_test project_flags)
if(CFIBER_REACTOR)
target_compile_definitions(test_death PRIVATE CFIBER_TEST_HAVE_REACTOR=1)
target_link_libraries(test_death PRIVATE cfiber_reactor)
Expand Down Expand Up @@ -179,7 +181,7 @@ if(CFIBER_STACK_SANITIZER)
add_executable(test_stack_sanitizer stack/test_stack_sanitizer.c)
endif()
target_include_directories(test_stack_sanitizer PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(test_stack_sanitizer PRIVATE cfiber cfiber_test project_flags)
target_link_libraries(test_stack_sanitizer PRIVATE cfiber_test project_flags)

if(_cfiber_register_ctest)
add_test(NAME stack_sanitizer COMMAND test_stack_sanitizer)
Expand Down
20 changes: 10 additions & 10 deletions utils/make.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@
# -t, --tests Build and run unit tests
# -v, --verbose Verbose build output
# --sanitizer Enable the stack sanitizer (canary + watermark)
# --asan Enable AddressSanitizer (hosted x86_64 only)
# --ubsan Enable UndefinedBehaviorSanitizer (hosted x86_64 only)
# --tsan Enable ThreadSanitizer (hosted x86_64 only; implies --reactor)
# --asan Enable AddressSanitizer (hosted native builds only)
# --ubsan Enable UndefinedBehaviorSanitizer (hosted native builds only)
# --tsan Enable ThreadSanitizer (Linux x86_64 only; implies --reactor)
# --shared Build cfiber as a shared library (default: static)
# --pic Build the static library with -fPIC
# --reactor Build + test the optional epoll reactor (Linux only)
Expand Down Expand Up @@ -62,11 +62,11 @@ Usage: $(basename "$0") [options]
-t, --tests Build and run unit tests
-v, --verbose Verbose build output
--sanitizer Enable the stack sanitizer (canary + watermark)
--asan Enable AddressSanitizer (hosted x86_64 only; mutually
exclusive with --sanitizer)
--ubsan Enable UndefinedBehaviorSanitizer (hosted x86_64 only; may
be combined with --asan)
--tsan Enable ThreadSanitizer (hosted x86_64 only; mutually
--asan Enable AddressSanitizer (hosted native builds only;
mutually exclusive with --sanitizer)
--ubsan Enable UndefinedBehaviorSanitizer (hosted native builds
only; may be combined with --asan)
--tsan Enable ThreadSanitizer (Linux x86_64 only; mutually
exclusive with --asan; implies --reactor)
--shared Build cfiber as a shared library (default: static)
--pic Build the static library with -fPIC (ignored with --shared)
Expand All @@ -90,12 +90,12 @@ project_root=$(cd "${script_dir}/.." && pwd)
cd "${project_root}"

# --------------------------------------------------------------------------------------
# Host OS / job-count detection. Linux, or macOS unverified. Not Windows.
# Host OS / job-count detection. Linux or macOS. Not Windows.
# --------------------------------------------------------------------------------------
host_os="$(uname -s)"
case "${host_os}" in
Linux|Darwin) ;;
*) die "unsupported host OS: '${host_os}' (cfiber builds on Linux; macOS is unverified)" ;;
*) die "unsupported host OS: '${host_os}' (cfiber builds on Linux and macOS)" ;;
esac

detect_jobs() {
Expand Down
Loading