From ee44c803490310209b9d569a5c3fff14c8027e71 Mon Sep 17 00:00:00 2001 From: Philipp Stephani Date: Sat, 22 Oct 2016 18:27:16 +0200 Subject: [PATCH] WIP: CSP based on libtask --- lib/libtask/386-ucontext.h | 119 ++++++++++ lib/libtask/COPYRIGHT | 42 ++++ lib/libtask/README | 244 ++++++++++++++++++++ lib/libtask/amd64-ucontext.h | 135 +++++++++++ lib/libtask/asm.S | 326 +++++++++++++++++++++++++++ lib/libtask/channel.c | 384 +++++++++++++++++++++++++++++++ lib/libtask/context.c | 141 ++++++++++++ lib/libtask/fd.c | 201 +++++++++++++++++ lib/libtask/httpload.c | 58 +++++ lib/libtask/makesun | 22 ++ lib/libtask/mips-ucontext.h | 77 +++++++ lib/libtask/net.c | 202 +++++++++++++++++ lib/libtask/power-ucontext.h | 36 +++ lib/libtask/primes.c | 61 +++++ lib/libtask/print.c | 249 ++++++++++++++++++++ lib/libtask/qlock.c | 142 ++++++++++++ lib/libtask/rendez.c | 45 ++++ lib/libtask/task.c | 424 +++++++++++++++++++++++++++++++++++ lib/libtask/task.h | 181 +++++++++++++++ lib/libtask/taskimpl.h | 200 +++++++++++++++++ lib/libtask/tcpproxy.c | 91 ++++++++ lib/libtask/testdelay.c | 40 ++++ lib/libtask/testdelay1.c | 7 + lisp/emacs-lisp/byte-opt.el | 19 ++ lisp/emacs-lisp/cconv.el | 23 ++ src/Makefile.in | 20 +- src/alloc.c | 45 ++++ src/coroutine.c | 400 +++++++++++++++++++++++++++++++++ src/emacs.c | 28 ++- src/eval.c | 50 +++++ src/lisp.h | 42 ++++ src/nsterm.m | 6 +- src/print.c | 34 +++ src/process.c | 14 +- src/sysselect.h | 8 + src/unexmacosx.c | 2 + src/xgselect.c | 4 +- src/xmenu.c | 2 +- src/xterm.c | 4 +- test/src/coroutine-tests.el | 166 ++++++++++++++ 40 files changed, 4267 insertions(+), 27 deletions(-) create mode 100644 lib/libtask/386-ucontext.h create mode 100644 lib/libtask/COPYRIGHT create mode 100644 lib/libtask/README create mode 100644 lib/libtask/amd64-ucontext.h create mode 100644 lib/libtask/asm.S create mode 100644 lib/libtask/channel.c create mode 100644 lib/libtask/context.c create mode 100644 lib/libtask/fd.c create mode 100644 lib/libtask/httpload.c create mode 100755 lib/libtask/makesun create mode 100644 lib/libtask/mips-ucontext.h create mode 100644 lib/libtask/net.c create mode 100644 lib/libtask/power-ucontext.h create mode 100644 lib/libtask/primes.c create mode 100644 lib/libtask/print.c create mode 100644 lib/libtask/qlock.c create mode 100644 lib/libtask/rendez.c create mode 100644 lib/libtask/task.c create mode 100644 lib/libtask/task.h create mode 100644 lib/libtask/taskimpl.h create mode 100644 lib/libtask/tcpproxy.c create mode 100644 lib/libtask/testdelay.c create mode 100644 lib/libtask/testdelay1.c create mode 100644 src/coroutine.c create mode 100644 test/src/coroutine-tests.el diff --git a/lib/libtask/386-ucontext.h b/lib/libtask/386-ucontext.h new file mode 100644 index 00000000000..b840f9c5fef --- /dev/null +++ b/lib/libtask/386-ucontext.h @@ -0,0 +1,119 @@ +#define setcontext(u) setmcontext(&(u)->uc_mcontext) +#define getcontext(u) getmcontext(&(u)->uc_mcontext) +typedef struct mcontext mcontext_t; +typedef struct ucontext ucontext_t; + +extern int swapcontext(ucontext_t*, const ucontext_t*); +extern void makecontext(ucontext_t*, void(*)(), int, ...); +extern int getmcontext(mcontext_t*); +extern void setmcontext(const mcontext_t*); + +/*- + * Copyright (c) 1999 Marcel Moolenaar + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer + * in this position and unchanged. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD: src/sys/sys/ucontext.h,v 1.4 1999/10/11 20:33:17 luoqi Exp $ + */ + +/* #include */ + +/*- + * Copyright (c) 1999 Marcel Moolenaar + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer + * in this position and unchanged. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD: src/sys/i386/include/ucontext.h,v 1.4 1999/10/11 20:33:09 luoqi Exp $ + */ + +struct mcontext { + /* + * The first 20 fields must match the definition of + * sigcontext. So that we can support sigcontext + * and ucontext_t at the same time. + */ + int mc_onstack; /* XXX - sigcontext compat. */ + int mc_gs; + int mc_fs; + int mc_es; + int mc_ds; + int mc_edi; + int mc_esi; + int mc_ebp; + int mc_isp; + int mc_ebx; + int mc_edx; + int mc_ecx; + int mc_eax; + int mc_trapno; + int mc_err; + int mc_eip; + int mc_cs; + int mc_eflags; + int mc_esp; /* machine state */ + int mc_ss; + + int mc_fpregs[28]; /* env87 + fpacc87 + u_long */ + int __spare__[17]; +}; + +struct ucontext { + /* + * Keep the order of the first two fields. Also, + * keep them the first two fields in the structure. + * This way we can have a union with struct + * sigcontext and ucontext_t. This allows us to + * support them both at the same time. + * note: the union is not defined, though. + */ + sigset_t uc_sigmask; + mcontext_t uc_mcontext; + + struct __ucontext *uc_link; + stack_t uc_stack; + int __spare__[8]; +}; diff --git a/lib/libtask/COPYRIGHT b/lib/libtask/COPYRIGHT new file mode 100644 index 00000000000..c1aedca1242 --- /dev/null +++ b/lib/libtask/COPYRIGHT @@ -0,0 +1,42 @@ + +This software was developed as part of a project at MIT. + +Copyright (c) 2005-2007 Russ Cox, + Massachusetts Institute of Technology + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +=== + +Contains parts of an earlier library that has: + +/* + * The authors of this software are Rob Pike, Sape Mullender, and Russ Cox + * Copyright (c) 2003 by Lucent Technologies. + * Permission to use, copy, modify, and distribute this software for any + * purpose without fee is hereby granted, provided that this entire notice + * is included in all copies of any software which is or includes a copy + * or modification of this software and in all copies of the supporting + * documentation for such software. + * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED + * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE ANY + * REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY + * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE. +*/ diff --git a/lib/libtask/README b/lib/libtask/README new file mode 100644 index 00000000000..191ead4432d --- /dev/null +++ b/lib/libtask/README @@ -0,0 +1,244 @@ +Libtask is a simple coroutine library. It runs on Linux (ARM, MIPS, and x86), +FreeBSD (x86), OS X (PowerPC x86, and x86-64), and SunOS Solaris (Sparc), +and is easy to port to other systems. + +Libtask gives the programmer the illusion of threads, but +the operating system sees only a single kernel thread. +For clarity, we refer to the coroutines as "tasks," not threads. + +Scheduling is cooperative. Only one task runs at a time, +and it cannot be rescheduled without explicitly giving up +the CPU. Most of the functions provided in task.h do have +the possibility of going to sleep. Programs using the task +functions should #include . + +--- Basic task manipulation + +int taskcreate(void (*f)(void *arg), void *arg, unsigned int stacksize); + + Create a new task running f(arg) on a stack of size stacksize. + +void tasksystem(void); + + Mark the current task as a "system" task. These are ignored + for the purposes of deciding the program is done running + (see taskexit next). + +void taskexit(int status); + + Exit the current task. If this is the last non-system task, + exit the entire program using the given exit status. + +void taskexitall(int status); + + Exit the entire program, using the given exit status. + +void taskmain(int argc, char *argv[]); + + Write this function instead of main. Libtask provides its own main. + +int taskyield(void); + + Explicitly give up the CPU. The current task will be scheduled + again once all the other currently-ready tasks have a chance + to run. Returns the number of other tasks that ran while the + current task was waiting. (Zero means there are no other tasks + trying to run.) + +int taskdelay(unsigned int ms) + + Explicitly give up the CPU for at least ms milliseconds. + Other tasks continue to run during this time. + +void** taskdata(void); + + Return a pointer to a single per-task void* pointer. + You can use this as a per-task storage place. + +void needstack(int n); + + Tell the task library that you need at least n bytes left + on the stack. If you don't have it, the task library will call abort. + (It's hard to figure out how big stacks should be. I usually make + them really big (say 32768) and then don't worry about it.) + +void taskname(char*, ...); + + Takes an argument list like printf. Sets the current task's name. + +char* taskgetname(void); + + Returns the current task's name. Is the actual buffer; do not free. + +void taskstate(char*, ...); +char* taskgetstate(void); + + Like taskname and taskgetname but for the task state. + + When you send a tasked program a SIGQUIT (or SIGINFO, on BSD) + it will print a list of all its tasks and their names and states. + This is useful for debugging why your program isn't doing anything! + +unsigned int taskid(void); + + Return the unique task id for the current task. + +--- Non-blocking I/O + +There is a small amount of runtime support for non-blocking I/O +on file descriptors. + +int fdnoblock(int fd); + + Sets I/O on the given fd to be non-blocking. Should be + called before any of the other fd routines. + +int fdread(int, void*, int); + + Like regular read(), but puts task to sleep while waiting for + data instead of blocking the whole program. + +int fdwrite(int, void*, int); + + Like regular write(), but puts task to sleep while waiting to + write data instead of blocking the whole program. + +void fdwait(int fd, int rw); + + Low-level call sitting underneath fdread and fdwrite. + Puts task to sleep while waiting for I/O to be possible on fd. + Rw specifies type of I/O: 'r' means read, 'w' means write, + anything else means just exceptional conditions (hang up, etc.) + The 'r' and 'w' also wake up for exceptional conditions. + +--- Network I/O + +These are convenient packaging of the ugly Unix socket routines. +They can all put the current task to sleep during the call. + +int netannounce(int proto, char *address, int port) + + Start a network listener running on address and port of protocol. + Proto is either TCP or UDP. Port is a port number. Address is a + string version of a host name or IP address. If address is null, + then announce binds to the given port on all available interfaces. + Returns a fd to use with netaccept. + Examples: netannounce(TCP, "localhost", 80) or + netannounce(TCP, "127.0.0.1", 80) or netannounce(TCP, 0, 80). + +int netaccept(int fd, char *server, int *port) + + Get the next connection that comes in to the listener fd. + Returns a fd to use to talk to the guy who just connected. + If server is not null, it must point at a buffer of at least + 16 bytes that is filled in with the remote IP address. + If port is not null, it is filled in with the report port. + Example: + char server[16]; + int port; + + if(netaccept(fd, server, &port) >= 0) + printf("connect from %s:%d", server, port); + +int netdial(int proto, char *name, int port) + + Create a new (outgoing) connection to a particular host. + Name can be an ip address or a domain name. If it's a domain name, + the entire program will block while the name is resolved + (the DNS library does not provide a nice non-blocking interface). + Example: netdial(TCP, "www.google.com", 80) + or netdial(TCP, "18.26.4.9", 80) + +--- Time + +unsigned int taskdelay(unsigned int ms) + + Put the current task to sleep for approximately ms milliseconds. + Return the actual amount of time slept, in milliseconds. + +--- Example programs + +In this directory, tcpproxy.c is a simple TCP proxy that illustrates +most of the above. You can run + + tcpproxy 1234 www.google.com 80 + +and then you should be able to visit http://localhost:1234/ and see Google. + +Other examples are: + primes.c - simple prime sieve + httpload.c - simple HTTP load generator + testdelay.c - test taskdelay() + +--- Building + +To build, run make. You can run make install to copy task.h and +libtask.a to the appropriate places in /usr/local. Then you +should be able to just link with -ltask in your programs +that use it. + +On SunOS Solaris machines, run makesun instead of just make. + +--- Contact Info + +Please email me with questions or problems. + +Russ Cox +rsc@swtch.com + + +--- Stuff you probably won't use at first --- +--- but might want to know about eventually --- + +void tasksleep(Rendez*); +int taskwakeup(Rendez*); +int taskwakeupall(Rendez*); + + A Rendez is a condition variable. You can declare a new one by + just allocating memory for it (or putting it in another structure) + and then zeroing the memory. Tasksleep(r) 'sleeps on r', giving + up the CPU. Multiple tasks can sleep on a single Rendez. + When another task comes along and calls taskwakeup(r), + the first task sleeping on r (if any) will be woken up. + Taskwakeupall(r) wakes up all the tasks sleeping on r. + They both return the actual number of tasks awakened. + + + +void qlock(QLock*); +int canqlock(QLock*); +void qunlock(QLock*); + + You probably won't need locks because of the cooperative + scheduling, but if you do, here are some. You can make a new + QLock by just declaring it and zeroing the memory. + Calling qlock will give up the CPU if the lock is held by someone else. + Calling qunlock will not give up the CPU. + Calling canqlock tries to lock the lock, but will not give up the CPU. + It returns 1 if the lock was acquired, 0 if it cannot be at this time. + +void rlock(RWLock*); +int canrlock(RWLock*); +void runlock(RWLock*); + +void wlock(RWLock*); +int canwlock(RWLock*); +void wunlock(RWLock*); + + RWLocks are reader-writer locks. Any number of readers + can lock them at once, but only one writer at a time. + If a writer is holding it, there can't be any readers. + + +Channel *chancreate(int, int); +etc. + + Channels are buffered communication pipes you can + use to send messages between tasks. Some people like + doing most of the inter-task communication using channels. + + For details on channels see the description of channels in + http://swtch.com/usr/local/plan9/man/man3/thread.html and + http://swtch.com/~rsc/thread/ + and also the example program primes.c, which implements + a concurrent prime sieve. diff --git a/lib/libtask/amd64-ucontext.h b/lib/libtask/amd64-ucontext.h new file mode 100644 index 00000000000..e4560e59e21 --- /dev/null +++ b/lib/libtask/amd64-ucontext.h @@ -0,0 +1,135 @@ +#define setcontext(u) setmcontext(&(u)->uc_mcontext) +#define getcontext(u) getmcontext(&(u)->uc_mcontext) +typedef struct mcontext mcontext_t; +typedef struct ucontext ucontext_t; + +extern int swapcontext(ucontext_t*, const ucontext_t*); +extern void makecontext(ucontext_t*, void(*)(), int, ...); +extern int getmcontext(mcontext_t*); +extern void setmcontext(const mcontext_t*); + +/*- + * Copyright (c) 1999 Marcel Moolenaar + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer + * in this position and unchanged. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD: src/sys/sys/ucontext.h,v 1.4 1999/10/11 20:33:17 luoqi Exp $ + */ + +/* #include */ + +/*- + * Copyright (c) 1999 Marcel Moolenaar + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer + * in this position and unchanged. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD: src/sys/i386/include/ucontext.h,v 1.4 1999/10/11 20:33:09 luoqi Exp $ + */ + +struct mcontext { + /* + * The first 20 fields must match the definition of + * sigcontext. So that we can support sigcontext + * and ucontext_t at the same time. + */ + long mc_onstack; /* XXX - sigcontext compat. */ + long mc_rdi; /* machine state (struct trapframe) */ + long mc_rsi; + long mc_rdx; + long mc_rcx; + long mc_r8; + long mc_r9; + long mc_rax; + long mc_rbx; + long mc_rbp; + long mc_r10; + long mc_r11; + long mc_r12; + long mc_r13; + long mc_r14; + long mc_r15; + long mc_trapno; + long mc_addr; + long mc_flags; + long mc_err; + long mc_rip; + long mc_cs; + long mc_rflags; + long mc_rsp; + long mc_ss; + + long mc_len; /* sizeof(mcontext_t) */ +#define _MC_FPFMT_NODEV 0x10000 /* device not present or configured */ +#define _MC_FPFMT_XMM 0x10002 + long mc_fpformat; +#define _MC_FPOWNED_NONE 0x20000 /* FP state not used */ +#define _MC_FPOWNED_FPU 0x20001 /* FP state came from FPU */ +#define _MC_FPOWNED_PCB 0x20002 /* FP state came from PCB */ + long mc_ownedfp; + /* + * See for the internals of mc_fpstate[]. + */ + long mc_fpstate[64]; + long mc_spare[8]; +}; + +struct ucontext { + /* + * Keep the order of the first two fields. Also, + * keep them the first two fields in the structure. + * This way we can have a union with struct + * sigcontext and ucontext_t. This allows us to + * support them both at the same time. + * note: the union is not defined, though. + */ + sigset_t uc_sigmask; + mcontext_t uc_mcontext; + + struct __ucontext *uc_link; + stack_t uc_stack; + int __spare__[8]; +}; diff --git a/lib/libtask/asm.S b/lib/libtask/asm.S new file mode 100644 index 00000000000..647dba5b0cf --- /dev/null +++ b/lib/libtask/asm.S @@ -0,0 +1,326 @@ +/* Copyright (c) 2005-2006 Russ Cox, MIT; see COPYRIGHT */ + +#if defined(__FreeBSD__) && defined(__i386__) && __FreeBSD__ < 5 +#define NEEDX86CONTEXT 1 +#define SET setmcontext +#define GET getmcontext +#endif + +#if defined(__OpenBSD__) && defined(__i386__) +#define NEEDX86CONTEXT 1 +#define SET setmcontext +#define GET getmcontext +#endif + +#if defined(__APPLE__) +#if defined(__i386__) +#define NEEDX86CONTEXT 1 +#define SET _setmcontext +#define GET _getmcontext +#elif defined(__x86_64__) +#define NEEDAMD64CONTEXT 1 +#define SET _setmcontext +#define GET _getmcontext +#else +#define NEEDPOWERCONTEXT 1 +#define SET __setmcontext +#define GET __getmcontext +#endif +#endif + +#if defined(__linux__) && defined(__arm__) +#define NEEDARMCONTEXT 1 +#define SET setmcontext +#define GET getmcontext +#endif + +#if defined(__linux__) && defined(__mips__) +#define NEEDMIPSCONTEXT 1 +#define SET setmcontext +#define GET getmcontext +#endif + +#ifdef NEEDX86CONTEXT +.globl SET +SET: + movl 4(%esp), %eax + + movl 8(%eax), %fs + movl 12(%eax), %es + movl 16(%eax), %ds + movl 76(%eax), %ss + movl 20(%eax), %edi + movl 24(%eax), %esi + movl 28(%eax), %ebp + movl 36(%eax), %ebx + movl 40(%eax), %edx + movl 44(%eax), %ecx + + movl 72(%eax), %esp + pushl 60(%eax) /* new %eip */ + movl 48(%eax), %eax + ret + +.globl GET +GET: + movl 4(%esp), %eax + + movl %fs, 8(%eax) + movl %es, 12(%eax) + movl %ds, 16(%eax) + movl %ss, 76(%eax) + movl %edi, 20(%eax) + movl %esi, 24(%eax) + movl %ebp, 28(%eax) + movl %ebx, 36(%eax) + movl %edx, 40(%eax) + movl %ecx, 44(%eax) + + movl $1, 48(%eax) /* %eax */ + movl (%esp), %ecx /* %eip */ + movl %ecx, 60(%eax) + leal 4(%esp), %ecx /* %esp */ + movl %ecx, 72(%eax) + + movl 44(%eax), %ecx /* restore %ecx */ + movl $0, %eax + ret +#endif + +#ifdef NEEDAMD64CONTEXT +.globl SET +SET: + movq 16(%rdi), %rsi + movq 24(%rdi), %rdx + movq 32(%rdi), %rcx + movq 40(%rdi), %r8 + movq 48(%rdi), %r9 + movq 56(%rdi), %rax + movq 64(%rdi), %rbx + movq 72(%rdi), %rbp + movq 80(%rdi), %r10 + movq 88(%rdi), %r11 + movq 96(%rdi), %r12 + movq 104(%rdi), %r13 + movq 112(%rdi), %r14 + movq 120(%rdi), %r15 + movq 184(%rdi), %rsp + pushq 160(%rdi) /* new %eip */ + movq 8(%rdi), %rdi + ret + +.globl GET +GET: + movq %rdi, 8(%rdi) + movq %rsi, 16(%rdi) + movq %rdx, 24(%rdi) + movq %rcx, 32(%rdi) + movq %r8, 40(%rdi) + movq %r9, 48(%rdi) + movq $1, 56(%rdi) /* %rax */ + movq %rbx, 64(%rdi) + movq %rbp, 72(%rdi) + movq %r10, 80(%rdi) + movq %r11, 88(%rdi) + movq %r12, 96(%rdi) + movq %r13, 104(%rdi) + movq %r14, 112(%rdi) + movq %r15, 120(%rdi) + + movq (%rsp), %rcx /* %rip */ + movq %rcx, 160(%rdi) + leaq 8(%rsp), %rcx /* %rsp */ + movq %rcx, 184(%rdi) + + movq 32(%rdi), %rcx /* restore %rcx */ + movq $0, %rax + ret +#endif + +#ifdef NEEDPOWERCONTEXT +/* get FPR and VR use flags with sc 0x7FF3 */ +/* get vsave with mfspr reg, 256 */ + +.text +.align 2 + +.globl GET +GET: /* xxx: instruction scheduling */ + mflr r0 + mfcr r5 + mfctr r6 + mfxer r7 + stw r0, 0*4(r3) + stw r5, 1*4(r3) + stw r6, 2*4(r3) + stw r7, 3*4(r3) + + stw r1, 4*4(r3) + stw r2, 5*4(r3) + li r5, 1 /* return value for setmcontext */ + stw r5, 6*4(r3) + + stw r13, (0+7)*4(r3) /* callee-save GPRs */ + stw r14, (1+7)*4(r3) /* xxx: block move */ + stw r15, (2+7)*4(r3) + stw r16, (3+7)*4(r3) + stw r17, (4+7)*4(r3) + stw r18, (5+7)*4(r3) + stw r19, (6+7)*4(r3) + stw r20, (7+7)*4(r3) + stw r21, (8+7)*4(r3) + stw r22, (9+7)*4(r3) + stw r23, (10+7)*4(r3) + stw r24, (11+7)*4(r3) + stw r25, (12+7)*4(r3) + stw r26, (13+7)*4(r3) + stw r27, (14+7)*4(r3) + stw r28, (15+7)*4(r3) + stw r29, (16+7)*4(r3) + stw r30, (17+7)*4(r3) + stw r31, (18+7)*4(r3) + + li r3, 0 /* return */ + blr + +.globl SET +SET: + lwz r13, (0+7)*4(r3) /* callee-save GPRs */ + lwz r14, (1+7)*4(r3) /* xxx: block move */ + lwz r15, (2+7)*4(r3) + lwz r16, (3+7)*4(r3) + lwz r17, (4+7)*4(r3) + lwz r18, (5+7)*4(r3) + lwz r19, (6+7)*4(r3) + lwz r20, (7+7)*4(r3) + lwz r21, (8+7)*4(r3) + lwz r22, (9+7)*4(r3) + lwz r23, (10+7)*4(r3) + lwz r24, (11+7)*4(r3) + lwz r25, (12+7)*4(r3) + lwz r26, (13+7)*4(r3) + lwz r27, (14+7)*4(r3) + lwz r28, (15+7)*4(r3) + lwz r29, (16+7)*4(r3) + lwz r30, (17+7)*4(r3) + lwz r31, (18+7)*4(r3) + + lwz r1, 4*4(r3) + lwz r2, 5*4(r3) + + lwz r0, 0*4(r3) + mtlr r0 + lwz r0, 1*4(r3) + mtcr r0 /* mtcrf 0xFF, r0 */ + lwz r0, 2*4(r3) + mtctr r0 + lwz r0, 3*4(r3) + mtxer r0 + + lwz r3, 6*4(r3) + blr +#endif + +#ifdef NEEDARMCONTEXT +.globl GET +GET: + str r1, [r0,#4] + str r2, [r0,#8] + str r3, [r0,#12] + str r4, [r0,#16] + str r5, [r0,#20] + str r6, [r0,#24] + str r7, [r0,#28] + str r8, [r0,#32] + str r9, [r0,#36] + str r10, [r0,#40] + str r11, [r0,#44] + str r12, [r0,#48] + str r13, [r0,#52] + str r14, [r0,#56] + /* store 1 as r0-to-restore */ + mov r1, #1 + str r1, [r0] + /* return 0 */ + mov r0, #0 + mov pc, lr + +.globl SET +SET: + ldr r1, [r0,#4] + ldr r2, [r0,#8] + ldr r3, [r0,#12] + ldr r4, [r0,#16] + ldr r5, [r0,#20] + ldr r6, [r0,#24] + ldr r7, [r0,#28] + ldr r8, [r0,#32] + ldr r9, [r0,#36] + ldr r10, [r0,#40] + ldr r11, [r0,#44] + ldr r12, [r0,#48] + ldr r13, [r0,#52] + ldr r14, [r0,#56] + ldr r0, [r0] + mov pc, lr +#endif + +#ifdef NEEDMIPSCONTEXT +.globl GET +GET: + sw $4, 24($4) + sw $5, 28($4) + sw $6, 32($4) + sw $7, 36($4) + + sw $16, 72($4) + sw $17, 76($4) + sw $18, 80($4) + sw $19, 84($4) + sw $20, 88($4) + sw $21, 92($4) + sw $22, 96($4) + sw $23, 100($4) + + sw $28, 120($4) /* gp */ + sw $29, 124($4) /* sp */ + sw $30, 128($4) /* fp */ + sw $31, 132($4) /* ra */ + + xor $2, $2, $2 + j $31 + nop + +.globl SET +SET: + lw $16, 72($4) + lw $17, 76($4) + lw $18, 80($4) + lw $19, 84($4) + lw $20, 88($4) + lw $21, 92($4) + lw $22, 96($4) + lw $23, 100($4) + + lw $28, 120($4) /* gp */ + lw $29, 124($4) /* sp */ + lw $30, 128($4) /* fp */ + + /* + * If we set $31 directly and j $31, + * we would loose the outer return address. + * Use a temporary register, then. + */ + lw $8, 132($4) /* ra */ + + /* bug: not setting the pc causes a bus error */ + lw $25, 132($4) /* pc */ + + lw $5, 28($4) + lw $6, 32($4) + lw $7, 36($4) + lw $4, 24($4) + + j $8 + nop +#endif diff --git a/lib/libtask/channel.c b/lib/libtask/channel.c new file mode 100644 index 00000000000..5d9d8cb3659 --- /dev/null +++ b/lib/libtask/channel.c @@ -0,0 +1,384 @@ +/* Copyright (c) 2005 Russ Cox, MIT; see COPYRIGHT */ + +#include "taskimpl.h" + +Channel* +chancreate(int elemsize, int bufsize) +{ + Channel *c; + + c = malloc(sizeof *c+bufsize*elemsize); + if(c == nil){ + fprint(2, "chancreate malloc: %r"); + exit(1); + } + memset(c, 0, sizeof *c); + c->elemsize = elemsize; + c->bufsize = bufsize; + c->nbuf = 0; + c->buf = (uchar*)(c+1); + return c; +} + +/* bug - work out races */ +void +chanfree(Channel *c) +{ + if(c == nil) + return; + free(c->name); + free(c->arecv.a); + free(c->asend.a); + free(c); +} + +static void +addarray(Altarray *a, Alt *alt) +{ + if(a->n == a->m){ + a->m += 16; + a->a = realloc(a->a, a->m*sizeof a->a[0]); + } + a->a[a->n++] = alt; +} + +static void +delarray(Altarray *a, int i) +{ + --a->n; + a->a[i] = a->a[a->n]; +} + +/* + * doesn't really work for things other than CHANSND and CHANRCV + * but is only used as arg to chanarray, which can handle it + */ +#define otherop(op) (CHANSND+CHANRCV-(op)) + +static Altarray* +chanarray(Channel *c, uint op) +{ + switch(op){ + default: + return nil; + case CHANSND: + return &c->asend; + case CHANRCV: + return &c->arecv; + } +} + +static int +altcanexec(Alt *a) +{ + Altarray *ar; + Channel *c; + + if(a->op == CHANNOP) + return 0; + c = a->c; + if(c->bufsize == 0){ + ar = chanarray(c, otherop(a->op)); + return ar && ar->n; + }else{ + switch(a->op){ + default: + return 0; + case CHANSND: + return c->nbuf < c->bufsize; + case CHANRCV: + return c->nbuf > 0; + } + } +} + +static void +altqueue(Alt *a) +{ + Altarray *ar; + + ar = chanarray(a->c, a->op); + addarray(ar, a); +} + +static void +altdequeue(Alt *a) +{ + int i; + Altarray *ar; + + ar = chanarray(a->c, a->op); + if(ar == nil){ + fprint(2, "bad use of altdequeue op=%d\n", a->op); + abort(); + } + + for(i=0; in; i++) + if(ar->a[i] == a){ + delarray(ar, i); + return; + } + fprint(2, "cannot find self in altdq\n"); + abort(); +} + +static void +altalldequeue(Alt *a) +{ + int i; + + for(i=0; a[i].op!=CHANEND && a[i].op!=CHANNOBLK; i++) + if(a[i].op != CHANNOP) + altdequeue(&a[i]); +} + +static void +amove(void *dst, void *src, uint n) +{ + if(dst){ + if(src == nil) + memset(dst, 0, n); + else + memmove(dst, src, n); + } +} + +/* + * Actually move the data around. There are up to three + * players: the sender, the receiver, and the channel itself. + * If the channel is unbuffered or the buffer is empty, + * data goes from sender to receiver. If the channel is full, + * the receiver removes some from the channel and the sender + * gets to put some in. + */ +static void +altcopy(Alt *s, Alt *r) +{ + Alt *t; + Channel *c; + uchar *cp; + + /* + * Work out who is sender and who is receiver + */ + if(s == nil && r == nil) + return; + assert(s != nil); + c = s->c; + if(s->op == CHANRCV){ + t = s; + s = r; + r = t; + } + assert(s==nil || s->op == CHANSND); + assert(r==nil || r->op == CHANRCV); + + /* + * Channel is empty (or unbuffered) - copy directly. + */ + if(s && r && c->nbuf == 0){ + amove(r->v, s->v, c->elemsize); + return; + } + + /* + * Otherwise it's always okay to receive and then send. + */ + if(r){ + cp = c->buf + c->off*c->elemsize; + amove(r->v, cp, c->elemsize); + --c->nbuf; + if(++c->off == c->bufsize) + c->off = 0; + } + if(s){ + cp = c->buf + (c->off+c->nbuf)%c->bufsize*c->elemsize; + amove(cp, s->v, c->elemsize); + ++c->nbuf; + } +} + +static void +altexec(Alt *a) +{ + int i; + Altarray *ar; + Alt *other; + Channel *c; + + c = a->c; + ar = chanarray(c, otherop(a->op)); + if(ar && ar->n){ + i = rand()%ar->n; + other = ar->a[i]; + altcopy(a, other); + altalldequeue(other->xalt); + other->xalt[0].xalt = other; + taskready(other->task); + }else + altcopy(a, nil); +} + +#define dbgalt 0 +int +chanalt(Alt *a) +{ + int i, j, ncan, n, canblock; + Channel *c; + Task *t; + + needstack(512); + for(i=0; a[i].op != CHANEND && a[i].op != CHANNOBLK; i++) + ; + n = i; + canblock = a[i].op == CHANEND; + + t = taskrunning; + for(i=0; iname) print("%s", c->name); else print("%p", c); } + if(altcanexec(&a[i])){ +if(dbgalt) print("*"); + ncan++; + } + } + if(ncan){ + j = rand()%ncan; + for(i=0; i %c:", "esrnb"[a[i].op]); +if(c->name) print("%s", c->name); else print("%p", c); +print("\n"); +} + altexec(&a[i]); + return i; + } + } + } + } +if(dbgalt)print("\n"); + + if(!canblock) + return -1; + + for(i=0; iuc_stack.ss_sp+ucp->uc_stack.ss_size/sizeof(ulong); + sp = tos - 16; + ucp->mc.pc = (long)func; + ucp->mc.sp = (long)sp; + va_start(arg, argc); + ucp->mc.r3 = va_arg(arg, long); + va_end(arg); +} +#endif + +#ifdef NEEDX86MAKECONTEXT +void +makecontext(ucontext_t *ucp, void (*func)(void), int argc, ...) +{ + int *sp; + + sp = (int*)ucp->uc_stack.ss_sp+ucp->uc_stack.ss_size/4; + sp -= argc; + sp = (void*)((uintptr_t)sp - (uintptr_t)sp%16); /* 16-align for OS X */ + memmove(sp, &argc+1, argc*sizeof(int)); + + *--sp = 0; /* return address */ + ucp->uc_mcontext.mc_eip = (long)func; + ucp->uc_mcontext.mc_esp = (int)sp; +} +#endif + +#ifdef NEEDAMD64MAKECONTEXT +void +makecontext(ucontext_t *ucp, void (*func)(void), int argc, ...) +{ + long *sp; + va_list va; + + memset(&ucp->uc_mcontext, 0, sizeof ucp->uc_mcontext); + if(argc != 2) +#ifdef EMACS + abort (); +#else + *(int*)0 = 0; +#endif + va_start(va, argc); + ucp->uc_mcontext.mc_rdi = va_arg(va, int); + ucp->uc_mcontext.mc_rsi = va_arg(va, int); + va_end(va); + sp = (long*)ucp->uc_stack.ss_sp+ucp->uc_stack.ss_size/sizeof(long); + sp -= argc; + sp = (void*)((uintptr_t)sp - (uintptr_t)sp%16); /* 16-align for OS X */ + *--sp = 0; /* return address */ + ucp->uc_mcontext.mc_rip = (long)func; + ucp->uc_mcontext.mc_rsp = (long)sp; +} +#endif + +#ifdef NEEDARMMAKECONTEXT +void +makecontext(ucontext_t *uc, void (*fn)(void), int argc, ...) +{ + int i, *sp; + va_list arg; + + sp = (int*)uc->uc_stack.ss_sp+uc->uc_stack.ss_size/4; + va_start(arg, argc); + for(i=0; i<4 && iuc_mcontext.gregs[i] = va_arg(arg, uint); + va_end(arg); + uc->uc_mcontext.gregs[13] = (uint)sp; + uc->uc_mcontext.gregs[14] = (uint)fn; +} +#endif + +#ifdef NEEDMIPSMAKECONTEXT +void +makecontext(ucontext_t *uc, void (*fn)(void), int argc, ...) +{ + int i, *sp; + va_list arg; + + va_start(arg, argc); + sp = (int*)uc->uc_stack.ss_sp+uc->uc_stack.ss_size/4; + for(i=0; i<4 && iuc_mcontext.mc_regs[i+4] = va_arg(arg, int); + va_end(arg); + uc->uc_mcontext.mc_regs[29] = (int)sp; + uc->uc_mcontext.mc_regs[31] = (int)fn; +} +#endif + +#ifdef NEEDSWAPCONTEXT +int +swapcontext(ucontext_t *oucp, const ucontext_t *ucp) +{ + if(getcontext(oucp) == 0) + setcontext(ucp); + return 0; +} +#endif diff --git a/lib/libtask/fd.c b/lib/libtask/fd.c new file mode 100644 index 00000000000..8be99b927d1 --- /dev/null +++ b/lib/libtask/fd.c @@ -0,0 +1,201 @@ +#include "taskimpl.h" +#include +#include + +enum +{ + MAXFD = 1024 +}; + +static struct pollfd pollfd[MAXFD]; +static Task *polltask[MAXFD]; +static int npollfd; +static int startedfdtask; +static Tasklist sleeping; +static int sleepingcounted; +static uvlong nsec(void); + +void +fdtask(void *v) +{ + int i, ms; + Task *t; + uvlong now; + + tasksystem(); + taskname("fdtask"); + for(;;){ + /* let everyone else run */ + while(taskyield() > 0) + ; + /* we're the only one runnable - poll for i/o */ + errno = 0; + taskstate("poll"); + if((t=sleeping.head) == nil) + ms = -1; + else{ + /* sleep at most 5s */ + now = nsec(); + if(now >= t->alarmtime) + ms = 0; + else if(now+5*1000*1000*1000LL >= t->alarmtime) + ms = (t->alarmtime - now)/1000000; + else + ms = 5000; + } + if(poll(pollfd, npollfd, ms) < 0){ + if(errno == EINTR) + continue; + fprint(2, "poll: %s\n", strerror(errno)); + taskexitall(0); + } + + /* wake up the guys who deserve it */ + for(i=0; i= t->alarmtime){ + deltask(&sleeping, t); + if(!t->system && --sleepingcounted == 0) + taskcount--; + taskready(t); + } + } +} + +uint +taskdelay(uint ms) +{ + uvlong when, now; + Task *t; + + if(!startedfdtask){ + startedfdtask = 1; + taskcreate(fdtask, 0, 32768); + } + + now = nsec(); + when = now+(uvlong)ms*1000000; + for(t=sleeping.head; t!=nil && t->alarmtime < when; t=t->next) + ; + + if(t){ + taskrunning->prev = t->prev; + taskrunning->next = t; + }else{ + taskrunning->prev = sleeping.tail; + taskrunning->next = nil; + } + + t = taskrunning; + t->alarmtime = when; + if(t->prev) + t->prev->next = t; + else + sleeping.head = t; + if(t->next) + t->next->prev = t; + else + sleeping.tail = t; + + if(!t->system && sleepingcounted++ == 0) + taskcount++; + taskswitch(); + + return (nsec() - now)/1000000; +} + +void +fdwait(int fd, int rw) +{ + int bits; + + if(!startedfdtask){ + startedfdtask = 1; + taskcreate(fdtask, 0, 32768); + } + + if(npollfd >= MAXFD){ + fprint(2, "too many poll file descriptors\n"); + abort(); + } + + taskstate("fdwait for %s", rw=='r' ? "read" : rw=='w' ? "write" : "error"); + bits = 0; + switch(rw){ + case 'r': + bits |= POLLIN; + break; + case 'w': + bits |= POLLOUT; + break; + } + + polltask[npollfd] = taskrunning; + pollfd[npollfd].fd = fd; + pollfd[npollfd].events = bits; + pollfd[npollfd].revents = 0; + npollfd++; + taskswitch(); +} + +/* Like fdread but always calls fdwait before reading. */ +int +fdread1(int fd, void *buf, int n) +{ + int m; + + do + fdwait(fd, 'r'); + while((m = read(fd, buf, n)) < 0 && errno == EAGAIN); + return m; +} + +int +fdread(int fd, void *buf, int n) +{ + int m; + + while((m=read(fd, buf, n)) < 0 && errno == EAGAIN) + fdwait(fd, 'r'); + return m; +} + +int +fdwrite(int fd, void *buf, int n) +{ + int m, tot; + + for(tot=0; tot +#include +#include +#include +#include +#include + +enum +{ + STACK = 32768 +}; + +char *server; +char *url; + +void fetchtask(void*); + +void +taskmain(int argc, char **argv) +{ + int i, n; + + if(argc != 4){ + fprintf(stderr, "usage: httpload n server url\n"); + taskexitall(1); + } + n = atoi(argv[1]); + server = argv[2]; + url = argv[3]; + + for(i=0; i 1) + ; + sleep(1); + } +} + +void +fetchtask(void *v) +{ + int fd, n; + char buf[512]; + + fprintf(stderr, "starting...\n"); + for(;;){ + if((fd = netdial(TCP, server, 80)) < 0){ + fprintf(stderr, "dial %s: %s (%s)\n", server, strerror(errno), taskgetstate()); + continue; + } + snprintf(buf, sizeof buf, "GET %s HTTP/1.0\r\nHost: %s\r\n\r\n", url, server); + fdwrite(fd, buf, strlen(buf)); + while((n = fdread(fd, buf, sizeof buf)) > 0) + ; + close(fd); + write(1, ".", 1); + } +} diff --git a/lib/libtask/makesun b/lib/libtask/makesun new file mode 100755 index 00000000000..62430488ce7 --- /dev/null +++ b/lib/libtask/makesun @@ -0,0 +1,22 @@ +#!/bin/sh + +case "x$CC" in +x|xcc) + CC=cc + CFLAGS="-mt -g -O -c -xCC -D__sun__ -I." + ;; +xgcc) + CC=gcc + CFLAGS="-Wall -c -I." + ;; +*) + echo 'unknown $CC' + exit 1 +esac + +u=`uname` +v=`uname -r` +s=`echo $u$v | tr '. ' '__'` +CFLAGS="$CFLAGS -D__${s}__" + +make "CC=$CC" "CFLAGS=$CFLAGS" "ASM=" "TCPLIBS=-lsocket -lnsl" diff --git a/lib/libtask/mips-ucontext.h b/lib/libtask/mips-ucontext.h new file mode 100644 index 00000000000..aa6f41e1461 --- /dev/null +++ b/lib/libtask/mips-ucontext.h @@ -0,0 +1,77 @@ +typedef struct mcontext mcontext_t; +typedef struct ucontext ucontext_t; + +extern int swapcontext(ucontext_t*, const ucontext_t*); +extern void makecontext(ucontext_t*, void(*)(), int, ...); + +/* + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Ralph Campbell. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)ucontext.h 8.1 (Berkeley) 6/10/93 + * JNPR: ucontext.h,v 1.2 2007/08/09 11:23:32 katta + * $FreeBSD: src/sys/mips/include/ucontext.h,v 1.2 2010/01/10 19:50:24 imp Exp $ + */ + +struct mcontext { + /* + * These fields must match the corresponding fields in struct + * sigcontext which follow 'sc_mask'. That way we can support + * struct sigcontext and ucontext_t at the same time. + */ + int mc_onstack; /* sigstack state to restore */ + int mc_pc; /* pc at time of signal */ + int mc_regs[32]; /* processor regs 0 to 31 */ + int sr; /* status register */ + int mullo, mulhi; /* mullo and mulhi registers... */ + int mc_fpused; /* fp has been used */ + int mc_fpregs[33]; /* fp regs 0 to 31 and csr */ + int mc_fpc_eir; /* fp exception instruction reg */ + void *mc_tls; /* pointer to TLS area */ + int __spare__[8]; /* XXX reserved */ +}; + +struct ucontext { + /* + * Keep the order of the first two fields. Also, + * keep them the first two fields in the structure. + * This way we can have a union with struct + * sigcontext and ucontext_t. This allows us to + * support them both at the same time. + * note: the union is not defined, though. + */ + sigset_t uc_sigmask; + mcontext_t uc_mcontext; + + struct __ucontext *uc_link; + stack_t uc_stack; + int uc_flags; + int __spare__[4]; +}; diff --git a/lib/libtask/net.c b/lib/libtask/net.c new file mode 100644 index 00000000000..179b9b00912 --- /dev/null +++ b/lib/libtask/net.c @@ -0,0 +1,202 @@ +#include "taskimpl.h" +#include +#include +#include +#include +#include +#include + +int +netannounce(int istcp, char *server, int port) +{ + int fd, n, proto; + struct sockaddr_in sa; + socklen_t sn; + uint32_t ip; + + taskstate("netannounce"); + proto = istcp ? SOCK_STREAM : SOCK_DGRAM; + memset(&sa, 0, sizeof sa); + sa.sin_family = AF_INET; + if(server != nil && strcmp(server, "*") != 0){ + if(netlookup(server, &ip) < 0){ + taskstate("netlookup failed"); + return -1; + } + memmove(&sa.sin_addr, &ip, 4); + } + sa.sin_port = htons(port); + if((fd = socket(AF_INET, proto, 0)) < 0){ + taskstate("socket failed"); + return -1; + } + + /* set reuse flag for tcp */ + if(istcp && getsockopt(fd, SOL_SOCKET, SO_TYPE, (void*)&n, &sn) >= 0){ + n = 1; + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char*)&n, sizeof n); + } + + if(bind(fd, (struct sockaddr*)&sa, sizeof sa) < 0){ + taskstate("bind failed"); + close(fd); + return -1; + } + + if(proto == SOCK_STREAM) + listen(fd, 16); + + fdnoblock(fd); + taskstate("netannounce succeeded"); + return fd; +} + +int +netaccept(int fd, char *server, int *port) +{ + int cfd, one; + struct sockaddr_in sa; + uchar *ip; + socklen_t len; + + fdwait(fd, 'r'); + + taskstate("netaccept"); + len = sizeof sa; + if((cfd = accept(fd, (void*)&sa, &len)) < 0){ + taskstate("accept failed"); + return -1; + } + if(server){ + ip = (uchar*)&sa.sin_addr; + snprint(server, 16, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); + } + if(port) + *port = ntohs(sa.sin_port); + fdnoblock(cfd); + one = 1; + setsockopt(cfd, IPPROTO_TCP, TCP_NODELAY, (char*)&one, sizeof one); + taskstate("netaccept succeeded"); + return cfd; +} + +#define CLASS(p) ((*(unsigned char*)(p))>>6) +static int +parseip(char *name, uint32_t *ip) +{ + unsigned char addr[4]; + char *p; + int i, x; + + p = name; + for(i=0; i<4 && *p; i++){ + x = strtoul(p, &p, 0); + if(x < 0 || x >= 256) + return -1; + if(*p != '.' && *p != 0) + return -1; + if(*p == '.') + p++; + addr[i] = x; + } + + switch(CLASS(addr)){ + case 0: + case 1: + if(i == 3){ + addr[3] = addr[2]; + addr[2] = addr[1]; + addr[1] = 0; + }else if(i == 2){ + addr[3] = addr[1]; + addr[2] = 0; + addr[1] = 0; + }else if(i != 4) + return -1; + break; + case 2: + if(i == 3){ + addr[3] = addr[2]; + addr[2] = 0; + }else if(i != 4) + return -1; + break; + } + *ip = *(uint32_t*)addr; + return 0; +} + +int +netlookup(char *name, uint32_t *ip) +{ + struct hostent *he; + + if(parseip(name, ip) >= 0) + return 0; + + /* BUG - Name resolution blocks. Need a non-blocking DNS. */ + taskstate("netlookup"); + if((he = gethostbyname(name)) != 0){ + *ip = *(uint32_t*)he->h_addr; + taskstate("netlookup succeeded"); + return 0; + } + + taskstate("netlookup failed"); + return -1; +} + +int +netdial(int istcp, char *server, int port) +{ + int proto, fd, n; + uint32_t ip; + struct sockaddr_in sa; + socklen_t sn; + + if(netlookup(server, &ip) < 0) + return -1; + + taskstate("netdial"); + proto = istcp ? SOCK_STREAM : SOCK_DGRAM; + if((fd = socket(AF_INET, proto, 0)) < 0){ + taskstate("socket failed"); + return -1; + } + fdnoblock(fd); + + /* for udp */ + if(!istcp){ + n = 1; + setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &n, sizeof n); + } + + /* start connecting */ + memset(&sa, 0, sizeof sa); + memmove(&sa.sin_addr, &ip, 4); + sa.sin_family = AF_INET; + sa.sin_port = htons(port); + if(connect(fd, (struct sockaddr*)&sa, sizeof sa) < 0 && errno != EINPROGRESS){ + taskstate("connect failed"); + close(fd); + return -1; + } + + /* wait for finish */ + fdwait(fd, 'w'); + sn = sizeof sa; + if(getpeername(fd, (struct sockaddr*)&sa, &sn) >= 0){ + taskstate("connect succeeded"); + return fd; + } + + /* report error */ + sn = sizeof n; + getsockopt(fd, SOL_SOCKET, SO_ERROR, (void*)&n, &sn); + if(n == 0) + n = ECONNREFUSED; + close(fd); + taskstate("connect failed"); + errno = n; + return -1; +} diff --git a/lib/libtask/power-ucontext.h b/lib/libtask/power-ucontext.h new file mode 100644 index 00000000000..ad6cdb39f84 --- /dev/null +++ b/lib/libtask/power-ucontext.h @@ -0,0 +1,36 @@ +#define setcontext(u) _setmcontext(&(u)->mc) +#define getcontext(u) _getmcontext(&(u)->mc) +typedef struct mcontext mcontext_t; +typedef struct ucontext ucontext_t; +struct mcontext +{ + ulong pc; /* lr */ + ulong cr; /* mfcr */ + ulong ctr; /* mfcr */ + ulong xer; /* mfcr */ + ulong sp; /* callee saved: r1 */ + ulong toc; /* callee saved: r2 */ + ulong r3; /* first arg to function, return register: r3 */ + ulong gpr[19]; /* callee saved: r13-r31 */ +/* +// XXX: currently do not save vector registers or floating-point state +// ulong pad; +// uvlong fpr[18]; / * callee saved: f14-f31 * / +// ulong vr[4*12]; / * callee saved: v20-v31, 256-bits each * / +*/ +}; + +struct ucontext +{ + struct { + void *ss_sp; + uint ss_size; + } uc_stack; + sigset_t uc_sigmask; + mcontext_t mc; +}; + +void makecontext(ucontext_t*, void(*)(void), int, ...); +int swapcontext(ucontext_t*, const ucontext_t*); +int _getmcontext(mcontext_t*); +void _setmcontext(const mcontext_t*); diff --git a/lib/libtask/primes.c b/lib/libtask/primes.c new file mode 100644 index 00000000000..e9aa5f52334 --- /dev/null +++ b/lib/libtask/primes.c @@ -0,0 +1,61 @@ +/* Copyright (c) 2005 Russ Cox, MIT; see COPYRIGHT */ + +#include +#include +#include +#include + +int quiet; +int goal; +int buffer; + +void +primetask(void *arg) +{ + Channel *c, *nc; + int p, i; + c = arg; + + p = chanrecvul(c); + if(p > goal) + taskexitall(0); + if(!quiet) + printf("%d\n", p); + nc = chancreate(sizeof(unsigned long), buffer); + taskcreate(primetask, nc, 32768); + for(;;){ + i = chanrecvul(c); + if(i%p) + chansendul(nc, i); + } +} + +void +taskmain(int argc, char **argv) +{ + int i; + Channel *c; + + if(argc>1) + goal = atoi(argv[1]); + else + goal = 100; + printf("goal=%d\n", goal); + + c = chancreate(sizeof(unsigned long), buffer); + taskcreate(primetask, c, 32768); + for(i=2;; i++) + chansendul(c, i); +} + +void* +emalloc(unsigned long n) +{ + return calloc(n ,1); +} + +long +lrand(void) +{ + return rand(); +} diff --git a/lib/libtask/print.c b/lib/libtask/print.c new file mode 100644 index 00000000000..485bb84a4a3 --- /dev/null +++ b/lib/libtask/print.c @@ -0,0 +1,249 @@ +/* Copyright (c) 2004 Russ Cox. See COPYRIGHT. */ + +#include "taskimpl.h" +#include /* for strerror! */ + +/* + * Stripped down print library. Plan 9 interface, new code. + */ + +enum +{ + FlagLong = 1<<0, + FlagLongLong = 1<<1, + FlagUnsigned = 1<<2, +}; + +static char* +printstr(char *dst, char *edst, char *s, int size) +{ + int l, n, sign; + + sign = 1; + if(size < 0){ + size = -size; + sign = -1; + } + if(dst >= edst) + return dst; + l = strlen(s); + n = l; + if(n < size) + n = size; + if(n >= edst-dst) + n = (edst-dst)-1; + if(l > n) + l = n; + if(sign < 0){ + memmove(dst, s, l); + if(n-l) + memset(dst+l, ' ', n-l); + }else{ + if(n-l) + memset(dst, ' ', n-l); + memmove(dst+n-l, s, l); + } + return dst+n; +} + +char* +vseprint(char *dst, char *edst, char *fmt, va_list arg) +{ + int fl, size, sign, base; + char *p, *w; + char cbuf[2]; + + w = dst; + for(p=fmt; *p && wowner == nil){ + l->owner = taskrunning; + return 1; + } + if(!block) + return 0; + addtask(&l->waiting, taskrunning); + taskstate("qlock"); + taskswitch(); + if(l->owner != taskrunning){ + fprint(2, "qlock: owner=%p self=%p oops\n", l->owner, taskrunning); + abort(); + } + return 1; +} + +void +qlock(QLock *l) +{ + _qlock(l, 1); +} + +int +canqlock(QLock *l) +{ + return _qlock(l, 0); +} + +void +qunlock(QLock *l) +{ + Task *ready; + + if(l->owner == 0){ + fprint(2, "qunlock: owner=0\n"); + abort(); + } + if((l->owner = ready = l->waiting.head) != nil){ + deltask(&l->waiting, ready); + taskready(ready); + } +} + +static int +_rlock(RWLock *l, int block) +{ + if(l->writer == nil && l->wwaiting.head == nil){ + l->readers++; + return 1; + } + if(!block) + return 0; + addtask(&l->rwaiting, taskrunning); + taskstate("rlock"); + taskswitch(); + return 1; +} + +void +rlock(RWLock *l) +{ + _rlock(l, 1); +} + +int +canrlock(RWLock *l) +{ + return _rlock(l, 0); +} + +static int +_wlock(RWLock *l, int block) +{ + if(l->writer == nil && l->readers == 0){ + l->writer = taskrunning; + return 1; + } + if(!block) + return 0; + addtask(&l->wwaiting, taskrunning); + taskstate("wlock"); + taskswitch(); + return 1; +} + +void +wlock(RWLock *l) +{ + _wlock(l, 1); +} + +int +canwlock(RWLock *l) +{ + return _wlock(l, 0); +} + +void +runlock(RWLock *l) +{ + Task *t; + + if(--l->readers == 0 && (t = l->wwaiting.head) != nil){ + deltask(&l->wwaiting, t); + l->writer = t; + taskready(t); + } +} + +void +wunlock(RWLock *l) +{ + Task *t; + + if(l->writer == nil){ + fprint(2, "wunlock: not locked\n"); + abort(); + } + l->writer = nil; + if(l->readers != 0){ + fprint(2, "wunlock: readers\n"); + abort(); + } + while((t = l->rwaiting.head) != nil){ + deltask(&l->rwaiting, t); + l->readers++; + taskready(t); + } + if(l->readers == 0 && (t = l->wwaiting.head) != nil){ + deltask(&l->wwaiting, t); + l->writer = t; + taskready(t); + } +} diff --git a/lib/libtask/rendez.c b/lib/libtask/rendez.c new file mode 100644 index 00000000000..d27fde7afbf --- /dev/null +++ b/lib/libtask/rendez.c @@ -0,0 +1,45 @@ +#include "taskimpl.h" + +/* + * sleep and wakeup + */ +void +tasksleep(Rendez *r) +{ + addtask(&r->waiting, taskrunning); + if(r->l) + qunlock(r->l); + taskstate("sleep"); + taskswitch(); + if(r->l) + qlock(r->l); +} + +static int +_taskwakeup(Rendez *r, int all) +{ + int i; + Task *t; + + for(i=0;; i++){ + if(i==1 && !all) + break; + if((t = r->waiting.head) == nil) + break; + deltask(&r->waiting, t); + taskready(t); + } + return i; +} + +int +taskwakeup(Rendez *r) +{ + return _taskwakeup(r, 0); +} + +int +taskwakeupall(Rendez *r) +{ + return _taskwakeup(r, 1); +} diff --git a/lib/libtask/task.c b/lib/libtask/task.c new file mode 100644 index 00000000000..ebb868183b3 --- /dev/null +++ b/lib/libtask/task.c @@ -0,0 +1,424 @@ +/* Copyright (c) 2005 Russ Cox, MIT; see COPYRIGHT */ + +#include "taskimpl.h" +#include +#include + +int taskdebuglevel; +int taskcount; +int tasknswitch; +int taskexitval; +Task *taskrunning; + +Context taskschedcontext; +Tasklist taskrunqueue; + +Task **alltask; +int nalltask; + +static char *argv0; +static void contextswitch(Context *from, Context *to); + +static void +taskdebug(char *fmt, ...) +{ + va_list arg; + char buf[128]; + Task *t; + char *p; + static int fd = -1; + +return; + va_start(arg, fmt); + vfprint(1, fmt, arg); + va_end(arg); +return; + + if(fd < 0){ + p = strrchr(argv0, '/'); + if(p) + p++; + else + p = argv0; + snprint(buf, sizeof buf, "/tmp/%s.tlog", p); + if((fd = open(buf, O_CREAT|O_WRONLY, 0666)) < 0) + fd = open("/dev/null", O_WRONLY); + } + + va_start(arg, fmt); + vsnprint(buf, sizeof buf, fmt, arg); + va_end(arg); + t = taskrunning; + if(t) + fprint(fd, "%d.%d: %s\n", getpid(), t->id, buf); + else + fprint(fd, "%d._: %s\n", getpid(), buf); +} + +static void +taskstart(uint y, uint x) +{ + Task *t; + ulong z; + + z = x<<16; /* hide undefined 32-bit shift from 32-bit compilers */ + z <<= 16; + z |= y; + t = (Task*)z; + +//print("taskstart %p\n", t); + t->startfn(t->startarg); +//print("taskexits %p\n", t); + taskexit(0); +//print("not reacehd\n"); +} + +static int taskidgen; + +static Task* +taskalloc(void (*fn)(void*), void *arg, uint stack) +{ + Task *t; + sigset_t zero; + uint x, y; + ulong z; + + /* allocate the task and stack together */ + t = malloc(sizeof *t+stack); + if(t == nil){ + fprint(2, "taskalloc malloc: %r\n"); + abort(); + } + memset(t, 0, sizeof *t); + t->stk = (uchar*)(t+1); + t->stksize = stack; + t->id = ++taskidgen; + t->startfn = fn; + t->startarg = arg; + +#ifdef EMACS + init_emacs_lisp_context (t->id == 1, &t->context.ec); +#endif + + /* do a reasonable initialization */ + memset(&t->context.uc, 0, sizeof t->context.uc); + sigemptyset(&zero); + sigprocmask(SIG_BLOCK, &zero, &t->context.uc.uc_sigmask); + + /* must initialize with current context */ + if(getcontext(&t->context.uc) < 0){ + fprint(2, "getcontext: %r\n"); + abort(); + } + + /* call makecontext to do the real work. */ + /* leave a few words open on both ends */ + t->context.uc.uc_stack.ss_sp = t->stk+8; + t->context.uc.uc_stack.ss_size = t->stksize-64; +#if defined(__sun__) && !defined(__MAKECONTEXT_V2_SOURCE) /* sigh */ +#warning "doing sun thing" + /* can avoid this with __MAKECONTEXT_V2_SOURCE but only on SunOS 5.9 */ + t->context.uc.uc_stack.ss_sp = + (char*)t->context.uc.uc_stack.ss_sp + +t->context.uc.uc_stack.ss_size; +#endif + /* + * All this magic is because you have to pass makecontext a + * function that takes some number of word-sized variables, + * and on 64-bit machines pointers are bigger than words. + */ +//print("make %p\n", t); + z = (ulong)t; + y = z; + z >>= 16; /* hide undefined 32-bit shift from 32-bit compilers */ + x = z>>16; + makecontext(&t->context.uc, (void(*)())taskstart, 2, y, x); + + return t; +} + +int +taskcreate(void (*fn)(void*), void *arg, uint stack) +{ + int id; + Task *t; + + t = taskalloc(fn, arg, stack); + taskcount++; + id = t->id; + if(nalltask%64 == 0){ + alltask = realloc(alltask, (nalltask+64)*sizeof(alltask[0])); + if(alltask == nil){ + fprint(2, "out of memory\n"); + abort(); + } + } + t->alltaskslot = nalltask; + alltask[nalltask++] = t; + taskready(t); + return id; +} + +void +tasksystem(void) +{ + if(!taskrunning->system){ + taskrunning->system = 1; + --taskcount; + } +} + +void +taskswitch(void) +{ + needstack(0); + contextswitch(&taskrunning->context, &taskschedcontext); +} + +void +taskready(Task *t) +{ + t->ready = 1; + addtask(&taskrunqueue, t); +} + +int +taskyield(void) +{ + int n; + + n = tasknswitch; + taskready(taskrunning); + taskstate("yield"); + taskswitch(); + return tasknswitch - n - 1; +} + +int +anyready(void) +{ + return taskrunqueue.head != nil; +} + +void +taskexitall(int val) +{ + exit(val); +} + +void +taskexit(int val) +{ + taskexitval = val; + taskrunning->exiting = 1; + taskswitch(); +} + +static void +contextswitch(Context *from, Context *to) +{ +#ifdef EMACS + switch_emacs_lisp_context (&from->ec, &to->ec); +#endif + if(swapcontext(&from->uc, &to->uc) < 0){ + fprint(2, "swapcontext failed: %r\n"); + assert(0); + } +} + +static void +taskscheduler(void) +{ + int i; + Task *t; + + taskdebug("scheduler enter"); + for(;;){ + if(taskcount == 0) + exit(taskexitval); + t = taskrunqueue.head; + if(t == nil){ + fprint(2, "no runnable tasks! %d tasks stalled\n", taskcount); + exit(1); + } + deltask(&taskrunqueue, t); + t->ready = 0; + taskrunning = t; + tasknswitch++; + taskdebug("run %d (%s)", t->id, t->name); + contextswitch(&taskschedcontext, &t->context); +//print("back in scheduler\n"); + taskrunning = nil; + if(t->exiting){ + if(!t->system) + taskcount--; + i = t->alltaskslot; + alltask[i] = alltask[--nalltask]; + alltask[i]->alltaskslot = i; + free(t); + } + } +} + +void** +taskdata(void) +{ + return &taskrunning->udata; +} + +/* + * debugging + */ +void +taskname(char *fmt, ...) +{ + va_list arg; + Task *t; + + t = taskrunning; + va_start(arg, fmt); + vsnprint(t->name, sizeof t->name, fmt, arg); + va_end(arg); +} + +char* +taskgetname(void) +{ + return taskrunning->name; +} + +void +taskstate(char *fmt, ...) +{ + va_list arg; + Task *t; + + t = taskrunning; + va_start(arg, fmt); + vsnprint(t->state, sizeof t->name, fmt, arg); + va_end(arg); +} + +char* +taskgetstate(void) +{ + return taskrunning->state; +} + +void +needstack(int n) +{ + Task *t; + + t = taskrunning; + + if((char*)&t <= (char*)t->stk + || (char*)&t - (char*)t->stk < 256+n){ + fprint(2, "task stack overflow: &t=%p tstk=%p n=%d\n", &t, t->stk, 256+n); + abort(); + } +} + +static void +taskinfo(int s) +{ + int i; + Task *t; + char *extra; + + fprint(2, "task list:\n"); + for(i=0; iready) + extra = " (ready)"; + else + extra = ""; + fprint(2, "%6d%c %-20s %s%s\n", + t->id, t->system ? 's' : ' ', + t->name, t->state, extra); + } +} + +/* + * startup + */ + +static int taskargc; +static char **taskargv; +int mainstacksize; + +static void +taskmainstart(void *v) +{ + taskname("taskmain"); + taskmain(taskargc, taskargv); +} + +int +main(int argc, char **argv) +{ + struct sigaction sa, osa; + + memset(&sa, 0, sizeof sa); + sa.sa_handler = taskinfo; + sa.sa_flags = SA_RESTART; + sigaction(SIGQUIT, &sa, &osa); + +#ifdef SIGINFO + sigaction(SIGINFO, &sa, &osa); +#endif + + argv0 = argv[0]; + taskargc = argc; + taskargv = argv; + + if(mainstacksize == 0) + mainstacksize = 256*1024; +#ifdef EMACS + init_emacs_main_task (); + mainstacksize = 0x400000; +#endif + taskcreate(taskmainstart, nil, mainstacksize); + taskscheduler(); + fprint(2, "taskscheduler returned in main!\n"); + abort(); + return 0; +} + +/* + * hooray for linked lists + */ +void +addtask(Tasklist *l, Task *t) +{ + if(l->tail){ + l->tail->next = t; + t->prev = l->tail; + }else{ + l->head = t; + t->prev = nil; + } + l->tail = t; + t->next = nil; +} + +void +deltask(Tasklist *l, Task *t) +{ + if(t->prev) + t->prev->next = t->next; + else + l->head = t->next; + if(t->next) + t->next->prev = t->prev; + else + l->tail = t->prev; +} + +unsigned int +taskid(void) +{ + return taskrunning->id; +} diff --git a/lib/libtask/task.h b/lib/libtask/task.h new file mode 100644 index 00000000000..d7fd4ffdfde --- /dev/null +++ b/lib/libtask/task.h @@ -0,0 +1,181 @@ +/* Copyright (c) 2005 Russ Cox, MIT; see COPYRIGHT */ + +#ifndef _TASK_H_ +#define _TASK_H_ 1 + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +/* + * basic procs and threads + */ + +typedef struct Task Task; +typedef struct Tasklist Tasklist; + +int anyready(void); +int taskcreate(void (*f)(void *arg), void *arg, unsigned int stacksize); +void taskexit(int); +void taskexitall(int); +void taskmain(int argc, char *argv[]); +int taskyield(void); +void** taskdata(void); +void needstack(int); +void taskname(char*, ...); +void taskstate(char*, ...); +char* taskgetname(void); +char* taskgetstate(void); +void tasksystem(void); +unsigned int taskdelay(unsigned int); +unsigned int taskid(void); + +struct Tasklist /* used internally */ +{ + Task *head; + Task *tail; +}; + +/* + * queuing locks + */ +typedef struct QLock QLock; +struct QLock +{ + Task *owner; + Tasklist waiting; +}; + +void qlock(QLock*); +int canqlock(QLock*); +void qunlock(QLock*); + +/* + * reader-writer locks + */ +typedef struct RWLock RWLock; +struct RWLock +{ + int readers; + Task *writer; + Tasklist rwaiting; + Tasklist wwaiting; +}; + +void rlock(RWLock*); +int canrlock(RWLock*); +void runlock(RWLock*); + +void wlock(RWLock*); +int canwlock(RWLock*); +void wunlock(RWLock*); + +/* + * sleep and wakeup (condition variables) + */ +typedef struct Rendez Rendez; + +struct Rendez +{ + QLock *l; + Tasklist waiting; +}; + +void tasksleep(Rendez*); +int taskwakeup(Rendez*); +int taskwakeupall(Rendez*); + +/* + * channel communication + */ +typedef struct Alt Alt; +typedef struct Altarray Altarray; +typedef struct Channel Channel; + +enum +{ + CHANEND, + CHANSND, + CHANRCV, + CHANNOP, + CHANNOBLK, +}; + +struct Alt +{ + Channel *c; + void *v; + unsigned int op; + Task *task; + Alt *xalt; +}; + +struct Altarray +{ + Alt **a; + unsigned int n; + unsigned int m; +}; + +struct Channel +{ + unsigned int bufsize; + unsigned int elemsize; + unsigned char *buf; + unsigned int nbuf; + unsigned int off; + Altarray asend; + Altarray arecv; + char *name; +}; + +int chanalt(Alt *alts); +Channel* chancreate(int elemsize, int elemcnt); +void chanfree(Channel *c); +int chaninit(Channel *c, int elemsize, int elemcnt); +int channbrecv(Channel *c, void *v); +void* channbrecvp(Channel *c); +unsigned long channbrecvul(Channel *c); +int channbsend(Channel *c, void *v); +int channbsendp(Channel *c, void *v); +int channbsendul(Channel *c, unsigned long v); +int chanrecv(Channel *c, void *v); +void* chanrecvp(Channel *c); +unsigned long chanrecvul(Channel *c); +int chansend(Channel *c, void *v); +int chansendp(Channel *c, void *v); +int chansendul(Channel *c, unsigned long v); + +/* + * Threaded I/O. + */ +int fdread(int, void*, int); +int fdread1(int, void*, int); /* always uses fdwait */ +int fdwrite(int, void*, int); +void fdwait(int, int); +int fdnoblock(int); + +void fdtask(void*); + +/* + * Network dialing - sets non-blocking automatically + */ +enum +{ + UDP = 0, + TCP = 1, +}; + +int netannounce(int, char*, int); +int netaccept(int, char*, int*); +int netdial(int, char*, int); +int netlookup(char*, uint32_t*); /* blocks entire program! */ +int netdial(int, char*, int); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/lib/libtask/taskimpl.h b/lib/libtask/taskimpl.h new file mode 100644 index 00000000000..03a26c29fb0 --- /dev/null +++ b/lib/libtask/taskimpl.h @@ -0,0 +1,200 @@ +/* Copyright (c) 2005-2006 Russ Cox, MIT; see COPYRIGHT */ + +#if defined(__sun__) +# define __EXTENSIONS__ 1 /* SunOS */ +# if defined(__SunOS5_6__) || defined(__SunOS5_7__) || defined(__SunOS5_8__) + /* NOT USING #define __MAKECONTEXT_V2_SOURCE 1 / * SunOS */ +# else +# define __MAKECONTEXT_V2_SOURCE 1 +# endif +#endif + +#define USE_UCONTEXT 1 + +#if defined(__OpenBSD__) || defined(__mips__) +#undef USE_UCONTEXT +#define USE_UCONTEXT 0 +#endif + +#if defined(__APPLE__) +#include +#if defined(MAC_OS_X_VERSION_10_5) +#undef USE_UCONTEXT +#define USE_UCONTEXT 0 +#endif +#endif + +#ifdef EMACS +#include +#include "lisp.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if USE_UCONTEXT +#include +#endif +#include +#include +#include "task.h" + +#define nil ((void*)0) +#define nelem(x) (sizeof(x)/sizeof((x)[0])) + +#define ulong task_ulong +#define uint task_uint +#define uchar task_uchar +#define ushort task_ushort +#define uvlong task_uvlong +#define vlong task_vlong + +typedef unsigned long ulong; +typedef unsigned int uint; +typedef unsigned char uchar; +typedef unsigned short ushort; +typedef unsigned long long uvlong; +typedef long long vlong; + +#define print task_print +#define fprint task_fprint +#define snprint task_snprint +#define seprint task_seprint +#define vprint task_vprint +#define vfprint task_vfprint +#define vsnprint task_vsnprint +#define vseprint task_vseprint +#define strecpy task_strecpy + +int print(char*, ...); +int fprint(int, char*, ...); +char *snprint(char*, uint, char*, ...); +char *seprint(char*, char*, char*, ...); +int vprint(char*, va_list); +int vfprint(int, char*, va_list); +char *vsnprint(char*, uint, char*, va_list); +char *vseprint(char*, char*, char*, va_list); +char *strecpy(char*, char*, char*); + +#if defined(__FreeBSD__) && __FreeBSD__ < 5 +extern int getmcontext(mcontext_t*); +extern void setmcontext(const mcontext_t*); +#define setcontext(u) setmcontext(&(u)->uc_mcontext) +#define getcontext(u) getmcontext(&(u)->uc_mcontext) +extern int swapcontext(ucontext_t*, const ucontext_t*); +extern void makecontext(ucontext_t*, void(*)(), int, ...); +#endif + +#if defined(__APPLE__) +# define mcontext libthread_mcontext +# define mcontext_t libthread_mcontext_t +# define ucontext libthread_ucontext +# define ucontext_t libthread_ucontext_t +# if defined(__i386__) +# include "386-ucontext.h" +# elif defined(__x86_64__) +# include "amd64-ucontext.h" +# else +# include "power-ucontext.h" +# endif +#endif + +#if defined(__OpenBSD__) +# define mcontext libthread_mcontext +# define mcontext_t libthread_mcontext_t +# define ucontext libthread_ucontext +# define ucontext_t libthread_ucontext_t +# if defined __i386__ +# include "386-ucontext.h" +# else +# include "power-ucontext.h" +# endif +extern pid_t rfork_thread(int, void*, int(*)(void*), void*); +#endif + +#if 0 && defined(__sun__) +# define mcontext libthread_mcontext +# define mcontext_t libthread_mcontext_t +# define ucontext libthread_ucontext +# define ucontext_t libthread_ucontext_t +# include "sparc-ucontext.h" +#endif + +#if defined(__arm__) +int getmcontext(mcontext_t*); +void setmcontext(const mcontext_t*); +#define setcontext(u) setmcontext(&(u)->uc_mcontext) +#define getcontext(u) getmcontext(&(u)->uc_mcontext) +#endif + +#if defined(__mips__) +#include "mips-ucontext.h" +int getmcontext(mcontext_t*); +void setmcontext(const mcontext_t*); +#define setcontext(u) setmcontext(&(u)->uc_mcontext) +#define getcontext(u) getmcontext(&(u)->uc_mcontext) +#endif + +typedef struct Context Context; + +enum +{ + STACK = 8192 +}; + +struct Context +{ + ucontext_t uc; +#ifdef EMACS + struct emacs_lisp_task_context ec; +#endif +}; + +struct Task +{ + char name[256]; // offset known to acid + char state[256]; + Task *next; + Task *prev; + Task *allnext; + Task *allprev; + Context context; + uvlong alarmtime; + uint id; + uchar *stk; + uint stksize; + int exiting; + int alltaskslot; + int system; + int ready; + void (*startfn)(void*); + void *startarg; + void *udata; +}; + +void taskready(Task*); +void taskswitch(void); + +void addtask(Tasklist*, Task*); +void deltask(Tasklist*, Task*); + +extern Task *taskrunning; +extern int taskcount; + + +#ifdef EMACS +#define malloc unexec_malloc +#define realloc unexec_realloc +#define free unexec_free +void *unexec_malloc (size_t size); +void *unexec_realloc (void *old_ptr, size_t new_size); +void unexec_free (void *ptr); +#endif diff --git a/lib/libtask/tcpproxy.c b/lib/libtask/tcpproxy.c new file mode 100644 index 00000000000..114f39c87ac --- /dev/null +++ b/lib/libtask/tcpproxy.c @@ -0,0 +1,91 @@ +#include +#include +#include +#include +#include +#include +#include + +enum +{ + STACK = 32768 +}; + +char *server; +int port; +void proxytask(void*); +void rwtask(void*); + +int* +mkfd2(int fd1, int fd2) +{ + int *a; + + a = malloc(2*sizeof a[0]); + if(a == 0){ + fprintf(stderr, "out of memory\n"); + abort(); + } + a[0] = fd1; + a[1] = fd2; + return a; +} + +void +taskmain(int argc, char **argv) +{ + int cfd, fd; + int rport; + char remote[16]; + + if(argc != 4){ + fprintf(stderr, "usage: tcpproxy localport server remoteport\n"); + taskexitall(1); + } + server = argv[2]; + port = atoi(argv[3]); + + if((fd = netannounce(TCP, 0, atoi(argv[1]))) < 0){ + fprintf(stderr, "cannot announce on tcp port %d: %s\n", atoi(argv[1]), strerror(errno)); + taskexitall(1); + } + fdnoblock(fd); + while((cfd = netaccept(fd, remote, &rport)) >= 0){ + fprintf(stderr, "connection from %s:%d\n", remote, rport); + taskcreate(proxytask, (void*)cfd, STACK); + } +} + +void +proxytask(void *v) +{ + int fd, remotefd; + + fd = (int)v; + if((remotefd = netdial(TCP, server, port)) < 0){ + close(fd); + return; + } + + fprintf(stderr, "connected to %s:%d\n", server, port); + + taskcreate(rwtask, mkfd2(fd, remotefd), STACK); + taskcreate(rwtask, mkfd2(remotefd, fd), STACK); +} + +void +rwtask(void *v) +{ + int *a, rfd, wfd, n; + char buf[2048]; + + a = v; + rfd = a[0]; + wfd = a[1]; + free(a); + + while((n = fdread(rfd, buf, sizeof buf)) > 0) + fdwrite(wfd, buf, n); + shutdown(wfd, SHUT_WR); + close(rfd); +} diff --git a/lib/libtask/testdelay.c b/lib/libtask/testdelay.c new file mode 100644 index 00000000000..7ebf70d5d17 --- /dev/null +++ b/lib/libtask/testdelay.c @@ -0,0 +1,40 @@ +#include +#include +#include +#include +#include +#include + +enum { STACK = 32768 }; + +Channel *c; + +void +delaytask(void *v) +{ + taskdelay((int)v); + printf("awake after %d ms\n", (int)v); + chansendul(c, 0); +} + +void +taskmain(int argc, char **argv) +{ + int i, n; + + c = chancreate(sizeof(unsigned long), 0); + + n = 0; + for(i=1; i + +void +taskmain(int argc, char *argv[]) +{ + taskdelay(1000); +} diff --git a/lisp/emacs-lisp/byte-opt.el b/lisp/emacs-lisp/byte-opt.el index 610c3b6c190..ab9354482b1 100644 --- a/lisp/emacs-lisp/byte-opt.el +++ b/lisp/emacs-lisp/byte-opt.el @@ -525,6 +525,25 @@ (cdr (cdr form)) (byte-optimize-body (cdr form) for-effect))))) + ((eq fn 'select) + (cons fn + (mapcar + (lambda (alt) + (cons + (pcase (car alt) + (`(receive ,chan . ,rest) + `(receive ,(byte-optimize-form chan) ,@rest)) + (`(send ,chan ,val) + `(send ,(byte-optimize-form chan) + ,(byte-optimize-form val))) + ('default 'default) + (invalid + (byte-compile-report-error + (format-message "invalid `select' alternative %S" + invalid)))) + (byte-optimize-body (cdr alt) for-effect))) + (cdr form)))) + ((eq fn 'ignore) ;; Don't treat the args to `ignore' as being ;; computed for effect. We want to avoid the warnings diff --git a/lisp/emacs-lisp/cconv.el b/lisp/emacs-lisp/cconv.el index 46b5a7f342c..598436f647c 100644 --- a/lisp/emacs-lisp/cconv.el +++ b/lisp/emacs-lisp/cconv.el @@ -759,6 +759,29 @@ and updates the data stored in ENV." ;; seem worth the trouble. (dolist (form forms) (cconv-analyze-form form nil))) + (`(select . ,alternatives) + (dolist (alternative alternatives) + (let ((env env) + (varstruct nil)) + (pcase (car alternative) + (`(receive ,chan) + (cconv-analyze-form chan env)) + (`(receive ,chan ,var) + (cconv-analyze-form chan env) + (setq varstruct (list var nil nil nil nil)) + (push varstruct env)) + (`(send ,chan ,val) + (cconv-analyze-form chan env) + (cconv-analyze-form val env)) + ('default) + (invalid + (byte-compile-report-error + (format-message "invalid `select' alternative %S" invalid)))) + (dolist (form (cdr alternative)) + (cconv-analyze-form form env)) + (when varstruct + (cconv--analyze-use varstruct form "variable"))))) + ;; `declare' should now be macro-expanded away (and if they're not, we're ;; in trouble because they *can* contain code nowadays). ;; (`(declare . ,_) nil) ;The args don't contain code. diff --git a/src/Makefile.in b/src/Makefile.in index 89f7a921faa..aa2db118d2c 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -398,6 +398,7 @@ base_obj = dispnew.o frame.o scroll.o xdisp.o menu.o $(XMENU_OBJ) window.o \ buffer.o filelock.o insdel.o marker.o \ minibuf.o fileio.o dired.o \ cmds.o casetab.o casefiddle.o indent.o search.o regex.o undo.o \ + coroutine.o \ alloc.o data.o doc.o editfns.o callint.o \ eval.o floatfns.o fns.o font.o print.o lread.o $(MODULES_OBJ) \ syntax.o $(UNEXEC_OBJ) bytecode.o \ @@ -598,14 +599,29 @@ LIBEGNU_ARCHIVE = $(lib)/lib$(if $(HYBRID_MALLOC),e)gnu.a $(LIBEGNU_ARCHIVE): $(config_h) $(MAKE) -C $(lib) all +libtask = \ + $(lib)/libtask/asm.o \ + $(lib)/libtask/channel.o \ + $(lib)/libtask/context.o \ + $(lib)/libtask/print.o \ + $(lib)/libtask/qlock.o \ + $(lib)/libtask/rendez.o \ + $(lib)/libtask/task.o + +$(libtask): $(lib)/libtask/*.h + +$(lib)/libtask/%.o: $(lib)/libtask/%.c $(lib)/libtask/*.h lisp.h config.h globals.h + $(AM_V_CC)$(CC) -c -DEMACS -I. -I$(lib) $(CPPFLAGS) $(CFLAGS) -o $@ $< + ## We have to create $(etc) here because init_cmdargs tests its ## existence when setting Vinstallation_directory (FIXME?). ## This goes on to affect various things, and the emacs binary fails ## to start if Vinstallation_directory has the wrong value. temacs$(EXEEXT): $(LIBXMENU) $(ALLOBJS) \ - $(LIBEGNU_ARCHIVE) $(EMACSRES) ${charsets} ${charscript} + $(LIBEGNU_ARCHIVE) $(EMACSRES) ${charsets} ${charscript} \ + $(libtask) $(AM_V_CCLD)$(CC) $(ALL_CFLAGS) $(TEMACS_LDFLAGS) $(LDFLAGS) \ - -o temacs $(ALLOBJS) $(LIBEGNU_ARCHIVE) $(W32_RES_LINK) $(LIBES) + -o temacs $(ALLOBJS) $(LIBEGNU_ARCHIVE) $(W32_RES_LINK) $(libtask) $(LIBES) $(MKDIR_P) $(etc) ifneq ($(CANNOT_DUMP),yes) ifneq ($(PAXCTL_notdumped),) diff --git a/src/alloc.c b/src/alloc.c index a58dc13cbd7..10b6c20e3e4 100644 --- a/src/alloc.c +++ b/src/alloc.c @@ -51,6 +51,8 @@ along with GNU Emacs. If not, see . */ #include #include /* For backtrace. */ +#include "libtask/task.h" + #ifdef HAVE_LINUX_SYSINFO #include #endif @@ -3865,6 +3867,28 @@ free_marker (Lisp_Object marker) free_misc (marker); } +DEFUN ("make-channel", Fmake_channel, Smake_channel, 0, 1, 0, + doc: /* Return a newly allocated communication channel. +BUFFER-SIZE, if provided, must be a non-negative integer specifying the +number of object that can be buffered in the channel. If BUFFER-SIZE +is not provided, nil, or 0, the channel is unbuffered. */) + (Lisp_Object buffer_size) +{ + int buf_size; + if (NILP (buffer_size)) + buf_size = 0; + else + { + CHECK_RANGED_INTEGER (buffer_size, 0, INT_MAX); + buf_size = XFASTINT (buffer_size); + } + + Lisp_Object obj = allocate_misc (Lisp_Misc_Channel); + struct Lisp_Channel *chan = XCHANNEL (obj); + chan->channel = chancreate (sizeof (Lisp_Object), buf_size); + return obj; +} + /* Return a newly created vector or string with specified arguments as elements. If all the arguments are characters that can fit @@ -6524,6 +6548,17 @@ mark_object (Lisp_Object arg) mark_object (XFINALIZER (obj)->function); break; + case Lisp_Misc_Channel: + { + XMISCANY (obj)->gcmarkbit = 1; + Channel *channel = XCHANNEL (obj)->channel; + eassert (channel->elemsize == sizeof (Lisp_Object)); + Lisp_Object *buffer_contents = (Lisp_Object *) channel->buf; + for (unsigned int i = 0; i < channel->nbuf; ++i) + mark_object (buffer_contents[channel->off + i]); + } + break; + #ifdef HAVE_MODULES case Lisp_Misc_User_Ptr: XMISCANY (obj)->gcmarkbit = true; @@ -6908,6 +6943,15 @@ sweep_misc (void) unchain_marker (&mblk->markers[i].m.u_marker); else if (mblk->markers[i].m.u_any.type == Lisp_Misc_Finalizer) unchain_finalizer (&mblk->markers[i].m.u_finalizer); + else if (mblk->markers[i].m.u_any.type == Lisp_Misc_Channel) + { + Channel *channel = mblk->markers[i].m.u_channel.channel; + eassert (channel != NULL); + eassert (channel->nbuf == 0); + eassert (channel->asend.n == 0); + eassert (channel->arecv.n == 0); + chanfree (channel); + } #ifdef HAVE_MODULES else if (mblk->markers[i].m.u_any.type == Lisp_Misc_User_Ptr) { @@ -7419,6 +7463,7 @@ The time is in seconds as a floating point value. */); defsubr (&Smake_symbol); defsubr (&Smake_marker); defsubr (&Smake_finalizer); + defsubr (&Smake_channel); defsubr (&Spurecopy); defsubr (&Sgarbage_collect); defsubr (&Smemory_limit); diff --git a/src/coroutine.c b/src/coroutine.c new file mode 100644 index 00000000000..601773aae05 --- /dev/null +++ b/src/coroutine.c @@ -0,0 +1,400 @@ +/* Coroutine support for GNU Emacs Lisp. + +Copyright (C) 2016 Free Software Foundation, Inc. + +This file is part of GNU Emacs. + +GNU Emacs is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or (at +your option) any later version. + +GNU Emacs is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Emacs. If not, see . */ + + +#include + +#include +#include +#include +#include +#include + +#include "lisp.h" + +#include "verify.h" +#include "sysselect.h" +#include "libtask/task.h" + +#if __has_attribute (cleanup) +enum { coroutine_has_cleanup = true }; +#else +enum { coroutine_has_cleanup = false }; +#endif + +#define COROUTINE_HANDLE_NONLOCAL_EXIT(retval) \ + COROUTINE_SETJMP (CONDITION_CASE, coroutine_handle_signal, retval); \ + COROUTINE_SETJMP (CATCHER_ALL, coroutine_handle_throw, retval) + +#define COROUTINE_SETJMP(handlertype, handlerfunc, retval) \ + COROUTINE_SETJMP_1 (handlertype, handlerfunc, retval, \ + internal_handler_##handlertype, \ + internal_cleanup_##handlertype) + +#define COROUTINE_SETJMP_1(handlertype, handlerfunc, retval, c, dummy) \ + struct handler *c = push_handler_nosignal (Qt, handlertype); \ + if (!c) \ + { \ + coroutine_out_of_memory (); \ + return retval; \ + } \ + verify (coroutine_has_cleanup); \ + int dummy __attribute__ ((cleanup (coroutine_reset_handlerlist))); \ + if (sys_setjmp (c->jmp)) \ + { \ + (handlerfunc) (c->val); \ + return retval; \ + } \ + do { } while (false) + +/* Must be called after setting up a handler immediately before + returning from the function. See the comments in lisp.h and the + code in eval.c for details. The macros below arrange for this + function to be called automatically. DUMMY is ignored. */ +static void +coroutine_reset_handlerlist (const int *dummy) +{ + handlerlist = handlerlist->next; +} + +/* Called on `signal'. ERR is a pair (SYMBOL . DATA). */ +static void +coroutine_handle_signal (Lisp_Object err) +{ + print_error_message (err, Qnil, "signal in coroutine caught: ", Qnil); + taskexit (1); +} + +/* Called on `throw'. TAG_VAL is a pair (TAG . VALUE). */ +static void +coroutine_handle_throw (Lisp_Object tag_val) +{ + print_error_message (tag_val, Qnil, "throw in coroutine caught: ", Qnil); + taskexit (1); +} + +static void +coroutine_out_of_memory (void) +{ + fputs ("OOM in coroutine\n", stderr); + taskexit (1); +} + +struct task_arguments +{ + Lisp_Object function; +}; + +static void +task_function (void *arg) +{ + struct task_arguments args = *(struct task_arguments *) arg; + xfree (arg); + COROUTINE_HANDLE_NONLOCAL_EXIT (); + ptrdiff_t count = SPECPDL_INDEX (); + specbind (Qinternal_interpreter_environment, Vinternal_interpreter_environment); + call0 (args.function); + unbind_to (count, Qnil); +} + +DEFUN ("start-coroutine", Fstart_coroutine, Sstart_coroutine, 1, 1, 0, + doc: /* Start FUNCTION in a background coroutine. */) + (Lisp_Object function) +{ + // args will be freed in task_function. + struct task_arguments *args = xmalloc (sizeof *args); + args->function = function; + taskcreate (task_function, args, 0x400000); + return Qnil; +} + +static void +CHECK_CHANNEL (Lisp_Object x) +{ + CHECK_TYPE (CHANNELP (x), Qchannelp, x); +} + +DEFUN ("channelp", Fchannelp, Schannelp, 1, 1, 0, + doc: /* Return t if OBJECT is a communication channel, nil otherwise. */) + (Lisp_Object object) +{ + return CHANNELP (object) ? Qt : Qnil; +} + +DEFUN ("receive-from-channel", Freceive_from_channel, Sreceive_from_channel, + 1, 1, 0, + doc: /* Receive an object from CHANNEL. +Block the current coroutine until an object is available on CHANNEL, +then return that object and remove it from CHANNEL. +CHANNEL must be a communication channel created by `make-channel'. */) + (Lisp_Object channel) +{ + CHECK_CHANNEL (channel); + Lisp_Object result; + int status = chanrecv (XCHANNEL (channel)->channel, &result); + eassert (status == 1); + return result; +} + +DEFUN ("try-receive-from-channel", Ftry_receive_from_channel, Stry_receive_from_channel, + 1, 1, 0, + doc: /* Attempt to receive an object from CHANNEL. +If an object can be read from CHANNEL, return a singleton list containing +the object and remove it from CHANNEL. Otherwise, return nil. +CHANNEL must be a communication channel created by `make-channel'. */) + (Lisp_Object channel) +{ + CHECK_CHANNEL (channel); + Lisp_Object result; + int status = channbrecv (XCHANNEL (channel)->channel, &result); + return (status == 1) ? list1 (result) : Qnil; +} + +DEFUN ("send-to-channel", Fsend_to_channel, Ssend_to_channel, + 2, 2, 0, + doc: /* Send an object to CHANNEL. +Block the current coroutine until an object can be put into CHANNEL, +then put VALUE into CHANNEL. CHANNEL must be a communication channel +created by `make-channel'. */) + (Lisp_Object channel, Lisp_Object value) +{ + CHECK_CHANNEL (channel); + int status = chansend (XCHANNEL (channel)->channel, &value); + eassert (status == 1); + return Qnil; +} + +DEFUN ("try-send-to-channel", Ftry_send_to_channel, Stry_send_to_channel, + 2, 2, 0, + doc: /* Attempt to send an object to CHANNEL. +If an object can be sent to channel CHANNEL, put VALUE into CHANNEL +and return t, otherwise return nil. CHANNEL must be a communication +channel created by `make-channel'. */) + (Lisp_Object channel, Lisp_Object value) +{ + CHECK_CHANNEL (channel); + int status = channbsend (XCHANNEL (channel)->channel, &value); + return (status == 1) ? Qt : Qnil; +} + +DEFUN ("select", Fselect, Sselect, 0, UNEVALLED, 0, + doc: /* Synchronously multiplex between ALTERNATIVES. +ALTERNATIVES must be a list of communication alternatives. +Each alternative must be of one of the four following forms: +1. ((receive CHANNEL) BODY...) +2. ((receive CHANNEL SYMBOL) BODY...) +3. ((send CHANNEL VALUE) BODY...) +4. (default BODY...) +At most one `default' form may be present. + +`select' checks for each of the alternative whether the respective +communication operation can progress. If there is at least one such +operation, one of the available ones is randomly selected and the +respective BODY forms are evaluated. If there is no such operation +(all operations would block), `select' either blocks until at least +one operation can make progress (if no `default' alternative is +given), or evaluates the BODY of the `default' alternative. + +The first two forms attempt to receive a value from CHANNEL (which is +evaluated), as if by `receive-from-channel'. If SYMBOL is given and +not nil, it is interpreted as an unevaluated symbol and bound to the +value that has been received from CHANNEL within BODY, otherwise the +value from CHANNEL is ignored. + +The third form attempts to send VALUE to CHANNEL, as if by +`send-to-channel'. Both VALUE and CHANNEL are evaluated. + +The value of the last form of the BODY of the alternative being chosen +is returned. */) + (Lisp_Object alternatives) +{ + CHECK_LIST (alternatives); + Lisp_Object length = Flength (alternatives); + + // libtask uses signed integers for counts, therefore we restrict + // the number of alternatives to INT_MAX. Because we allocate one + // more Alt object for the delimiter, actually use INT_MAX − 1. + CHECK_RANGED_INTEGER (length, 0, INT_MAX - 1); + int num_alts = XINT (length); + + // Allocate array of alternatives given to chanalt below. + // alts[num_alts] is the delimiter (CHANEND or CHANNOBLK, depending + // on whether a default alternative is present). + Alt *alts = xmalloc ((num_alts + 1) * sizeof *alts); + + // The objects in this structure correspond to VALUE, SYMBOL, and + // BODY in the docstring, possibly evaluated. The body for a + // default alternative, if present, is stored elsewhere. + struct { + Lisp_Object value; + Lisp_Object symbol; + Lisp_Object body; + } *comms = xmalloc (num_alts * sizeof *comms); + + bool has_default = false; + Lisp_Object default_body; + + // Fill in the contents of alts and comms. + int i = 0; + for (Lisp_Object rest = alternatives; ! NILP (rest); rest = CDR (rest)) + { + eassert (i < num_alts); + CHECK_CONS (rest); + Lisp_Object alternative = XCAR (rest); + CHECK_CONS (alternative); + Lisp_Object comm = XCAR (alternative); + Lisp_Object body = XCDR (alternative); + CHECK_LIST (body); + if (EQ (comm, Qdefault)) + { + if (has_default) + signal_error ("Cannot have two default alternatives", alternatives); + has_default = true; + default_body = body; + // As a default alternative is allowed anywhere, simply skip + // it here. + alts[i].op = CHANNOP; + } + else + { + CHECK_CONS (comm); + Lisp_Object operation = XCAR (comm); + Lisp_Object channel_arg = XCDR (comm); + CHECK_CONS (channel_arg); + Lisp_Object channel = eval_sub (XCAR (channel_arg)); + CHECK_CHANNEL (channel); + alts[i].c = XCHANNEL (channel)->channel; + Lisp_Object rest = XCDR (channel_arg); + CHECK_LIST (rest); + if (CONSP (rest) && ! NILP (XCDR (rest))) + signal_error ("Communication specification must have at most three elements", comm); + if (EQ (operation, Qreceive)) + { + alts[i].op = CHANRCV; + Lisp_Object var = NILP (rest) ? Qnil : XCAR (rest); + CHECK_SYMBOL (var); + // We don’t care whether VAR refers to a constant here, + // `let' below will do it. + comms[i].value = Qnil; + comms[i].symbol = var; + // If alts[i].v is NULL, chanalt will throw away the + // received value. + alts[i].v = NILP (var) ? NULL : &comms[i].value; + } + else if (EQ (operation, Qsend)) + { + alts[i].op = CHANSND; + CHECK_CONS (rest); + comms[i].value = eval_sub (XCAR (rest)); + alts[i].v = &comms[i].value; + } + else + wrong_choice (list2 (Qreceive, Qsend), operation); + comms[i].body = body; + } + i++; + } + eassert (i == num_alts); + alts[num_alts].op = has_default ? CHANNOBLK : CHANEND; + + // Actually perform the select operation. + int choice = chanalt (alts); + + if (choice == -1) + { + // Default alternative has been chosen. + eassert (has_default); + eassert (alts[num_alts].op == CHANNOBLK); + return Fprogn (default_body); + } + else + { + // Non-default alternative has been chosen. + eassert (choice >= 0 && choice < num_alts); + Lisp_Object body = comms[choice].body; + if (alts[choice].op == CHANRCV && ! NILP (comms[choice].symbol)) + { + Lisp_Object symbol = comms[choice].symbol; + Lisp_Object value = comms[choice].value; + return Flet (Fcons (list1 (list2 (symbol, value)), body)); + } + else + return Fprogn (body); + } +} + +struct pselect_args { + int nfds; + fd_set *readfds; + fd_set *writefds; + fd_set *errorfds; + const struct timespec *timeout; + const sigset_t *sigmask; + Rendez rendez; + int result; +}; + +static void +do_pselect (void *arg) +{ + struct pselect_args *args = arg; + args->result = pselect (args->nfds, + args->readfds, args->writefds, args->errorfds, + args->timeout, args->sigmask); + int woken = taskwakeup (&args->rendez); + eassert (woken == 1); +} + +// Non-blocking variant of pselect. Uses a task to run in the +// background. +int +pselect_noblock (int nfds, + fd_set *restrict readfds, + fd_set *restrict writefds, + fd_set *restrict errorfds, + const struct timespec *restrict timeout, + const sigset_t *restrict sigmask) +{ + struct pselect_args args = { + .nfds = nfds, + .readfds = readfds, + .writefds = writefds, + .errorfds = errorfds, + .timeout = timeout, + .sigmask = sigmask, + }; + taskcreate (do_pselect, &args, 0x1000); + tasksleep (&args.rendez); + return args.result; +} + +void +syms_of_coroutine (void) +{ + DEFSYM (Qchannelp, "channelp"); + DEFSYM (Qreceive, "receive"); + DEFSYM (Qsend, "send"); + + defsubr (&Sstart_coroutine); + defsubr (&Sreceive_from_channel); + defsubr (&Stry_receive_from_channel); + defsubr (&Ssend_to_channel); + defsubr (&Stry_send_to_channel); + defsubr (&Sselect); +} diff --git a/src/emacs.c b/src/emacs.c index 13378c4c3b0..adb972e1db8 100644 --- a/src/emacs.c +++ b/src/emacs.c @@ -91,6 +91,8 @@ along with GNU Emacs. If not, see . */ #include "systime.h" #include "puresize.h" +#include "libtask/task.h" + #include "getpagesize.h" #include "gnutls.h" @@ -661,8 +663,8 @@ close_output_streams (void) } /* ARGSUSED */ -int -main (int argc, char **argv) +void +taskmain (int argc, char **argv) { Lisp_Object dummy; char stack_bottom_variable; @@ -739,12 +741,6 @@ main (int argc, char **argv) run_time_remap (argv[0]); #endif -/* If using unexmacosx.c (set by s/darwin.h), we must do this. */ -#ifdef DARWIN_OS - if (!initialized) - unexec_init_emacs_zone (); -#endif - init_standard_fds (); atexit (close_output_streams); @@ -1436,6 +1432,7 @@ Using an Emacs configured with --with-x-toolkit=lucid does not have this problem syms_of_ccl (); syms_of_character (); syms_of_cmds (); + syms_of_coroutine (); syms_of_dired (); syms_of_display (); syms_of_doc (); @@ -1657,8 +1654,6 @@ Using an Emacs configured with --with-x-toolkit=lucid does not have this problem /* Enter editor command loop. This never returns. */ Frecursive_edit (); - /* NOTREACHED */ - return 0; } /* Sort the args so we can find the most important ones @@ -2066,6 +2061,19 @@ shut_down_emacs (int sig, Lisp_Object stuff) +/* Called by libtask before allocating the main task. Use this + function to initialize allocation. After this, alloc should be + usable. */ +void +init_emacs_main_task (void) +{ + /* If using unexmacosx.c (set by s/darwin.h), we must do this. */ +#ifdef DARWIN_OS + if (!initialized) + unexec_init_emacs_zone (); +#endif +} + #ifndef CANNOT_DUMP #include "unexec.h" diff --git a/src/eval.c b/src/eval.c index a9bad2491fa..98abce84196 100644 --- a/src/eval.c +++ b/src/eval.c @@ -3759,6 +3759,56 @@ Lisp_Object backtrace_top_function (void) return (backtrace_p (pdl) ? backtrace_function (pdl) : Qnil); } +void +init_emacs_lisp_context (bool main_task, + struct emacs_lisp_task_context *context) +{ + if (main_task) + { +#define SET(var) do { context->var = (var); } while (false) + SET(handlerlist); + SET(handlerlist_sentinel); + SET(specpdl_size); + SET(specpdl); + SET(specpdl_ptr); + SET(lisp_eval_depth); + SET(Vinternal_interpreter_environment); +#undef SET + } + else + { + context->handlerlist = context->handlerlist_sentinel.nextfree = &context->handlerlist_sentinel; + context->specpdl_size = 50; + context->specpdl = xmalloc ((1 + context->specpdl_size) * sizeof *context->specpdl); + ++context->specpdl; + context->specpdl_ptr = context->specpdl; + struct handler *prev = handlerlist; + handlerlist = context->handlerlist; + struct handler *c = push_handler_nosignal (Qunbound, CATCHER); + handlerlist = prev; + eassert (c == &context->handlerlist_sentinel); + context->handlerlist_sentinel.nextfree = NULL; + context->handlerlist_sentinel.next = NULL; + context->lisp_eval_depth = 0; + context->Vinternal_interpreter_environment = CONSP (Vinternal_interpreter_environment) ? list1 (Qt) : Qnil; + } +} + +void +switch_emacs_lisp_context (struct emacs_lisp_task_context *from, + const struct emacs_lisp_task_context *to) +{ +#define SWAP(var) do { from->var = (var); (var) = to->var; } while (false) + SWAP(handlerlist); + SWAP(handlerlist_sentinel); + SWAP(specpdl_size); + SWAP(specpdl); + SWAP(specpdl_ptr); + SWAP(lisp_eval_depth); + SWAP(Vinternal_interpreter_environment); +#undef SWAP +} + void syms_of_eval (void) { diff --git a/src/lisp.h b/src/lisp.h index 2e46592c3d4..7efdd4573e1 100644 --- a/src/lisp.h +++ b/src/lisp.h @@ -459,6 +459,7 @@ enum Lisp_Misc_Type Lisp_Misc_Overlay, Lisp_Misc_Save_Value, Lisp_Misc_Finalizer, + Lisp_Misc_Channel, #ifdef HAVE_MODULES Lisp_Misc_User_Ptr, #endif @@ -611,6 +612,8 @@ extern bool might_dump; Used during startup to detect startup of dumped Emacs. */ extern bool initialized; +void init_emacs_main_task (void); + /* Defined in floatfns.c. */ extern double extract_float (Lisp_Object); @@ -2252,6 +2255,12 @@ XSAVE_OBJECT (Lisp_Object obj, int n) return XSAVE_VALUE (obj)->data[n].object; } +struct Lisp_Channel +{ + struct Lisp_Misc_Any base; + struct Channel* channel; +}; + #ifdef HAVE_MODULES struct Lisp_User_Ptr { @@ -2299,6 +2308,7 @@ union Lisp_Misc struct Lisp_Overlay u_overlay; struct Lisp_Save_Value u_save_value; struct Lisp_Finalizer u_finalizer; + struct Lisp_Channel u_channel; #ifdef HAVE_MODULES struct Lisp_User_Ptr u_user_ptr; #endif @@ -2351,6 +2361,19 @@ XFINALIZER (Lisp_Object a) return & XMISC (a)->u_finalizer; } +INLINE bool +CHANNELP (Lisp_Object a) +{ + return MISCP (a) && XMISCTYPE (a) == Lisp_Misc_Channel; +} + +INLINE struct Lisp_Channel * +XCHANNEL (Lisp_Object a) +{ + eassert (CHANNELP (a)); + return & XMISC (a)->u_channel; +} + #ifdef HAVE_MODULES INLINE struct Lisp_User_Ptr * XUSER_PTR (Lisp_Object a) @@ -3412,6 +3435,9 @@ extern void init_coding (void); extern void init_coding_once (void); extern void syms_of_coding (void); +/* Defined in coroutine.c. */ +extern void syms_of_coroutine (void); + /* Defined in character.c. */ extern ptrdiff_t chars_in_text (const unsigned char *, ptrdiff_t); extern ptrdiff_t multibyte_chars_in_text (const unsigned char *, ptrdiff_t); @@ -3932,6 +3958,22 @@ Lisp_Object backtrace_top_function (void); extern bool let_shadows_buffer_binding_p (struct Lisp_Symbol *symbol); extern bool let_shadows_global_binding_p (Lisp_Object symbol); +struct emacs_lisp_task_context { + struct handler *handlerlist; + struct handler handlerlist_sentinel; + ptrdiff_t specpdl_size; + union specbinding *specpdl; + union specbinding *specpdl_ptr; + EMACS_INT lisp_eval_depth; + struct emacs_globals globals; +}; + +void init_emacs_lisp_context (bool main_task, + struct emacs_lisp_task_context *context); + +void switch_emacs_lisp_context (struct emacs_lisp_task_context *from, + const struct emacs_lisp_task_context *to); + #ifdef HAVE_MODULES /* Defined in alloc.c. */ extern Lisp_Object make_user_ptr (void (*finalizer) (void *), void *p); diff --git a/src/nsterm.m b/src/nsterm.m index 1b44a73cd8b..b0883f3140b 100644 --- a/src/nsterm.m +++ b/src/nsterm.m @@ -4143,7 +4143,7 @@ ns_select (int nfds, fd_set *readfds, fd_set *writefds, if (NSApp == nil || (timeout && timeout->tv_sec == 0 && timeout->tv_nsec == 0)) - return pselect (nfds, readfds, writefds, exceptfds, timeout, sigmask); + return pselect_noblock (nfds, readfds, writefds, exceptfds, timeout, sigmask); [outerpool release]; outerpool = [[NSAutoreleasePool alloc] init]; @@ -5509,7 +5509,7 @@ not_in_argv (NSString *arg) fd_set fds; FD_ZERO (&fds); FD_SET (selfds[0], &fds); - result = select (selfds[0]+1, &fds, NULL, NULL, NULL); + result = pselect_noblock (selfds[0]+1, &fds, NULL, NULL, NULL, NULL); if (result > 0 && read (selfds[0], &c, 1) == 1 && c == 'g') waiting = 0; } @@ -5543,7 +5543,7 @@ not_in_argv (NSString *arg) FD_SET (selfds[0], &readfds); if (selfds[0] >= nfds) nfds = selfds[0]+1; - result = pselect (nfds, &readfds, wfds, NULL, tmo, NULL); + result = pselect_noblock (nfds, &readfds, wfds, NULL, tmo, NULL); if (result == 0) ns_send_appdefined (-2); diff --git a/src/print.c b/src/print.c index f3db6748d03..bb38b3d67e3 100644 --- a/src/print.c +++ b/src/print.c @@ -38,6 +38,8 @@ along with GNU Emacs. If not, see . */ #include #include +#include "libtask/task.h" + #ifdef WINDOWSNT # include /* for F_DUPFD_CLOEXEC */ #endif @@ -2006,6 +2008,38 @@ print_object (Lisp_Object obj, Lisp_Object printcharfun, bool escapeflag) printchar ('>', printcharfun); break; + case Lisp_Misc_Channel: + { + print_c_string ("#channel; + { + int n = snprintf (buf, sizeof buf, " at %p", channel); + strout (buf, n, n, printcharfun); + } + if (channel->bufsize != 0) + { + int n = snprintf (buf, sizeof buf, " with a buffer size of %d", channel->bufsize); + strout (buf, n, n, printcharfun); + } + if (channel->nbuf != 0) + { + int n = snprintf (buf, sizeof buf, " with %d buffered objects", channel->nbuf); + strout (buf, n, n, printcharfun); + } + if (channel->arecv.n != 0) + { + int n = snprintf (buf, sizeof buf, " with %d queued receivers", channel->arecv.n); + strout (buf, n, n, printcharfun); + } + if (channel->asend.n != 0) + { + int n = snprintf (buf, sizeof buf, " with %d queued senders", channel->asend.n); + strout (buf, n, n, printcharfun); + } + printchar ('>', printcharfun); + } + break; + #ifdef HAVE_MODULES case Lisp_Misc_User_Ptr: { diff --git a/src/process.c b/src/process.c index 8cf045ca9c2..f6266686962 100644 --- a/src/process.c +++ b/src/process.c @@ -3279,7 +3279,7 @@ connect_network_socket (Lisp_Object proc, Lisp_Object addrinfos, FD_ZERO (&fdset); FD_SET (s, &fdset); QUIT; - sc = pselect (s + 1, NULL, &fdset, NULL, NULL, NULL); + sc = pselect_noblock (s + 1, NULL, &fdset, NULL, NULL, NULL); if (sc == -1) { if (errno == EINTR) @@ -5003,10 +5003,10 @@ wait_reading_process_output (intmax_t time_limit, int nsecs, int read_kbd, Ctemp = write_mask; timeout = make_timespec (0, 0); - if ((pselect (max (max_process_desc, max_input_desc) + 1, - &Atemp, - (num_pending_connects > 0 ? &Ctemp : NULL), - NULL, &timeout, NULL) + if ((pselect_noblock (max (max_process_desc, max_input_desc) + 1, + &Atemp, + (num_pending_connects > 0 ? &Ctemp : NULL), + NULL, &timeout, NULL) <= 0)) { /* It's okay for us to do this and then continue with @@ -5187,7 +5187,7 @@ wait_reading_process_output (intmax_t time_limit, int nsecs, int read_kbd, #elif defined (HAVE_GLIB) nfds = xg_select #else - nfds = pselect + nfds = pselect_noblock #endif (max (max_process_desc, max_input_desc) + 1, &Available, @@ -7410,7 +7410,7 @@ wait_reading_process_output (intmax_t time_limit, int nsecs, int read_kbd, { if (read_kbd || !NILP (wait_for_cell)) FD_SET (0, &waitchannels); - nfds = pselect (1, &waitchannels, NULL, NULL, &timeout, NULL); + nfds = pselect_noblock (1, &waitchannels, NULL, NULL, &timeout, NULL); } xerrno = errno; diff --git a/src/sysselect.h b/src/sysselect.h index 0bf9b40a3cb..21a3f596662 100644 --- a/src/sysselect.h +++ b/src/sysselect.h @@ -90,4 +90,12 @@ INLINE_HEADER_END #endif /* !WINDOWSNT */ +// Defined in coroutine.c. +int pselect_noblock (int nfds, + fd_set *restrict readfds, + fd_set *restrict writefds, + fd_set *restrict errorfds, + const struct timespec *restrict timeout, + const sigset_t *restrict sigmask); + #endif diff --git a/src/unexmacosx.c b/src/unexmacosx.c index ea8e884f177..48a302c2e3f 100644 --- a/src/unexmacosx.c +++ b/src/unexmacosx.c @@ -1377,6 +1377,8 @@ unexec_malloc (size_t size) void * unexec_realloc (void *old_ptr, size_t new_size) { + if (old_ptr == NULL) + return unexec_malloc (new_size); if (in_dumped_exec) { void *p; diff --git a/src/xgselect.c b/src/xgselect.c index 7850a16e9c0..02e33cc0902 100644 --- a/src/xgselect.c +++ b/src/xgselect.c @@ -109,8 +109,8 @@ xg_select (int fds_lim, fd_set *rfds, fd_set *wfds, fd_set *efds, } fds_lim = max_fds + 1; - nfds = pselect (fds_lim, &all_rfds, have_wfds ? &all_wfds : NULL, - efds, tmop, sigmask); + nfds = pselect_noblock (fds_lim, &all_rfds, have_wfds ? &all_wfds : NULL, + efds, tmop, sigmask); if (nfds < 0) retval = nfds; diff --git a/src/xmenu.c b/src/xmenu.c index 9ab7bdf971f..d5577bb0831 100644 --- a/src/xmenu.c +++ b/src/xmenu.c @@ -195,7 +195,7 @@ x_menu_wait_for_event (void *data) xg_select so that timeout gets triggered. */ xg_select (n + 1, &read_fds, NULL, NULL, ntp, NULL); #else - pselect (n + 1, &read_fds, NULL, NULL, ntp, NULL); + pselect_noblock (n + 1, &read_fds, NULL, NULL, ntp, NULL); #endif } } diff --git a/src/xterm.c b/src/xterm.c index 747669446f5..6f57734a422 100644 --- a/src/xterm.c +++ b/src/xterm.c @@ -3976,7 +3976,7 @@ XTflash (struct frame *f) timeout = make_timespec (0, 10 * 1000 * 1000); /* Try to wait that long--but we might wake up sooner. */ - pselect (0, NULL, NULL, NULL, &timeout, NULL); + pselect_noblock (0, NULL, NULL, NULL, &timeout, NULL); } } @@ -10564,7 +10564,7 @@ x_wait_for_event (struct frame *f, int eventtype) break; tmo = timespec_sub (tmo_at, time_now); - if (pselect (fd + 1, &fds, NULL, NULL, &tmo, NULL) == 0) + if (pselect_noblock (fd + 1, &fds, NULL, NULL, &tmo, NULL) == 0) break; /* Timeout */ } diff --git a/test/src/coroutine-tests.el b/test/src/coroutine-tests.el new file mode 100644 index 00000000000..d86fe6c6115 --- /dev/null +++ b/test/src/coroutine-tests.el @@ -0,0 +1,166 @@ +;;; coroutine-tests.el --- coroutine tests -*- lexical-binding: t; -*- + +;; Copyright (C) 2016 Free Software Foundation, Inc. + +;; Author: Philipp Stephani + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; Unit tests for src/coroutine.c. + +;;; Code: + +(defun coroutine-tests--prime-task (channel) + (let ((prime (receive-from-channel channel))) + (message "%d" prime) + (let ((new-channel (make-channel))) + (start-coroutine (lambda () (coroutine-tests--prime-task new-channel))) + (while t + (let ((i (receive-from-channel channel))) + (unless (zerop (mod i prime)) + (send-to-channel new-channel i))))))) + +(ert-deftest coroutines-tests--prime () + (let ((channel (make-channel))) + (start-coroutine (lambda () (coroutine-tests--prime-task channel))) + (cl-loop for i from 2 to 100 + do (send-to-channel channel i)))) + +(ert-deftest coroutines-test--go-tour-1 () + (cl-flet ((say (s) + (dotimes (_ 5) + (sleep-for 0.1) + (message "%s" s)))) + (start-coroutine (lambda () (say "world"))) + (say "hello"))) + +(ert-deftest coroutines-test--go-tour-2 () + (cl-flet ((sum (s c) + (let ((sum 0)) + (dolist (v s) (cl-incf sum v)) + (send-to-channel c sum)))) + (let ((s '(7 2 8 -9 4 0)) + (c (make-channel))) + (start-coroutine (lambda () (sum (butlast s (/ (length s) 2)) c))) + (start-coroutine (lambda () (sum (nthcdr (/ (length s) 2) s) c))) + (let ((x (receive-from-channel c)) + (y (receive-from-channel c))) + (message "%d" (+ x y)) + (should (equal (+ x y) (apply #'+ s))))))) + +(ert-deftest coroutines-test--go-tour-3 () + (let ((ch (make-channel 2))) + (send-to-channel ch 1) + (send-to-channel ch 2) + (should (equal (receive-from-channel ch) 1)) + (should (equal (receive-from-channel ch) 2)))) + +(ert-deftest coroutines-test--go-tour-4 () + (cl-flet ((fibonacci (n c close) + (let ((x 0) (y 1)) + (dotimes (_ n) + (send-to-channel c x) + (cl-psetq x y + y (+ x y)))) + (send-to-channel close nil))) + (let ((c (make-channel 10)) + (close (make-channel 1))) + (start-coroutine (lambda () (fibonacci 10 c close))) + (cl-block nil + (while t + (select + ((receive c i) + (message "%d" i)) + ((receive close) + (cl-return)))))))) + +(ert-deftest coroutines-test--go-tour-5 () + (cl-flet ((fibonacci (c quit) + (cl-block nil + (let ((x 0) (y 1)) + (while t + (select + ((send c x) + (cl-psetq x y + y (+ x y))) + ((receive quit) + (message "quit") + (cl-return)))))))) + (let ((c (make-channel)) + (quit (make-channel))) + (start-coroutine + (lambda () + (dotimes (_ 10) + (message "%S" (receive-from-channel c))) + (send-to-channel quit nil))) + (fibonacci c quit)))) + +(defun coroutines-test--time-tick (d) + (cl-check-type d (and number cl-plus)) + (let ((c (make-channel 1))) + (run-at-time nil d (lambda () + (try-send-to-channel c (current-time)))) + c)) + +(defun coroutines-test--time-after (d) + (cl-check-type d (and number cl-plus)) + (let ((c (make-channel 1))) + (run-at-time d nil (lambda () + (try-send-to-channel c (current-time)))) + c)) + +(ert-deftest coroutines-test--go-tour-6 () + (cl-block nil + (let ((tick (coroutines-test--time-tick 0.1)) + (boom (coroutines-test--time-after 0.5))) + (while t + (select + ((receive tick) + (message "tick.")) + ((receive boom) + (message "BOOM!") + (cl-return)) + (default + (message " .") + (sleep-for 0.05))))))) + +(defvar coroutines-test--var nil) + +(ert-deftest coroutines-test--specbind () + (let ((coroutines-test--var 'main) + (channel (make-channel))) + (start-coroutine + (lambda () + (let ((coroutines-test--var 'child)) + (setq coroutines-test--var 'child-setq) + (receive-from-channel channel) + (should (equal coroutines-test--var 'child-setq))))) + (should (equal coroutines-test--var 'main)) + (setq coroutines-test--var 'main-setq) + (send-to-channel channel nil) + (should (equal coroutines-test--var 'main-setq)))) + +;; (ert-deftest coroutines-test--deadlock () +;; (let ((ch-1 (make-channel)) +;; (ch-2 (make-channel))) +;; (start-coroutine +;; (lambda () +;; (receive-from-channel ch-1) +;; (send-to-channel ch-2 nil))) +;; (receive-from-channel ch-2) +;; (send-to-channel ch-1 nil))) + +;;; coroutine-tests.el ends here -- 2.39.5