aboutsummaryrefslogtreecommitdiff
path: root/sys
diff options
context:
space:
mode:
authorNicholas Noll <nbnoll@eml.cc>2020-04-19 09:23:31 -0700
committerNicholas Noll <nbnoll@eml.cc>2020-04-19 09:23:31 -0700
commit60ce7fba21a6d37c0acbe152039fbc3d0e692bf0 (patch)
tree73fa0295dace25c23a00f4ec4e654b4f4fb85619 /sys
parent1ae9a10d56fca8fe585e77533c49e5c9d680ff12 (diff)
chore: reorganized structure to allow for more parallel projects
Diffstat (limited to 'sys')
-rw-r--r--sys/cc/rules.mk0
-rw-r--r--sys/libc/rules.mk0
-rw-r--r--sys/libn/coro.c65
-rw-r--r--sys/libn/coro_unix_x64.s113
-rw-r--r--sys/libn/error.c14
-rw-r--r--sys/libn/memory.c49
-rw-r--r--sys/libn/rules.mk46
-rw-r--r--sys/libn/string.c507
-rw-r--r--sys/libn/test.c105
-rw-r--r--sys/nixos/rules.mk0
-rw-r--r--sys/rules.mk20
11 files changed, 919 insertions, 0 deletions
diff --git a/sys/cc/rules.mk b/sys/cc/rules.mk
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/sys/cc/rules.mk
diff --git a/sys/libc/rules.mk b/sys/libc/rules.mk
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/sys/libc/rules.mk
diff --git a/sys/libn/coro.c b/sys/libn/coro.c
new file mode 100644
index 0000000..af0a359
--- /dev/null
+++ b/sys/libn/coro.c
@@ -0,0 +1,65 @@
+#include <u.h>
+
+// -----------------------------------------------------------------------
+// Assembly routines
+
+extern void _newcoro(coro *co, uintptr (*func)(coro*, uintptr), void *stk);
+extern uintptr _coroyield(coro *co, uintptr arg);
+
+// -----------------------------------------------------------------------
+// Globals
+
+// static thread_local coro *CONTEXT;
+
+// -----------------------------------------------------------------------
+// C interface
+
+/* Co-routine context */
+struct coro
+{
+ void* sp;
+ void* bp;
+ uintptr size;
+ void* user;
+};
+
+coro*
+coro·new(uintptr stk, uintptr (*func)(coro*, uintptr))
+{
+ if (!func) return nil;
+ if (stk == 0) stk = 8192;
+
+ byte *block = malloc(stk);
+ coro *co = (coro*)&block[stk - sizeof(coro)];
+ co->bp = block;
+ co->size = stk;
+
+ _newcoro(co, func, co);
+ return co;
+}
+
+error
+coro·free(coro *co)
+{
+ enum
+ {
+ NIL,
+ GOOD,
+ EMPTY,
+ LOST,
+ };
+
+ if (!co) return NIL;
+ if (!co->bp) return LOST;
+ if (co->size == 0) return EMPTY;
+
+ free(co->bp);
+
+ return GOOD;
+}
+
+uintptr
+coro·yield(coro *c, uintptr arg)
+{
+ return _coroyield(c, arg);
+}
diff --git a/sys/libn/coro_unix_x64.s b/sys/libn/coro_unix_x64.s
new file mode 100644
index 0000000..d7de2a2
--- /dev/null
+++ b/sys/libn/coro_unix_x64.s
@@ -0,0 +1,113 @@
+; Nicholas Noll 2019
+;
+; ===================================================================
+%use altreg
+
+ bits 64
+ default rel
+ global _newcoro
+ global _coroyield
+
+; ===================================================================
+ section .text
+; -------------------------------------------------------------------
+
+%assign L.coro -8
+%assign L.func -16
+
+coroinit:
+ mov R7, [RBP + L.coro]
+ mov R6, R0
+ call [RBP + L.func]
+
+rerun:
+ mov R7, [RBP + L.coro]
+ mov R6, R0
+ call _coroyield
+ jmp rerun
+
+; -------------------------------------------------------------------
+; # Register Mapping
+;
+; R0 R1 R2 R3 R4 R5 R6 R7 R8 ...
+; RAX RCX RDX RBX RSP RBP RSI RDI R8 ...
+;
+; # Sys V calling convention
+; func(R7, R6, R2, R1, R8, R9, Z0-7): R0
+;
+; # Stack layout of an in-flight coro
+; *coro
+; *func
+; *bp (base pointer of stack)
+; ....... STACK .........
+; Saved Clobbers
+;
+; ###
+; Stack layout of an init coro
+; Stores the func pointer to init
+; Stores the clobber registers.
+;
+; L.coro [8]
+; L.func [7]
+; coroinit [6]
+; RBP [5]
+; R3 [4]
+; R12 [3]
+; R13 [2]
+; R14 [1]
+; R15 [0]
+
+%define WORDSZ 8
+%define NSAVES 9
+
+; coro *coro·new(co *coro, fn func, bp *stack)
+_newcoro:
+ lea R0, [coroinit] ; Store address of init function
+ lea R1, [R2 - NSAVES*WORDSZ] ; Store offset address of stack
+
+ mov [R1 + 8*WORDSZ], R7 ; Store context pointer
+ mov [R1 + 7*WORDSZ], R6 ; Store function pointer
+ mov [R1 + 6*WORDSZ], R0 ; Store initializer pointer
+ mov [R1 + 5*WORDSZ], R2 ; Store stack base pointer
+
+ xor R0, R0
+
+ ; Start of mutable stack
+ ; Blank out the clobbers
+ mov [R1 + 4*WORDSZ], R0 ; R3
+ mov [R1 + 3*WORDSZ], R0 ; R12
+ mov [R1 + 2*WORDSZ], R0 ; R13
+ mov [R1 + 1*WORDSZ], R0 ; R14
+ mov [R1 + 0*WORDSZ], R0 ; R15
+
+ mov [R7], R1
+ ret
+
+; Saves register state
+%macro pushclobs 0
+ push RBP
+ push R3
+ push R12
+ push R13
+ push R14
+ push R15
+%endmacro
+
+; Restores register state
+%macro popclobs 0
+ pop R15
+ pop R14
+ pop R13
+ pop R12
+ pop R3
+ pop RBP
+%endmacro
+
+; uintptr coro.yield(co *coro, data uintptr)
+_coroyield:
+ pushclobs
+ mov R0, R6 ; Move return value into return register.
+ xchg RSP, [R7] ; Atomically swap the stack pointer with the yieldee.
+ popclobs
+
+ ret
diff --git a/sys/libn/error.c b/sys/libn/error.c
new file mode 100644
index 0000000..b3f2eb0
--- /dev/null
+++ b/sys/libn/error.c
@@ -0,0 +1,14 @@
+#include <u.h>
+
+void
+errorf(const byte* fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+
+ printf("error: ");
+ vprintf(fmt, args);
+ printf("\n");
+
+ va_end(args);
+}
diff --git a/sys/libn/memory.c b/sys/libn/memory.c
new file mode 100644
index 0000000..3d35299
--- /dev/null
+++ b/sys/libn/memory.c
@@ -0,0 +1,49 @@
+#include <u.h>
+
+// -------------------------------------------------------------------------
+// Dynamic buffer.
+
+/* Grow to particular size */
+void*
+bufgrow(void* buf, vlong newLen, vlong eltsize)
+{
+ Assert(bufcap(buf) <= (SIZE_MAX - 1) / 2);
+
+ vlong newCap = MAX(16, MAX(1 + 2 * bufcap(buf), newLen));
+
+ Assert(newLen <= newCap);
+ Assert(newCap <= (SIZE_MAX - offsetof(bufHdr, buf)) / eltsize);
+
+ vlong newSize = offsetof(bufHdr, buf) + newCap * eltsize;
+
+ bufHdr* newHdr;
+ if (buf) {
+ newHdr = _bufHdr(buf);
+ newHdr = (bufHdr*)realloc((void*)newHdr, newSize);
+ } else {
+ newHdr = (bufHdr*)malloc(newSize);
+ newHdr->len = 0;
+ }
+
+ newHdr->cap = newCap;
+ return (void*)newHdr->buf;
+}
+
+/* Pop out a value */
+void
+_bufpop(void *buf, int i, vlong eltsize)
+{
+ int n;
+ byte *b;
+ byte stk[1024];
+ Assert(eltsize < sizeof(stk));
+
+ b = (byte*) buf;
+ if (n = buflen(buf), i < n) {
+ memcpy(stk, b+eltsize*i, eltsize);
+ memcpy(b+eltsize*i, b+eltsize*(i+1), eltsize*(n-i-1));
+ memcpy(b+eltsize*(n-1), stk, eltsize);
+ }
+ _bufHdr(buf)->len--;
+}
+
diff --git a/sys/libn/rules.mk b/sys/libn/rules.mk
new file mode 100644
index 0000000..b9efc37
--- /dev/null
+++ b/sys/libn/rules.mk
@@ -0,0 +1,46 @@
+# ---- Push on stack ----
+SP := $(SP).x
+DIRSTACK_$(SP) := $(d)
+d := $(DIR)
+
+# Iterate through subdirectory tree
+DIR := $(d)/bufio
+include $(DIR)/rules.mk
+# ...
+
+# Local sources
+SRCS_$(d) := $(wildcard $(d)/*.c)
+ASMS_$(d) := $(wildcard $(d)/*.s)
+OBJS_$(d) := $(SRCS_$(d):.c=.o)
+OBJS_$(d) += $(ASMS_$(d):.s=.o)
+OBJS_$(d) := $(patsubst $(SRC_DIR)/%, $(OBJ_DIR)/%, $(OBJS_$(d)))
+DEPS_$(d) := $(OBJS_$(d):.o=.d)
+
+OBJS := $(OBJS) $(OBJS_$(d))
+DEPS := $(DEPS) $(DEPS_$(d))
+
+# Local targets
+LIBS_$(d) := $(d)/libnbn.a
+LIBS_$(d) := $(patsubst $(SRC_DIR)/%, $(OBJ_DIR)/%, $(LIBS_$(d)))
+LIBS := $(LIBS) $(LIBS_$(d))
+
+BINS_$(d) := $(d)/test
+BINS_$(d) := $(patsubst $(SRC_DIR)/%, $(OBJ_DIR)/%, $(BINS_$(d)))
+BINS := $(BINS) $(BINS_$(d))
+
+# Local rules
+# $(LIBS_$(d)) := TGTFLAGS :=
+# $(LIBS_$(d)) := TGTINCS :=
+# $(LIBS_$(d)) := TGTLIBS :=
+
+$(LIBS_$(d)): $(OBJS_$(d))
+ $(ARCHIVE)
+
+$(BINS_$(d)): $(OBJ_DIR)/test.o
+ $(LINK)
+
+# ---- Pop off stack ----
+-include $(DEPS_$(d))
+
+d := $(DIRSTACK_$(SP))
+SP := $(basename $(SP))
diff --git a/sys/libn/string.c b/sys/libn/string.c
new file mode 100644
index 0000000..5e66282
--- /dev/null
+++ b/sys/libn/string.c
@@ -0,0 +1,507 @@
+#include <u.h>
+
+#define MAX_STRING_ALLOC 1024 * 1024
+
+// -------------------------------------------------------------------------
+// UTF-8 functions
+
+#define Bit(i) (7-(i))
+/* N 0's preceded by i 1's e.g. T(Bit(2)) is 1100 0000 */
+#define Tbyte(i) (((1 << (Bit(i)+1))-1) ^ 0xFF)
+/* 0000 0000 0000 0111 1111 1111 */
+#define RuneX(i) ((1 << (Bit(i) + ((i)-1)*Bitx))-1)
+
+enum
+{
+ Bitx = Bit(1),
+ Tx = Tbyte(1),
+ Rune1 = (1 << (Bit(0)+0*Bitx)) - 1,
+
+ Maskx = (1 << Bitx) - 1, /* 0011 1111 */
+ Testx = Maskx ^ 0xff, /* 1100 0000 */
+
+ SurrogateMin = 0xD800,
+ SurrogateMax = 0xDFFF,
+ Bad = RuneErr,
+};
+
+int
+utf8·charToRune(Rune* r, byte* s)
+{
+ int c[UTFmax], i;
+ Rune l;
+
+ c[0] = *(ubyte*)(s);
+ if (c[0] < Tx) {
+ *r = c[0];
+ return 1;
+ }
+
+ l = c[0];
+ for (i = 1; i < UTFmax; i++) {
+ c[i] = *(ubyte*)(s+i);
+ c[i] ^= Tx;
+ if (c[i] & Testx) goto bad;
+
+ l = (l << Bitx) | c[i];
+ if (c[0] < Tbyte(i + 2)) {
+ l &= RuneX(i + 1);
+ if (i == 1) {
+ if (c[0] < Tbyte(2) || l <= Rune1)
+ goto bad;
+ } else if (l <= RuneX(i) || l > RuneMax)
+ goto bad;
+ if (i == 2 && SurrogateMin <= l && l <= SurrogateMax)
+ goto bad;
+
+ *r = l;
+ return i + 1;
+ }
+ }
+bad:
+ *r = RuneErr;
+ return 1;
+}
+
+int
+utf8·runeToChar(byte* s, Rune* r)
+{
+ int i, j;
+ Rune c;
+
+ c = *r;
+ if (c <= Rune1) {
+ s[0] = c;
+ return 1;
+ }
+
+ for (i = 2; i < UTFmax + 1; i++){
+ if (i == 3){
+ if (c > RuneMax)
+ c = RuneErr;
+ if (SurrogateMin <= c && c <= SurrogateMax)
+ c = RuneErr;
+ }
+ if (c <= RuneX(i) || i == UTFmax) {
+ s[0] = Tbyte(i) | (c >> (i - 1)*Bitx);
+ for(j = 1; j < i; j++)
+ s[j] = Tx | ((c >> (i - j - 1)*Bitx) & Maskx);
+ return i;
+ }
+ }
+
+ return UTFmax;
+}
+
+int
+utf8·runeLen(Rune r)
+{
+ byte s[10];
+ return utf8·runeToChar(s, &r);
+}
+
+int
+utf8·fullRune(byte* s, int n)
+{
+ int i;
+ Rune c;
+
+ if (n <= 0) return 0;
+ c = *(ubyte*) s;
+ if (c < Tx) return 1;
+
+ for (i = 3; i < UTFmax + 1; i++) {
+ if (c < Tbyte(i)) return n >= i - 1;
+ }
+
+ return n >= UTFmax;
+}
+
+byte*
+utf8·findRune(byte* s, long c)
+{
+ long c1;
+ Rune r;
+ int n;
+
+ if (c < RuneSync) return strchr(s, c);
+
+ for (;;) {
+ c1 = *(ubyte*)s;
+ if (c1 < RuneSelf) {
+ if (c1 == 0) return nil;
+ if (c1 == c) return s;
+ s++;
+ continue;
+ }
+ n = utf8·charToRune(&r, s);
+ if (r == c) return s;
+ s += n;
+ }
+
+ return nil;
+}
+
+#undef Bit
+#undef Tbyte
+#undef RuneX
+
+#include ".generated/utf8.c"
+
+// -------------------------------------------------------------------------
+// Dynamic string functions
+
+// New returns a new dynamic string object, initialized from the given C string.
+// len defines the length of the C substring that we will copy into our buffer.
+// The backing buffer will have capacity cap.
+string
+str·newCap(const byte* s, vlong len, vlong cap)
+{
+ struct str·Hdr* h;
+
+ h = malloc(sizeof(*h) + cap + 1);
+ if (s == nil) memset(h, 0, sizeof(*h));
+
+ if (h == nil) return nil; // Allocation failed.
+
+ h->len = (s == nil) ? 0 : len;
+ h->cap = cap;
+
+ if (cap < h->len) goto cleanup;
+
+ if (s != nil && cap > 0) {
+ memcpy(h->buf, s, h->len);
+ memset(h->buf + h->len, '\0', h->cap - h->len + 1);
+ }
+
+ return h->buf;
+
+cleanup:
+ free(h);
+ panicf("Attempted to create a string with less capacity than length");
+}
+
+// New returns a new dynamic string object, initialized from the given C string.
+// The backing buffer capacity is equivalent to the string length.
+string
+str·newLen(const byte* s, vlong len)
+{
+ vlong sl = (s == nil) ? 0 : strlen(s);
+ if (sl < len) panicf("attempted to take a bigger substring than string length");
+
+ vlong cap = (len == 0) ? 1 : len;
+ return str·newCap(s, len, cap);
+}
+
+// New returns a new dynamic string object, initialized from the given C string.
+// The backing buffer capacity is equivalent to the string length.
+string
+str·new(const byte* s)
+{
+ vlong len = (s == nil) ? 0 : strlen(s);
+ return str·newLen(s, len);
+}
+
+// Newf returns a new dynamic string object
+string
+str·newf(const byte* fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+ vlong n = vsnprintf(nil, 0, fmt, args);
+ va_end(args);
+
+ string s = str·newCap(nil, 0, n);
+
+ va_start(args, fmt);
+ vsnprintf(s, n + 1, fmt, args);
+ va_end(args);
+
+ str·Hdr* h = (str·Hdr*)(s - sizeof(str·Hdr));
+ h->len = n;
+
+ return s;
+}
+
+// Free returns memory associated to the buffer.
+void
+str·free(string s)
+{
+ free(s - sizeof(str·Hdr));
+}
+
+// Len returns the length of the string.
+int
+str·len(const string s)
+{
+ str·Hdr* h = (str·Hdr*)(s - sizeof(str·Hdr));
+ return h->len;
+}
+
+// Cap returns the capacity of the string buffer.
+int
+str·cap(const string s)
+{
+ str·Hdr* h = (str·Hdr*)(s - sizeof(str·Hdr));
+ return h->cap;
+}
+
+string
+str·clear(string s)
+{
+ str·Hdr* h = (str·Hdr*)(s - sizeof(str·Hdr));
+ h->len = 0;
+ *s = 0;
+
+ return s;
+}
+
+// Grow ensures that the string can encompass AT LEAST delta bytes.
+// If it already can, this is a NO OP.
+// If it can't, the string will be reallocated.
+string
+str·grow(string s, vlong delta)
+{
+ str·Hdr *h, *newh;
+ vlong cap = str·cap(s);
+ vlong len = str·len(s);
+ Assert(cap >= len); // To prevent unsigned behavior
+
+ if (cap - len >= delta) return s;
+
+ h = (str·Hdr*)(s - sizeof(str·Hdr));
+
+ vlong newCap = cap + delta;
+ Assert(newCap >= cap); // To prevent unsigned behavior
+ if (newCap < MAX_STRING_ALLOC) {
+ newCap *= 2;
+ } else
+ newCap += MAX_STRING_ALLOC;
+
+ newh = (str·Hdr*)realloc(h, sizeof(*h) + newCap + 1);
+ if (newh == nil) return nil;
+
+ memset(newh->buf + len, '\0', newCap - len);
+ newh->cap = newCap;
+ newh->len = len;
+
+ return newh->buf;
+}
+
+// Fit reallocates the string such that the buffer is exactly sized for the
+// buffer. If the capacity equals the length, then the function is a NOOP. The
+// byte array is unchanged.
+string
+str·fit(string s)
+{
+ str·Hdr* h;
+ vlong cap = str·cap(s);
+ vlong len = str·len(s);
+
+ if (cap == len) return s;
+
+ h = (str·Hdr*)(s - sizeof(str·Hdr));
+ h = realloc(h, sizeof(*h) + len + 1);
+ h->cap = len;
+
+ return h->buf;
+}
+
+// Append will append the given null terminated C string to the string data
+// structure. This variant can append a substring of length len of the given
+// string to our buffer. The result is reallocated if not enough room is present
+// in the buffer.
+string
+str·appendCount(string s, vlong n, const byte* b)
+{
+ vlong bl = strlen(b);
+ if (n > bl) panicf("attempted to make a substring longer than string");
+
+ s = str·grow(s, n);
+ if (s == nil) return nil;
+
+ str·Hdr* h = (str·Hdr*)(s - sizeof(str·Hdr));
+
+ memcpy(s + str·len(s), b, n);
+ h->len += n;
+ s[h->len] = '\0';
+
+ return s;
+}
+
+// Append will append the given null terminated C string to the string data
+// structure. This variant will append the entire string.
+string
+str·append(string s, const byte* b)
+{
+ return str·appendCount(s, strlen(b), b);
+}
+
+// AppendByte will append the given byte to our string.
+// NOTE: As the byte is on the stack, it is not null-terminated.
+// Can not pass to the above functions.
+string
+str·appendByte(string s, const byte b)
+{
+ s = str·grow(s, 1);
+ if (s == nil) return nil;
+
+ str·Hdr* h = (str·Hdr*)(s - sizeof(str·Hdr));
+
+ *(s + str·len(s)) = b;
+ h->len++;
+ s[h->len] = '\0'; // NOTE: I don't think an explicit zero is required..?
+
+ return s;
+}
+
+// Equals returns true if string s and t are equivalent.
+bool
+str·equals(const string s, const string t)
+{
+ vlong sL = str·len(s);
+ vlong tL = str·len(t);
+ if (sL != tL) return false;
+
+ return memcmp(s, t, sL) == 0;
+}
+
+//------------------------------------------------------------------------
+// Utility Methods
+
+/*
+ * Appendf will append the given formatted string to our buffer.
+ * Returns the newly minted string
+ */
+string
+str·appendf(string s, const byte* fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+ int remain = str·cap(s) - str·len(s);
+ int n = vsnprintf(s + str·len(s), remain + 1, fmt, args);
+ va_end(args);
+
+ if (n > remain) {
+ // If the first write was incomplete, we overwite the data again.
+ s = str·grow(s, n);
+ va_list args;
+ va_start(args, fmt);
+ n = vsnprintf(s + str·len(s), n + 1, fmt, args);
+ Assert(n - remain <= str·cap(s));
+ va_end(args);
+ }
+
+ str·Hdr* h = (str·Hdr*)(s - sizeof(str·Hdr));
+ h->len += n;
+
+ return s;
+}
+
+// Find will find the first occurence of
+// substr in the string Returns -1 if nothing was found.
+int
+str·find(string s, const byte* substr)
+{
+ byte* loc = strstr(s, substr);
+ if (loc == nil) return -1;
+ return (int)(loc - s);
+}
+
+//
+// Lower will force all runes in the string to be lowercase.
+void
+str·Lower(string s)
+{
+ byte *b, *e;
+ b = s;
+ e = b + str·len(s);
+ while (b++ != e)
+ *b = tolower(*b);
+}
+
+// Upper will force all runes in the string to be uppercase.
+void
+str·upper(string s)
+{
+ byte *b, *e;
+ b = s;
+ e = b + str·len(s);
+ while (b++ != e)
+ *b = toupper(*b);
+}
+
+// Replace will replace all occurences of the given bytes 'from' to bytes 'to'
+// Edits are done in place and modify the string.
+// NOTE: As of now strings from and to must be the same size.
+void
+str·replace(string s, const byte* from, const byte* to)
+{
+ vlong fromL = strlen(from);
+ vlong toL = strlen(to);
+ if (toL != fromL) { panicf("different sized replacement string not supported"); }
+
+ vlong l = str·len(s);
+ vlong i = l;
+ vlong j = l;
+
+ for (i = 0; i < l; i++) {
+ for (j = 0; j < toL; j++) {
+ if (s[i] == from[j]) {
+ s[i] = to[j];
+ break;
+ }
+ }
+ }
+}
+
+// Split will split the string by the given token.
+// Returns a stretchy buffer of strings that result from the partition.
+// It is the caller's responsibility to clean the memory.
+string*
+str·split(string s, const byte* tok)
+{
+ string* fields = nil;
+ vlong start = 0;
+
+ vlong sL = str·len(s);
+ vlong tokL = strlen(tok);
+ if (sL == 0 || tokL == 0) return nil;
+
+ buffit(fields, 5);
+
+ for (vlong i = 0; i < sL - tokL; i++) {
+ if ((tokL == 1 && s[i] == tokL) || !memcmp(s + i, tok, tokL)) {
+ bufpush(fields, str·newLen(s + start, i - start));
+ if (fields[buflen(fields) - 1] == nil) goto cleanup;
+
+ start = i + tokL;
+ i += tokL - 1;
+ }
+ }
+
+ bufpush(fields, str·newLen(s + start, sL - start));
+
+ return fields;
+
+cleanup:
+ for (vlong i = 0; i < buflen(fields); i++) {
+ str·free(fields[i]);
+ }
+ buffree(fields);
+ return nil;
+}
+
+string
+str·join(vlong len, byte** fields, const byte* sep)
+{
+ string s = str·newCap(nil, 0, 10);
+ int j = 0;
+
+ for (j = 0; j < len; j++) {
+ s = str·append(s, fields[j]);
+ if (j < len - 1) { s = str·appendCount(s, 1, sep); }
+ }
+
+ return s;
+}
diff --git a/sys/libn/test.c b/sys/libn/test.c
new file mode 100644
index 0000000..3f8e608
--- /dev/null
+++ b/sys/libn/test.c
@@ -0,0 +1,105 @@
+#include <u.h>
+
+uintptr
+printtest(coro *c, uintptr d)
+{
+ printf("--> Recieved %lu\n", d);
+ d = coro·yield(c, d+10);
+ printf("--> Now %lu\n", d);
+
+ return d;
+}
+
+uintptr
+sequence(coro *c, uintptr start)
+{
+ int d = start;
+ for (;;) {
+ coro·yield(c, d++);
+ }
+
+ return d;
+}
+
+struct PrimeMsg
+{
+ coro *seq;
+ int p;
+};
+
+uintptr
+filter(coro *c, uintptr data)
+{
+ int x, p;
+ coro *seq;
+ struct PrimeMsg *msg;
+
+ // Need to copy relevant variables onto the local stack
+ // Data is volatile.
+ msg = (struct PrimeMsg*)data;
+ seq = msg->seq;
+ p = msg->p;
+
+ for (;;) {
+ x = coro·yield(seq, x);
+ if (x % p != 0) {
+ x = coro·yield(c, x);
+ }
+ }
+
+ return 0;
+}
+
+int
+main()
+{
+ int i;
+ coro *c[4];
+ uintptr d;
+
+ printf("Starting singleton test\n");
+
+ for (i = 0; i < arrlen(c); i++) {
+ c[i] = coro·new(0, &printtest);
+ }
+
+ /* Singleton test */
+ d = 0;
+ for (i = 0; i < 10; i++) {
+ d = coro·yield(c[0], d);
+ }
+
+ printf("Starting triplet test\n");
+
+ /* Triplet test */
+ for (i = 0; i < 10; i++) {
+ d = coro·yield(c[1], d);
+ d = coro·yield(c[2], d+100);
+ d = coro·yield(c[3], d+200);
+ }
+
+ for (i = 0; i < arrlen(c); i++) {
+ coro·free(c[i]);
+ }
+
+ /* Prime sieve */
+ printf("Starting prime test\n");
+ uintptr num;
+ coro *cur, *seq[50];
+
+ num = 2;
+ seq[0] = coro·new(4096, &sequence);
+ cur = *seq;
+
+ num = coro·yield(cur, num);
+ for (i = 1; i < arrlen(seq); i++) {
+ seq[i] = coro·new(4096, &filter);
+ struct PrimeMsg msg = {
+ .seq = cur,
+ .p = num,
+ };
+ cur = seq[i];
+ num = coro·yield(cur, (uintptr)&msg);
+ printf("--> prime number %lu\n", num);
+ }
+}
diff --git a/sys/nixos/rules.mk b/sys/nixos/rules.mk
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/sys/nixos/rules.mk
diff --git a/sys/rules.mk b/sys/rules.mk
new file mode 100644
index 0000000..b4b0159
--- /dev/null
+++ b/sys/rules.mk
@@ -0,0 +1,20 @@
+# ---- Push on stack ----
+SP := $(SP).x
+DIRSTACK_$(SP) := $(d)
+d := $(DIR)
+
+# Iterate through subdirectory tree
+DIR := $(d)/libc
+include $(DIR)/rules.mk
+
+DIR := $(d)/cc
+include $(DIR)/rules.mk
+
+DIR := $(d)/nixos
+include $(DIR)/rules.mk
+
+# ---- Pop off stack ----
+-include $(DEPS_$(d))
+
+d := $(DIRSTACK_$(SP))
+SP := $(basename $(SP))