Skip to content
Closed
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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -96,4 +96,5 @@ Makefile
IoInstallPrefix.h
xcuserdata
build
.idea
.idea
cmake-install
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions libs/coroutine/source/Coro.h
Original file line number Diff line number Diff line change
Expand Up @@ -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__)
Expand Down Expand Up @@ -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 <sys/ucontext.h>
#define CORO_IMPLEMENTATION "ucontext"
Expand All @@ -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)
Expand Down
182 changes: 101 additions & 81 deletions libs/coroutine/source/Coro_arm64.c
Original file line number Diff line number Diff line change
@@ -1,23 +1,32 @@
#if defined(__arm64__) || defined(__aarch64__)
#include "Base.h"
#include "Coro.h"
#include <stdint.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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 <valgrind/valgrind.h>
#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;
Expand All @@ -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 = &marker;
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
22 changes: 15 additions & 7 deletions libs/coroutine/source/arm64-ucontext.h
Original file line number Diff line number Diff line change
@@ -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 <stdint.h>

#include <ucontext.h>
// 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
2 changes: 2 additions & 0 deletions libs/coroutine/source/context.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion libs/iovm/tests/correctness/ObjectTest.io
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change is incorrect. The original assert is correct; test is failing on MacOS (actually on AARCH64 Linux too) in release mode though it passes (for me anyway) in debug mode.

I have a patch to fix this but it is not quite ready yet (more testing needed)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah sorry this is my bad 😞 Forgot to undo this 😔

)


Expand Down