// license that can be found in the LICENSE file.
#include "runtime.h"
+#include "amd64_darwin.h"
#include "signals.h"
typedef uint64 __uint64_t;
void
sys·sleep(int64 ms)
{
- unimplemented("sleep");
+ struct timeval tv;
+
+ tv.tv_sec = ms/1000;
+ tv.tv_usec = ms%1000 * 1000;
+ select(0, nil, nil, nil, &tv);
+}
+
+// Thread-safe allocation of a semaphore.
+// Psema points at a kernel semaphore key.
+// It starts out zero, meaning no semaphore.
+// Fill it in, being careful of others calling initsema
+// simultaneously.
+static void
+initsema(uint32 *psema)
+{
+ uint32 sema;
+
+ if(*psema != 0) // already have one
+ return;
+
+ sema = semcreate();
+ if(!cas(psema, 0, sema)){
+ // Someone else filled it in. Use theirs.
+ semdestroy(sema);
+ return;
+ }
+}
+
+
+// Atomic add and return new value.
+static uint32
+xadd(uint32 volatile *val, int32 delta)
+{
+ uint32 oval, nval;
+
+ for(;;){
+ oval = *val;
+ nval = oval + delta;
+ if(cas(val, oval, nval))
+ return nval;
+ }
}
+
+// Blocking locks.
+
+// Implement Locks, using semaphores.
+// l->key is the number of threads who want the lock.
+// In a race, one thread increments l->key from 0 to 1
+// and the others increment it from >0 to >1. The thread
+// who does the 0->1 increment gets the lock, and the
+// others wait on the semaphore. When the 0->1 thread
+// releases the lock by decrementing l->key, l->key will
+// be >0, so it will increment the semaphore to wake up
+// one of the others. This is the same algorithm used
+// in Plan 9's user-space locks.
+//
+// Note that semaphores are never destroyed (the kernel
+// will clean up when the process exits). We assume for now
+// that Locks are only used for long-lived structures like M and G.
+
void
lock(Lock *l)
{
- if(cas(&l->key, 0, 1))
- return;
- unimplemented("lock wait");
+ // Allocate semaphore if needed.
+ if(l->sema == 0)
+ initsema(&l->sema);
+
+ if(xadd(&l->key, 1) > 1) // someone else has it; wait
+ semacquire(l->sema);
}
void
unlock(Lock *l)
{
- if(cas(&l->key, 1, 0))
- return;
- unimplemented("unlock wakeup");
+ if(xadd(&l->key, -1) > 0) // someone else is waiting
+ semrelease(l->sema);
}
+
+// Event notifications.
void
noteclear(Note *n)
{
- n->lock.key = 0;
- lock(&n->lock);
+ n->wakeup = 0;
}
void
notesleep(Note *n)
{
- lock(&n->lock);
- unlock(&n->lock);
+ if(n->sema == 0)
+ initsema(&n->sema);
+ while(!n->wakeup)
+ semacquire(n->sema);
}
void
notewakeup(Note *n)
{
- unlock(&n->lock);
+ if(n->sema == 0)
+ initsema(&n->sema);
+ n->wakeup = 1;
+ semrelease(n->sema);
}
+
+// BSD interface for threading.
void
-newosproc(M *mm, G *gg, void *stk, void (*fn)(void))
+osinit(void)
+{
+ // Register our thread-creation callback (see sys_amd64_darwin.s).
+ bsdthread_register();
+}
+
+void
+newosproc(M *m, G *g, void *stk, void (*fn)(void))
+{
+ bsdthread_create(stk, m, g, fn);
+}
+
+
+// Mach IPC, to get at semaphores
+// Definitions are in /usr/include/mach on a Mac.
+
+static void
+macherror(kern_return_t r, int8 *fn)
{
- unimplemented("newosproc");
+ prints("mach error ");
+ prints(fn);
+ prints(": ");
+ sys·printint(r);
+ prints("\n");
+ throw("mach error");
}
-int32
-getprocid(void)
+enum
+{
+ DebugMach = 0
+};
+
+typedef int32 mach_msg_option_t;
+typedef uint32 mach_msg_bits_t;
+typedef uint32 mach_msg_id_t;
+typedef uint32 mach_msg_size_t;
+typedef uint32 mach_msg_timeout_t;
+typedef uint32 mach_port_name_t;
+typedef uint64 mach_vm_address_t;
+
+typedef struct mach_msg_header_t mach_msg_header_t;
+typedef struct mach_msg_body_t mach_msg_body_t;
+typedef struct mach_msg_port_descriptor_t mach_msg_port_descriptor_t;
+typedef struct NDR_record_t NDR_record_t;
+
+enum
+{
+ MACH_MSG_TYPE_MOVE_RECEIVE = 16,
+ MACH_MSG_TYPE_MOVE_SEND = 17,
+ MACH_MSG_TYPE_MOVE_SEND_ONCE = 18,
+ MACH_MSG_TYPE_COPY_SEND = 19,
+ MACH_MSG_TYPE_MAKE_SEND = 20,
+ MACH_MSG_TYPE_MAKE_SEND_ONCE = 21,
+ MACH_MSG_TYPE_COPY_RECEIVE = 22,
+
+ MACH_MSG_PORT_DESCRIPTOR = 0,
+ MACH_MSG_OOL_DESCRIPTOR = 1,
+ MACH_MSG_OOL_PORTS_DESCRIPTOR = 2,
+ MACH_MSG_OOL_VOLATILE_DESCRIPTOR = 3,
+
+ MACH_MSGH_BITS_COMPLEX = 0x80000000,
+
+ MACH_SEND_MSG = 1,
+ MACH_RCV_MSG = 2,
+ MACH_RCV_LARGE = 4,
+
+ MACH_SEND_TIMEOUT = 0x10,
+ MACH_SEND_INTERRUPT = 0x40,
+ MACH_SEND_CANCEL = 0x80,
+ MACH_SEND_ALWAYS = 0x10000,
+ MACH_SEND_TRAILER = 0x20000,
+ MACH_RCV_TIMEOUT = 0x100,
+ MACH_RCV_NOTIFY = 0x200,
+ MACH_RCV_INTERRUPT = 0x400,
+ MACH_RCV_OVERWRITE = 0x1000,
+};
+
+mach_port_t mach_task_self(void);
+mach_port_t mach_thread_self(void);
+
+#pragma pack on
+struct mach_msg_header_t
+{
+ mach_msg_bits_t bits;
+ mach_msg_size_t size;
+ mach_port_t remote_port;
+ mach_port_t local_port;
+ mach_msg_size_t reserved;
+ mach_msg_id_t id;
+};
+
+struct mach_msg_body_t
+{
+ uint32 descriptor_count;
+};
+
+struct mach_msg_port_descriptor_t
+{
+ mach_port_t name;
+ uint32 pad1;
+ uint16 pad2;
+ uint8 disposition;
+ uint8 type;
+};
+
+enum
+{
+ NDR_PROTOCOL_2_0 = 0,
+ NDR_INT_BIG_ENDIAN = 0,
+ NDR_INT_LITTLE_ENDIAN = 1,
+ NDR_FLOAT_IEEE = 0,
+ NDR_CHAR_ASCII = 0
+};
+
+struct NDR_record_t
+{
+ uint8 mig_vers;
+ uint8 if_vers;
+ uint8 reserved1;
+ uint8 mig_encoding;
+ uint8 int_rep;
+ uint8 char_rep;
+ uint8 float_rep;
+ uint8 reserved2;
+};
+#pragma pack off
+
+static NDR_record_t zerondr;
+
+#define MACH_MSGH_BITS(a, b) ((a) | ((b)<<8))
+
+// Mach system calls (in sys_amd64_darwin.s)
+kern_return_t mach_msg_trap(mach_msg_header_t*,
+ mach_msg_option_t, mach_msg_size_t, mach_msg_size_t,
+ mach_port_name_t, mach_msg_timeout_t, mach_port_name_t);
+mach_port_t mach_reply_port(void);
+mach_port_t mach_task_self(void);
+mach_port_t mach_thread_self(void);
+
+static kern_return_t
+mach_msg(mach_msg_header_t *h,
+ mach_msg_option_t op,
+ mach_msg_size_t send_size,
+ mach_msg_size_t rcv_size,
+ mach_port_name_t rcv_name,
+ mach_msg_timeout_t timeout,
+ mach_port_name_t notify)
+{
+ // TODO: Loop on interrupt.
+ return mach_msg_trap(h, op, send_size, rcv_size, rcv_name, timeout, notify);
+}
+
+
+// Mach RPC (MIG)
+// I'm not using the Mach names anymore. They're too long.
+
+enum
+{
+ MinMachMsg = 48,
+ Reply = 100,
+};
+
+#pragma pack on
+typedef struct CodeMsg CodeMsg;
+struct CodeMsg
{
+ mach_msg_header_t h;
+ NDR_record_t NDR;
+ kern_return_t code;
+};
+#pragma pack off
+
+static kern_return_t
+machcall(mach_msg_header_t *h, int32 maxsize, int32 rxsize)
+{
+ uint32 *p;
+ int32 i, ret, id;
+ mach_port_t port;
+ CodeMsg *c;
+
+ if((port = m->machport) == 0){
+ port = mach_reply_port();
+ m->machport = port;
+ }
+
+ h->bits |= MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, MACH_MSG_TYPE_MAKE_SEND_ONCE);
+ h->local_port = port;
+ h->reserved = 0;
+ id = h->id;
+
+ if(DebugMach){
+ p = (uint32*)h;
+ prints("send:\t");
+ for(i=0; i<h->size/sizeof(p[0]); i++){
+ prints(" ");
+ sys·printpointer((void*)p[i]);
+ if(i%8 == 7)
+ prints("\n\t");
+ }
+ if(i%8)
+ prints("\n");
+ }
+
+ ret = mach_msg(h, MACH_SEND_MSG|MACH_RCV_MSG,
+ h->size, maxsize, port, 0, 0);
+ if(ret != 0){
+ if(DebugMach){
+ prints("mach_msg error ");
+ sys·printint(ret);
+ prints("\n");
+ }
+ return ret;
+ }
+
+ if(DebugMach){
+ p = (uint32*)h;
+ prints("recv:\t");
+ for(i=0; i<h->size/sizeof(p[0]); i++){
+ prints(" ");
+ sys·printpointer((void*)p[i]);
+ if(i%8 == 7)
+ prints("\n\t");
+ }
+ if(i%8)
+ prints("\n");
+ }
+
+ if(h->id != id+Reply){
+ if(DebugMach){
+ prints("mach_msg reply id mismatch ");
+ sys·printint(h->id);
+ prints(" != ");
+ sys·printint(id+Reply);
+ prints("\n");
+ }
+ return -303; // MIG_REPLY_MISMATCH
+ }
+
+ // Look for a response giving the return value.
+ // Any call can send this back with an error,
+ // and some calls only have return values so they
+ // send it back on success too. I don't quite see how
+ // you know it's one of these and not the full response
+ // format, so just look if the message is right.
+ c = (CodeMsg*)h;
+ if(h->size == sizeof(CodeMsg)
+ && !(h->bits & MACH_MSGH_BITS_COMPLEX)){
+ if(DebugMach){
+ prints("mig result ");
+ sys·printint(c->code);
+ prints("\n");
+ }
+ return c->code;
+ }
+
+ if(h->size != rxsize){
+ if(DebugMach){
+ prints("mach_msg reply size mismatch ");
+ sys·printint(h->size);
+ prints(" != ");
+ sys·printint(rxsize);
+ prints("\n");
+ }
+ return -307; // MIG_ARRAY_TOO_LARGE
+ }
+
return 0;
}
+
+
+// Semaphores!
+
+enum
+{
+ Tsemcreate = 3418,
+ Rsemcreate = Tsemcreate + Reply,
+
+ Tsemdestroy = 3419,
+ Rsemdestroy = Tsemdestroy + Reply,
+};
+
+typedef struct TsemcreateMsg TsemcreateMsg;
+typedef struct RsemcreateMsg RsemcreateMsg;
+typedef struct TsemdestroyMsg TsemdestroyMsg;
+// RsemdestroyMsg = CodeMsg
+
+#pragma pack on
+struct TsemcreateMsg
+{
+ mach_msg_header_t h;
+ NDR_record_t ndr;
+ int32 policy;
+ int32 value;
+};
+
+struct RsemcreateMsg
+{
+ mach_msg_header_t h;
+ mach_msg_body_t body;
+ mach_msg_port_descriptor_t semaphore;
+};
+
+struct TsemdestroyMsg
+{
+ mach_msg_header_t h;
+ mach_msg_body_t body;
+ mach_msg_port_descriptor_t semaphore;
+};
+#pragma pack off
+
+mach_port_t
+semcreate(void)
+{
+ union {
+ TsemcreateMsg tx;
+ RsemcreateMsg rx;
+ uint8 pad[MinMachMsg];
+ } m;
+ kern_return_t r;
+
+ m.tx.h.bits = 0;
+ m.tx.h.size = sizeof(m.tx);
+ m.tx.h.remote_port = mach_task_self();
+ m.tx.h.id = Tsemcreate;
+ m.tx.ndr = zerondr;
+
+ m.tx.policy = 0; // 0 = SYNC_POLICY_FIFO
+ m.tx.value = 0;
+
+ if((r = machcall(&m.tx.h, sizeof m, sizeof(m.rx))) != 0)
+ macherror(r, "semaphore_create");
+ if(m.rx.body.descriptor_count != 1)
+ unimplemented("semcreate desc count");
+ return m.rx.semaphore.name;
+}
+
+void
+semdestroy(mach_port_t sem)
+{
+ union {
+ TsemdestroyMsg tx;
+ uint8 pad[MinMachMsg];
+ } m;
+ kern_return_t r;
+
+ m.tx.h.bits = MACH_MSGH_BITS_COMPLEX;
+ m.tx.h.size = sizeof(m.tx);
+ m.tx.h.remote_port = mach_task_self();
+ m.tx.h.id = Tsemdestroy;
+ m.tx.body.descriptor_count = 1;
+ m.tx.semaphore.name = sem;
+ m.tx.semaphore.disposition = MACH_MSG_TYPE_MOVE_SEND;
+ m.tx.semaphore.type = 0;
+
+ if((r = machcall(&m.tx.h, sizeof m, 0)) != 0)
+ macherror(r, "semaphore_destroy");
+}
+
+// The other calls have simple system call traps
+// in sys_amd64_darwin.s
+kern_return_t mach_semaphore_wait(uint32 sema);
+kern_return_t mach_semaphore_timedwait(uint32 sema, uint32 sec, uint32 nsec);
+kern_return_t mach_semaphore_signal(uint32 sema);
+kern_return_t mach_semaphore_signal_all(uint32 sema);
+
+void
+semacquire(mach_port_t sem)
+{
+ kern_return_t r;
+
+ if((r = mach_semaphore_wait(sem)) != 0)
+ macherror(r, "semaphore_wait");
+}
+
+void
+semrelease(mach_port_t sem)
+{
+ kern_return_t r;
+
+ if((r = mach_semaphore_signal(sem)) != 0)
+ macherror(r, "semaphore_signal");
+}
+
//
// System calls and other sys.stuff for AMD64, Darwin
+// See http://fxr.watson.org/fxr/source/bsd/kern/syscalls.c?v=xnu-1228
+// or /usr/include/sys/syscall.h (on a Mac) for system call numbers.
//
-// TODO(rsc): Either sys·exit or exit1 is wrong!
-// It looks like sys·exit is correct (exits the entire program)
-// and exit1 should be mimicking the OS X library routine
-// __bsdthread_terminate.
+// Exit the entire program (like C exit)
TEXT sys·exit(SB),7,$-8
MOVL 8(SP), DI // arg 1 exit status
MOVL $(0x2000000+1), AX // syscall entry
CALL notok(SB)
RET
+// Exit this OS thread (like pthread_exit, which eventually
+// calls __bsdthread_terminate).
TEXT exit1(SB),7,$-8
MOVL 8(SP), DI // arg 1 exit status
- MOVL $(0x2000000+1), AX // syscall entry
+ MOVL $(0x2000000+361), AX // syscall entry
SYSCALL
CALL notok(SB)
RET
MOVQ x+8(FP), BX
MOVQ BX, -8(AX) // set calling pc
RET
+
+// void bsdthread_create(void *stk, M *m, G *g, void (*fn)(void))
+TEXT bsdthread_create(SB),7,$-8
+ // Set up arguments to bsdthread_create system call.
+ // The ones in quotes pass through to the thread callback
+ // uninterpreted, so we can put whatever we want there.
+ MOVQ fn+32(SP), DI // "func"
+ MOVQ m+16(SP), SI // "arg"
+ MOVQ stk+8(SP), DX // stack
+ MOVQ g+24(SP), R10 // "pthread"
+ MOVQ $0, R10 // flags
+ MOVQ $(0x2000000+360), AX // bsdthread_create
+ SYSCALL
+ JCC 2(PC)
+ CALL notok(SB)
+ RET
+
+// The thread that bsdthread_create creates starts executing here,
+// because we registered this function using bsdthread_register
+// at startup.
+// DI = "pthread" (= g)
+// SI = mach thread port
+// DX = "func" (= fn)
+// CX = "arg" (= m)
+// R8 = stack
+// R9 = flags (= 0)
+// SP = stack - C_64_REDZONE_LEN (= stack - 128)
+TEXT bsdthread_start(SB),7,$-8
+ MOVQ CX, R14 // m
+ MOVQ DI, R15 // g
+ MOVQ SI, 24(R14) // thread port is m->procid
+ CALL DX // fn
+ CALL exit1(SB)
+ RET
+
+// void bsdthread_register(void)
+// registers callbacks for threadstart (see bsdthread_create above
+// and wqthread and pthsize (not used). returns 0 on success.
+TEXT bsdthread_register(SB),7,$-8
+ MOVQ $bsdthread_start(SB), DI // threadstart
+ MOVQ $0, SI // wqthread, not used by us
+ MOVQ $0, DX // pthsize, not used by us
+ MOVQ $(0x2000000+366), AX // bsdthread_register
+ SYSCALL
+ JCC 2(PC)
+ CALL notok(SB)
+ RET
+
+// int64 select(int32, void*, void*, void*, void*)
+TEXT select(SB),7,$0
+ MOVL 8(SP), DI
+ MOVQ 16(SP), SI
+ MOVQ 24(SP), DX
+ MOVQ 32(SP), R10
+ MOVQ 40(SP), R8
+ MOVL $(0x2000000+407), AX // select_nocancel
+ SYSCALL
+ RET
+
+// Mach system calls use 0x1000000 instead of the BSD's 0x2000000.
+
+// uint32 mach_msg_trap(void*, uint32, uint32, uint32, uint32, uint32, uint32)
+TEXT mach_msg_trap(SB),7,$0
+ MOVQ 8(SP), DI
+ MOVL 16(SP), SI
+ MOVL 20(SP), DX
+ MOVL 24(SP), R10
+ MOVL 28(SP), R8
+ MOVL 32(SP), R9
+ MOVL 36(SP), R11
+ PUSHQ R11 // seventh arg, on stack
+ MOVL $(0x1000000+31), AX // mach_msg_trap
+ SYSCALL
+ POPQ R11
+ RET
+
+TEXT mach_task_self(SB),7,$0
+ MOVL $(0x1000000+28), AX // task_self_trap
+ SYSCALL
+ RET
+
+TEXT mach_thread_self(SB),7,$0
+ MOVL $(0x1000000+27), AX // thread_self_trap
+ SYSCALL
+ RET
+
+TEXT mach_reply_port(SB),7,$0
+ MOVL $(0x1000000+26), AX // mach_reply_port
+ SYSCALL
+ RET
+
+// Mach provides trap versions of the semaphore ops,
+// instead of requiring the use of RPC.
+
+// uint32 mach_semaphore_wait(uint32)
+TEXT mach_semaphore_wait(SB),7,$0
+ MOVL 8(SP), DI
+ MOVL $(0x1000000+36), AX // semaphore_wait_trap
+ SYSCALL
+ RET
+
+// uint32 mach_semaphore_timedwait(uint32, uint32, uint32)
+TEXT mach_semaphore_timedwait(SB),7,$0
+ MOVL 8(SP), DI
+ MOVL 12(SP), SI
+ MOVL 16(SP), DX
+ MOVL $(0x1000000+38), AX // semaphore_timedwait_trap
+ SYSCALL
+ RET
+
+// uint32 mach_semaphore_signal(uint32)
+TEXT mach_semaphore_signal(SB),7,$0
+ MOVL 8(SP), DI
+ MOVL $(0x1000000+33), AX // semaphore_signal_trap
+ SYSCALL
+ RET
+
+// uint32 mach_semaphore_signal_all(uint32)
+TEXT mach_semaphore_signal_all(SB),7,$0
+ MOVL 8(SP), DI
+ MOVL $(0x1000000+34), AX // semaphore_signal_all_trap
+ SYSCALL
+ RET
+