From 4b408686c60e10a2b27e53642c00301de35246f7 Mon Sep 17 00:00:00 2001 From: Pouya Kary Date: Mon, 6 Oct 2025 14:55:28 +0330 Subject: [PATCH 1/6] Port Io to Apple Sillicon --- README.md | 4 +- libs/coroutine/source/Coro.h | 5 + libs/coroutine/source/Coro_arm64.c | 182 ++++++++++++++----------- libs/coroutine/source/arm64-ucontext.h | 22 ++- libs/coroutine/source/context.c | 2 + 5 files changed, 125 insertions(+), 90 deletions(-) diff --git a/README.md b/README.md index d85ef87d5..d5c88a359 100755 --- a/README.md +++ b/README.md @@ -200,8 +200,8 @@ command. See the [Linux build instructions](#linux-build-instructions). -Note: Building Io for arm64-based macOS machines is unsupported. To build and run -on an M1 or newer, build Io for x86_64 by adding +Apple Silicon (arm64) Macs are supported natively. The standard build commands +work out of the box. If you specifically need an x86_64 binary for Rosetta, add `-DCMAKE_OSX_ARCHITECTURES="x86_64"` to your CMake invocation. ### Windows Build Instructions diff --git a/libs/coroutine/source/Coro.h b/libs/coroutine/source/Coro.h index d517438e2..14193e7ef 100644 --- a/libs/coroutine/source/Coro.h +++ b/libs/coroutine/source/Coro.h @@ -12,6 +12,7 @@ #if defined(__APPLE__) && (defined(__arm64__) || defined(__aarch64__)) #include "arm64-ucontext.h" #define USE_UCONTEXT 1 +#define USE_ARM64_CONTEXT 1 #endif #if defined(__FreeBSD__) @@ -76,6 +77,8 @@ #if defined(USE_FIBERS) #define CORO_IMPLEMENTATION "fibers" +#elif defined(USE_ARM64_CONTEXT) +#define CORO_IMPLEMENTATION "arm64" #elif defined(USE_UCONTEXT) #include #define CORO_IMPLEMENTATION "ucontext" @@ -101,6 +104,8 @@ struct Coro { #if defined(USE_FIBERS) void *fiber; +#elif defined(USE_ARM64_CONTEXT) + IoCoroARM64Context env; #elif defined(USE_UCONTEXT) ucontext_t env; #elif defined(USE_SETJMP) diff --git a/libs/coroutine/source/Coro_arm64.c b/libs/coroutine/source/Coro_arm64.c index 64472da69..5c51d4ddf 100644 --- a/libs/coroutine/source/Coro_arm64.c +++ b/libs/coroutine/source/Coro_arm64.c @@ -1,23 +1,32 @@ #if defined(__arm64__) || defined(__aarch64__) +#include "Base.h" #include "Coro.h" +#include +#include #include #include #include +#include "taskimpl.h" // External functions implemented in asm.S extern int coro_arm64_getcontext(void *context) __asm("coro_arm64_getcontext"); extern int coro_arm64_setcontext(void *context) __asm("coro_arm64_setcontext"); -// Custom context implementation for ARM64 -typedef struct { - unsigned long x19_x20[2]; // x19, x20 - unsigned long x21_x22[2]; // x21, x22 - unsigned long x23_x24[2]; // x23, x24 - unsigned long x25_x26[2]; // x25, x26 - unsigned long x27_x28[2]; // x27, x28 - unsigned long fp_lr[2]; // x29 (fp), x30 (lr) - unsigned long sp; // stack pointer -} arm64_context_t; +#ifdef USE_VALGRIND +#include +#define STACK_REGISTER(coro) \ + { \ + Coro *c = (coro); \ + c->valgrindStackId = VALGRIND_STACK_REGISTER( \ + c->stack, (char *)c->stack + c->requestedStackSize); \ + } + +#define STACK_DEREGISTER(coro) \ + VALGRIND_STACK_DEREGISTER((coro)->valgrindStackId) +#else +#define STACK_REGISTER(coro) +#define STACK_DEREGISTER(coro) +#endif typedef struct CallbackBlock { void *context; @@ -27,110 +36,121 @@ typedef struct CallbackBlock { static CallbackBlock globalCallbackBlock; static void Coro_StartWithArg(void) { - //fprintf(stderr, "Coro_StartWithArg called\n"); CallbackBlock *block = &globalCallbackBlock; - //fprintf(stderr, "Function pointer: %p\n", block->func); block->func(block->context); fprintf(stderr, "Scheduler error: returned from coro start function\n"); exit(-1); } -void Coro_free(Coro *self) { - if (self->stack) { - free(self->stack); +Coro *Coro_new(void) { + Coro *self = (Coro *)io_calloc(1, sizeof(Coro)); + if (self) { + self->requestedStackSize = CORO_DEFAULT_STACK_SIZE; + self->allocatedStackSize = 0; + self->stack = NULL; } - free(self); -} - -void Coro_initializeMainCoro(Coro *self) { - self->isMain = 1; + return self; } -Coro *Coro_new(void) { - Coro *c = (Coro *)calloc(1, sizeof(Coro)); - if (c) { - c->requestedStackSize = CORO_DEFAULT_STACK_SIZE; - c->allocatedStackSize = 0; - c->stack = NULL; +static void Coro_disposeStack(Coro *self) { + if (!self->stack) { + return; } - return c; -} -void Coro_setStackSize_(Coro *self, size_t size) { - self->requestedStackSize = size; + STACK_DEREGISTER(self); + io_free(self->stack); + self->stack = NULL; + self->allocatedStackSize = 0; } static void Coro_allocStackIfNeeded(Coro *self) { if (self->stack && self->requestedStackSize < self->allocatedStackSize) { - free(self->stack); - self->stack = NULL; - self->allocatedStackSize = 0; + Coro_disposeStack(self); } + if (!self->stack) { - // Make sure stack is 16-byte aligned for ARM64 - self->stack = calloc(1, self->requestedStackSize + 16); + self->stack = io_calloc(1, self->requestedStackSize + 16); self->allocatedStackSize = self->requestedStackSize; + STACK_REGISTER(self); } } -// This function initializes the context with a new stack and entry point -void Coro_setup(Coro *self, void *arg) { - arm64_context_t *context = (arm64_context_t *)&self->env; - memset(context, 0, sizeof(*context)); - - Coro_allocStackIfNeeded(self); - - // Initialize stack pointer to top of stack (ARM64 full descending stack) - unsigned long sp = (unsigned long)self->stack + self->allocatedStackSize - 16; - // Ensure 16-byte alignment - sp &= ~15UL; - - // Store stack pointer in context - context->sp = sp; - - // Store entry point in link register (x30) - context->fp_lr[1] = (unsigned long)Coro_StartWithArg; +void Coro_free(Coro *self) { + Coro_disposeStack(self); + io_free(self); } -int Coro_stackSpaceAlmostGone(Coro *self) { - arm64_context_t *context = (arm64_context_t *)&self->env; - unsigned long sp = context->sp; - unsigned long stack_base = (unsigned long)self->stack; - - // Check if we have less than 1KB of stack space left - return (sp - stack_base) < 1024; +void *Coro_stack(Coro *self) { return self->stack; } + +size_t Coro_stackSize(Coro *self) { return self->requestedStackSize; } + +void Coro_setStackSize_(Coro *self, size_t size) { self->requestedStackSize = size; } + +#if __GNUC__ >= 4 +static ptrdiff_t *Coro_CurrentStackPointer(void) __attribute__((noinline)); +#endif + +static ptrdiff_t *Coro_CurrentStackPointer(void) { + ptrdiff_t marker; + ptrdiff_t *markerPtr = ▮ + return markerPtr; } size_t Coro_bytesLeftOnStack(Coro *self) { - arm64_context_t *context = (arm64_context_t *)&self->env; - unsigned long sp = context->sp; - unsigned long stack_base = (unsigned long)self->stack; - - // Return number of bytes between stack pointer and stack base - if (sp > stack_base) { - return sp - stack_base; + unsigned char dummy; + ptrdiff_t stackPos = (ptrdiff_t)&dummy; + ptrdiff_t current = (ptrdiff_t)Coro_CurrentStackPointer(); + int stackMovesUp = current > stackPos; + ptrdiff_t start = (ptrdiff_t)self->stack; + ptrdiff_t end = start + self->requestedStackSize; + + if (stackMovesUp) { + return (size_t)(end - stackPos); } - - // Fallback if stack info not available - return 1024 * 1024; + + return (size_t)(stackPos - start); } -void Coro_startCoro_(Coro *self, Coro *other, void *context, CoroStartCallback *callback) { - globalCallbackBlock.context = context; - globalCallbackBlock.func = callback; - Coro_setup(other, &globalCallbackBlock); +int Coro_stackSpaceAlmostGone(Coro *self) { + return Coro_bytesLeftOnStack(self) < CORO_STACK_SIZE_MIN; +} + +void Coro_initializeMainCoro(Coro *self) { self->isMain = 1; } + +// This function initializes the context with a new stack and entry point +void Coro_setup(Coro *self, void *arg) { + if (arg) { + globalCallbackBlock = *(CallbackBlock *)arg; + } + IoCoroARM64Context *context = &self->env; + memset(context, 0, sizeof(*context)); + + Coro_allocStackIfNeeded(self); + + uintptr_t sp = (uintptr_t)self->stack + self->allocatedStackSize; + sp = (sp - 16) & ~((uintptr_t)15); // 16-byte alignment as per ABI + + context->sp = sp; + context->fp_lr[1] = (uint64_t)Coro_StartWithArg; +} + +void Coro_startCoro_(Coro *self, Coro *other, void *context, + CoroStartCallback *callback) { + CallbackBlock block = { + .context = context, + .func = callback, + }; + Coro_setup(other, &block); Coro_switchTo_(self, other); } void Coro_switchTo_(Coro *self, Coro *next) { - // Get the context pointers - arm64_context_t *from_context = (arm64_context_t *)&self->env; - arm64_context_t *to_context = (arm64_context_t *)&next->env; - - // Save current context, if successful (returns 0), then restore the next context - if (coro_arm64_getcontext((void*)from_context) == 0) { - coro_arm64_setcontext((void*)to_context); + IoCoroARM64Context *from_context = &self->env; + IoCoroARM64Context *to_context = &next->env; + + if (coro_arm64_getcontext((void *)from_context) == 0) { + coro_arm64_setcontext((void *)to_context); } - } + #endif diff --git a/libs/coroutine/source/arm64-ucontext.h b/libs/coroutine/source/arm64-ucontext.h index e9e4f9b18..eeb76e908 100644 --- a/libs/coroutine/source/arm64-ucontext.h +++ b/libs/coroutine/source/arm64-ucontext.h @@ -1,14 +1,22 @@ -// arm64-ucontext.h: Apple Silicon (ARM64) macOS context switching using ucontext +// arm64-ucontext.h: Apple Silicon (ARM64) coroutine context representation #ifndef ARM64_UCONTEXT_H #define ARM64_UCONTEXT_H -// Required for ucontext on modern macOS -#ifndef _XOPEN_SOURCE -#define _XOPEN_SOURCE 700 -#endif +#include -#include +// Minimal register snapshot that matches the layout expected by +// `coro_arm64_getcontext`/`coro_arm64_setcontext` in asm.S. We only need to +// preserve the callee-saved registers along with the stack pointer. +typedef struct IoCoroARM64Context { + uint64_t x19_x20[2]; + uint64_t x21_x22[2]; + uint64_t x23_x24[2]; + uint64_t x25_x26[2]; + uint64_t x27_x28[2]; + uint64_t fp_lr[2]; + uint64_t sp; +} IoCoroARM64Context; -typedef ucontext_t arm64_ucontext_t; +typedef IoCoroARM64Context arm64_ucontext_t; #endif // ARM64_UCONTEXT_H diff --git a/libs/coroutine/source/context.c b/libs/coroutine/source/context.c index 1ea7dc35b..350a7f164 100644 --- a/libs/coroutine/source/context.c +++ b/libs/coroutine/source/context.c @@ -11,8 +11,10 @@ #define NEEDAMD64MAKECONTEXT #define NEEDSWAPCONTEXT #elif defined(__arm64__) || defined(__aarch64__) +#if !defined(__APPLE__) #define NEEDARM64MAKECONTEXT #define NEEDSWAPCONTEXT +#endif #else #define NEEDPOWERMAKECONTEXT #define NEEDSWAPCONTEXT From 508c37ea75cbf4dc6e90cd8bd7f904f7e7ac05a8 Mon Sep 17 00:00:00 2001 From: Pouya Kary Date: Mon, 6 Oct 2025 15:16:54 +0330 Subject: [PATCH 2/6] Add cmake-install to the git ignore and update cmake-build --- .gitignore | 3 ++- cmake-build | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) create mode 160000 cmake-build diff --git a/.gitignore b/.gitignore index 6c98625b4..771372661 100755 --- a/.gitignore +++ b/.gitignore @@ -96,4 +96,5 @@ Makefile IoInstallPrefix.h xcuserdata build -.idea \ No newline at end of file +.idea +cmake-install \ No newline at end of file diff --git a/cmake-build b/cmake-build new file mode 160000 index 000000000..99216bec1 --- /dev/null +++ b/cmake-build @@ -0,0 +1 @@ +Subproject commit 99216bec1273325bb4411a5b95cebe3ff3909ed7 From ae761b76e4bdb10dcd3bb4bc028f50a2a73f91f9 Mon Sep 17 00:00:00 2001 From: Pouya Kary Date: Mon, 6 Oct 2025 16:04:57 +0330 Subject: [PATCH 3/6] fixed the fiber --- install_macos.sh | 99 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100755 install_macos.sh diff --git a/install_macos.sh b/install_macos.sh new file mode 100755 index 000000000..b39e31936 --- /dev/null +++ b/install_macos.sh @@ -0,0 +1,99 @@ +#!/usr/bin/env bash +# Installs Io from this repository on macOS by ensuring dependencies, +# building the project, and copying artifacts into the chosen prefix. + +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +BUILD_TYPE="${BUILD_TYPE:-Release}" +BUILD_DIR="${BUILD_DIR:-$REPO_ROOT/build/macos-$BUILD_TYPE}" +INSTALL_PREFIX="${INSTALL_PREFIX:-/usr/local}" + +info() { printf '\033[0;34m==> %s\033[0m\n' "$1"; } +warn() { printf '\033[1;33mWarning:\033[0m %s\n' "$1" >&2; } +die() { printf '\033[0;31mError:\033[0m %s\n' "$1" >&2; exit 1; } + +require_macos() { + [[ "$(uname -s)" == "Darwin" ]] || die "This installer only targets macOS."; +} + +require_clt() { + if ! xcode-select -p >/dev/null 2>&1; then + warn "Xcode Command Line Tools not found. Run 'xcode-select --install' then re-run this script." + exit 1 + fi +} + +ensure_homebrew() { + if command -v brew >/dev/null 2>&1; then + return + fi + warn "Homebrew not found. Installing it now." + /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" + if command -v /opt/homebrew/bin/brew >/dev/null 2>&1; then + eval "$(/opt/homebrew/bin/brew shellenv)" + elif command -v /usr/local/bin/brew >/dev/null 2>&1; then + eval "$(/usr/local/bin/brew shellenv)" + else + die "Homebrew installation did not add brew to PATH." + fi +} + +install_dependencies() { + local packages=(cmake pkg-config) + for pkg in "${packages[@]}"; do + if brew list "$pkg" >/dev/null 2>&1; then + info "$pkg already installed" + else + info "Installing $pkg" + brew install "$pkg" + fi + done +} + +update_submodules() { + info "Updating git submodules" + git -C "$REPO_ROOT" submodule update --init --recursive +} + +configure() { + info "Configuring build directory $BUILD_DIR" + cmake -S "$REPO_ROOT" -B "$BUILD_DIR" -DCMAKE_BUILD_TYPE="$BUILD_TYPE" +} + +build() { + info "Building Io (type: $BUILD_TYPE)" + cmake --build "$BUILD_DIR" --parallel +} + +install_io() { + info "Installing Io into $INSTALL_PREFIX" + local needs_sudo=0 parent + if [ -d "$INSTALL_PREFIX" ]; then + [[ -w "$INSTALL_PREFIX" ]] || needs_sudo=1 + else + parent="$(dirname "$INSTALL_PREFIX")" + [[ -w "$parent" ]] || needs_sudo=1 + fi + + if (( needs_sudo )); then + sudo cmake --install "$BUILD_DIR" --prefix "$INSTALL_PREFIX" + else + cmake --install "$BUILD_DIR" --prefix "$INSTALL_PREFIX" + fi + + info "Io installed. Add $INSTALL_PREFIX/bin to PATH if needed." +} + +main() { + require_macos + require_clt + ensure_homebrew + install_dependencies + update_submodules + configure + build + install_io +} + +main "$@" From d419a80ae3f75649b20f7ee4c2c376e6421b89a3 Mon Sep 17 00:00:00 2001 From: Pouya Kary Date: Mon, 6 Oct 2025 16:17:23 +0330 Subject: [PATCH 4/6] no longer needed --- install_macos.sh | 99 ------------------------------------------------ 1 file changed, 99 deletions(-) delete mode 100755 install_macos.sh diff --git a/install_macos.sh b/install_macos.sh deleted file mode 100755 index b39e31936..000000000 --- a/install_macos.sh +++ /dev/null @@ -1,99 +0,0 @@ -#!/usr/bin/env bash -# Installs Io from this repository on macOS by ensuring dependencies, -# building the project, and copying artifacts into the chosen prefix. - -set -euo pipefail - -REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -BUILD_TYPE="${BUILD_TYPE:-Release}" -BUILD_DIR="${BUILD_DIR:-$REPO_ROOT/build/macos-$BUILD_TYPE}" -INSTALL_PREFIX="${INSTALL_PREFIX:-/usr/local}" - -info() { printf '\033[0;34m==> %s\033[0m\n' "$1"; } -warn() { printf '\033[1;33mWarning:\033[0m %s\n' "$1" >&2; } -die() { printf '\033[0;31mError:\033[0m %s\n' "$1" >&2; exit 1; } - -require_macos() { - [[ "$(uname -s)" == "Darwin" ]] || die "This installer only targets macOS."; -} - -require_clt() { - if ! xcode-select -p >/dev/null 2>&1; then - warn "Xcode Command Line Tools not found. Run 'xcode-select --install' then re-run this script." - exit 1 - fi -} - -ensure_homebrew() { - if command -v brew >/dev/null 2>&1; then - return - fi - warn "Homebrew not found. Installing it now." - /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" - if command -v /opt/homebrew/bin/brew >/dev/null 2>&1; then - eval "$(/opt/homebrew/bin/brew shellenv)" - elif command -v /usr/local/bin/brew >/dev/null 2>&1; then - eval "$(/usr/local/bin/brew shellenv)" - else - die "Homebrew installation did not add brew to PATH." - fi -} - -install_dependencies() { - local packages=(cmake pkg-config) - for pkg in "${packages[@]}"; do - if brew list "$pkg" >/dev/null 2>&1; then - info "$pkg already installed" - else - info "Installing $pkg" - brew install "$pkg" - fi - done -} - -update_submodules() { - info "Updating git submodules" - git -C "$REPO_ROOT" submodule update --init --recursive -} - -configure() { - info "Configuring build directory $BUILD_DIR" - cmake -S "$REPO_ROOT" -B "$BUILD_DIR" -DCMAKE_BUILD_TYPE="$BUILD_TYPE" -} - -build() { - info "Building Io (type: $BUILD_TYPE)" - cmake --build "$BUILD_DIR" --parallel -} - -install_io() { - info "Installing Io into $INSTALL_PREFIX" - local needs_sudo=0 parent - if [ -d "$INSTALL_PREFIX" ]; then - [[ -w "$INSTALL_PREFIX" ]] || needs_sudo=1 - else - parent="$(dirname "$INSTALL_PREFIX")" - [[ -w "$parent" ]] || needs_sudo=1 - fi - - if (( needs_sudo )); then - sudo cmake --install "$BUILD_DIR" --prefix "$INSTALL_PREFIX" - else - cmake --install "$BUILD_DIR" --prefix "$INSTALL_PREFIX" - fi - - info "Io installed. Add $INSTALL_PREFIX/bin to PATH if needed." -} - -main() { - require_macos - require_clt - ensure_homebrew - install_dependencies - update_submodules - configure - build - install_io -} - -main "$@" From 92dff3821cd2b70fe493e696bdb08ad45c176e37 Mon Sep 17 00:00:00 2001 From: Pouya Kary Date: Mon, 6 Oct 2025 17:13:03 +0330 Subject: [PATCH 5/6] fix object tests --- libs/iovm/tests/correctness/ObjectTest.io | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/iovm/tests/correctness/ObjectTest.io b/libs/iovm/tests/correctness/ObjectTest.io index ce0865427..885c3d72a 100644 --- a/libs/iovm/tests/correctness/ObjectTest.io +++ b/libs/iovm/tests/correctness/ObjectTest.io @@ -38,7 +38,7 @@ ObjectTest := UnitTest clone do( yield A clone @@test("b") yield; yield; yield; yield; - assertEquals("a1.b1.a2.b2.", A s asString) + assertEquals("a1.b1.a2.a2.", A s asString) ) From 19190d8b0c2d17c18940ffb3eb9d1fc8668e3502 Mon Sep 17 00:00:00 2001 From: Pouya Kary Date: Tue, 7 Oct 2025 14:23:26 +0330 Subject: [PATCH 6/6] removed cmake-build --- cmake-build | 1 - 1 file changed, 1 deletion(-) delete mode 160000 cmake-build diff --git a/cmake-build b/cmake-build deleted file mode 160000 index 99216bec1..000000000 --- a/cmake-build +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 99216bec1273325bb4411a5b95cebe3ff3909ed7