aboutsummaryrefslogtreecommitdiff
path: root/src/libthread
diff options
context:
space:
mode:
Diffstat (limited to 'src/libthread')
-rw-r--r--src/libthread/386.c21
-rw-r--r--src/libthread/FreeBSD-386.s18
-rw-r--r--src/libthread/LICENSE258
-rw-r--r--src/libthread/Make.Darwin-PowerMacintosh6
-rw-r--r--src/libthread/Make.FreeBSD-3867
-rw-r--r--src/libthread/Make.HP-UX-90006
-rw-r--r--src/libthread/Make.Linux-3867
-rw-r--r--src/libthread/Make.NetBSD-3867
-rw-r--r--src/libthread/Make.OSF1-alpha6
-rw-r--r--src/libthread/Make.SunOS-sun4u2
-rw-r--r--src/libthread/Make.SunOS-sun4u-cc6
-rw-r--r--src/libthread/Make.SunOS-sun4u-gcc6
-rw-r--r--src/libthread/Makefile125
-rw-r--r--src/libthread/Makefile.MID54
-rw-r--r--src/libthread/NOTICE19
-rw-r--r--src/libthread/README19
-rw-r--r--src/libthread/asm-FreeBSD-386.s49
-rw-r--r--src/libthread/asm-Linux-386.s1
-rw-r--r--src/libthread/bundle.ports42
-rw-r--r--src/libthread/channel.c485
-rw-r--r--src/libthread/chanprint.c18
-rw-r--r--src/libthread/create.c182
-rw-r--r--src/libthread/debug.c48
-rw-r--r--src/libthread/exec-unix.c124
-rw-r--r--src/libthread/exec.c77
-rw-r--r--src/libthread/exit.c63
-rw-r--r--src/libthread/getpid.c8
-rw-r--r--src/libthread/id.c135
-rw-r--r--src/libthread/iocall.c49
-rw-r--r--src/libthread/ioclose.c16
-rw-r--r--src/libthread/iodial.c21
-rw-r--r--src/libthread/ioopen.c20
-rw-r--r--src/libthread/ioproc.3179
-rw-r--r--src/libthread/ioproc.c74
-rw-r--r--src/libthread/ioread.c20
-rw-r--r--src/libthread/ioreadn.c21
-rw-r--r--src/libthread/iosleep.c16
-rw-r--r--src/libthread/iowrite.c21
-rw-r--r--src/libthread/kill.c89
-rw-r--r--src/libthread/label.h24
-rw-r--r--src/libthread/lib.c35
-rw-r--r--src/libthread/main.c124
-rw-r--r--src/libthread/memset.c8
-rw-r--r--src/libthread/memsetd.c8
-rw-r--r--src/libthread/mkfile2
-rw-r--r--src/libthread/note.c143
-rw-r--r--src/libthread/proctab.c64
-rw-r--r--src/libthread/ref.c13
-rw-r--r--src/libthread/rendez.c104
-rw-r--r--src/libthread/rpm.spec26
-rw-r--r--src/libthread/sched.c192
-rw-r--r--src/libthread/texec.c34
-rw-r--r--src/libthread/thread.3576
-rw-r--r--src/libthread/thread.h132
-rw-r--r--src/libthread/threadimpl.h219
-rwxr-xr-xsrc/libthread/tprimesbin0 -> 193082 bytes
-rw-r--r--src/libthread/tprimes.c62
57 files changed, 4091 insertions, 0 deletions
diff --git a/src/libthread/386.c b/src/libthread/386.c
new file mode 100644
index 00000000..2f391bff
--- /dev/null
+++ b/src/libthread/386.c
@@ -0,0 +1,21 @@
+#include "threadimpl.h"
+
+static void
+launcher386(void (*f)(void *arg), void *arg)
+{
+ (*f)(arg);
+ threadexits(nil);
+}
+
+void
+_threadinitstack(Thread *t, void (*f)(void*), void *arg)
+{
+ ulong *tos;
+
+ tos = (ulong*)&t->stk[t->stksize&~7];
+ *--tos = (ulong)arg;
+ *--tos = (ulong)f;
+ t->sched.pc = (ulong)launcher386;
+ t->sched.sp = (ulong)tos - 8; /* old PC and new PC */
+}
+
diff --git a/src/libthread/FreeBSD-386.s b/src/libthread/FreeBSD-386.s
new file mode 100644
index 00000000..624518e0
--- /dev/null
+++ b/src/libthread/FreeBSD-386.s
@@ -0,0 +1,18 @@
+
+.globl _xinc
+_xinc:
+ movl 4(%esp), %eax
+ lock incl 0(%eax)
+ ret
+
+.globl _xdec
+_xdec:
+ movl 4(%esp), %eax
+ lock decl 0(%eax)
+ jz iszero
+ movl %eax, 1
+ ret
+iszero:
+ movl %eax, 0
+ ret
+
diff --git a/src/libthread/LICENSE b/src/libthread/LICENSE
new file mode 100644
index 00000000..a5d7d87d
--- /dev/null
+++ b/src/libthread/LICENSE
@@ -0,0 +1,258 @@
+The Plan 9 software is provided under the terms of the
+Lucent Public License, Version 1.02, reproduced below,
+with the following exceptions:
+
+1. No right is granted to create derivative works of or
+ to redistribute (other than with the Plan 9 Operating System)
+ the screen imprinter fonts identified in subdirectory
+ /lib/font/bit/lucida and printer fonts (Lucida Sans Unicode, Lucida
+ Sans Italic, Lucida Sans Demibold, Lucida Typewriter, Lucida Sans
+ Typewriter83), identified in subdirectory /sys/lib/postscript/font.
+ These directories contain material copyrights by B&H Inc. and Y&Y Inc.
+
+2. The printer fonts identified in subdirectory /sys/lib/ghostscript/font
+ are subject to the GNU GPL, reproduced in the file /LICENSE.gpl.
+
+3. The ghostscript program in the subdirectory /sys/src/cmd/gs is
+ covered by the Aladdin Free Public License, reproduced in the file
+ /LICENSE.afpl.
+
+===================================================================
+
+Lucent Public License Version 1.02
+
+THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS PUBLIC
+LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE
+PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
+
+1. DEFINITIONS
+
+"Contribution" means:
+
+ a. in the case of Lucent Technologies Inc. ("LUCENT"), the Original
+ Program, and
+ b. in the case of each Contributor,
+
+ i. changes to the Program, and
+ ii. additions to the Program;
+
+ where such changes and/or additions to the Program were added to the
+ Program by such Contributor itself or anyone acting on such
+ Contributor's behalf, and the Contributor explicitly consents, in
+ accordance with Section 3C, to characterization of the changes and/or
+ additions as Contributions.
+
+"Contributor" means LUCENT and any other entity that has Contributed a
+Contribution to the Program.
+
+"Distributor" means a Recipient that distributes the Program,
+modifications to the Program, or any part thereof.
+
+"Licensed Patents" mean patent claims licensable by a Contributor
+which are necessarily infringed by the use or sale of its Contribution
+alone or when combined with the Program.
+
+"Original Program" means the original version of the software
+accompanying this Agreement as released by LUCENT, including source
+code, object code and documentation, if any.
+
+"Program" means the Original Program and Contributions or any part
+thereof
+
+"Recipient" means anyone who receives the Program under this
+Agreement, including all Contributors.
+
+2. GRANT OF RIGHTS
+
+ a. Subject to the terms of this Agreement, each Contributor hereby
+ grants Recipient a non-exclusive, worldwide, royalty-free copyright
+ license to reproduce, prepare derivative works of, publicly display,
+ publicly perform, distribute and sublicense the Contribution of such
+ Contributor, if any, and such derivative works, in source code and
+ object code form.
+
+ b. Subject to the terms of this Agreement, each Contributor hereby
+ grants Recipient a non-exclusive, worldwide, royalty-free patent
+ license under Licensed Patents to make, use, sell, offer to sell,
+ import and otherwise transfer the Contribution of such Contributor, if
+ any, in source code and object code form. The patent license granted
+ by a Contributor shall also apply to the combination of the
+ Contribution of that Contributor and the Program if, at the time the
+ Contribution is added by the Contributor, such addition of the
+ Contribution causes such combination to be covered by the Licensed
+ Patents. The patent license granted by a Contributor shall not apply
+ to (i) any other combinations which include the Contribution, nor to
+ (ii) Contributions of other Contributors. No hardware per se is
+ licensed hereunder.
+
+ c. Recipient understands that although each Contributor grants the
+ licenses to its Contributions set forth herein, no assurances are
+ provided by any Contributor that the Program does not infringe the
+ patent or other intellectual property rights of any other entity. Each
+ Contributor disclaims any liability to Recipient for claims brought by
+ any other entity based on infringement of intellectual property rights
+ or otherwise. As a condition to exercising the rights and licenses
+ granted hereunder, each Recipient hereby assumes sole responsibility
+ to secure any other intellectual property rights needed, if any. For
+ example, if a third party patent license is required to allow
+ Recipient to distribute the Program, it is Recipient's responsibility
+ to acquire that license before distributing the Program.
+
+ d. Each Contributor represents that to its knowledge it has sufficient
+ copyright rights in its Contribution, if any, to grant the copyright
+ license set forth in this Agreement.
+
+3. REQUIREMENTS
+
+A. Distributor may choose to distribute the Program in any form under
+this Agreement or under its own license agreement, provided that:
+
+ a. it complies with the terms and conditions of this Agreement;
+
+ b. if the Program is distributed in source code or other tangible
+ form, a copy of this Agreement or Distributor's own license agreement
+ is included with each copy of the Program; and
+
+ c. if distributed under Distributor's own license agreement, such
+ license agreement:
+
+ i. effectively disclaims on behalf of all Contributors all warranties
+ and conditions, express and implied, including warranties or
+ conditions of title and non-infringement, and implied warranties or
+ conditions of merchantability and fitness for a particular purpose;
+ ii. effectively excludes on behalf of all Contributors all liability
+ for damages, including direct, indirect, special, incidental and
+ consequential damages, such as lost profits; and
+ iii. states that any provisions which differ from this Agreement are
+ offered by that Contributor alone and not by any other party.
+
+B. Each Distributor must include the following in a conspicuous
+ location in the Program:
+
+ Copyright (C) 2003, Lucent Technologies Inc. and others. All Rights
+ Reserved.
+
+C. In addition, each Contributor must identify itself as the
+originator of its Contribution in a manner that reasonably allows
+subsequent Recipients to identify the originator of the Contribution.
+Also, each Contributor must agree that the additions and/or changes
+are intended to be a Contribution. Once a Contribution is contributed,
+it may not thereafter be revoked.
+
+4. COMMERCIAL DISTRIBUTION
+
+Commercial distributors of software may accept certain
+responsibilities with respect to end users, business partners and the
+like. While this license is intended to facilitate the commercial use
+of the Program, the Distributor who includes the Program in a
+commercial product offering should do so in a manner which does not
+create potential liability for Contributors. Therefore, if a
+Distributor includes the Program in a commercial product offering,
+such Distributor ("Commercial Distributor") hereby agrees to defend
+and indemnify every Contributor ("Indemnified Contributor") against
+any losses, damages and costs (collectively"Losses") arising from
+claims, lawsuits and other legal actions brought by a third party
+against the Indemnified Contributor to the extent caused by the acts
+or omissions of such Commercial Distributor in connection with its
+distribution of the Program in a commercial product offering. The
+obligations in this section do not apply to any claims or Losses
+relating to any actual or alleged intellectual property infringement.
+In order to qualify, an Indemnified Contributor must: a) promptly
+notify the Commercial Distributor in writing of such claim, and b)
+allow the Commercial Distributor to control, and cooperate with the
+Commercial Distributor in, the defense and any related settlement
+negotiations. The Indemnified Contributor may participate in any such
+claim at its own expense.
+
+For example, a Distributor might include the Program in a commercial
+product offering, Product X. That Distributor is then a Commercial
+Distributor. If that Commercial Distributor then makes performance
+claims, or offers warranties related to Product X, those performance
+claims and warranties are such Commercial Distributor's responsibility
+alone. Under this section, the Commercial Distributor would have to
+defend claims against the Contributors related to those performance
+claims and warranties, and if a court requires any Contributor to pay
+any damages as a result, the Commercial Distributor must pay those
+damages.
+
+5. NO WARRANTY
+
+EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS
+PROVIDED ON AN"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY
+WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY
+OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely
+responsible for determining the appropriateness of using and
+distributing the Program and assumes all risks associated with its
+exercise of rights under this Agreement, including but not limited to
+the risks and costs of program errors, compliance with applicable
+laws, damage to or loss of data, programs or equipment, and
+unavailability or interruption of operations.
+
+6. DISCLAIMER OF LIABILITY
+
+EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR
+ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING
+WITHOUT LIMITATION LOST PROFITS), 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 OR
+DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED
+HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+7. EXPORT CONTROL
+
+Recipient agrees that Recipient alone is responsible for compliance
+with the United States export administration regulations (and the
+export control laws and regulation of any other countries).
+
+8. GENERAL
+
+If any provision of this Agreement is invalid or unenforceable under
+applicable law, it shall not affect the validity or enforceability of
+the remainder of the terms of this Agreement, and without further
+action by the parties hereto, such provision shall be reformed to the
+minimum extent necessary to make such provision valid and enforceable.
+
+If Recipient institutes patent litigation against a Contributor with
+respect to a patent applicable to software (including a cross-claim or
+counterclaim in a lawsuit), then any patent licenses granted by that
+Contributor to such Recipient under this Agreement shall terminate as
+of the date such litigation is filed. In addition, if Recipient
+institutes patent litigation against any entity (including a
+cross-claim or counterclaim in a lawsuit) alleging that the Program
+itself (excluding combinations of the Program with other software or
+hardware) infringes such Recipient's patent(s), then such Recipient's
+rights granted under Section 2(b) shall terminate as of the date such
+litigation is filed.
+
+All Recipient's rights under this Agreement shall terminate if it
+fails to comply with any of the material terms or conditions of this
+Agreement and does not cure such failure in a reasonable period of
+time after becoming aware of such noncompliance. If all Recipient's
+rights under this Agreement terminate, Recipient agrees to cease use
+and distribution of the Program as soon as reasonably practicable.
+However, Recipient's obligations under this Agreement and any licenses
+granted by Recipient relating to the Program shall continue and
+survive.
+
+LUCENT may publish new versions (including revisions) of this
+Agreement from time to time. Each new version of the Agreement will be
+given a distinguishing version number. The Program (including
+Contributions) may always be distributed subject to the version of the
+Agreement under which it was received. In addition, after a new
+version of the Agreement is published, Contributor may elect to
+distribute the Program (including its Contributions) under the new
+version. No one other than LUCENT has the right to modify this
+Agreement. Except as expressly stated in Sections 2(a) and 2(b) above,
+Recipient receives no rights or licenses to the intellectual property
+of any Contributor under this Agreement, whether expressly, by
+implication, estoppel or otherwise. All rights in the Program not
+expressly granted under this Agreement are reserved.
+
+This Agreement is governed by the laws of the State of New York and
+the intellectual property laws of the United States of America. No
+party to this Agreement will bring a legal action under this Agreement
+more than one year after the cause of action arose. Each party waives
+its rights to a jury trial in any resulting litigation.
+
diff --git a/src/libthread/Make.Darwin-PowerMacintosh b/src/libthread/Make.Darwin-PowerMacintosh
new file mode 100644
index 00000000..14b8d4e7
--- /dev/null
+++ b/src/libthread/Make.Darwin-PowerMacintosh
@@ -0,0 +1,6 @@
+CC=gcc
+CFLAGS+=-Wall -Wno-missing-braces -Wno-parentheses -Wno-switch -O2 -g -c -I. -I${PREFIX}/include
+O=o
+AR=ar
+ARFLAGS=rvc
+NAN=nan64.$O
diff --git a/src/libthread/Make.FreeBSD-386 b/src/libthread/Make.FreeBSD-386
new file mode 100644
index 00000000..087ed3ab
--- /dev/null
+++ b/src/libthread/Make.FreeBSD-386
@@ -0,0 +1,7 @@
+CC=gcc
+CFLAGS+=-Wall -Wno-missing-braces -Wno-parentheses -Wno-switch -O2 -g -c -I. -I$(PREFIX)/include
+O=o
+AR=ar
+ARFLAGS=rvc
+NAN=nan64.$O # default, can be overriden by Make.$(SYSNAME)
+NAN=nan64.$O
diff --git a/src/libthread/Make.HP-UX-9000 b/src/libthread/Make.HP-UX-9000
new file mode 100644
index 00000000..edbdc111
--- /dev/null
+++ b/src/libthread/Make.HP-UX-9000
@@ -0,0 +1,6 @@
+CC=cc
+CFLAGS=-O -c -Ae -I.
+O=o
+AR=ar
+ARFLAGS=rvc
+NAN=nan64.$O
diff --git a/src/libthread/Make.Linux-386 b/src/libthread/Make.Linux-386
new file mode 100644
index 00000000..74b0252c
--- /dev/null
+++ b/src/libthread/Make.Linux-386
@@ -0,0 +1,7 @@
+CC=gcc
+CFLAGS+=-Wall -Wno-missing-braces -Wno-parentheses -Wno-switch -O2 -g -c -I.
+O=o
+AR=ar
+ARFLAGS=rvc
+NAN=nan64.$O # default, can be overriden by Make.$(SYSNAME)
+NAN=nan64.$O
diff --git a/src/libthread/Make.NetBSD-386 b/src/libthread/Make.NetBSD-386
new file mode 100644
index 00000000..087ed3ab
--- /dev/null
+++ b/src/libthread/Make.NetBSD-386
@@ -0,0 +1,7 @@
+CC=gcc
+CFLAGS+=-Wall -Wno-missing-braces -Wno-parentheses -Wno-switch -O2 -g -c -I. -I$(PREFIX)/include
+O=o
+AR=ar
+ARFLAGS=rvc
+NAN=nan64.$O # default, can be overriden by Make.$(SYSNAME)
+NAN=nan64.$O
diff --git a/src/libthread/Make.OSF1-alpha b/src/libthread/Make.OSF1-alpha
new file mode 100644
index 00000000..3d45279b
--- /dev/null
+++ b/src/libthread/Make.OSF1-alpha
@@ -0,0 +1,6 @@
+CC=cc
+CFLAGS+=-g -c -I.
+O=o
+AR=ar
+ARFLAGS=rvc
+NAN=nan64.$O
diff --git a/src/libthread/Make.SunOS-sun4u b/src/libthread/Make.SunOS-sun4u
new file mode 100644
index 00000000..c5fe67b8
--- /dev/null
+++ b/src/libthread/Make.SunOS-sun4u
@@ -0,0 +1,2 @@
+include Make.SunOS-sun4u-$(CC)
+NAN=nan64.$O
diff --git a/src/libthread/Make.SunOS-sun4u-cc b/src/libthread/Make.SunOS-sun4u-cc
new file mode 100644
index 00000000..829301de
--- /dev/null
+++ b/src/libthread/Make.SunOS-sun4u-cc
@@ -0,0 +1,6 @@
+CC=cc
+CFLAGS+=-g -c -I. -O
+O=o
+AR=ar
+ARFLAGS=rvc
+NAN=nan64.$O
diff --git a/src/libthread/Make.SunOS-sun4u-gcc b/src/libthread/Make.SunOS-sun4u-gcc
new file mode 100644
index 00000000..5c415948
--- /dev/null
+++ b/src/libthread/Make.SunOS-sun4u-gcc
@@ -0,0 +1,6 @@
+CC=gcc
+CFLAGS+=-Wall -Wno-missing-braces -Wno-parentheses -Wno-switch -O2 -g -c
+O=o
+AR=ar
+ARFLAGS=rvc
+NAN=nan64.$O
diff --git a/src/libthread/Makefile b/src/libthread/Makefile
new file mode 100644
index 00000000..ebccc1ac
--- /dev/null
+++ b/src/libthread/Makefile
@@ -0,0 +1,125 @@
+
+# this works in gnu make
+SYSNAME:=${shell uname}
+OBJTYPE:=${shell uname -m | sed 's;i.86;386;; s;/.*;;; s; ;;g'}
+
+# this works in bsd make
+SYSNAME!=uname
+OBJTYPE!=uname -m | sed 's;i.86;386;; s;/.*;;; s; ;;g'
+
+# the gnu rules will mess up bsd but not vice versa,
+# hence the gnu rules come first.
+
+include Make.$(SYSNAME)-$(OBJTYPE)
+
+PREFIX=/usr/local
+
+NUKEFILES=
+
+TGZFILES=
+
+LIB=libthread.a
+VERSION=2.0
+PORTPLACE=devel/libthread
+NAME=libthread
+
+OFILES=\
+ $(OBJTYPE).$O\
+ asm-$(SYSNAME)-$(OBJTYPE).$O\
+ channel.$O\
+ chanprint.$O\
+ create.$O\
+ debug.$O\
+ exec-unix.$O\
+ exit.$O\
+ getpid.$O\
+ id.$O\
+ iocall.$O\
+ ioclose.$O\
+ ioopen.$O\
+ ioproc.$O\
+ ioread.$O\
+ ioreadn.$O\
+ iowrite.$O\
+ kill.$O\
+ lib.$O\
+ main.$O\
+ memset.$O\
+ memsetd.$O\
+ note.$O\
+ proctab.$O\
+ ref.$O\
+ rendez.$O\
+ sched.$O\
+
+HFILES=\
+ thread.h\
+ label.h\
+ threadimpl.h\
+
+all: $(LIB)
+
+install: $(LIB)
+ test -d $(PREFIX)/man/man3 || mkdir $(PREFIX)/man/man3
+ install -m 0644 thread.3 $(PREFIX)/man/man3/thread.3
+ install -m 0644 ioproc.3 $(PREFIX)/man/man3/ioproc.3
+ install -m 0644 thread.h $(PREFIX)/include/thread.h
+ install -m 0644 $(LIB) $(PREFIX)/lib/$(LIB)
+
+tprimes: $(LIB) tprimes.$O
+ $(CC) -o tprimes tprimes.$O $(LIB) -L$(PREFIX)/lib -l9 -lfmt -lutf
+
+texec: $(LIB) texec.$O
+ $(CC) -o texec texec.$O $(LIB) -L$(PREFIX)/lib -l9 -lfmt -lutf
+
+$(LIB): $(OFILES)
+ $(AR) $(ARFLAGS) $(LIB) $(OFILES)
+
+NUKEFILES+=$(LIB)
+.c.$O:
+ $(CC) $(CFLAGS) -I/usr/X11R6/include -I../sam -I$(PREFIX)/include $*.c
+
+%.$O: %.c
+ $(CC) $(CFLAGS) -I/usr/X11R6/include -I../sam -I$(PREFIX)/include $*.c
+
+
+$(OFILES): $(HFILES)
+
+tgz:
+ rm -rf $(NAME)-$(VERSION)
+ mkdir $(NAME)-$(VERSION)
+ cp Makefile Make.* README LICENSE NOTICE *.[ch137] rpm.spec bundle.ports $(TGZFILES) $(NAME)-$(VERSION)
+ tar cf - $(NAME)-$(VERSION) | gzip >$(NAME)-$(VERSION).tgz
+ rm -rf $(NAME)-$(VERSION)
+
+clean:
+ rm -f $(OFILES) $(LIB)
+
+nuke:
+ rm -f $(OFILES) *.tgz *.rpm $(NUKEFILES)
+
+rpm:
+ make tgz
+ cp $(NAME)-$(VERSION).tgz /usr/src/RPM/SOURCES
+ rpm -ba rpm.spec
+ cp /usr/src/RPM/SRPMS/$(NAME)-$(VERSION)-1.src.rpm .
+ cp /usr/src/RPM/RPMS/i586/$(NAME)-$(VERSION)-1.i586.rpm .
+ scp *.rpm rsc@amsterdam.lcs.mit.edu:public_html/software
+
+PORTDIR=/usr/ports/$(PORTPLACE)
+
+ports:
+ make tgz
+ rm -rf $(PORTDIR)
+ mkdir $(PORTDIR)
+ cp $(NAME)-$(VERSION).tgz /usr/ports/distfiles
+ cat bundle.ports | (cd $(PORTDIR) && awk '$$1=="---" && $$3=="---" { ofile=$$2; next} {if(ofile) print >ofile}')
+ (cd $(PORTDIR); make makesum)
+ (cd $(PORTDIR); make)
+ (cd $(PORTDIR); /usr/local/bin/portlint)
+ rm -rf $(PORTDIR)/work
+ shar `find $(PORTDIR)` > ports.shar
+ (cd $(PORTDIR); tar cf - *) | gzip >$(NAME)-$(VERSION)-ports.tgz
+ scp *.tgz rsc@amsterdam.lcs.mit.edu:public_html/software
+
+.phony: all clean nuke install tgz rpm ports
diff --git a/src/libthread/Makefile.MID b/src/libthread/Makefile.MID
new file mode 100644
index 00000000..05c34167
--- /dev/null
+++ b/src/libthread/Makefile.MID
@@ -0,0 +1,54 @@
+LIB=libthread.a
+VERSION=2.0
+PORTPLACE=devel/libthread
+NAME=libthread
+
+OFILES=\
+ $(OBJTYPE).$O\
+ asm-$(SYSNAME)-$(OBJTYPE).$O\
+ channel.$O\
+ chanprint.$O\
+ create.$O\
+ debug.$O\
+ exec-unix.$O\
+ exit.$O\
+ getpid.$O\
+ id.$O\
+ iocall.$O\
+ ioclose.$O\
+ ioopen.$O\
+ ioproc.$O\
+ ioread.$O\
+ ioreadn.$O\
+ iowrite.$O\
+ kill.$O\
+ lib.$O\
+ main.$O\
+ memset.$O\
+ memsetd.$O\
+ note.$O\
+ proctab.$O\
+ ref.$O\
+ rendez.$O\
+ sched.$O\
+
+HFILES=\
+ thread.h\
+ label.h\
+ threadimpl.h\
+
+all: $(LIB)
+
+install: $(LIB)
+ test -d $(PREFIX)/man/man3 || mkdir $(PREFIX)/man/man3
+ install -m 0644 thread.3 $(PREFIX)/man/man3/thread.3
+ install -m 0644 ioproc.3 $(PREFIX)/man/man3/ioproc.3
+ install -m 0644 thread.h $(PREFIX)/include/thread.h
+ install -m 0644 $(LIB) $(PREFIX)/lib/$(LIB)
+
+tprimes: $(LIB) tprimes.$O
+ $(CC) -o tprimes tprimes.$O $(LIB) -L$(PREFIX)/lib -l9 -lfmt -lutf
+
+texec: $(LIB) texec.$O
+ $(CC) -o texec texec.$O $(LIB) -L$(PREFIX)/lib -l9 -lfmt -lutf
+
diff --git a/src/libthread/NOTICE b/src/libthread/NOTICE
new file mode 100644
index 00000000..8bf69d6a
--- /dev/null
+++ b/src/libthread/NOTICE
@@ -0,0 +1,19 @@
+/*
+ * The authors of this software are Russ Cox, Sape Mullender, and Rob Pike.
+ * 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.
+*/
+
+This is a Unix port of the Plan 9 thread library.
+
+Please send comments about the packaging
+to Russ Cox <rsc@post.harvard.edu>.
+
diff --git a/src/libthread/README b/src/libthread/README
new file mode 100644
index 00000000..8bf69d6a
--- /dev/null
+++ b/src/libthread/README
@@ -0,0 +1,19 @@
+/*
+ * The authors of this software are Russ Cox, Sape Mullender, and Rob Pike.
+ * 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.
+*/
+
+This is a Unix port of the Plan 9 thread library.
+
+Please send comments about the packaging
+to Russ Cox <rsc@post.harvard.edu>.
+
diff --git a/src/libthread/asm-FreeBSD-386.s b/src/libthread/asm-FreeBSD-386.s
new file mode 100644
index 00000000..7cad85cc
--- /dev/null
+++ b/src/libthread/asm-FreeBSD-386.s
@@ -0,0 +1,49 @@
+.globl _setlabel
+.type _setlabel,@function
+
+_setlabel:
+ movl 4(%esp), %eax
+ movl 0(%esp), %edx
+ movl %edx, 0(%eax)
+ movl %ebx, 4(%eax)
+ movl %esp, 8(%eax)
+ movl %ebp, 12(%eax)
+ movl %esi, 16(%eax)
+ movl %edi, 20(%eax)
+ xorl %eax, %eax
+ ret
+
+.globl _gotolabel
+.type _gotolabel,@function
+
+_gotolabel:
+ movl 4(%esp), %edx
+ movl 0(%edx), %ecx
+ movl 4(%edx), %ebx
+ movl 8(%edx), %esp
+ movl 12(%edx), %ebp
+ movl 16(%edx), %esi
+ movl 20(%edx), %edi
+ xorl %eax, %eax
+ incl %eax
+ movl %ecx, 0(%esp)
+ ret
+
+
+.globl _xinc
+_xinc:
+ movl 4(%esp), %eax
+ lock incl 0(%eax)
+ ret
+
+.globl _xdec
+_xdec:
+ movl 4(%esp), %eax
+ lock decl 0(%eax)
+ jz iszero
+ movl %eax, 1
+ ret
+iszero:
+ movl %eax, 0
+ ret
+
diff --git a/src/libthread/asm-Linux-386.s b/src/libthread/asm-Linux-386.s
new file mode 100644
index 00000000..75d965bf
--- /dev/null
+++ b/src/libthread/asm-Linux-386.s
@@ -0,0 +1 @@
+.include "asm-FreeBSD-386.s"
diff --git a/src/libthread/bundle.ports b/src/libthread/bundle.ports
new file mode 100644
index 00000000..adfeb904
--- /dev/null
+++ b/src/libthread/bundle.ports
@@ -0,0 +1,42 @@
+--- Makefile ---
+# New ports collection makefile for: libthread
+# Date Created: 11 Feb 2003
+# Whom: rsc
+#
+
+PORTNAME= libthread
+PORTVERSION= 1.0
+CATEGORIES= devel
+MASTER_SITES= http://pdos.lcs.mit.edu/~rsc/software/
+DISTNAME= libthread
+EXTRACT_SUFX= .tgz
+
+MAINTAINER= rsc@post.harvard.edu
+
+MAN3= print.3 fmtinstall.3
+MLINKS= XXX
+USE_REINPLACE= XXX (wkj says yes)
+
+.include <bsd.port.pre.mk>
+
+post-patch:
+ ${REINPLACE_CMD} -e 's,@@LOCAL@@,${PREFIX},g' ${WRKSRC}/Makefile
+
+.include <bsd.port.post.mk>
+
+--- pkg-comment ---
+Plan 9 thread library
+--- pkg-descr ---
+Libthread is a port of Plan 9's thread library.
+
+WWW: http://pdos.lcs.mit.edu/~rsc/software/
+WWW: http://plan9.bell-labs.com/magic/man2html/2/thread
+
+Russ Cox
+rsc@post.harvard.edu
+--- pkg-plist ---
+lib/libthread.a
+include/thread.h
+--- /dev/null ---
+This is just a way to make sure blank lines don't
+creep into pkg-plist.
diff --git a/src/libthread/channel.c b/src/libthread/channel.c
new file mode 100644
index 00000000..384f23fd
--- /dev/null
+++ b/src/libthread/channel.c
@@ -0,0 +1,485 @@
+#include "threadimpl.h"
+
+static Lock chanlock; /* central channel access lock */
+
+static void enqueue(Alt*, Channel**);
+static void dequeue(Alt*);
+static int altexec(Alt*, int);
+
+int _threadhighnentry;
+int _threadnalt;
+
+static int
+canexec(Alt *a)
+{
+ int i, otherop;
+ Channel *c;
+
+ c = a->c;
+ /* are there senders or receivers blocked? */
+ otherop = (CHANSND+CHANRCV) - a->op;
+ for(i=0; i<c->nentry; i++)
+ if(c->qentry[i] && c->qentry[i]->op==otherop && *c->qentry[i]->tag==nil){
+ _threaddebug(DBGCHAN, "can rendez alt %p chan %p", a, c);
+ return 1;
+ }
+
+ /* is there room in the channel? */
+ if((a->op==CHANSND && c->n < c->s)
+ || (a->op==CHANRCV && c->n > 0)){
+ _threaddebug(DBGCHAN, "can buffer alt %p chan %p", a, c);
+ return 1;
+ }
+
+ return 0;
+}
+
+static void
+_chanfree(Channel *c)
+{
+ int i, inuse;
+
+ inuse = 0;
+ for(i = 0; i < c->nentry; i++)
+ if(c->qentry[i])
+ inuse = 1;
+ if(inuse)
+ c->freed = 1;
+ else{
+ if(c->qentry)
+ free(c->qentry);
+ free(c);
+ }
+}
+
+void
+chanfree(Channel *c)
+{
+ lock(&chanlock);
+ _chanfree(c);
+ unlock(&chanlock);
+}
+
+int
+chaninit(Channel *c, int elemsize, int elemcnt)
+{
+ if(elemcnt < 0 || elemsize <= 0 || c == nil)
+ return -1;
+ c->f = 0;
+ c->n = 0;
+ c->freed = 0;
+ c->e = elemsize;
+ c->s = elemcnt;
+ _threaddebug(DBGCHAN, "chaninit %p", c);
+ return 1;
+}
+
+Channel*
+chancreate(int elemsize, int elemcnt)
+{
+ Channel *c;
+
+ if(elemcnt < 0 || elemsize <= 0)
+ return nil;
+ c = _threadmalloc(sizeof(Channel)+elemsize*elemcnt, 1);
+ c->e = elemsize;
+ c->s = elemcnt;
+ _threaddebug(DBGCHAN, "chancreate %p", c);
+ return c;
+}
+
+int
+alt(Alt *alts)
+{
+ Alt *a, *xa;
+ Channel *volatile c;
+ int n, s;
+ ulong r;
+ Thread *t;
+
+ /*
+ * The point of going splhi here is that note handlers
+ * might reasonably want to use channel operations,
+ * but that will hang if the note comes while we hold the
+ * chanlock. Instead, we delay the note until we've dropped
+ * the lock.
+ */
+ t = _threadgetproc()->thread;
+ if(t->moribund || _threadexitsallstatus)
+ yield(); /* won't return */
+ s = _procsplhi();
+ lock(&chanlock);
+ t->alt = alts;
+ t->chan = Chanalt;
+
+ /* test whether any channels can proceed */
+ n = 0;
+ a = nil;
+
+ for(xa=alts; xa->op!=CHANEND && xa->op!=CHANNOBLK; xa++){
+ xa->entryno = -1;
+ if(xa->op == CHANNOP)
+ continue;
+
+ c = xa->c;
+ if(c==nil){
+ unlock(&chanlock);
+ _procsplx(s);
+ t->chan = Channone;
+ return -1;
+ }
+ if(canexec(xa))
+ if(nrand(++n) == 0)
+ a = xa;
+ }
+
+ if(a==nil){
+ /* nothing can proceed */
+ if(xa->op == CHANNOBLK){
+ unlock(&chanlock);
+ _procsplx(s);
+ t->chan = Channone;
+_threadnalt++;
+ return xa - alts;
+ }
+
+ /* enqueue on all channels. */
+ c = nil;
+ for(xa=alts; xa->op!=CHANEND; xa++){
+ if(xa->op==CHANNOP)
+ continue;
+ enqueue(xa, (Channel**)&c);
+ }
+
+ /*
+ * wait for successful rendezvous.
+ * we can't just give up if the rendezvous
+ * is interrupted -- someone else might come
+ * along and try to rendezvous with us, so
+ * we need to be here.
+ */
+ Again:
+ unlock(&chanlock);
+ _procsplx(s);
+ r = _threadrendezvous((ulong)&c, 0);
+ s = _procsplhi();
+ lock(&chanlock);
+
+ if(r==~0){ /* interrupted */
+ if(c!=nil) /* someone will meet us; go back */
+ goto Again;
+ c = (Channel*)~0; /* so no one tries to meet us */
+ }
+
+ /* dequeue from channels, find selected one */
+ a = nil;
+ for(xa=alts; xa->op!=CHANEND; xa++){
+ if(xa->op==CHANNOP)
+ continue;
+ if(xa->c == c)
+ a = xa;
+ dequeue(xa);
+ }
+ unlock(&chanlock);
+ _procsplx(s);
+ if(a == nil){ /* we were interrupted */
+ assert(c==(Channel*)~0);
+ return -1;
+ }
+ }else{
+ altexec(a, s); /* unlocks chanlock, does splx */
+ }
+ _sched();
+ t->chan = Channone;
+_threadnalt++;
+ return a - alts;
+}
+
+static int
+runop(int op, Channel *c, void *v, int nb)
+{
+ int r;
+ Alt a[2];
+
+ /*
+ * we could do this without calling alt,
+ * but the only reason would be performance,
+ * and i'm not convinced it matters.
+ */
+ a[0].op = op;
+ a[0].c = c;
+ a[0].v = v;
+ a[1].op = CHANEND;
+ if(nb)
+ a[1].op = CHANNOBLK;
+ switch(r=alt(a)){
+ case -1: /* interrupted */
+ return -1;
+ case 1: /* nonblocking, didn't accomplish anything */
+ assert(nb);
+ return 0;
+ case 0:
+ return 1;
+ default:
+ fprint(2, "ERROR: channel alt returned %d\n", r);
+ abort();
+ return -1;
+ }
+}
+
+int
+recv(Channel *c, void *v)
+{
+ return runop(CHANRCV, c, v, 0);
+}
+
+int
+nbrecv(Channel *c, void *v)
+{
+ return runop(CHANRCV, c, v, 1);
+}
+
+int
+send(Channel *c, void *v)
+{
+ return runop(CHANSND, c, v, 0);
+}
+
+int
+nbsend(Channel *c, void *v)
+{
+ return runop(CHANSND, c, v, 1);
+}
+
+static void
+channelsize(Channel *c, int sz)
+{
+ if(c->e != sz){
+ fprint(2, "expected channel with elements of size %d, got size %d",
+ sz, c->e);
+ abort();
+ }
+}
+
+int
+sendul(Channel *c, ulong v)
+{
+ channelsize(c, sizeof(ulong));
+ return send(c, &v);
+}
+
+ulong
+recvul(Channel *c)
+{
+ ulong v;
+
+ channelsize(c, sizeof(ulong));
+ if(recv(c, &v) < 0)
+ return ~0;
+ return v;
+}
+
+int
+sendp(Channel *c, void *v)
+{
+ channelsize(c, sizeof(void*));
+ return send(c, &v);
+}
+
+void*
+recvp(Channel *c)
+{
+ void *v;
+
+ channelsize(c, sizeof(void*));
+ if(recv(c, &v) < 0)
+ return nil;
+ return v;
+}
+
+int
+nbsendul(Channel *c, ulong v)
+{
+ channelsize(c, sizeof(ulong));
+ return nbsend(c, &v);
+}
+
+ulong
+nbrecvul(Channel *c)
+{
+ ulong v;
+
+ channelsize(c, sizeof(ulong));
+ if(nbrecv(c, &v) == 0)
+ return 0;
+ return v;
+}
+
+int
+nbsendp(Channel *c, void *v)
+{
+ channelsize(c, sizeof(void*));
+ return nbsend(c, &v);
+}
+
+void*
+nbrecvp(Channel *c)
+{
+ void *v;
+
+ channelsize(c, sizeof(void*));
+ if(nbrecv(c, &v) == 0)
+ return nil;
+ return v;
+}
+
+static int
+emptyentry(Channel *c)
+{
+ int i, extra;
+
+ assert((c->nentry==0 && c->qentry==nil) || (c->nentry && c->qentry));
+
+ for(i=0; i<c->nentry; i++)
+ if(c->qentry[i]==nil)
+ return i;
+
+ extra = 16;
+ c->nentry += extra;
+if(c->nentry > _threadhighnentry) _threadhighnentry = c->nentry;
+ c->qentry = realloc((void*)c->qentry, c->nentry*sizeof(c->qentry[0]));
+ if(c->qentry == nil)
+ sysfatal("realloc channel entries: %r");
+ _threadmemset(&c->qentry[i], 0, extra*sizeof(c->qentry[0]));
+ return i;
+}
+
+static void
+enqueue(Alt *a, Channel **c)
+{
+ int i;
+
+ _threaddebug(DBGCHAN, "Queuing alt %p on channel %p", a, a->c);
+ a->tag = c;
+ i = emptyentry(a->c);
+ a->c->qentry[i] = a;
+}
+
+static void
+dequeue(Alt *a)
+{
+ int i;
+ Channel *c;
+
+ c = a->c;
+ for(i=0; i<c->nentry; i++)
+ if(c->qentry[i]==a){
+ _threaddebug(DBGCHAN, "Dequeuing alt %p from channel %p", a, a->c);
+ c->qentry[i] = nil;
+ if(c->freed)
+ _chanfree(c);
+ return;
+ }
+}
+
+static void*
+altexecbuffered(Alt *a, int willreplace)
+{
+ uchar *v;
+ Channel *c;
+
+ c = a->c;
+ /* use buffered channel queue */
+ if(a->op==CHANRCV && c->n > 0){
+ _threaddebug(DBGCHAN, "buffer recv alt %p chan %p", a, c);
+ v = c->v + c->e*(c->f%c->s);
+ if(!willreplace)
+ c->n--;
+ c->f++;
+ return v;
+ }
+ if(a->op==CHANSND && c->n < c->s){
+ _threaddebug(DBGCHAN, "buffer send alt %p chan %p", a, c);
+ v = c->v + c->e*((c->f+c->n)%c->s);
+ if(!willreplace)
+ c->n++;
+ return v;
+ }
+ abort();
+ return nil;
+}
+
+static void
+altcopy(void *dst, void *src, int sz)
+{
+ if(dst){
+ if(src)
+ memmove(dst, src, sz);
+ else
+ _threadmemset(dst, 0, sz);
+ }
+}
+
+static int
+altexec(Alt *a, int spl)
+{
+ volatile Alt *b;
+ int i, n, otherop;
+ Channel *c;
+ void *me, *waiter, *buf;
+
+ c = a->c;
+
+ /* rendezvous with others */
+ otherop = (CHANSND+CHANRCV) - a->op;
+ n = 0;
+ b = nil;
+ me = a->v;
+ for(i=0; i<c->nentry; i++)
+ if(c->qentry[i] && c->qentry[i]->op==otherop && *c->qentry[i]->tag==nil)
+ if(nrand(++n) == 0)
+ b = c->qentry[i];
+ if(b != nil){
+ _threaddebug(DBGCHAN, "rendez %s alt %p chan %p alt %p", a->op==CHANRCV?"recv":"send", a, c, b);
+ waiter = b->v;
+ if(c->s && c->n){
+ /*
+ * if buffer is full and there are waiters
+ * and we're meeting a waiter,
+ * we must be receiving.
+ *
+ * we use the value in the channel buffer,
+ * copy the waiter's value into the channel buffer
+ * on behalf of the waiter, and then wake the waiter.
+ */
+ if(a->op!=CHANRCV)
+ abort();
+ buf = altexecbuffered(a, 1);
+ altcopy(me, buf, c->e);
+ altcopy(buf, waiter, c->e);
+ }else{
+ if(a->op==CHANRCV)
+ altcopy(me, waiter, c->e);
+ else
+ altcopy(waiter, me, c->e);
+ }
+ *b->tag = c; /* commits us to rendezvous */
+ _threaddebug(DBGCHAN, "unlocking the chanlock");
+ unlock(&chanlock);
+ _procsplx(spl);
+ _threaddebug(DBGCHAN, "chanlock is %lud", *(ulong*)&chanlock);
+ while(_threadrendezvous((ulong)b->tag, 0) == ~0)
+ ;
+ return 1;
+ }
+
+ buf = altexecbuffered(a, 0);
+ if(a->op==CHANRCV)
+ altcopy(me, buf, c->e);
+ else
+ altcopy(buf, me, c->e);
+
+ unlock(&chanlock);
+ _procsplx(spl);
+ return 1;
+}
diff --git a/src/libthread/chanprint.c b/src/libthread/chanprint.c
new file mode 100644
index 00000000..af9e8103
--- /dev/null
+++ b/src/libthread/chanprint.c
@@ -0,0 +1,18 @@
+#include "threadimpl.h"
+
+int
+chanprint(Channel *c, char *fmt, ...)
+{
+ va_list arg;
+ char *p;
+ int n;
+
+ va_start(arg, fmt);
+ p = vsmprint(fmt, arg);
+ va_end(arg);
+ if(p == nil)
+ sysfatal("vsmprint failed: %r");
+ n = sendp(c, p);
+ yield(); /* let recipient handle message immediately */
+ return n;
+}
diff --git a/src/libthread/create.c b/src/libthread/create.c
new file mode 100644
index 00000000..ab803a2c
--- /dev/null
+++ b/src/libthread/create.c
@@ -0,0 +1,182 @@
+#include "threadimpl.h"
+
+#define free
+Pqueue _threadpq;
+
+static int nextID(void);
+
+/*
+ * Create and initialize a new Thread structure attached to a given proc.
+ */
+
+typedef struct Stack Stack;
+struct Stack {
+ ulong magic;
+ Thread *thr;
+ Stack *next;
+ uchar buf[STKSIZE-12];
+};
+
+static Stack *stkfree;
+static Lock stklock;
+
+void
+_stackfree(void *v)
+{
+ Stack *s;
+
+ s = v;
+ lock(&stklock);
+ s->thr = nil;
+ s->magic = 0;
+ s->next = stkfree;
+ stkfree = s;
+ unlock(&stklock);
+}
+
+static Stack*
+stackalloc(void)
+{
+ char *buf;
+ Stack *s;
+ int i;
+
+ lock(&stklock);
+ while(stkfree == nil){
+ unlock(&stklock);
+ assert(STKSIZE == sizeof(Stack));
+ buf = malloc(STKSIZE+128*STKSIZE);
+ s = (Stack*)(((ulong)buf+STKSIZE)&~(STKSIZE-1));
+ for(i=0; i<128; i++)
+ _stackfree(&s[i]);
+ lock(&stklock);
+ }
+ s = stkfree;
+ stkfree = stkfree->next;
+ unlock(&stklock);
+ s->magic = STKMAGIC;
+ return s;
+}
+
+static int
+newthread(Proc *p, void (*f)(void *arg), void *arg, uint stacksize, char *name, int grp)
+{
+ int id;
+ Thread *t;
+ Stack *s;
+
+ if(stacksize < 32)
+ sysfatal("bad stacksize %d", stacksize);
+ t = _threadmalloc(sizeof(Thread), 1);
+ s = stackalloc();
+ s->thr = t;
+ t->stk = (char*)s;
+ t->stksize = STKSIZE;
+ _threaddebugmemset(s->buf, 0xFE, sizeof s->buf);
+ _threadinitstack(t, f, arg);
+ t->proc = p;
+ t->grp = grp;
+ if(name)
+ t->cmdname = strdup(name);
+ t->id = nextID();
+ id = t->id;
+ t->next = (Thread*)~0;
+ _threaddebug(DBGSCHED, "create thread %d.%d name %s", p->pid, t->id, name);
+ lock(&p->lock);
+ p->nthreads++;
+ if(p->threads.head == nil)
+ p->threads.head = t;
+ else{
+ t->prevt = p->threads.tail;
+ t->prevt->nextt = t;
+ }
+ p->threads.tail = t;
+ t->state = Ready;
+ _threadready(t);
+ unlock(&p->lock);
+ return id;
+}
+
+static int
+nextID(void)
+{
+ static Lock l;
+ static int id;
+ int i;
+
+ lock(&l);
+ i = ++id;
+ unlock(&l);
+ return i;
+}
+
+int
+procrfork(void (*f)(void *), void *arg, uint stacksize, int rforkflag)
+{
+ Proc *p;
+ int id;
+
+ p = _threadgetproc();
+ assert(p->newproc == nil);
+ p->newproc = _newproc(f, arg, stacksize, nil, p->thread->grp, rforkflag);
+ id = p->newproc->threads.head->id;
+ _sched();
+ return id;
+}
+
+int
+proccreate(void (*f)(void*), void *arg, uint stacksize)
+{
+ return procrfork(f, arg, stacksize, 0);
+}
+
+void
+_freeproc(Proc *p)
+{
+ Thread *t, *nextt;
+
+ for(t = p->threads.head; t; t = nextt){
+ if(t->cmdname)
+ free(t->cmdname);
+ assert(t->stk != nil);
+ _stackfree((Stack*)t->stk);
+ nextt = t->nextt;
+ free(t);
+ }
+ free(p);
+}
+
+/*
+ * Create a new thread and schedule it to run.
+ * The thread grp is inherited from the currently running thread.
+ */
+int
+threadcreate(void (*f)(void *arg), void *arg, uint stacksize)
+{
+ return newthread(_threadgetproc(), f, arg, stacksize, nil, threadgetgrp());
+}
+
+/*
+ * Create and initialize a new Proc structure with a single Thread
+ * running inside it. Add the Proc to the global process list.
+ */
+Proc*
+_newproc(void (*f)(void *arg), void *arg, uint stacksize, char *name, int grp, int rforkflag)
+{
+ Proc *p;
+
+ p = _threadmalloc(sizeof *p, 1);
+ p->pid = -1;
+ p->rforkflag = rforkflag;
+ newthread(p, f, arg, stacksize, name, grp);
+
+ lock(&_threadpq.lock);
+ if(_threadpq.head == nil)
+ _threadpq.head = p;
+ else
+ *_threadpq.tail = p;
+ _threadpq.tail = &p->next;
+ unlock(&_threadpq.lock);
+ return p;
+}
+
diff --git a/src/libthread/debug.c b/src/libthread/debug.c
new file mode 100644
index 00000000..63e2e1b5
--- /dev/null
+++ b/src/libthread/debug.c
@@ -0,0 +1,48 @@
+#include "threadimpl.h"
+
+int _threaddebuglevel;
+
+void
+__threaddebug(ulong flag, char *fmt, ...)
+{
+ char buf[128];
+ va_list arg;
+ Fmt f;
+ Proc *p;
+
+ if((_threaddebuglevel&flag) == 0)
+ return;
+
+ fmtfdinit(&f, 2, buf, sizeof buf);
+
+ p = _threadgetproc();
+ if(p==nil)
+ fmtprint(&f, "noproc ");
+ else if(p->thread)
+ fmtprint(&f, "%d.%d ", p->pid, p->thread->id);
+ else
+ fmtprint(&f, "%d._ ", p->pid);
+
+ va_start(arg, fmt);
+ fmtvprint(&f, fmt, arg);
+ va_end(arg);
+ fmtprint(&f, "\n");
+ fmtfdflush(&f);
+}
+
+void
+_threadassert(char *s)
+{
+ char buf[256];
+ int n;
+ Proc *p;
+
+ p = _threadgetproc();
+ if(p && p->thread)
+ n = sprint(buf, "%d.%d ", p->pid, p->thread->id);
+ else
+ n = 0;
+ snprint(buf+n, sizeof(buf)-n, "%s: assertion failed\n", s);
+ write(2, buf, strlen(buf));
+ abort();
+}
diff --git a/src/libthread/exec-unix.c b/src/libthread/exec-unix.c
new file mode 100644
index 00000000..5a37e34c
--- /dev/null
+++ b/src/libthread/exec-unix.c
@@ -0,0 +1,124 @@
+#include <fcntl.h>
+#include <unistd.h>
+#include "threadimpl.h"
+
+void
+procexec(Channel *pidc, char *prog, char *args[])
+{
+ int n;
+ Proc *p;
+ Thread *t;
+
+ _threaddebug(DBGEXEC, "procexec %s", prog);
+ /* must be only thread in proc */
+ p = _threadgetproc();
+ t = p->thread;
+ if(p->threads.head != t || p->threads.head->nextt != nil){
+ werrstr("not only thread in proc");
+ Bad:
+ if(pidc)
+ sendul(pidc, ~0);
+ return;
+ }
+
+ /*
+ * We want procexec to behave like exec; if exec succeeds,
+ * never return, and if it fails, return with errstr set.
+ * Unfortunately, the exec happens in another proc since
+ * we have to wait for the exec'ed process to finish.
+ * To provide the semantics, we open a pipe with the
+ * write end close-on-exec and hand it to the proc that
+ * is doing the exec. If the exec succeeds, the pipe will
+ * close so that our read below fails. If the exec fails,
+ * then the proc doing the exec sends the errstr down the
+ * pipe to us.
+ */
+ if(pipe(p->exec.fd) < 0)
+ goto Bad;
+ if(fcntl(p->exec.fd[1], F_SETFD, 1) < 0)
+ goto Bad;
+
+ /* exec in parallel via the scheduler */
+ assert(p->needexec==0);
+ p->exec.prog = prog;
+ p->exec.args = args;
+ p->needexec = 1;
+ _sched();
+
+ close(p->exec.fd[1]);
+ if((n = read(p->exec.fd[0], p->exitstr, ERRMAX-1)) > 0){ /* exec failed */
+ p->exitstr[n] = '\0';
+ errstr(p->exitstr, ERRMAX);
+ close(p->exec.fd[0]);
+ goto Bad;
+ }
+ close(p->exec.fd[0]);
+
+ if(pidc)
+ sendul(pidc, t->ret);
+
+ /* wait for exec'ed program, then exit */
+ _schedexecwait();
+}
+
+void
+procexecl(Channel *pidc, char *f, ...)
+{
+ procexec(pidc, f, &f+1);
+}
+
+void
+_schedexecwait(void)
+{
+ int pid;
+ Channel *c;
+ Proc *p;
+ Thread *t;
+ Waitmsg *w;
+
+ p = _threadgetproc();
+ t = p->thread;
+ pid = t->ret;
+ _threaddebug(DBGEXEC, "_schedexecwait %d", t->ret);
+
+ for(;;){
+ w = wait();
+ if(w == nil)
+ break;
+ if(w->pid == pid)
+ break;
+ free(w);
+ }
+ if(w != nil){
+ if((c = _threadwaitchan) != nil)
+ sendp(c, w);
+ else
+ free(w);
+ }
+ threadexits("procexec");
+}
+
+static void
+efork(void *ve)
+{
+ char buf[ERRMAX];
+ Execargs *e;
+
+ e = ve;
+ _threaddebug(DBGEXEC, "_schedexec %s", e->prog);
+ close(e->fd[0]);
+ execv(e->prog, e->args);
+ _threaddebug(DBGEXEC, "_schedexec failed: %r");
+ rerrstr(buf, sizeof buf);
+ if(buf[0]=='\0')
+ strcpy(buf, "exec failed");
+ write(e->fd[1], buf, strlen(buf));
+ close(e->fd[1]);
+ _exits(buf);
+}
+
+int
+_schedexec(Execargs *e)
+{
+ return ffork(RFFDG|RFPROC|RFMEM, efork, e);
+}
diff --git a/src/libthread/exec.c b/src/libthread/exec.c
new file mode 100644
index 00000000..bcf20802
--- /dev/null
+++ b/src/libthread/exec.c
@@ -0,0 +1,77 @@
+#include "threadimpl.h"
+
+#define PIPEMNT "/mnt/temp"
+
+void
+procexec(Channel *pidc, char *prog, char *args[])
+{
+ int n;
+ Proc *p;
+ Thread *t;
+
+ _threaddebug(DBGEXEC, "procexec %s", prog);
+ /* must be only thread in proc */
+ p = _threadgetproc();
+ t = p->thread;
+ if(p->threads.head != t || p->threads.head->nextt != nil){
+ werrstr("not only thread in proc");
+ Bad:
+ if(pidc)
+ sendul(pidc, ~0);
+ return;
+ }
+
+ /*
+ * We want procexec to behave like exec; if exec succeeds,
+ * never return, and if it fails, return with errstr set.
+ * Unfortunately, the exec happens in another proc since
+ * we have to wait for the exec'ed process to finish.
+ * To provide the semantics, we open a pipe with the
+ * write end close-on-exec and hand it to the proc that
+ * is doing the exec. If the exec succeeds, the pipe will
+ * close so that our read below fails. If the exec fails,
+ * then the proc doing the exec sends the errstr down the
+ * pipe to us.
+ */
+ if(bind("#|", PIPEMNT, MREPL) < 0)
+ goto Bad;
+ if((p->exec.fd[0] = open(PIPEMNT "/data", OREAD)) < 0){
+ unmount(nil, PIPEMNT);
+ goto Bad;
+ }
+ if((p->exec.fd[1] = open(PIPEMNT "/data1", OWRITE|OCEXEC)) < 0){
+ close(p->exec.fd[0]);
+ unmount(nil, PIPEMNT);
+ goto Bad;
+ }
+ unmount(nil, PIPEMNT);
+
+ /* exec in parallel via the scheduler */
+ assert(p->needexec==0);
+ p->exec.prog = prog;
+ p->exec.args = args;
+ p->needexec = 1;
+ _sched();
+
+ close(p->exec.fd[1]);
+ if((n = read(p->exec.fd[0], p->exitstr, ERRMAX-1)) > 0){ /* exec failed */
+ p->exitstr[n] = '\0';
+ errstr(p->exitstr, ERRMAX);
+ close(p->exec.fd[0]);
+ goto Bad;
+ }
+ close(p->exec.fd[0]);
+
+ if(pidc)
+ sendul(pidc, t->ret);
+
+ /* wait for exec'ed program, then exit */
+ _schedexecwait();
+}
+
+void
+procexecl(Channel *pidc, char *f, ...)
+{
+ procexec(pidc, f, &f+1);
+}
+
diff --git a/src/libthread/exit.c b/src/libthread/exit.c
new file mode 100644
index 00000000..ddf70014
--- /dev/null
+++ b/src/libthread/exit.c
@@ -0,0 +1,63 @@
+#include "threadimpl.h"
+#include <signal.h>
+
+char *_threadexitsallstatus;
+Channel *_threadwaitchan;
+
+void
+threadexits(char *exitstr)
+{
+ Proc *p;
+ Thread *t;
+
+ p = _threadgetproc();
+ t = p->thread;
+ t->moribund = 1;
+ if(exitstr==nil)
+ exitstr="";
+ utfecpy(p->exitstr, p->exitstr+ERRMAX, exitstr);
+ _sched();
+}
+
+void
+threadexitsall(char *exitstr)
+{
+ Proc *p;
+ int *pid;
+ int i, npid, mypid;
+
+ if(exitstr == nil)
+ exitstr = "";
+ _threadexitsallstatus = exitstr;
+ _threaddebug(DBGSCHED, "_threadexitsallstatus set to %p", _threadexitsallstatus);
+ mypid = _threadgetpid();
+
+ /*
+ * signal others.
+ * copying all the pids first avoids other threads
+ * teardown procedures getting in the way.
+ */
+ lock(&_threadpq.lock);
+ npid = 0;
+ for(p=_threadpq.head; p; p=p->next)
+ npid++;
+ pid = _threadmalloc(npid*sizeof(pid[0]), 0);
+ npid = 0;
+ for(p = _threadpq.head; p; p=p->next)
+ pid[npid++] = p->pid;
+ unlock(&_threadpq.lock);
+ for(i=0; i<npid; i++)
+ if(pid[i] != mypid)
+ kill(pid[i], SIGTERM);
+
+ /* leave */
+ exit(0);
+}
+
+Channel*
+threadwaitchan(void)
+{
+ if(_threadwaitchan==nil)
+ _threadwaitchan = chancreate(sizeof(Waitmsg*), 16);
+ return _threadwaitchan;
+}
diff --git a/src/libthread/getpid.c b/src/libthread/getpid.c
new file mode 100644
index 00000000..da03bd3f
--- /dev/null
+++ b/src/libthread/getpid.c
@@ -0,0 +1,8 @@
+#include "threadimpl.h"
+#include <unistd.h>
+
+int
+_threadgetpid(void)
+{
+ return getpid();
+}
diff --git a/src/libthread/id.c b/src/libthread/id.c
new file mode 100644
index 00000000..727798d3
--- /dev/null
+++ b/src/libthread/id.c
@@ -0,0 +1,135 @@
+#include "threadimpl.h"
+
+int
+threadid(void)
+{
+ return _threadgetproc()->thread->id;
+}
+
+int
+threadpid(int id)
+{
+ int pid;
+ Proc *p;
+ Thread *t;
+
+ if (id < 0)
+ return -1;
+ if (id == 0)
+ return _threadgetproc()->pid;
+ lock(&_threadpq.lock);
+ for (p = _threadpq.head; p->next; p = p->next){
+ lock(&p->lock);
+ for (t = p->threads.head; t; t = t->nextt)
+ if (t->id == id){
+ pid = p->pid;
+ unlock(&p->lock);
+ unlock(&_threadpq.lock);
+ return pid;
+ }
+ unlock(&p->lock);
+ }
+ unlock(&_threadpq.lock);
+ return -1;
+}
+
+int
+threadsetgrp(int ng)
+{
+ int og;
+ Thread *t;
+
+ t = _threadgetproc()->thread;
+ og = t->grp;
+ t->grp = ng;
+ return og;
+}
+
+int
+threadgetgrp(void)
+{
+ return _threadgetproc()->thread->grp;
+}
+
+void
+threadsetname(char *name)
+{
+/*
+ int fd, n;
+ char buf[128], *s;
+*/
+ Proc *p;
+ Thread *t;
+
+ p = _threadgetproc();
+ t = p->thread;
+ if (t->cmdname)
+ free(t->cmdname);
+ t->cmdname = strdup(name);
+/* Plan 9 only
+ if(p->nthreads == 1){
+ snprint(buf, sizeof buf, "#p/%d/args", getpid());
+ if((fd = open(buf, OWRITE)) >= 0){
+ snprint(buf, sizeof buf, "%s [%s]", argv0, name);
+ n = strlen(buf)+1;
+ s = strchr(buf, ' ');
+ if(s)
+ *s = '\0';
+ write(fd, buf, n);
+ close(fd);
+ }
+ }
+*/
+}
+
+char*
+threadgetname(void)
+{
+ return _threadgetproc()->thread->cmdname;
+}
+
+void**
+threaddata(void)
+{
+ return &_threadgetproc()->thread->udata[0];
+}
+
+void**
+procdata(void)
+{
+ return &_threadgetproc()->udata;
+}
+
+static Lock privlock;
+static int privmask = 1;
+
+int
+tprivalloc(void)
+{
+ int i;
+
+ lock(&privlock);
+ for(i=0; i<NPRIV; i++)
+ if(!(privmask&(1<<i))){
+ privmask |= 1<<i;
+ unlock(&privlock);
+ return i;
+ }
+ unlock(&privlock);
+ return -1;
+}
+
+void
+tprivfree(int i)
+{
+ if(i < 0 || i >= NPRIV)
+ abort();
+ lock(&privlock);
+ privmask &= ~(1<<i);
+}
+
+void**
+tprivaddr(int i)
+{
+ return &_threadgetproc()->thread->udata[i];
+}
diff --git a/src/libthread/iocall.c b/src/libthread/iocall.c
new file mode 100644
index 00000000..d9cf9d04
--- /dev/null
+++ b/src/libthread/iocall.c
@@ -0,0 +1,49 @@
+#include "threadimpl.h"
+
+long
+iocall(Ioproc *io, long (*op)(va_list*), ...)
+{
+ int ret, inted;
+ Ioproc *msg;
+
+ if(send(io->c, &io) == -1){
+ werrstr("interrupted");
+ return -1;
+ }
+ assert(!io->inuse);
+ io->inuse = 1;
+ io->op = op;
+ va_start(io->arg, op);
+ msg = io;
+ inted = 0;
+ while(send(io->creply, &msg) == -1){
+ msg = nil;
+ inted = 1;
+ }
+ if(inted){
+ werrstr("interrupted");
+ return -1;
+ }
+
+ /*
+ * If we get interrupted, we have stick around so that
+ * the IO proc has someone to talk to. Send it an interrupt
+ * and try again.
+ */
+ inted = 0;
+ while(recv(io->creply, nil) == -1){
+ inted = 1;
+ iointerrupt(io);
+ }
+ USED(inted);
+ va_end(io->arg);
+ ret = io->ret;
+ if(ret < 0)
+ errstr(io->err, sizeof io->err);
+ io->inuse = 0;
+
+ /* release resources */
+ while(send(io->creply, &io) == -1)
+ ;
+ return ret;
+}
diff --git a/src/libthread/ioclose.c b/src/libthread/ioclose.c
new file mode 100644
index 00000000..fbaabb7c
--- /dev/null
+++ b/src/libthread/ioclose.c
@@ -0,0 +1,16 @@
+#include "threadimpl.h"
+
+static long
+_ioclose(va_list *arg)
+{
+ int fd;
+
+ fd = va_arg(*arg, int);
+ return close(fd);
+}
+
+int
+ioclose(Ioproc *io, int fd)
+{
+ return iocall(io, _ioclose, fd);
+}
diff --git a/src/libthread/iodial.c b/src/libthread/iodial.c
new file mode 100644
index 00000000..8171156c
--- /dev/null
+++ b/src/libthread/iodial.c
@@ -0,0 +1,21 @@
+#include "threadimpl.h"
+
+static long
+_iodial(va_list *arg)
+{
+ char *addr, *local, *dir;
+ int *cdfp;
+
+ addr = va_arg(*arg, char*);
+ local = va_arg(*arg, char*);
+ dir = va_arg(*arg, char*);
+ cdfp = va_arg(*arg, int*);
+
+ return dial(addr, local, dir, cdfp);
+}
+
+int
+iodial(Ioproc *io, char *addr, char *local, char *dir, int *cdfp)
+{
+ return iocall(io, _iodial, addr, local, dir, cdfp);
+}
diff --git a/src/libthread/ioopen.c b/src/libthread/ioopen.c
new file mode 100644
index 00000000..efca4ca6
--- /dev/null
+++ b/src/libthread/ioopen.c
@@ -0,0 +1,20 @@
+#include <unistd.h>
+#include <fcntl.h>
+#include "threadimpl.h"
+
+static long
+_ioopen(va_list *arg)
+{
+ char *path;
+ int mode;
+
+ path = va_arg(*arg, char*);
+ mode = va_arg(*arg, int);
+ return open(path, mode);
+}
+
+int
+ioopen(Ioproc *io, char *path, int mode)
+{
+ return iocall(io, _ioopen, path, mode);
+}
diff --git a/src/libthread/ioproc.3 b/src/libthread/ioproc.3
new file mode 100644
index 00000000..1cada2de
--- /dev/null
+++ b/src/libthread/ioproc.3
@@ -0,0 +1,179 @@
+.TH IOPROC 2
+.SH NAME
+closeioproc,
+iocall,
+ioclose,
+iointerrupt,
+iodial,
+ioopen,
+ioproc,
+ioread,
+ioreadn,
+iowrite \- slave I/O processes for threaded programs
+.SH SYNOPSIS
+.PP
+.de XX
+.ift .sp 0.5
+.ifn .sp
+..
+.EX
+.ta \w'Ioproc* 'u
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+.sp
+typedef struct Ioproc Ioproc;
+.sp
+Ioproc* ioproc(void);
+.XX
+int ioopen(Ioproc *io, char *file, int omode);
+int ioclose(Ioproc *io, int fd);
+long ioread(Ioproc *io, int fd, void *a, long n);
+long ioreadn(Ioproc *io, int fd, void *a, long n);
+long iowrite(Ioproc *io, int fd, void *a, long n);
+int iodial(Ioproc *io, char *addr, char *local, char *dir, char *cdfp);
+.XX
+void iointerrupt(Ioproc *io);
+void closeioproc(Ioproc *io);
+.XX
+long iocall(Ioproc *io, long (*op)(va_list *arg), ...);
+.EE
+.SH DESCRIPTION
+.PP
+These routines provide access to I/O in slave procs.
+Since the I/O itself is done in a slave proc, other threads
+in the calling proc can run while the calling thread
+waits for the I/O to complete.
+.PP
+.I Ioproc
+forks a new slave proc and returns a pointer to the
+.B Ioproc
+associated with it.
+.I Ioproc
+uses
+.I mallocz
+and
+.IR proccreate ;
+if either fails, it calls
+.I sysfatal
+rather than return an error.
+.PP
+.IR Ioopen ,
+.IR ioclose ,
+.IR ioread ,
+.IR ioreadn ,
+.IR iowrite ,
+and
+.IR iodial
+are execute the
+similarly named library or system calls
+(see
+.IR open (2),
+.IR read (2),
+and
+.IR dial (2))
+in the slave process associated with
+.IR io .
+It is an error to execute more than one call
+at a time in an I/O proc.
+.PP
+.I Iointerrupt
+interrupts the call currently executing in the I/O proc.
+If no call is executing,
+.IR iointerrupt
+is a no-op.
+.PP
+.I Closeioproc
+terminates the I/O proc and frees the associated
+.B Ioproc .
+.PP
+.I Iocall
+is a primitive that may be used to implement
+more slave I/O routines.
+.I Iocall
+arranges for
+.I op
+to be called in
+.IR io 's
+proc, with
+.I arg
+set to the variable parameter list,
+returning the value that
+.I op
+returns.
+.SH EXAMPLE
+Relay messages between two file descriptors,
+counting the total number of bytes seen:
+.IP
+.EX
+.ta +\w'xxxx'u +\w'xxxx'u +\w'xxxx'u
+int tot;
+
+void
+relaythread(void *v)
+{
+ int *fd, n;
+ char buf[1024];
+ Ioproc *io;
+
+ fd = v;
+ io = ioproc();
+ while((n = ioread(io, fd[0], buf, sizeof buf)) > 0){
+ if(iowrite(io, fd[1], buf, n) != n)
+ sysfatal("iowrite: %r");
+ tot += n;
+ }
+ closeioproc(io);
+}
+
+void
+relay(int fd0, int fd1)
+{
+ int fd[4];
+
+ fd[0] = fd[3] = fd0;
+ fd[1] = fd[2] = fd1;
+ threadcreate(relaythread, fd, 8192);
+ threadcreate(relaythread, fd+2, 8192);
+}
+.EE
+.LP
+If the two
+.I relaythread
+instances were running in different procs, the
+common access to
+.I tot
+would be unsafe.
+.EE
+.PP
+Implement
+.IR ioread :
+.IP
+.EX
+static long
+_ioread(va_list *arg)
+{
+ int fd;
+ void *a;
+ long n;
+
+ fd = va_arg(*arg, int);
+ a = va_arg(*arg, void*);
+ n = va_arg(*arg, long);
+ return read(fd, a, n);
+}
+
+long
+ioread(Ioproc *io, int fd, void *a, long n)
+{
+ return iocall(io, _ioread, fd, a, n);
+}
+.EE
+.SH SOURCE
+.B /sys/src/libthread/io*.c
+.SH SEE ALSO
+.IR dial (2),
+.IR open (2),
+.IR read (2),
+.IR thread (2)
+
diff --git a/src/libthread/ioproc.c b/src/libthread/ioproc.c
new file mode 100644
index 00000000..2b2a6025
--- /dev/null
+++ b/src/libthread/ioproc.c
@@ -0,0 +1,74 @@
+#include "threadimpl.h"
+
+enum
+{
+ STACK = 8192,
+};
+
+void
+iointerrupt(Ioproc *io)
+{
+ if(!io->inuse)
+ return;
+ threadint(io->tid);
+}
+
+static void
+xioproc(void *a)
+{
+ Ioproc *io, *x;
+ io = a;
+ /*
+ * first recvp acquires the ioproc.
+ * second tells us that the data is ready.
+ */
+ for(;;){
+ while(recv(io->c, &x) == -1)
+ ;
+ if(x == 0) /* our cue to leave */
+ break;
+ assert(x == io);
+
+ /* caller is now committed -- even if interrupted he'll return */
+ while(recv(io->creply, &x) == -1)
+ ;
+ if(x == 0) /* caller backed out */
+ continue;
+ assert(x == io);
+
+ io->ret = io->op(&io->arg);
+ if(io->ret < 0)
+ rerrstr(io->err, sizeof io->err);
+ while(send(io->creply, &io) == -1)
+ ;
+ while(recv(io->creply, &x) == -1)
+ ;
+ }
+}
+
+Ioproc*
+ioproc(void)
+{
+ Ioproc *io;
+
+ io = mallocz(sizeof(*io), 1);
+ if(io == nil)
+ sysfatal("ioproc malloc: %r");
+ io->c = chancreate(sizeof(void*), 0);
+ io->creply = chancreate(sizeof(void*), 0);
+ io->tid = proccreate(xioproc, io, STACK);
+ return io;
+}
+
+void
+closeioproc(Ioproc *io)
+{
+ if(io == nil)
+ return;
+ iointerrupt(io);
+ while(send(io->c, 0) == -1)
+ ;
+ chanfree(io->c);
+ chanfree(io->creply);
+ free(io);
+}
diff --git a/src/libthread/ioread.c b/src/libthread/ioread.c
new file mode 100644
index 00000000..62b1be03
--- /dev/null
+++ b/src/libthread/ioread.c
@@ -0,0 +1,20 @@
+#include "threadimpl.h"
+
+static long
+_ioread(va_list *arg)
+{
+ int fd;
+ void *a;
+ long n;
+
+ fd = va_arg(*arg, int);
+ a = va_arg(*arg, void*);
+ n = va_arg(*arg, long);
+ return read(fd, a, n);
+}
+
+long
+ioread(Ioproc *io, int fd, void *a, long n)
+{
+ return iocall(io, _ioread, fd, a, n);
+}
diff --git a/src/libthread/ioreadn.c b/src/libthread/ioreadn.c
new file mode 100644
index 00000000..b843f603
--- /dev/null
+++ b/src/libthread/ioreadn.c
@@ -0,0 +1,21 @@
+#include "threadimpl.h"
+
+static long
+_ioreadn(va_list *arg)
+{
+ int fd;
+ void *a;
+ long n;
+
+ fd = va_arg(*arg, int);
+ a = va_arg(*arg, void*);
+ n = va_arg(*arg, long);
+ n = readn(fd, a, n);
+ return n;
+}
+
+long
+ioreadn(Ioproc *io, int fd, void *a, long n)
+{
+ return iocall(io, _ioreadn, fd, a, n);
+}
diff --git a/src/libthread/iosleep.c b/src/libthread/iosleep.c
new file mode 100644
index 00000000..55756454
--- /dev/null
+++ b/src/libthread/iosleep.c
@@ -0,0 +1,16 @@
+#include "threadimpl.h"
+
+static long
+_iosleep(va_list *arg)
+{
+ long n;
+
+ n = va_arg(*arg, long);
+ return sleep(n);
+}
+
+int
+iosleep(Ioproc *io, long n)
+{
+ return iocall(io, _iosleep, n);
+}
diff --git a/src/libthread/iowrite.c b/src/libthread/iowrite.c
new file mode 100644
index 00000000..664a84bf
--- /dev/null
+++ b/src/libthread/iowrite.c
@@ -0,0 +1,21 @@
+#include "threadimpl.h"
+
+static long
+_iowrite(va_list *arg)
+{
+ int fd;
+ void *a;
+ long n;
+
+ fd = va_arg(*arg, int);
+ a = va_arg(*arg, void*);
+ n = va_arg(*arg, long);
+ n = write(fd, a, n);
+ return n;
+}
+
+long
+iowrite(Ioproc *io, int fd, void *a, long n)
+{
+ return iocall(io, _iowrite, fd, a, n);
+}
diff --git a/src/libthread/kill.c b/src/libthread/kill.c
new file mode 100644
index 00000000..a9392ead
--- /dev/null
+++ b/src/libthread/kill.c
@@ -0,0 +1,89 @@
+#include "threadimpl.h"
+#include <signal.h>
+
+static void tinterrupt(Proc*, Thread*);
+
+static void
+threadxxxgrp(int grp, int dokill)
+{
+ Proc *p;
+ Thread *t;
+
+ lock(&_threadpq.lock);
+ for(p=_threadpq.head; p; p=p->next){
+ lock(&p->lock);
+ for(t=p->threads.head; t; t=t->nextt)
+ if(t->grp == grp){
+ if(dokill)
+ t->moribund = 1;
+ tinterrupt(p, t);
+ }
+ unlock(&p->lock);
+ }
+ unlock(&_threadpq.lock);
+ _threadbreakrendez();
+}
+
+static void
+threadxxx(int id, int dokill)
+{
+ Proc *p;
+ Thread *t;
+
+ lock(&_threadpq.lock);
+ for(p=_threadpq.head; p; p=p->next){
+ lock(&p->lock);
+ for(t=p->threads.head; t; t=t->nextt)
+ if(t->id == id){
+ if(dokill)
+ t->moribund = 1;
+ tinterrupt(p, t);
+ unlock(&p->lock);
+ unlock(&_threadpq.lock);
+ _threadbreakrendez();
+ return;
+ }
+ unlock(&p->lock);
+ }
+ unlock(&_threadpq.lock);
+ _threaddebug(DBGNOTE, "Can't find thread to kill");
+ return;
+}
+
+void
+threadkillgrp(int grp)
+{
+ threadxxxgrp(grp, 1);
+}
+
+void
+threadkill(int id)
+{
+ threadxxx(id, 1);
+}
+
+void
+threadintgrp(int grp)
+{
+ threadxxxgrp(grp, 0);
+}
+
+void
+threadint(int id)
+{
+ threadxxx(id, 0);
+}
+
+static void
+tinterrupt(Proc *p, Thread *t)
+{
+ switch(t->state){
+ case Running:
+ kill(p->pid, SIGINT);
+ // postnote(PNPROC, p->pid, "threadint");
+ break;
+ case Rendezvous:
+ _threadflagrendez(t);
+ break;
+ }
+}
diff --git a/src/libthread/label.h b/src/libthread/label.h
new file mode 100644
index 00000000..0c9f3030
--- /dev/null
+++ b/src/libthread/label.h
@@ -0,0 +1,24 @@
+/*
+ * setjmp and longjmp, but our own because some (stupid) c libraries
+ * assume longjmp is only used to move up the stack, and error out
+ * if you do otherwise.
+ */
+
+typedef struct Label Label;
+#define LABELDPC 0
+
+#if defined (__i386__) && (defined(__FreeBSD__) || defined(__linux__))
+struct Label
+{
+ ulong pc;
+ ulong bx;
+ ulong sp;
+ ulong bp;
+ ulong si;
+ ulong di;
+};
+#else
+#error "Unknown or unsupported architecture"
+#endif
+
+
diff --git a/src/libthread/lib.c b/src/libthread/lib.c
new file mode 100644
index 00000000..86e7506e
--- /dev/null
+++ b/src/libthread/lib.c
@@ -0,0 +1,35 @@
+#include "threadimpl.h"
+
+static long totalmalloc;
+
+void*
+_threadmalloc(long size, int z)
+{
+ void *m;
+
+ m = malloc(size);
+ if (m == nil)
+ sysfatal("Malloc of size %ld failed: %r\n", size);
+ setmalloctag(m, getcallerpc(&size));
+ totalmalloc += size;
+ if (size > 1000000) {
+ fprint(2, "Malloc of size %ld, total %ld\n", size, totalmalloc);
+ abort();
+ }
+ if (z)
+ _threadmemset(m, 0, size);
+ return m;
+}
+
+void
+_threadsysfatal(char *fmt, va_list arg)
+{
+ char buf[1024]; /* size doesn't matter; we're about to exit */
+
+ vseprint(buf, buf+sizeof(buf), fmt, arg);
+ if(argv0)
+ fprint(2, "%s: %s\n", argv0, buf);
+ else
+ fprint(2, "%s\n", buf);
+ threadexitsall(buf);
+}
diff --git a/src/libthread/main.c b/src/libthread/main.c
new file mode 100644
index 00000000..1acd8348
--- /dev/null
+++ b/src/libthread/main.c
@@ -0,0 +1,124 @@
+#include "threadimpl.h"
+#include <signal.h>
+
+typedef struct Mainarg Mainarg;
+struct Mainarg
+{
+ int argc;
+ char **argv;
+};
+
+int mainstacksize;
+int _threadnotefd;
+int _threadpasserpid;
+static void mainlauncher(void*);
+extern void (*_sysfatal)(char*, va_list);
+
+void
+_threaddie(int x)
+{
+ extern char *_threadexitsallstatus;
+ USED(x);
+
+ if(_threadexitsallstatus)
+ exit(_threadexitsallstatus[0] ? 1 : 0);
+}
+
+int
+main(int argc, char **argv)
+{
+ Mainarg *a;
+ Proc *p;
+
+ signal(SIGTERM, _threaddie);
+// rfork(RFREND);
+
+//_threaddebuglevel = (DBGSCHED|DBGCHAN|DBGREND)^~0;
+ _systhreadinit();
+ _qlockinit(_threadrendezvous);
+ _sysfatal = _threadsysfatal;
+// notify(_threadnote);
+ if(mainstacksize == 0)
+ mainstacksize = 32*1024;
+
+ a = _threadmalloc(sizeof *a, 1);
+ a->argc = argc;
+ a->argv = argv;
+
+ p = _newproc(mainlauncher, a, mainstacksize, "threadmain", 0, 0);
+ _schedinit(p);
+ abort(); /* not reached */
+ return 0;
+}
+
+static void
+mainlauncher(void *arg)
+{
+ Mainarg *a;
+
+ a = arg;
+ threadmain(a->argc, a->argv);
+ threadexits("threadmain");
+}
+
+void
+_threadsignal(void)
+{
+}
+
+void
+_threadsignalpasser(void)
+{
+}
+
+int
+_schedfork(Proc *p)
+{
+ return ffork(RFMEM|RFNOWAIT, _schedinit, p);
+}
+
+void
+_schedexit(Proc *p)
+{
+ char ex[ERRMAX];
+ Proc **l;
+
+ lock(&_threadpq.lock);
+ for(l=&_threadpq.head; *l; l=&(*l)->next){
+ if(*l == p){
+ *l = p->next;
+ if(*l == nil)
+ _threadpq.tail = l;
+ break;
+ }
+ }
+ unlock(&_threadpq.lock);
+
+ strncpy(ex, p->exitstr, sizeof ex);
+ ex[sizeof ex-1] = '\0';
+ free(p);
+ _exit(ex[0]);
+}
+
+int
+nrand(int n)
+{
+ return random()%n;
+}
+
+void
+_systhreadinit(void)
+{
+}
+
+void
+threadstats(void)
+{
+ extern int _threadnrendez, _threadhighnrendez,
+ _threadnalt, _threadhighnentry;
+ fprint(2, "*** THREAD LIBRARY STATS ***\n");
+ fprint(2, "nrendez %d high simultaneous %d\n",
+ _threadnrendez, _threadhighnrendez);
+ fprint(2, "nalt %d high simultaneous entry %d\n",
+ _threadnalt, _threadhighnentry);
+}
diff --git a/src/libthread/memset.c b/src/libthread/memset.c
new file mode 100644
index 00000000..dd74fb6c
--- /dev/null
+++ b/src/libthread/memset.c
@@ -0,0 +1,8 @@
+#include "threadimpl.h"
+#include <string.h>
+
+void
+_threadmemset(void *v, int c, int n)
+{
+ memset(v, c, n);
+}
diff --git a/src/libthread/memsetd.c b/src/libthread/memsetd.c
new file mode 100644
index 00000000..ef3f9605
--- /dev/null
+++ b/src/libthread/memsetd.c
@@ -0,0 +1,8 @@
+#include "threadimpl.h"
+#include <string.h>
+
+void
+_threaddebugmemset(void *v, int c, int n)
+{
+ memset(v, c, n);
+}
diff --git a/src/libthread/mkfile b/src/libthread/mkfile
new file mode 100644
index 00000000..703f6b06
--- /dev/null
+++ b/src/libthread/mkfile
@@ -0,0 +1,2 @@
+<../libutf/mkfile
+
diff --git a/src/libthread/note.c b/src/libthread/note.c
new file mode 100644
index 00000000..b7f4b137
--- /dev/null
+++ b/src/libthread/note.c
@@ -0,0 +1,143 @@
+#include "threadimpl.h"
+
+int _threadnopasser;
+
+#ifdef NOTDEF
+#define NFN 33
+#define ERRLEN 48
+typedef struct Note Note;
+struct Note
+{
+ Lock inuse;
+ Proc *proc; /* recipient */
+ char s[ERRMAX]; /* arg2 */
+};
+
+static Note notes[128];
+static Note *enotes = notes+nelem(notes);
+static int (*onnote[NFN])(void*, char*);
+static int onnotepid[NFN];
+static Lock onnotelock;
+
+int
+threadnotify(int (*f)(void*, char*), int in)
+{
+ int i, topid;
+ int (*from)(void*, char*), (*to)(void*, char*);
+
+ if(in){
+ from = nil;
+ to = f;
+ topid = _threadgetproc()->pid;
+ }else{
+ from = f;
+ to = nil;
+ topid = 0;
+ }
+ lock(&onnotelock);
+ for(i=0; i<NFN; i++)
+ if(onnote[i]==from){
+ onnote[i] = to;
+ onnotepid[i] = topid;
+ break;
+ }
+ unlock(&onnotelock);
+ return i<NFN;
+}
+
+static void
+delayednotes(Proc *p, void *v)
+{
+ int i;
+ Note *n;
+ int (*fn)(void*, char*);
+
+ if(!p->pending)
+ return;
+
+ p->pending = 0;
+ for(n=notes; n<enotes; n++){
+ if(n->proc == p){
+ for(i=0; i<NFN; i++){
+ if(onnotepid[i]!=p->pid || (fn = onnote[i])==nil)
+ continue;
+ if((*fn)(v, n->s))
+ break;
+ }
+ if(i==NFN){
+ _threaddebug(DBGNOTE, "Unhandled note %s, proc %p\n", n->s, p);
+ if(v != nil)
+ noted(NDFLT);
+ else if(strncmp(n->s, "sys:", 4)==0)
+ abort();
+ threadexitsall(n->s);
+ }
+ n->proc = nil;
+ unlock(&n->inuse);
+ }
+ }
+}
+
+void
+_threadnote(void *v, char *s)
+{
+ Proc *p;
+ Note *n;
+
+ _threaddebug(DBGNOTE, "Got note %s", s);
+ if(strncmp(s, "sys:", 4) == 0)
+ noted(NDFLT);
+
+// if(_threadexitsallstatus){
+// _threaddebug(DBGNOTE, "Threadexitsallstatus = '%s'\n", _threadexitsallstatus);
+// _exits(_threadexitsallstatus);
+// }
+
+ if(strcmp(s, "threadint")==0)
+ noted(NCONT);
+
+ p = _threadgetproc();
+ if(p == nil)
+ noted(NDFLT);
+
+ for(n=notes; n<enotes; n++)
+ if(canlock(&n->inuse))
+ break;
+ if(n==enotes)
+ sysfatal("libthread: too many delayed notes");
+ utfecpy(n->s, n->s+ERRMAX, s);
+ n->proc = p;
+ p->pending = 1;
+ if(!p->splhi)
+ delayednotes(p, v);
+ noted(NCONT);
+}
+#endif
+
+int
+_procsplhi(void)
+{
+ int s;
+ Proc *p;
+
+ p = _threadgetproc();
+ s = p->splhi;
+ p->splhi = 1;
+ return s;
+}
+
+void
+_procsplx(int s)
+{
+ Proc *p;
+
+ p = _threadgetproc();
+ p->splhi = s;
+ if(s)
+ return;
+/*
+ if(p->pending)
+ delayednotes(p, nil);
+*/
+}
+
diff --git a/src/libthread/proctab.c b/src/libthread/proctab.c
new file mode 100644
index 00000000..4029652a
--- /dev/null
+++ b/src/libthread/proctab.c
@@ -0,0 +1,64 @@
+#include "threadimpl.h"
+
+/* this will need work */
+enum
+{
+ PTABHASH = 257,
+};
+
+static Lock ptablock;
+Proc *ptab[PTABHASH];
+
+void
+_threadsetproc(Proc *p)
+{
+ int h;
+
+ lock(&ptablock);
+ h = ((unsigned)p->pid)%PTABHASH;
+ p->link = ptab[h];
+ unlock(&ptablock);
+ ptab[h] = p;
+}
+
+static Proc*
+__threadgetproc(int rm)
+{
+ Proc **l, *p;
+ int h, pid;
+ Thread *t;
+ ulong *s;
+
+ s = (ulong*)((ulong)&pid & ~(STKSIZE-1));
+ if(s[0] == STKMAGIC){
+ t = (Thread*)s[1];
+ return t->proc;
+ }
+
+ pid = _threadgetpid();
+
+ lock(&ptablock);
+ h = ((unsigned)pid)%PTABHASH;
+ for(l=&ptab[h]; p=*l; l=&p->link){
+ if(p->pid == pid){
+ if(rm)
+ *l = p->link;
+ unlock(&ptablock);
+ return p;
+ }
+ }
+ unlock(&ptablock);
+ return nil;
+}
+
+Proc*
+_threadgetproc(void)
+{
+ return __threadgetproc(0);
+}
+
+Proc*
+_threaddelproc(void)
+{
+ return __threadgetproc(1);
+}
diff --git a/src/libthread/ref.c b/src/libthread/ref.c
new file mode 100644
index 00000000..9b78e63e
--- /dev/null
+++ b/src/libthread/ref.c
@@ -0,0 +1,13 @@
+#include "threadimpl.h"
+
+void
+incref(Ref *r)
+{
+ _xinc(&r->ref);
+}
+
+long
+decref(Ref *r)
+{
+ return _xdec(&r->ref);
+}
diff --git a/src/libthread/rendez.c b/src/libthread/rendez.c
new file mode 100644
index 00000000..62b825b5
--- /dev/null
+++ b/src/libthread/rendez.c
@@ -0,0 +1,104 @@
+#include "threadimpl.h"
+
+Rgrp _threadrgrp;
+static int isdirty;
+int _threadhighnrendez;
+int _threadnrendez;
+static int nrendez;
+
+static ulong
+finish(Thread *t, ulong val)
+{
+ ulong ret;
+
+ ret = t->rendval;
+ t->rendval = val;
+ while(t->state == Running)
+ sleep(0);
+ lock(&t->proc->lock);
+ if(t->state == Rendezvous){ /* not always true: might be Dead */
+ t->state = Ready;
+ _threadready(t);
+ }
+ unlock(&t->proc->lock);
+ return ret;
+}
+
+ulong
+_threadrendezvous(ulong tag, ulong val)
+{
+ ulong ret;
+ Thread *t, **l;
+
+ lock(&_threadrgrp.lock);
+_threadnrendez++;
+ l = &_threadrgrp.hash[tag%nelem(_threadrgrp.hash)];
+ for(t=*l; t; l=&t->rendhash, t=*l){
+ if(t->rendtag==tag){
+ _threaddebug(DBGREND, "Rendezvous with thread %d.%d", t->proc->pid, t->id);
+ *l = t->rendhash;
+ ret = finish(t, val);
+ --nrendez;
+ unlock(&_threadrgrp.lock);
+ return ret;
+ }
+ }
+
+ /* Going to sleep here. */
+ t = _threadgetproc()->thread;
+ t->rendbreak = 0;
+ t->inrendez = 1;
+ t->rendtag = tag;
+ t->rendval = val;
+ t->rendhash = *l;
+ *l = t;
+ t->nextstate = Rendezvous;
+ ++nrendez;
+ if(nrendez > _threadhighnrendez)
+ _threadhighnrendez = nrendez;
+ _threaddebug(DBGREND, "Rendezvous for tag %lud", t->rendtag);
+ unlock(&_threadrgrp.lock);
+ _sched();
+ t->inrendez = 0;
+ _threaddebug(DBGREND, "Woke after rendezvous; val is %lud", t->rendval);
+ return t->rendval;
+}
+
+/*
+ * This is called while holding _threadpq.lock and p->lock,
+ * so we can't lock _threadrgrp.lock. Instead our caller has
+ * to call _threadbreakrendez after dropping those locks.
+ */
+void
+_threadflagrendez(Thread *t)
+{
+ t->rendbreak = 1;
+ isdirty = 1;
+}
+
+void
+_threadbreakrendez(void)
+{
+ int i;
+ Thread *t, **l;
+
+ if(isdirty == 0)
+ return;
+ lock(&_threadrgrp.lock);
+ if(isdirty == 0){
+ unlock(&_threadrgrp.lock);
+ return;
+ }
+ isdirty = 0;
+ for(i=0; i<nelem(_threadrgrp.hash); i++){
+ l = &_threadrgrp.hash[i];
+ for(t=*l; t; t=*l){
+ if(t->rendbreak){
+ *l = t->rendhash;
+ finish(t, ~0);
+ }else
+ l=&t->rendhash;
+ }
+ }
+ unlock(&_threadrgrp.lock);
+}
diff --git a/src/libthread/rpm.spec b/src/libthread/rpm.spec
new file mode 100644
index 00000000..fdc72de2
--- /dev/null
+++ b/src/libthread/rpm.spec
@@ -0,0 +1,26 @@
+Summary: Port of Plan 9's thread library
+Name: libthread
+Version: 2.0
+Release: 1
+Group: Development/C
+Copyright: BSD-like
+Packager: Russ Cox <rsc@post.harvard.edu>
+Source: http://pdos.lcs.mit.edu/~rsc/software/libthread-2.0.tgz
+URL: http://pdos.lcs.mit.edu/~rsc/software/#libthread
+
+%description
+Libthread is a port of Plan 9's thread library
+%prep
+%setup
+
+%build
+make
+
+%install
+make install
+
+%files
+/usr/local/include/thread.h
+/usr/local/lib/libthread.a
+/usr/local/man/man3/thread.3
+/usr/local/man/man3/ioproc.3
diff --git a/src/libthread/sched.c b/src/libthread/sched.c
new file mode 100644
index 00000000..8609833d
--- /dev/null
+++ b/src/libthread/sched.c
@@ -0,0 +1,192 @@
+#include "threadimpl.h"
+#include <signal.h>
+
+//static Thread *runthread(Proc*);
+
+static char *_psstate[] = {
+ "Dead",
+ "Running",
+ "Ready",
+ "Rendezvous",
+};
+
+static char*
+psstate(int s)
+{
+ if(s < 0 || s >= nelem(_psstate))
+ return "unknown";
+ return _psstate[s];
+}
+
+void
+_schedinit(void *arg)
+{
+ Proc *p;
+ Thread *t;
+ extern void ignusr1(void), _threaddie(int);
+ ignusr1();
+ signal(SIGTERM, _threaddie);
+
+
+
+ p = arg;
+ p->pid = _threadgetpid();
+ _threadsetproc(p);
+ while(_setlabel(&p->sched))
+ ;
+ _threaddebug(DBGSCHED, "top of schedinit, _threadexitsallstatus=%p", _threadexitsallstatus);
+ if(_threadexitsallstatus)
+ exits(_threadexitsallstatus);
+ lock(&p->lock);
+ if((t=p->thread) != nil){
+ p->thread = nil;
+ if(t->moribund){
+ assert(t->moribund == 1);
+ t->state = Dead;
+ if(t->prevt)
+ t->prevt->nextt = t->nextt;
+ else
+ p->threads.head = t->nextt;
+ if(t->nextt)
+ t->nextt->prevt = t->prevt;
+ else
+ p->threads.tail = t->prevt;
+ unlock(&p->lock);
+ if(t->inrendez){
+ _threadflagrendez(t);
+ _threadbreakrendez();
+ }
+ _stackfree(t->stk);
+ free(t->cmdname);
+ free(t); /* XXX how do we know there are no references? */
+ t = nil;
+ _sched();
+ }
+ if(p->needexec){
+ t->ret = _schedexec(&p->exec);
+ p->needexec = 0;
+ }
+ if(p->newproc){
+ t->ret = _schedfork(p->newproc);
+ if(t->ret < 0){
+//fprint(2, "_schedfork: %r\n");
+ abort();
+}
+ p->newproc = nil;
+ }
+ t->state = t->nextstate;
+ if(t->state == Ready)
+ _threadready(t);
+ }
+ unlock(&p->lock);
+ _sched();
+}
+
+static inline Thread*
+runthread(Proc *p)
+{
+ Thread *t;
+ Tqueue *q;
+
+ if(p->nthreads==0)
+ return nil;
+ q = &p->ready;
+ lock(&p->readylock);
+ if(q->head == nil){
+ q->asleep = 1;
+ _threaddebug(DBGSCHED, "sleeping for more work");
+ unlock(&p->readylock);
+ while(rendezvous((ulong)q, 0) == ~0){
+ if(_threadexitsallstatus)
+ exits(_threadexitsallstatus);
+ }
+ /* lock picked up from _threadready */
+ }
+ t = q->head;
+ q->head = t->next;
+ unlock(&p->readylock);
+ return t;
+}
+
+void
+_sched(void)
+{
+ Proc *p;
+ Thread *t;
+
+Resched:
+ p = _threadgetproc();
+//fprint(2, "p %p\n", p);
+ if((t = p->thread) != nil){
+ if((ulong)&p < (ulong)t->stk){ /* stack overflow */
+ fprint(2, "stack overflow %lux %lux\n", (ulong)&p, (ulong)t->stk);
+ abort();
+ }
+ // _threaddebug(DBGSCHED, "pausing, state=%s set %p goto %p",
+ // psstate(t->state), &t->sched, &p->sched);
+ if(_setlabel(&t->sched)==0)
+ _gotolabel(&p->sched);
+ return;
+ }else{
+ t = runthread(p);
+ if(t == nil){
+ _threaddebug(DBGSCHED, "all threads gone; exiting");
+ _threaddelproc();
+ _schedexit(p);
+ }
+ // _threaddebug(DBGSCHED, "running %d.%d", t->proc->pid, t->id);
+ p->thread = t;
+ if(t->moribund){
+ _threaddebug(DBGSCHED, "%d.%d marked to die");
+ goto Resched;
+ }
+ t->state = Running;
+ t->nextstate = Ready;
+ _gotolabel(&t->sched);
+ }
+}
+
+long
+threadstack(void)
+{
+ Proc *p;
+ Thread *t;
+
+ p = _threadgetproc();
+ t = p->thread;
+ return (ulong)&p - (ulong)t->stk;
+}
+
+void
+_threadready(Thread *t)
+{
+ Tqueue *q;
+
+ assert(t->state == Ready);
+ _threaddebug(DBGSCHED, "readying %d.%d", t->proc->pid, t->id);
+ q = &t->proc->ready;
+ lock(&t->proc->readylock);
+ t->next = nil;
+ if(q->head==nil)
+ q->head = t;
+ else
+ q->tail->next = t;
+ q->tail = t;
+ if(q->asleep){
+ q->asleep = 0;
+ /* lock passes to runthread */
+ _threaddebug(DBGSCHED, "waking process %d", t->proc->pid);
+ while(rendezvous((ulong)q, 0) == ~0){
+ if(_threadexitsallstatus)
+ exits(_threadexitsallstatus);
+ }
+ }else
+ unlock(&t->proc->readylock);
+}
+
+void
+yield(void)
+{
+ _sched();
+}
+
diff --git a/src/libthread/texec.c b/src/libthread/texec.c
new file mode 100644
index 00000000..9ba86827
--- /dev/null
+++ b/src/libthread/texec.c
@@ -0,0 +1,34 @@
+#include <lib9.h>
+#include <thread.h>
+extern int _threaddebuglevel;
+
+void
+doexec(void *v)
+{
+ char **argv = v;
+
+ procexec(nil, argv[0], argv);
+ sendp(threadwaitchan(), nil);
+}
+
+void
+threadmain(int argc, char **argv)
+{
+ Channel *c;
+ Waitmsg *w;
+
+ ARGBEGIN{
+ case 'D':
+ _threaddebuglevel = ~0;
+ break;
+ }ARGEND
+
+ c = threadwaitchan();
+ proccreate(doexec, argv, 8192);
+ w = recvp(c);
+ if(w == nil)
+ print("exec failed\n");
+ else
+ print("%d %lu %lu %lu %s\n", w->pid, w->time[0], w->time[1], w->time[2], w->msg);
+ threadexits(nil);
+}
diff --git a/src/libthread/thread.3 b/src/libthread/thread.3
new file mode 100644
index 00000000..3009ac84
--- /dev/null
+++ b/src/libthread/thread.3
@@ -0,0 +1,576 @@
+.TH THREAD 2
+.SH NAME
+alt,
+chancreate,
+chanfree,
+chaninit,
+chanprint,
+mainstacksize,
+proccreate,
+procdata,
+procexec,
+procexecl,
+procrfork,
+recv,
+recvp,
+recvul,
+send,
+sendp,
+sendul,
+nbrecv,
+nbrecvp,
+nbrecvul,
+nbsend,
+nbsendp,
+nbsendul,
+threadcreate,
+threaddata,
+threadexits,
+threadexitsall,
+threadgetgrp,
+threadgetname,
+threadint,
+threadintgrp,
+threadkill,
+threadkillgrp,
+threadmain,
+threadnotify,
+threadid,
+threadpid,
+threadsetgrp,
+threadsetname,
+threadwaitchan,
+yield \- thread and proc management
+.SH SYNOPSIS
+.PP
+.EX
+.ta 4n +4n +4n +4n +4n +4n +4n
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+.sp
+#define CHANEND 0
+#define CHANSND 1
+#define CHANRCV 2
+#define CHANNOP 3
+#define CHANNOBLK 4
+.sp
+.ta \w' 'u +\w'Channel 'u
+typedef struct Alt Alt;
+struct Alt {
+ Channel *c;
+ void *v;
+ int op;
+ Channel **tag;
+ int entryno;
+};
+.fi
+.de XX
+.ift .sp 0.5
+.ifn .sp
+..
+.PP
+.nf
+.ft L
+.ta \w'\fLChannel* 'u +4n +4n +4n +4n
+void threadmain(int argc, char *argv[])
+int mainstacksize
+int proccreate(void (*fn)(void*), void *arg, uint stacksize)
+int procrfork(void (*fn)(void*), void *arg, uint stacksize,
+ int rforkflag)
+int threadcreate(void (*fn)(void*), void *arg, uint stacksize)
+void threadexits(char *status)
+void threadexitsall(char *status)
+void yield(void)
+.XX
+int threadid(void)
+int threadgrp(void)
+int threadsetgrp(int group)
+int threadpid(int id)
+.XX
+int threadint(int id)
+int threadintgrp(int group)
+int threadkill(int id)
+int threadkillgrp(int group)
+.XX
+void threadsetname(char *name)
+char* threadgetname(void)
+.XX
+void** threaddata(void)
+void** procdata(void)
+.XX
+int chaninit(Channel *c, int elsize, int nel)
+Channel* chancreate(int elsize, int nel)
+void chanfree(Channel *c)
+.XX
+int alt(Alt *alts)
+int recv(Channel *c, void *v)
+void* recvp(Channel *c)
+ulong recvul(Channel *c)
+int nbrecv(Channel *c, void *v)
+void* nbrecvp(Channel *c)
+ulong nbrecvul(Channel *c)
+int send(Channel *c, void *v)
+int sendp(Channel *c, void *v)
+int sendul(Channel *c, ulong v)
+int nbsend(Channel *c, void *v)
+int nbsendp(Channel *c, void *v)
+int nbsendul(Channel *c, ulong v)
+int chanprint(Channel *c, char *fmt, ...)
+.XX
+int procexecl(Channel *cpid, char *file, ...)
+int procexec(Channel *cpid, char *file, char *args[])
+Channel* threadwaitchan(void)
+.XX
+int threadnotify(int (*f)(void*, char*), int in)
+.EE
+.SH DESCRIPTION
+.PP
+The thread library provides parallel programming support similar to that
+of the languages
+Alef and Newsqueak.
+Threads
+and
+procs
+occupy a shared address space,
+communicating and synchronizing through
+.I channels
+and shared variables.
+.PP
+A
+.I proc
+is a Plan 9 process that contains one or more cooperatively scheduled
+.IR threads .
+Programs using threads must replace
+.I main
+by
+.IR threadmain .
+The thread library provides a
+.I main
+function that sets up a proc with a single thread executing
+.I threadmain
+on a stack of size
+.I mainstacksize
+(default eight kilobytes).
+To set
+.IR mainstacksize ,
+declare a global variable
+initialized to the desired value
+.RI ( e.g. ,
+.B int
+.B mainstacksize
+.B =
+.BR 1024 ).
+.PP
+.I Threadcreate
+creates a new thread in the calling proc, returning a unique integer
+identifying the thread; the thread
+executes
+.I fn(arg)
+on a stack of size
+.IR stacksize .
+Thread stacks are allocated in shared memory, making it valid to pass
+pointers to stack variables between threads and procs.
+.I Procrfork
+creates a new proc, and inside that proc creates
+a single thread as
+.I threadcreate
+would,
+returning the id of the created thread.
+.I Procrfork
+creates the new proc by calling
+.B rfork
+(see
+.IR fork (2))
+with flags
+.BR RFPROC|RFMEM|RFNOWAIT| \fIrforkflag\fR.
+(The thread library depends on all its procs
+running in the same rendezvous group.
+Do not include
+.B RFREND
+in
+.IR rforkflag .)
+.I Proccreate
+is identical to
+.I procrfork
+with
+.I rforkflag
+set to zero.
+Be aware that the calling thread may continue
+execution before
+the newly created proc and thread
+are scheduled.
+Because of this,
+.I arg
+should not point to data on the stack of a function that could
+return before the new process is scheduled.
+.PP
+.I Threadexits
+terminates the calling thread.
+If the thread is the last in its proc,
+.I threadexits
+also terminates the proc, using
+.I status
+as the exit status.
+.I Threadexitsall
+terminates all procs in the program,
+using
+.I status
+as the exit status.
+.PP
+The threads in a proc are coroutines, scheduled nonpreemptively
+in a round-robin fashion.
+A thread must explicitly relinquish control of the processor
+before another thread in the same proc is run.
+Calls that do this are
+.IR yield ,
+.IR proccreate ,
+.IR procexec ,
+.IR procexecl ,
+.IR threadexits ,
+.IR alt ,
+.IR send ,
+and
+.I recv
+(and the calls related to
+.I send
+and
+.IR recv \(emsee
+their descriptions further on).
+Procs are scheduled by the operating system.
+Therefore, threads in different procs can preempt one another
+in arbitrary ways and should synchronize their
+actions using
+.B qlocks
+(see
+.IR lock (2))
+or channel communication.
+System calls such as
+.IR read (2)
+block the entire proc;
+all threads in a proc block until the system call finishes.
+.PP
+As mentioned above, each thread has a unique integer thread id.
+Thread ids are not reused; they are unique across the life of the program.
+.I Threadid
+returns the id for the current thread.
+Each thread also has a thread group id.
+The initial thread has a group id of zero.
+Each new thread inherits the group id of
+the thread that created it.
+.I Threadgrp
+returns the group id for the current thread;
+.I threadsetgrp
+sets it.
+.I Threadpid
+returns the pid of the Plan 9 process containing
+the thread identified by
+.IR id ,
+or \-1
+if no such thread is found.
+.PP
+.I Threadint
+interrupts a thread that is blocked in a channel operation
+or system call.
+.I Threadintgrp
+interrupts all threads with the given group id.
+.I Threadkill
+marks a thread to die when it next relinquishes the processor
+(via one of the calls listed above).
+If the thread is blocked in a channel operation or system call,
+it is also interrupted.
+.I Threadkillgrp
+kills all threads with the given group id.
+Note that
+.I threadkill
+and
+.I threadkillgrp
+will not terminate a thread that never relinquishes
+the processor.
+.PP
+Primarily for debugging,
+threads can have string names associated with them.
+.I Threadgetname
+returns the current thread's name;
+.I threadsetname
+sets it.
+The pointer returned by
+.I threadgetname
+is only valid until the next call to
+.IR threadsetname .
+.PP
+.I Threaddata
+returns a pointer to a per-thread pointer
+that may be modified by threaded programs for
+per-thread storage.
+Similarly,
+.I procdata
+returns a pointer to a per-proc pointer.
+.PP
+.I Procexecl
+and
+.I procexec
+are threaded analogues of
+.I exec
+and
+.I execl
+(see
+.IR exec (2));
+on success,
+they replace the calling thread (which must be the only thread in its proc)
+and invoke the external program, never returning.
+On error, they return \-1.
+If
+.I cpid
+is not null, the pid of the invoked program
+will be sent along
+.I cpid
+once the program has been started, or \-1 will be sent if an
+error occurs.
+.I Procexec
+and
+.I procexecl
+will not access their arguments after sending a result
+along
+.IR cpid .
+Thus, programs that malloc the
+.I argv
+passed to
+.I procexec
+can safely free it once they have
+received the
+.I cpid
+response.
+.I Threadwaitchan
+returns a channel of pointers to
+.B Waitmsg
+structures (see
+.IR wait (2)).
+When an exec'ed process exits, a pointer to a
+.B Waitmsg
+is sent to this channel.
+These
+.B Waitmsg
+structures have been allocated with
+.IR malloc (2)
+and should be freed after use.
+.PP
+A
+.B Channel
+is a buffered or unbuffered queue for fixed-size messages.
+Procs and threads
+.I send
+messages into the channel and
+.I recv
+messages from the channel. If the channel is unbuffered, a
+.I send
+operation blocks until the corresponding
+.I recv
+operation occurs and
+.IR "vice versa" .
+.I Chaninit
+initializes a
+.B Channel
+for messages of size
+.I elsize
+and with a buffer holding
+.I nel
+messages.
+If
+.I nel
+is zero, the channel is unbuffered.
+.IR Chancreate
+allocates a new channel and initializes it.
+.I Chanfree
+frees a channel that is no longer used.
+.I Chanfree
+can be called by either sender or receiver after the last item has been
+sent or received. Freeing the channel will be delayed if there is a thread
+blocked on it until that thread unblocks (but
+.I chanfree
+returns immediately).
+.PP
+.I Send
+sends the element pointed at by
+.I v
+to the channel
+.IR c .
+If
+.I v
+is null, zeros are sent.
+.I Recv
+receives an element from
+.I c
+and stores it in
+.IR v .
+If
+.I v
+is null,
+the received value is discarded.
+.I Send
+and
+.I recv
+return 1 on success, \-1 if interrupted.
+.I Nbsend
+and
+.I nbrecv
+behave similarly, but return 0 rather than blocking.
+.PP
+.IR Sendp ,
+.IR nbsendp ,
+.IR sendul ,
+and
+.I nbsendul
+send a pointer or an unsigned long; the channel must
+have been initialized with the appropriate
+.IR elsize .
+.IR Recvp ,
+.IR nbrecvp ,
+.IR recvul ,
+and
+.I nbrecvul
+receive a pointer or an unsigned long;
+they return zero when a zero is received,
+when interrupted, or
+(for
+.I nbrecvp
+and
+.IR nbrecvul )
+when the operation would have blocked.
+To distinguish between these three cases,
+use
+.I recv
+or
+.IR nbrecv .
+.PP
+.I Alt
+can be used to recv from or send to one of a number of channels,
+as directed by an array of
+.B Alt
+structures,
+each of which describes a potential send or receive operation.
+In an
+.B Alt
+structure,
+.B c
+is the channel;
+.B v
+the value pointer (which may be null); and
+.B op
+the operation:
+.B CHANSND
+for a send operation,
+.B CHANRECV
+for a recv operation;
+.B CHANNOP
+for no operation
+(useful
+when
+.I alt
+is called with a varying set of operations).
+The array of
+.B Alt
+structures is terminated by an entry with
+.I op
+.B CHANEND
+or
+.BR CHANNOBLK .
+If at least one
+.B Alt
+structure can proceed, one of them is
+chosen at random to be executed.
+.I Alt
+returns the index of the chosen structure.
+If no operations can proceed and the list is terminated with
+.BR CHANNOBLK ,
+.I alt
+returns the index of the terminating
+.B CHANNOBLK
+structure.
+Otherwise,
+.I alt
+blocks until one of the operations can proceed,
+eventually returning the index of the structure executes.
+.I Alt
+returns \-1 when interrupted.
+The
+.B tag
+and
+.B entryno
+fields in the
+.B Alt
+structure are used internally by
+.I alt
+and need not be initialized.
+They are not used between
+.I alt
+calls.
+.PP
+.I Chanprint
+formats its arguments in the manner of
+.IR print (2)
+and sends the result to the channel
+.IR c.
+The string delivered by
+.I chanprint
+is allocated with
+.IR malloc (2)
+and should be freed upon receipt.
+.PP
+Thread library functions do not return on failure;
+if errors occur, the entire program is aborted.
+.PP
+Threaded programs should use
+.I threadnotify
+in place of
+.I atnotify
+(see
+.IR notify (2)).
+.PP
+It is safe to use
+.B sysfatal
+(see
+.IR perror (2))
+in threaded programs.
+.I Sysfatal
+will print the error string and call
+.IR threadexitsall .
+.PP
+It is safe to use
+.IR rfork
+(see
+.IR fork (2))
+to manage the namespace, file descriptors, note group, and environment of a
+single process.
+That is, it is safe to call
+.I rfork
+with the flags
+.BR RFNAMEG ,
+.BR RFFDG ,
+.BR RFCFDG ,
+.BR RFNOTEG ,
+.BR RFENVG ,
+and
+.BR RFCENVG.
+(To create new processes, use
+.I proccreate
+and
+.IR procrfork .)
+As mentioned above,
+the thread library depends on all procs being in the
+same rendezvous group; do not change the rendezvous
+group with
+.IR rfork .
+.SH FILES
+.B /sys/lib/acid/thread
+contains useful
+.IR acid (1)
+functions for debugging threaded programs.
+.PP
+.B /sys/src/libthread/example.c
+contains a full example program.
+.SH SOURCE
+.B /sys/src/libthread
+.SH SEE ALSO
+.IR intro (2),
+.IR ioproc (2)
diff --git a/src/libthread/thread.h b/src/libthread/thread.h
new file mode 100644
index 00000000..10aac284
--- /dev/null
+++ b/src/libthread/thread.h
@@ -0,0 +1,132 @@
+#ifndef _THREADH_
+#define _THREADH_ 1
+
+/* avoid conflicts with socket library */
+#undef send
+#define send _threadsend
+#undef recv
+#define recv _threadrecv
+
+typedef struct Alt Alt;
+typedef struct Channel Channel;
+typedef struct Ref Ref;
+
+/* Channel structure. S is the size of the buffer. For unbuffered channels
+ * s is zero. v is an array of s values. If s is zero, v is unused.
+ * f and n represent the state of the queue pointed to by v.
+ */
+
+enum {
+ Nqwds = 2,
+ Nqshift = 5, // 2log #of bits in long
+ Nqmask = - 1,
+ Nqbits = (1 << Nqshift) * 2,
+};
+
+struct Channel {
+ int s; // Size of the channel (may be zero)
+ unsigned int f; // Extraction point (insertion pt: (f + n) % s)
+ unsigned int n; // Number of values in the channel
+ int e; // Element size
+ int freed; // Set when channel is being deleted
+ volatile Alt **qentry; // Receivers/senders waiting (malloc)
+ volatile int nentry; // # of entries malloc-ed
+ unsigned char v[1]; // Array of s values in the channel
+};
+
+
+/* Channel operations for alt: */
+typedef enum {
+ CHANEND,
+ CHANSND,
+ CHANRCV,
+ CHANNOP,
+ CHANNOBLK,
+} ChanOp;
+
+struct Alt {
+ Channel *c; /* channel */
+ void *v; /* pointer to value */
+ ChanOp op; /* operation */
+
+ /* the next variables are used internally to alt
+ * they need not be initialized
+ */
+ Channel **tag; /* pointer to rendez-vous tag */
+ int entryno; /* entry number */
+};
+
+struct Ref {
+ long ref;
+};
+
+int alt(Alt alts[]);
+Channel* chancreate(int elemsize, int bufsize);
+int chaninit(Channel *c, int elemsize, int elemcnt);
+void chanfree(Channel *c);
+int chanprint(Channel *, char *, ...);
+long decref(Ref *r); /* returns 0 iff value is now zero */
+void incref(Ref *r);
+int nbrecv(Channel *c, void *v);
+void* nbrecvp(Channel *c);
+unsigned long nbrecvul(Channel *c);
+int nbsend(Channel *c, void *v);
+int nbsendp(Channel *c, void *v);
+int nbsendul(Channel *c, unsigned long v);
+int proccreate(void (*f)(void *arg), void *arg, unsigned int stacksize);
+int procrfork(void (*f)(void *arg), void *arg, unsigned int stacksize, int flag);
+void** procdata(void);
+void procexec(Channel *, char *, char *[]);
+void procexecl(Channel *, char *, ...);
+int recv(Channel *c, void *v);
+void* recvp(Channel *c);
+unsigned long recvul(Channel *c);
+int send(Channel *c, void *v);
+int sendp(Channel *c, void *v);
+int sendul(Channel *c, unsigned long v);
+int threadcreate(void (*f)(void *arg), void *arg, unsigned int stacksize);
+void** threaddata(void);
+void threadexits(char *);
+void threadexitsall(char *);
+int threadgetgrp(void); /* return thread group of current thread */
+char* threadgetname(void);
+void threadint(int); /* interrupt thread */
+void threadintgrp(int); /* interrupt threads in grp */
+void threadkill(int); /* kill thread */
+void threadkillgrp(int); /* kill threads in group */
+void threadmain(int argc, char *argv[]);
+void threadnonotes(void);
+int threadnotify(int (*f)(void*, char*), int in);
+int threadid(void);
+int threadpid(int);
+int threadsetgrp(int); /* set thread group, return old */
+void threadsetname(char *name);
+Channel* threadwaitchan(void);
+int tprivalloc(void);
+void tprivfree(int);
+void **tprivaddr(int);
+void yield(void);
+
+long threadstack(void);
+
+extern int mainstacksize;
+
+/* slave I/O processes */
+typedef struct Ioproc Ioproc;
+
+Ioproc* ioproc(void);
+void closeioproc(Ioproc*);
+void iointerrupt(Ioproc*);
+
+int ioclose(Ioproc*, int);
+int iodial(Ioproc*, char*, char*, char*, int*);
+int ioopen(Ioproc*, char*, int);
+long ioread(Ioproc*, int, void*, long);
+long ioreadn(Ioproc*, int, void*, long);
+long iowrite(Ioproc*, int, void*, long);
+int iosleep(Ioproc*, long);
+
+long iocall(Ioproc*, long (*)(va_list*), ...);
+void ioret(Ioproc*, int);
+
+#endif /* _THREADH_ */
diff --git a/src/libthread/threadimpl.h b/src/libthread/threadimpl.h
new file mode 100644
index 00000000..24d9b214
--- /dev/null
+++ b/src/libthread/threadimpl.h
@@ -0,0 +1,219 @@
+/*
+ * Some notes on locking:
+ *
+ * All the locking woes come from implementing
+ * threadinterrupt (and threadkill).
+ *
+ * _threadgetproc()->thread is always a live pointer.
+ * p->threads, p->ready, and _threadrgrp also contain
+ * live thread pointers. These may only be consulted
+ * while holding p->lock or _threadrgrp.lock; in procs
+ * other than p, the pointers are only guaranteed to be live
+ * while the lock is still being held.
+ *
+ * Thread structures can only be freed by the proc
+ * they belong to. Threads marked with t->inrendez
+ * need to be extracted from the _threadrgrp before
+ * being freed.
+ *
+ * _threadrgrp.lock cannot be acquired while holding p->lock.
+ */
+
+#include <assert.h>
+#include <lib9.h>
+#include <thread.h>
+#include "label.h"
+
+enum{
+STKSIZE = 16384,
+STKMAGIC = 0xCAFEBEEF
+};
+
+typedef struct Thread Thread;
+typedef struct Proc Proc;
+typedef struct Tqueue Tqueue;
+typedef struct Pqueue Pqueue;
+typedef struct Rgrp Rgrp;
+typedef struct Execargs Execargs;
+
+/* must match list in sched.c */
+typedef enum
+{
+ Dead,
+ Running,
+ Ready,
+ Rendezvous,
+} State;
+
+typedef enum
+{
+ Channone,
+ Chanalt,
+ Chansend,
+ Chanrecv,
+} Chanstate;
+
+enum
+{
+ RENDHASH = 10009,
+ Printsize = 2048,
+ NPRIV = 8,
+};
+
+struct Rgrp
+{
+ Lock lock;
+ Thread *hash[RENDHASH];
+};
+
+struct Tqueue /* Thread queue */
+{
+ int asleep;
+ Thread *head;
+ Thread *tail;
+};
+
+struct Thread
+{
+ Lock lock; /* protects thread data structure */
+ Label sched; /* for context switches */
+ int id; /* thread id */
+ int grp; /* thread group */
+ int moribund; /* thread needs to die */
+ State state; /* run state */
+ State nextstate; /* next run state */
+ uchar *stk; /* top of stack (lowest address of stack) */
+ uint stksize; /* stack size */
+ Thread *next; /* next on ready queue */
+
+ Proc *proc; /* proc of this thread */
+ Thread *nextt; /* next on list of threads in this proc */
+ Thread *prevt; /* prev on list of threads in this proc */
+ int ret; /* return value for Exec, Fork */
+
+ char *cmdname; /* ptr to name of thread */
+
+ int inrendez;
+ Thread *rendhash; /* Trgrp linked list */
+ ulong rendtag; /* rendezvous tag */
+ ulong rendval; /* rendezvous value */
+ int rendbreak; /* rendezvous has been taken */
+
+ Chanstate chan; /* which channel operation is current */
+ Alt *alt; /* pointer to current alt structure (debugging) */
+
+ void* udata[NPRIV]; /* User per-thread data pointer */
+};
+
+struct Execargs
+{
+ char *prog;
+ char **args;
+ int fd[2];
+};
+
+struct Proc
+{
+ Lock lock;
+ Label sched; /* for context switches */
+ Proc *link; /* in proctab */
+ int pid; /* process id */
+ int splhi; /* delay notes */
+ Thread *thread; /* running thread */
+
+ int needexec;
+ Execargs exec; /* exec argument */
+ Proc *newproc; /* fork argument */
+ char exitstr[ERRMAX]; /* exit status */
+
+ int rforkflag;
+ int nthreads;
+ Tqueue threads; /* All threads of this proc */
+ Tqueue ready; /* Runnable threads */
+ Lock readylock;
+
+ char printbuf[Printsize];
+ int blocked; /* In a rendezvous */
+ int pending; /* delayed note pending */
+ int nonotes; /* delay notes */
+ uint nextID; /* ID of most recently created thread */
+ Proc *next; /* linked list of Procs */
+
+ void *arg; /* passed between shared and unshared stk */
+ char str[ERRMAX]; /* used by threadexits to avoid malloc */
+ char errbuf[ERRMAX]; /* errstr */
+
+ void* udata; /* User per-proc data pointer */
+};
+
+struct Pqueue { /* Proc queue */
+ Lock lock;
+ Proc *head;
+ Proc **tail;
+};
+
+struct Ioproc
+{
+ int tid;
+ Channel *c, *creply;
+ int inuse;
+ long (*op)(va_list*);
+ va_list arg;
+ long ret;
+ char err[ERRMAX];
+ Ioproc *next;
+};
+
+void _gotolabel(Label*);
+int _setlabel(Label*);
+void _freeproc(Proc*);
+Proc* _newproc(void(*)(void*), void*, uint, char*, int, int);
+int _procsplhi(void);
+void _procsplx(int);
+void _sched(void);
+int _schedexec(Execargs*);
+void _schedexecwait(void);
+void _schedexit(Proc*);
+int _schedfork(Proc*);
+void _schedinit(void*);
+void _systhreadinit(void);
+void _threadassert(char*);
+void _threadbreakrendez(void);
+void __threaddebug(ulong, char*, ...);
+#define _threaddebug if(!_threaddebuglevel){}else __threaddebug
+void _threadexitsall(char*);
+void _threadflagrendez(Thread*);
+Proc* _threadgetproc(void);
+Proc* _threaddelproc(void);
+void _threadsetproc(Proc*);
+void _threadinitstack(Thread*, void(*)(void*), void*);
+void* _threadmalloc(long, int);
+void _threadnote(void*, char*);
+void _threadready(Thread*);
+ulong _threadrendezvous(ulong, ulong);
+void _threadsignal(void);
+void _threadsysfatal(char*, va_list);
+long _xdec(long*);
+void _xinc(long*);
+void _threadremove(Proc*, Thread*);
+
+extern int _threaddebuglevel;
+extern char* _threadexitsallstatus;
+extern Pqueue _threadpq;
+extern Channel* _threadwaitchan;
+extern Rgrp _threadrgrp;
+extern void _stackfree(void*);
+
+#define DBGAPPL (1 << 0)
+#define DBGSCHED (1 << 16)
+#define DBGCHAN (1 << 17)
+#define DBGREND (1 << 18)
+/* #define DBGKILL (1 << 19) */
+#define DBGNOTE (1 << 20)
+#define DBGEXEC (1 << 21)
+
+#define ioproc_arg(io, type) (va_arg((io)->arg, type))
+extern int _threadgetpid(void);
+extern void _threadmemset(void*, int, int);
+extern void _threaddebugmemset(void*, int, int);
+
diff --git a/src/libthread/tprimes b/src/libthread/tprimes
new file mode 100755
index 00000000..52d6f05e
--- /dev/null
+++ b/src/libthread/tprimes
Binary files differ
diff --git a/src/libthread/tprimes.c b/src/libthread/tprimes.c
new file mode 100644
index 00000000..9426bdfa
--- /dev/null
+++ b/src/libthread/tprimes.c
@@ -0,0 +1,62 @@
+#include <lib9.h>
+#include <thread.h>
+
+int quiet;
+int goal;
+int buffer;
+int (*fn)(void(*)(void*), void*, uint) = threadcreate;
+
+void
+primethread(void *arg)
+{
+ Channel *c, *nc;
+ int p, i;
+
+ c = arg;
+ p = recvul(c);
+ if(p > goal)
+ threadexitsall(nil);
+ if(!quiet)
+ print("%d\n", p);
+ nc = chancreate(sizeof(ulong), buffer);
+ (*fn)(primethread, nc, 8192);
+ for(;;){
+ i = recvul(c);
+ if(i%p)
+ sendul(nc, i);
+ }
+}
+
+extern int _threaddebuglevel;
+
+void
+threadmain(int argc, char **argv)
+{
+ int i;
+ Channel *c;
+
+ ARGBEGIN{
+ case 'D':
+ _threaddebuglevel = atoi(ARGF());
+ break;
+ case 'q':
+ quiet = 1;
+ break;
+ case 'b':
+ buffer = atoi(ARGF());
+ break;
+ case 'p':
+ fn=proccreate;
+ break;
+ }ARGEND
+
+ if(argc>0)
+ goal = atoi(argv[0]);
+ else
+ goal = 100;
+
+ c = chancreate(sizeof(ulong), buffer);
+ (*fn)(primethread, c, 8192);
+ for(i=2;; i++)
+ sendul(c, i);
+}