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/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 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) )