From 60ce7fba21a6d37c0acbe152039fbc3d0e692bf0 Mon Sep 17 00:00:00 2001 From: Nicholas Noll Date: Sun, 19 Apr 2020 09:23:31 -0700 Subject: chore: reorganized structure to allow for more parallel projects --- sys/cc/rules.mk | 0 sys/libc/rules.mk | 0 sys/libn/coro.c | 65 ++++++ sys/libn/coro_unix_x64.s | 113 +++++++++++ sys/libn/error.c | 14 ++ sys/libn/memory.c | 49 +++++ sys/libn/rules.mk | 46 +++++ sys/libn/string.c | 507 +++++++++++++++++++++++++++++++++++++++++++++++ sys/libn/test.c | 105 ++++++++++ sys/nixos/rules.mk | 0 sys/rules.mk | 20 ++ 11 files changed, 919 insertions(+) create mode 100644 sys/cc/rules.mk create mode 100644 sys/libc/rules.mk create mode 100644 sys/libn/coro.c create mode 100644 sys/libn/coro_unix_x64.s create mode 100644 sys/libn/error.c create mode 100644 sys/libn/memory.c create mode 100644 sys/libn/rules.mk create mode 100644 sys/libn/string.c create mode 100644 sys/libn/test.c create mode 100644 sys/nixos/rules.mk create mode 100644 sys/rules.mk (limited to 'sys') diff --git a/sys/cc/rules.mk b/sys/cc/rules.mk new file mode 100644 index 0000000..e69de29 diff --git a/sys/libc/rules.mk b/sys/libc/rules.mk new file mode 100644 index 0000000..e69de29 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 + +// ----------------------------------------------------------------------- +// 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 + +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 + +// ------------------------------------------------------------------------- +// 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 + +#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 + +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 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)) -- cgit v1.2.1