Browse Source

Implement core:thread and core:sync on Unix using pthreads

Also do some cleanup and refactoring of the thread, sync and time APIs.

- remove 'semaphore_release' because 'post' and 'wait' is easier to understand

- change 'semaphore_wait' to '*_wait_for' to match Condition

- pthreads can be given a stack, but doing so requires the user to set up the guard
  pages manually. BE WARNED. The alignment requirements of the stack are also
  platform-dependant; it may need to be page size aligned on some systems.
  Unclear which systems, however. See 'os.get_page_size', and 'mem.make_aligned'.
  HOWEVER: I was unable to get custom stacks with guard pages working reliably,
  so while you can do it, the API does not support it.

- add 'os.get_page_size', 'mem.make_aligned', and 'mem.new_aligned'.

- removed thread return values because windows and linux are not consistent; windows returns 'i32'
  and pthreads return 'void*'; besides which, if you really wanted to communicate how the
  thread exited, you probably wouldn't do it with the thread's exit code.

- fixed 'thread.is_done' on Windows; it didn't report true immediately after calling 'thread.join'.

- moved time related stuff out of 'core:os' to 'core:time'.

- add 'mem.align_backward'

- fixed default allocator alignment
  The heap on Windows, and calloc on Linux, both have no facility to request alignment.
  It's a bit of hack, but the heap_allocator now overallocates; `size + alignment` bytes,
  and aligns things to at least 2.
  It does both of these things to ensure that there is at least two bytes before the payload,
  which it uses to store how much padding it needed to insert in order to fulfil the alignment
  requested.

- make conditions more sane by matching the Windows behaviour.
  The fact that they were signalled now lingers until a thread tries to wait,
  causing them to just pass by uninterrupted, without sleeping or locking the
  underlying mutex, as it would otherwise need to do.
  This means that a thread no longer has to be waiting in order to be signalled, which
  avoids timing bugs that causes deadlocks that are hard to debug and fix.
  See the comment on the `sync.Condition.flag` field.

- add thread priority: `thread.create(worker_proc, .High)`
Tetralux 5 years ago
parent
commit
99121d6ff2

+ 9 - 3
core/mem/alloc.odin

@@ -84,7 +84,10 @@ delete :: proc{
 
 
 new :: inline proc($T: typeid, allocator := context.allocator, loc := #caller_location) -> ^T {
-	ptr := (^T)(alloc(size_of(T), align_of(T), allocator, loc));
+	return new_aligned(T, align_of(T), allocator, loc);
+}
+new_aligned :: inline proc($T: typeid, alignment: int, allocator := context.allocator, loc := #caller_location) -> ^T {
+	ptr := (^T)(alloc(size_of(T), alignment, allocator, loc));
 	if ptr != nil do ptr^ = T{};
 	return ptr;
 }
@@ -95,9 +98,12 @@ new_clone :: inline proc(data: $T, allocator := context.allocator, loc := #calle
 }
 
 
-make_slice :: proc($T: typeid/[]$E, auto_cast len: int, allocator := context.allocator, loc := #caller_location) -> T {
+make_slice :: inline proc($T: typeid/[]$E, auto_cast len: int, allocator := context.allocator, loc := #caller_location) -> T {
+	return make_aligned(T, len, align_of(E), allocator, loc);
+}
+make_aligned :: proc($T: typeid/[]$E, auto_cast len: int, alignment: int, allocator := context.allocator, loc := #caller_location) -> T {
 	runtime.make_slice_error_loc(loc, len);
-	data := alloc(size_of(E)*len, align_of(E), allocator, loc);
+	data := alloc(size_of(E)*len, alignment, allocator, loc);
 	s := Raw_Slice{data, len};
 	return transmute(T)s;
 }

+ 24 - 16
core/mem/mem.odin

@@ -151,27 +151,19 @@ is_power_of_two :: inline proc(x: uintptr) -> bool {
 	return (x & (x-1)) == 0;
 }
 
-align_forward :: proc(ptr: rawptr, align: uintptr) -> rawptr {
-	assert(is_power_of_two(align));
-
-	a := uintptr(align);
-	p := uintptr(ptr);
-	modulo := p & (a-1);
-	if modulo != 0 do p += a - modulo;
-	return rawptr(p);
+align_forward :: inline proc(ptr: rawptr, align: uintptr) -> rawptr {
+	return rawptr(align_forward_uintptr(uintptr(ptr), align));
 }
 
 align_forward_uintptr :: proc(ptr, align: uintptr) -> uintptr {
 	assert(is_power_of_two(align));
 
-	a := uintptr(align);
-	p := uintptr(ptr);
-	modulo := p & (a-1);
-	if modulo != 0 do p += a - modulo;
-	return uintptr(p);
+	p := ptr;
+	modulo := p & (align-1);
+	if modulo != 0 do p += align - modulo;
+	return p;
 }
 
-
 align_forward_int :: inline proc(ptr, align: int) -> int {
 	return int(align_forward_uintptr(uintptr(ptr), uintptr(align)));
 }
@@ -179,6 +171,24 @@ align_forward_uint :: inline proc(ptr, align: uint) -> uint {
 	return uint(align_forward_uintptr(uintptr(ptr), uintptr(align)));
 }
 
+align_backward :: inline proc(ptr: rawptr, align: uintptr) -> rawptr {
+	return rawptr(align_backward_uintptr(uintptr(ptr), align));
+}
+
+align_backward_uintptr :: proc(ptr, align: uintptr) -> uintptr {
+	assert(is_power_of_two(align));
+
+	ptr := rawptr(ptr - align);
+	return uintptr(align_forward(ptr, align));
+}
+
+align_backward_int :: inline proc(ptr, align: int) -> int {
+	return int(align_backward_uintptr(uintptr(ptr), uintptr(align)));
+}
+align_backward_uint :: inline proc(ptr, align: uint) -> uint {
+	return uint(align_backward_uintptr(uintptr(ptr), uintptr(align)));
+}
+
 context_from_allocator :: proc(a: Allocator) -> type_of(context) {
 	context.allocator = a;
 	return context;
@@ -226,5 +236,3 @@ calc_padding_with_header :: proc(ptr: uintptr, align: uintptr, header_size: int)
 
 	return int(padding);
 }
-
-

+ 45 - 6
core/os/os.odin

@@ -119,16 +119,54 @@ read_ptr :: proc(fd: Handle, data: rawptr, len: int) -> (int, Errno) {
 	return read(fd, s);
 }
 
-
 heap_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
                             size, alignment: int,
                             old_memory: rawptr, old_size: int, flags: u64 = 0, loc := #caller_location) -> rawptr {
+	//
+	// NOTE(tetra, 2019-11-10): The heap doesn't respect alignment.
+	// HACK: Overallocate, align forwards, and then use the two bytes immediately before
+	// the address we return, to store the padding we inserted.
+	// This allows us to pass the original pointer we got back from the heap to `free` later.
+	//
+
+	align_and_store_padding :: proc(ptr: rawptr, alignment: int) -> rawptr {
+		ptr := mem.ptr_offset(cast(^u8) ptr, 2);
+		new_ptr := cast(^u8) mem.align_forward(ptr, uintptr(alignment));
+		offset := mem.ptr_sub(new_ptr, cast(^u8) ptr) + 2;
+		assert(offset < int(max(u16)));
+		(^[2]u8)(mem.ptr_offset(new_ptr, -2))^ = transmute([2]u8) u16(offset);
+		return new_ptr;
+	}
+
+	recover_original_pointer :: proc(ptr: rawptr) -> rawptr {
+		ptr := cast(^u8) ptr;
+		offset := transmute(u16) (^[2]u8)(mem.ptr_offset(ptr, -2))^;
+		ptr = mem.ptr_offset(ptr, -int(offset));
+		return ptr;
+	}
+
+	aligned_heap_alloc :: proc(size: int, alignment: int) -> rawptr {
+		// NOTE(tetra): Alignment 1 will mean we only have one extra byte.
+		// This is not enough for a u16 - so we ensure there is at least two bytes extra.
+		// This also means that the pointer is always aligned to at least 2.
+		extra := alignment;
+		if extra <= 1 do extra = 2;
+
+		orig := cast(^u8) heap_alloc(size + extra);
+		if orig == nil do return nil;
+		ptr := align_and_store_padding(orig, alignment);
+		assert(recover_original_pointer(ptr) == orig);
+		return ptr;
+	}
+
 	switch mode {
 	case .Alloc:
-		return heap_alloc(size);
+		return aligned_heap_alloc(size, alignment);
 
 	case .Free:
-		heap_free(old_memory);
+		assert(old_memory != nil);
+		ptr := recover_original_pointer(old_memory);
+		heap_free(ptr);
 		return nil;
 
 	case .Free_All:
@@ -136,11 +174,12 @@ heap_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
 
 	case .Resize:
 		if old_memory == nil {
-			return heap_alloc(size);
+			return aligned_heap_alloc(size, alignment);
 		}
-		ptr := heap_resize(old_memory, size);
+		ptr := recover_original_pointer(old_memory);
+		ptr = heap_resize(ptr, size);
 		assert(ptr != nil);
-		return ptr;
+		return align_and_store_padding(ptr, alignment);
 	}
 
 	return nil;

+ 147 - 0
core/os/os_darwin.odin

@@ -14,6 +14,142 @@ Errno     :: distinct int;
 
 INVALID_HANDLE :: ~Handle(0);
 
+ERROR_NONE: Errno : 0;
+EPERM:		Errno : 1;		/* Operation not permitted */
+ENOENT:		Errno : 2;		/* No such file or directory */
+ESRCH:		Errno : 3;		/* No such process */
+EINTR:		Errno : 4;		/* Interrupted system call */
+EIO:		Errno : 5;		/* Input/output error */
+ENXIO:		Errno : 6;		/* Device not configured */
+E2BIG:		Errno : 7;		/* Argument list too long */
+ENOEXEC:	Errno : 8;		/* Exec format error */
+EBADF:		Errno : 9;		/* Bad file descriptor */
+ECHILD:		Errno : 10;		/* No child processes */
+EDEADLK:	Errno : 11;		/* Resource deadlock avoided */
+ENOMEM:		Errno : 12;		/* Cannot allocate memory */
+EACCES:		Errno : 13;		/* Permission denied */
+EFAULT:		Errno : 14;		/* Bad address */
+ENOTBLK:	Errno : 15;		/* Block device required */
+EBUSY:		Errno : 16;		/* Device / Resource busy */
+EEXIST:		Errno : 17;		/* File exists */
+EXDEV:		Errno : 18;		/* Cross-device link */
+ENODEV:		Errno : 19;		/* Operation not supported by device */
+ENOTDIR:	Errno : 20;		/* Not a directory */
+EISDIR:		Errno : 21;		/* Is a directory */
+EINVAL:		Errno : 22;		/* Invalid argument */
+ENFILE:		Errno : 23;		/* Too many open files in system */
+EMFILE:		Errno : 24;		/* Too many open files */
+ENOTTY:		Errno : 25;		/* Inappropriate ioctl for device */
+ETXTBSY:	Errno : 26;		/* Text file busy */
+EFBIG:		Errno : 27;		/* File too large */
+ENOSPC:		Errno : 28;		/* No space left on device */
+ESPIPE:		Errno : 29;		/* Illegal seek */
+EROFS:		Errno : 30;		/* Read-only file system */
+EMLINK:		Errno : 31;		/* Too many links */
+EPIPE:		Errno : 32;		/* Broken pipe */
+
+/* math software */
+EDOM:		Errno : 33;		/* Numerical argument out of domain */
+ERANGE:		Errno : 34;		/* Result too large */
+
+/* non-blocking and interrupt i/o */
+EAGAIN:			Errno : 35;		/* Resource temporarily unavailable */
+EWOULDBLOCK: 	Errno : EAGAIN;		/* Operation would block */
+EINPROGRESS: 	Errno : 36;		/* Operation now in progress */
+EALREADY:		Errno : 37;		/* Operation already in progress */
+
+/* ipc/network software -- argument errors */
+ENOTSOCK:			Errno : 38;		/* Socket operation on non-socket */
+EDESTADDRREQ:		Errno : 39;		/* Destination address required */
+EMSGSIZE:			Errno : 40;		/* Message too long */
+EPROTOTYPE:			Errno : 41;		/* Protocol wrong type for socket */
+ENOPROTOOPT:		Errno : 42;		/* Protocol not available */
+EPROTONOSUPPORT:	Errno : 43;		/* Protocol not supported */
+ESOCKTNOSUPPORT:	Errno : 44;		/* Socket type not supported */
+ENOTSUP:			Errno : 45;		/* Operation not supported */
+EPFNOSUPPORT:		Errno : 46;		/* Protocol family not supported */
+EAFNOSUPPORT:		Errno : 47;		/* Address family not supported by protocol family */
+EADDRINUSE:			Errno : 48;		/* Address already in use */
+EADDRNOTAVAIL:		Errno : 49;		/* Can't assign requested address */
+
+/* ipc/network software -- operational errors */
+ENETDOWN:		Errno : 50;		/* Network is down */
+ENETUNREACH:	Errno : 51;		/* Network is unreachable */
+ENETRESET:		Errno : 52;		/* Network dropped connection on reset */
+ECONNABORTED:	Errno : 53;		/* Software caused connection abort */
+ECONNRESET:		Errno : 54;		/* Connection reset by peer */
+ENOBUFS:		Errno : 55;		/* No buffer space available */
+EISCONN:		Errno : 56;		/* Socket is already connected */
+ENOTCONN:		Errno : 57;		/* Socket is not connected */
+ESHUTDOWN:		Errno : 58;		/* Can't send after socket shutdown */
+ETOOMANYREFS:	Errno : 59;		/* Too many references: can't splice */
+ETIMEDOUT:		Errno : 60;		/* Operation timed out */
+ECONNREFUSED:	Errno : 61;		/* Connection refused */
+
+ELOOP:			Errno : 62;		/* Too many levels of symbolic links */
+ENAMETOOLONG:	Errno : 63;		/* File name too long */
+
+/* should be rearranged */
+EHOSTDOWN:		Errno : 64;		/* Host is down */
+EHOSTUNREACH:	Errno : 65;		/* No route to host */
+ENOTEMPTY:		Errno : 66;		/* Directory not empty */
+
+/* quotas & mush */
+EPROCLIM:		Errno : 67;		/* Too many processes */
+EUSERS:			Errno : 68;		/* Too many users */
+EDQUOT:			Errno : 69;		/* Disc quota exceeded */
+
+/* Network File System */
+ESTALE:			Errno : 70;		/* Stale NFS file handle */
+EREMOTE:		Errno : 71;		/* Too many levels of remote in path */
+EBADRPC:		Errno : 72;		/* RPC struct is bad */
+ERPCMISMATCH:	Errno : 73;		/* RPC version wrong */
+EPROGUNAVAIL:	Errno : 74;		/* RPC prog. not avail */
+EPROGMISMATCH:	Errno : 75;		/* Program version wrong */
+EPROCUNAVAIL:	Errno : 76;		/* Bad procedure for program */
+
+ENOLCK:	Errno : 77;		/* No locks available */
+ENOSYS:	Errno : 78;		/* Function not implemented */
+
+EFTYPE:	Errno : 79;		/* Inappropriate file type or format */
+EAUTH:	Errno : 80;		/* Authentication error */
+ENEEDAUTH:	Errno : 81;		/* Need authenticator */
+
+/* Intelligent device errors */
+EPWROFF:	Errno : 82;	/* Device power is off */
+EDEVERR:	Errno : 83;	/* Device error, e.g. paper out */
+EOVERFLOW:	Errno : 84;		/* Value too large to be stored in data type */
+
+/* Program loading errors */
+EBADEXEC:	Errno : 85;	/* Bad executable */
+EBADARCH:	Errno : 86;	/* Bad CPU type in executable */
+ESHLIBVERS:	Errno : 87;	/* Shared library version mismatch */
+EBADMACHO:	Errno : 88;	/* Malformed Macho file */
+
+ECANCELED:	Errno : 89;		/* Operation canceled */
+
+EIDRM:		Errno : 90;		/* Identifier removed */
+ENOMSG:		Errno : 91;		/* No message of desired type */
+EILSEQ:		Errno : 92;		/* Illegal byte sequence */
+ENOATTR:	Errno : 93;		/* Attribute not found */
+
+EBADMSG:	Errno : 94;		/* Bad message */
+EMULTIHOP:	Errno : 95;		/* Reserved */
+ENODATA:	Errno : 96;		/* No message available on STREAM */
+ENOLINK:	Errno : 97;		/* Reserved */
+ENOSR:		Errno : 98;		/* No STREAM resources */
+ENOSTR:		Errno : 99;		/* Not a STREAM */
+EPROTO:		Errno : 100;		/* Protocol error */
+ETIME:		Errno : 101;		/* STREAM ioctl timeout */
+
+ENOPOLICY:	Errno : 103;		/* No such policy registered */
+
+ENOTRECOVERABLE:	Errno : 104;		/* State not recoverable */
+EOWNERDEAD:			Errno : 105;		/* Previous owner died */
+
+EQFULL:	Errno : 106;		/* Interface output queue is full */
+ELAST:	Errno : 106;		/* Must be equal largest errno */
+
 O_RDONLY   :: 0x00000;
 O_WRONLY   :: 0x00001;
 O_RDWR     :: 0x00002;
@@ -133,6 +269,7 @@ foreign libc {
 	@(link_name="write")   _unix_write   :: proc(handle: Handle, buffer: rawptr, count: int) -> int ---;
 	@(link_name="lseek")   _unix_lseek   :: proc(fs: Handle, offset: int, whence: int) -> int ---;
 	@(link_name="gettid")  _unix_gettid  :: proc() -> u64 ---;
+	@(link_name="getpagesize") _unix_getpagesize :: proc() -> i32 ---;
 	@(link_name="stat")    _unix_stat    :: proc(path: cstring, stat: ^Stat) -> int ---;
 	@(link_name="access")  _unix_access  :: proc(path: cstring, mask: int) -> int ---;
 
@@ -287,6 +424,16 @@ dlerror :: proc() -> string {
 	return string(_unix_dlerror());
 }
 
+get_page_size :: proc() -> int {
+	// NOTE(tetra): The page size never changes, so why do anything complicated
+	// if we don't have to.
+	@static page_size := -1;
+	if page_size != -1 do return page_size;
+
+	page_size = int(_unix_getpagesize());
+	return page_size;
+}
+
 
 _alloc_command_line_arguments :: proc() -> []string {
 	res := make([]string, len(runtime.args__));

+ 143 - 70
core/os/os_linux.odin

@@ -15,37 +15,138 @@ Syscall   :: distinct int;
 
 INVALID_HANDLE :: ~Handle(0);
 
-ERROR_NONE:    Errno : 0;
-EPERM:         Errno : 1;
-ENOENT:        Errno : 2;
-EINTR:         Errno : 4;
-EIO:           Errno : 5;
-ENXIO:         Errno : 6;
-EBADF:         Errno : 9;
-EAGAIN:        Errno : 11;
-EWOULDBLOCK:   Errno : EAGAIN;
-ENOMEM:        Errno : 12;
-EACCES:        Errno : 13;
-EFAULT:        Errno : 14;
-EEXIST:        Errno : 17;
-ENODEV:        Errno : 19;
-ENOTDIR:       Errno : 20;
-EISDIR:        Errno : 21;
-EINVAL:        Errno : 22;
-ENFILE:        Errno : 23;
-EMFILE:        Errno : 24;
-ETXTBSY:       Errno : 26;
-EFBIG:         Errno : 27;
-ENOSPC:        Errno : 28;
-ESPIPE:        Errno : 29;
-EROFS:         Errno : 30;
-EPIPE:         Errno : 32;
-ENAMETOOLONG:  Errno : 36;
-ELOOP:         Errno : 40;
-EOVERFLOW:     Errno : 75;
-EDESTADDRREQ:  Errno : 89;
-EOPNOTSUPP:    Errno : 95;
-EDQUOT:        Errno : 122;
+ERROR_NONE:    	Errno : 0;
+EPERM:         	Errno : 1;
+ENOENT:        	Errno : 2;
+ESRCH:         	Errno : 3;
+EINTR:         	Errno : 4;
+EIO:           	Errno : 5;
+ENXIO:         	Errno : 6;
+EBADF:         	Errno : 9;
+EAGAIN:        	Errno : 11;
+ENOMEM:        	Errno : 12;
+EACCES:        	Errno : 13;
+EFAULT:        	Errno : 14;
+EEXIST:        	Errno : 17;
+ENODEV:        	Errno : 19;
+ENOTDIR:       	Errno : 20;
+EISDIR:        	Errno : 21;
+EINVAL:        	Errno : 22;
+ENFILE:        	Errno : 23;
+EMFILE:        	Errno : 24;
+ETXTBSY:       	Errno : 26;
+EFBIG:         	Errno : 27;
+ENOSPC:        	Errno : 28;
+ESPIPE:        	Errno : 29;
+EROFS:         	Errno : 30;
+EPIPE:         	Errno : 32;
+
+EDEADLK: 		Errno :	35;	/* Resource deadlock would occur */
+ENAMETOOLONG: 	Errno : 36;	/* File name too long */
+ENOLCK: 		Errno : 37;	/* No record locks available */
+
+ENOSYS: Errno : 38;	/* Invalid system call number */
+
+ENOTEMPTY: 	Errno : 39;	/* Directory not empty */
+ELOOP: 		Errno : 40;	/* Too many symbolic links encountered */
+EWOULDBLOCK: Errno : EAGAIN;	/* Operation would block */
+ENOMSG: 	Errno : 42;	/* No message of desired type */
+EIDRM: 		Errno : 43;	/* Identifier removed */
+ECHRNG: 	Errno : 44;	/* Channel number out of range */
+EL2NSYNC: 	Errno : 45;	/* Level 2 not synchronized */
+EL3HLT: 	Errno : 46;	/* Level 3 halted */
+EL3RST: 	Errno : 47;	/* Level 3 reset */
+ELNRNG: 	Errno : 48;	/* Link number out of range */
+EUNATCH: 	Errno : 49;	/* Protocol driver not attached */
+ENOCSI: 	Errno : 50;	/* No CSI structure available */
+EL2HLT: 	Errno : 51;	/* Level 2 halted */
+EBADE: 		Errno : 52;	/* Invalid exchange */
+EBADR: 		Errno : 53;	/* Invalid request descriptor */
+EXFULL: 	Errno : 54;	/* Exchange full */
+ENOANO: 	Errno : 55;	/* No anode */
+EBADRQC: 	Errno : 56;	/* Invalid request code */
+EBADSLT: 	Errno : 57;	/* Invalid slot */
+EDEADLOCK:  Errno : EDEADLK;
+EBFONT: 	Errno : 59;	/* Bad font file format */
+ENOSTR: 	Errno : 60;	/* Device not a stream */
+ENODATA: 	Errno : 61;	/* No data available */
+ETIME: 		Errno : 62;	/* Timer expired */
+ENOSR: 		Errno : 63;	/* Out of streams resources */
+ENONET: 	Errno : 64;	/* Machine is not on the network */
+ENOPKG: 	Errno : 65;	/* Package not installed */
+EREMOTE: 	Errno : 66;	/* Object is remote */
+ENOLINK: 	Errno : 67;	/* Link has been severed */
+EADV: 		Errno : 68;	/* Advertise error */
+ESRMNT: 	Errno : 69;	/* Srmount error */
+ECOMM: 		Errno : 70;	/* Communication error on send */
+EPROTO: 	Errno : 71;	/* Protocol error */
+EMULTIHOP: 	Errno : 72;	/* Multihop attempted */
+EDOTDOT: 	Errno : 73;	/* RFS specific error */
+EBADMSG: 	Errno : 74;	/* Not a data message */
+EOVERFLOW: 	Errno : 75;	/* Value too large for defined data type */
+ENOTUNIQ: 	Errno : 76;	/* Name not unique on network */
+EBADFD: 	Errno : 77;	/* File descriptor in bad state */
+EREMCHG: 	Errno : 78;	/* Remote address changed */
+ELIBACC: 	Errno : 79;	/* Can not access a needed shared library */
+ELIBBAD: 	Errno : 80;	/* Accessing a corrupted shared library */
+ELIBSCN: 	Errno : 81;	/* .lib section in a.out corrupted */
+ELIBMAX: 	Errno : 82;	/* Attempting to link in too many shared libraries */
+ELIBEXEC: 	Errno : 83;	/* Cannot exec a shared library directly */
+EILSEQ: 	Errno : 84;	/* Illegal byte sequence */
+ERESTART: 	Errno : 85;	/* Interrupted system call should be restarted */
+ESTRPIPE: 	Errno : 86;	/* Streams pipe error */
+EUSERS: 	Errno : 87;	/* Too many users */
+ENOTSOCK: 	Errno : 88;	/* Socket operation on non-socket */
+EDESTADDRREQ: Errno : 89;	/* Destination address required */
+EMSGSIZE: 	Errno : 90;	/* Message too long */
+EPROTOTYPE: Errno : 91;	/* Protocol wrong type for socket */
+ENOPROTOOPT: 	Errno : 92;	/* Protocol not available */
+EPROTONOSUPPORT: Errno : 93;	/* Protocol not supported */
+ESOCKTNOSUPPORT: Errno : 94;	/* Socket type not supported */
+EOPNOTSUPP: 	Errno : 95;	/* Operation not supported on transport endpoint */
+EPFNOSUPPORT: 	Errno : 96;	/* Protocol family not supported */
+EAFNOSUPPORT: 	Errno : 97;	/* Address family not supported by protocol */
+EADDRINUSE: 	Errno : 98;	/* Address already in use */
+EADDRNOTAVAIL: 	Errno : 99;	/* Cannot assign requested address */
+ENETDOWN: 		Errno : 100;	/* Network is down */
+ENETUNREACH: 	Errno : 101;	/* Network is unreachable */
+ENETRESET: 		Errno : 102;	/* Network dropped connection because of reset */
+ECONNABORTED: 	Errno : 103;	/* Software caused connection abort */
+ECONNRESET: 	Errno : 104;	/* Connection reset by peer */
+ENOBUFS: 		Errno : 105;	/* No buffer space available */
+EISCONN: 		Errno : 106;	/* Transport endpoint is already connected */
+ENOTCONN: 		Errno : 107;	/* Transport endpoint is not connected */
+ESHUTDOWN: 		Errno : 108;	/* Cannot send after transport endpoint shutdown */
+ETOOMANYREFS: 	Errno : 109;	/* Too many references: cannot splice */
+ETIMEDOUT: 		Errno : 110;	/* Connection timed out */
+ECONNREFUSED: 	Errno : 111;	/* Connection refused */
+EHOSTDOWN: 		Errno : 112;	/* Host is down */
+EHOSTUNREACH: 	Errno : 113;	/* No route to host */
+EALREADY: 		Errno : 114;	/* Operation already in progress */
+EINPROGRESS: 	Errno : 115;	/* Operation now in progress */
+ESTALE: 		Errno : 116;	/* Stale file handle */
+EUCLEAN: 		Errno : 117;	/* Structure needs cleaning */
+ENOTNAM: 		Errno : 118;	/* Not a XENIX named type file */
+ENAVAIL: 		Errno : 119;	/* No XENIX semaphores available */
+EISNAM: 		Errno : 120;	/* Is a named type file */
+EREMOTEIO: 		Errno : 121;	/* Remote I/O error */
+EDQUOT: 		Errno : 122;	/* Quota exceeded */
+
+ENOMEDIUM: 		Errno : 123;	/* No medium found */
+EMEDIUMTYPE: 	Errno : 124;	/* Wrong medium type */
+ECANCELED: 		Errno : 125;	/* Operation Canceled */
+ENOKEY: 		Errno : 126;	/* Required key not available */
+EKEYEXPIRED: 	Errno : 127;	/* Key has expired */
+EKEYREVOKED: 	Errno : 128;	/* Key has been revoked */
+EKEYREJECTED: 	Errno : 129;	/* Key was rejected by service */
+
+/* for robust mutexes */
+EOWNERDEAD: 	Errno : 130;	/* Owner died */
+ENOTRECOVERABLE: Errno : 131;	/* State not recoverable */
+
+ERFKILL: 		Errno : 132;	/* Operation not possible due to RF-kill */
+
+EHWPOISON: 		Errno : 133;	/* Memory page has hardware error */
 
 O_RDONLY   :: 0x00000;
 O_WRONLY   :: 0x00001;
@@ -152,22 +253,6 @@ X_OK :: 1; // Test for execute permission
 W_OK :: 2; // Test for write permission
 R_OK :: 4; // Test for read permission
 
-TimeSpec :: struct {
-	tv_sec  : i64,  /* seconds */
-	tv_nsec : i64,  /* nanoseconds */
-};
-
-CLOCK_REALTIME           :: 0;
-CLOCK_MONOTONIC          :: 1;
-CLOCK_PROCESS_CPUTIME_ID :: 2;
-CLOCK_THREAD_CPUTIME_ID  :: 3;
-CLOCK_MONOTONIC_RAW      :: 4;
-CLOCK_REALTIME_COARSE    :: 5;
-CLOCK_MONOTONIC_COARSE   :: 6;
-CLOCK_BOOTTIME           :: 7;
-CLOCK_REALTIME_ALARM     :: 8;
-CLOCK_BOOTTIME_ALARM     :: 9;
-
 SYS_GETTID: Syscall : 186;
 
 foreign libc {
@@ -180,6 +265,7 @@ foreign libc {
 	@(link_name="write")            _unix_write         :: proc(fd: Handle, buf: rawptr, size: int) -> int ---;
 	@(link_name="lseek64")          _unix_seek          :: proc(fd: Handle, offset: i64, whence: i32) -> i64 ---;
 	@(link_name="gettid")           _unix_gettid        :: proc() -> u64 ---;
+	@(link_name="getpagesize")      _unix_getpagesize   :: proc() -> i32 ---;
 	@(link_name="stat")             _unix_stat          :: proc(path: cstring, stat: ^Stat) -> int ---;
 	@(link_name="fstat")            _unix_fstat         :: proc(fd: Handle, stat: ^Stat) -> int ---;
 	@(link_name="access")           _unix_access        :: proc(path: cstring, mask: int) -> int ---;
@@ -190,10 +276,6 @@ foreign libc {
 	@(link_name="realloc")          _unix_realloc       :: proc(ptr: rawptr, size: int) -> rawptr ---;
 	@(link_name="getenv")           _unix_getenv        :: proc(cstring) -> cstring ---;
 
-	@(link_name="clock_gettime")    _unix_clock_gettime :: proc(clock_id: u64, timespec: ^TimeSpec) ---;
-	@(link_name="nanosleep")        _unix_nanosleep     :: proc(requested: ^TimeSpec, remaining: ^TimeSpec) -> int ---;
-	@(link_name="sleep")            _unix_sleep         :: proc(seconds: u64) -> int ---;
-
 	@(link_name="exit")             _unix_exit          :: proc(status: int) -> ! ---;
 }
 foreign dl {
@@ -349,25 +431,6 @@ exit :: proc(code: int) -> ! {
 	_unix_exit(code);
 }
 
-clock_gettime :: proc(clock_id: u64) -> TimeSpec {
-	ts : TimeSpec;
-	_unix_clock_gettime(clock_id, &ts);
-	return ts;
-}
-
-sleep :: proc(seconds: u64) -> int {
-
-	return _unix_sleep(seconds);
-}
-
-nanosleep :: proc(nanoseconds: i64) -> int {
-	assert(nanoseconds <= 999999999);
-	requested, remaining : TimeSpec;
-	requested = TimeSpec{tv_nsec = nanoseconds};
-
-	return _unix_nanosleep(&requested, &remaining);
-}
-
 current_thread_id :: proc "contextless" () -> int {
 	return syscall(SYS_GETTID);
 }
@@ -393,6 +456,16 @@ dlerror :: proc() -> string {
 	return string(_unix_dlerror());
 }
 
+get_page_size :: proc() -> int {
+	// NOTE(tetra): The page size never changes, so why do anything complicated
+	// if we don't have to.
+	@static page_size := -1;
+	if page_size != -1 do return page_size;
+
+	page_size = int(_unix_getpagesize());
+	return page_size;
+}
+
 
 _alloc_command_line_arguments :: proc() -> []string {
 	res := make([]string, len(runtime.args__));

+ 12 - 1
core/os/os_windows.odin

@@ -210,7 +210,6 @@ get_std_handle :: proc(h: int) -> Handle {
 
 
 
-
 last_write_time :: proc(fd: Handle) -> (File_Time, Errno) {
 	file_info: win32.By_Handle_File_Information;
 	if !win32.get_file_information_by_handle(win32.Handle(fd), &file_info) {
@@ -253,6 +252,18 @@ heap_free :: proc(ptr: rawptr) {
 	win32.heap_free(win32.get_process_heap(), 0, ptr);
 }
 
+get_page_size :: proc() -> int {
+	// NOTE(tetra): The page size never changes, so why do anything complicated
+	// if we don't have to.
+	@static page_size := -1;
+	if page_size != -1 do return page_size;
+
+	info: win32.System_Info;
+	win32.get_system_info(&info);
+	page_size = int(info.page_size);
+	return page_size;
+}
+
 
 exit :: proc(code: int) -> ! {
 	win32.exit_process(u32(code));

+ 27 - 0
core/sync/sync.odin

@@ -0,0 +1,27 @@
+package sync
+
+foreign {
+	@(link_name="llvm.x86.sse2.pause")
+	yield_processor :: proc() ---
+}
+
+Ticket_Mutex :: struct {
+	ticket:  u64,
+	serving: u64,
+}
+
+ticket_mutex_init :: proc(m: ^Ticket_Mutex) {
+	atomic_store(&m.ticket,  0, .Relaxed);
+	atomic_store(&m.serving, 0, .Relaxed);
+}
+
+ticket_mutex_lock :: inline proc(m: ^Ticket_Mutex) {
+	ticket := atomic_add(&m.ticket, 1, .Relaxed);
+	for ticket != m.serving {
+		yield_processor();
+	}
+}
+
+ticket_mutex_unlock :: inline proc(m: ^Ticket_Mutex) {
+	atomic_add(&m.serving, 1, .Relaxed);
+}

+ 39 - 0
core/sync/sync_darwin.odin

@@ -0,0 +1,39 @@
+package sync
+
+import "core:sys/darwin"
+
+import "core:c"
+
+// The Darwin docs say it best:
+// A semaphore is much like a lock, except that a finite number of threads can hold it simultaneously.
+// Semaphores can be thought of as being much like piles of tokens; multiple threads can take these tokens, 
+// but when there are none left, a thread must wait until another thread returns one.
+Semaphore :: struct #align 16 {
+	handle: darwin.semaphore_t,
+}
+// TODO(tetra): Only marked with alignment because we cannot mark distinct integers with alignments.
+// See core/sys/unix/pthread_linux.odin/pthread_t.
+
+semaphore_init :: proc(s: ^Semaphore, initial_count := 0) {
+	ct := darwin.mach_task_self();
+	res := darwin.semaphore_create(ct, &s.handle, 0, c.int(initial_count));
+	assert(res == 0);
+}
+
+semaphore_destroy :: proc(s: ^Semaphore) {
+	ct := darwin.mach_task_self();
+	res := darwin.semaphore_destroy(ct, s.handle);
+	assert(res == 0);
+	s.handle = {};
+}
+
+semaphore_post :: proc(s: ^Semaphore, count := 1) {
+	assert(count == 1);
+	res := darwin.semaphore_signal(s.handle);
+	assert(res == 0);
+}
+
+semaphore_wait_for :: proc(s: ^Semaphore) {
+	res := darwin.semaphore_wait(s.handle);
+	assert(res == 0);
+}

+ 15 - 85
core/sync/sync_linux.odin

@@ -1,98 +1,28 @@
 package sync
 
-/*
+import "core:sys/unix"
 
-import "core:atomics"
-import "core:os"
-
-Semaphore :: struct {
-	// _handle: win32.Handle,
-}
-
-Mutex :: struct {
-	_semaphore: Semaphore,
-	_counter:   i32,
-	_owner:     i32,
-	_recursion: i32,
-}
-
-current_thread_id :: proc() -> i32 {
-	return i32(os.current_thread_id());
+// The Darwin docs say it best:
+// A semaphore is much like a lock, except that a finite number of threads can hold it simultaneously.
+// Semaphores can be thought of as being much like piles of tokens; multiple threads can take these tokens, 
+// but when there are none left, a thread must wait until another thread returns one.
+Semaphore :: struct #align 16 {
+	handle: unix.sem_t,
 }
 
-semaphore_init :: proc(s: ^Semaphore) {
-	// s._handle = win32.CreateSemaphoreA(nil, 0, 1<<31-1, nil);
+semaphore_init :: proc(s: ^Semaphore, initial_count := 0) {
+	assert(unix.sem_init(&s.handle, 0, u32(initial_count)) == 0);
 }
 
 semaphore_destroy :: proc(s: ^Semaphore) {
-	// win32.CloseHandle(s._handle);
+	assert(unix.sem_destroy(&s.handle) == 0);
+	s.handle = {};
 }
 
-semaphore_post :: proc(s: ^Semaphore, count: int) {
-	// win32.ReleaseSemaphore(s._handle, cast(i32)count, nil);
+semaphore_post :: proc(s: ^Semaphore, count := 1) {
+	assert(unix.sem_post(&s.handle) == 0);
 }
 
-semaphore_release :: inline proc(s: ^Semaphore) {
-	semaphore_post(s, 1);
+semaphore_wait_for :: proc(s: ^Semaphore) {
+	assert(unix.sem_wait(&s.handle) == 0);
 }
-
-semaphore_wait :: proc(s: ^Semaphore) {
-	// win32.WaitForSingleObject(s._handle, win32.INFINITE);
-}
-
-
-mutex_init :: proc(m: ^Mutex) {
-	atomics.store(&m._counter, 0);
-	atomics.store(&m._owner, current_thread_id());
-	semaphore_init(&m._semaphore);
-	m._recursion = 0;
-}
-mutex_destroy :: proc(m: ^Mutex) {
-	semaphore_destroy(&m._semaphore);
-}
-mutex_lock :: proc(m: ^Mutex) {
-	thread_id := current_thread_id();
-	if atomics.fetch_add(&m._counter, 1) > 0 {
-		if thread_id != atomics.load(&m._owner) {
-			semaphore_wait(&m._semaphore);
-		}
-	}
-	atomics.store(&m._owner, thread_id);
-	m._recursion += 1;
-}
-mutex_try_lock :: proc(m: ^Mutex) -> bool {
-	thread_id := current_thread_id();
-	if atomics.load(&m._owner) == thread_id {
-		atomics.fetch_add(&m._counter, 1);
-	} else {
-		expected: i32 = 0;
-		if atomics.load(&m._counter) != 0 {
-			return false;
-		}
-		if atomics.compare_exchange(&m._counter, expected, 1) == 0 {
-			return false;
-		}
-		atomics.store(&m._owner, thread_id);
-	}
-	m._recursion += 1;
-	return true;
-}
-mutex_unlock :: proc(m: ^Mutex) {
-	recursion: i32;
-	thread_id := current_thread_id();
-	assert(thread_id == atomics.load(&m._owner));
-
-	m._recursion -= 1;
-	recursion = m._recursion;
-	if recursion == 0 {
-		atomics.store(&m._owner, thread_id);
-	}
-
-	if atomics.fetch_add(&m._counter, -1) > 1 {
-		if recursion == 0 {
-			semaphore_release(&m._semaphore);
-		}
-	}
-}
-
-*/

+ 99 - 0
core/sync/sync_unix.odin

@@ -0,0 +1,99 @@
+// +build linux, darwin
+package sync
+
+import "core:sys/unix"
+
+// A lock that can only be held by one thread at once.
+Mutex :: struct {
+	handle: unix.pthread_mutex_t,
+}
+
+// Blocks until signalled, and then lets past exactly
+// one thread.
+Condition :: struct {
+	handle: unix.pthread_cond_t,
+
+	// NOTE(tetra, 2019-11-11): Used to mimic the more sane behavior of Windows' AutoResetEvent.
+	// This means that you may signal the condition before anyone is waiting to cause the
+	// next thread that tries to wait to just pass by uninterrupted, without sleeping.
+	// Without this, signalling a condition will only wake up a thread which is already waiting,
+	// but not one that is about to wait, which can cause your program to become out of sync in
+	// ways that are hard to debug or fix.
+	flag: bool, // atomically mutated
+
+	mutex: Mutex,
+}
+
+
+
+mutex_init :: proc(m: ^Mutex) {
+	// NOTE(tetra, 2019-11-01): POSIX OOM if we cannot init the attrs or the mutex.
+	attrs: unix.pthread_mutexattr_t;
+	assert(unix.pthread_mutexattr_init(&attrs) == 0);
+	defer unix.pthread_mutexattr_destroy(&attrs); // ignores destruction error
+
+	assert(unix.pthread_mutex_init(&m.handle, &attrs) == 0);
+}
+
+mutex_destroy :: proc(m: ^Mutex) {
+	assert(unix.pthread_mutex_destroy(&m.handle) == 0);
+	m.handle = {};
+}
+
+mutex_lock :: proc(m: ^Mutex) {
+	assert(unix.pthread_mutex_lock(&m.handle) == 0);
+}
+
+// Returns false if someone else holds the lock.
+mutex_try_lock :: proc(m: ^Mutex) -> bool {
+	return unix.pthread_mutex_trylock(&m.handle) == 0;
+}
+
+mutex_unlock :: proc(m: ^Mutex) {
+	assert(unix.pthread_mutex_unlock(&m.handle) == 0);
+}
+
+
+condition_init :: proc(c: ^Condition) {
+	// NOTE(tetra, 2019-11-01): POSIX OOM if we cannot init the attrs or the condition.
+	attrs: unix.pthread_condattr_t;
+	assert(unix.pthread_condattr_init(&attrs) == 0);
+	defer unix.pthread_condattr_destroy(&attrs); // ignores destruction error
+
+	assert(unix.pthread_cond_init(&c.handle, &attrs) == 0);
+
+	mutex_init(&c.mutex);
+	c.flag = false;
+}
+
+condition_destroy :: proc(c: ^Condition) {
+	assert(unix.pthread_cond_destroy(&c.handle) == 0);
+	mutex_destroy(&c.mutex);
+	c.handle = {};
+}
+
+// Awaken exactly one thread who is waiting on the condition.
+condition_signal :: proc(c: ^Condition) {
+	mutex_lock(&c.mutex);
+	defer mutex_unlock(&c.mutex);
+	atomic_swap(&c.flag, true, .Sequentially_Consistent);
+	assert(unix.pthread_cond_signal(&c.handle) == 0);
+}
+
+// Wait for the condition to be signalled.
+// Does not block if the condition has been signalled and no one
+// has waited on it yet.
+condition_wait_for :: proc(c: ^Condition) {
+	mutex_lock(&c.mutex);
+	defer mutex_unlock(&c.mutex);
+	// NOTE(tetra): If a thread comes by and steals the flag immediately after the signal occurs,
+	// the thread that gets signalled and wakes up, discovers that the flag was taken and goes
+	// back to sleep.
+	// Though this overall behavior is the most sane, there may be a better way to do this that means that
+	// the first thread to wait, gets the flag first.
+	if atomic_swap(&c.flag, false, .Sequentially_Consistent) do return;
+	for {
+		assert(unix.pthread_cond_wait(&c.handle, &c.mutex.handle) == 0);
+		if atomic_swap(&c.flag, false, .Sequentially_Consistent) do break;
+	}
+}

+ 23 - 48
core/sync/sync_windows.odin

@@ -1,51 +1,40 @@
+// +build windows
 package sync
 
 import "core:sys/win32"
 
-foreign {
-	@(link_name="llvm.x86.sse2.pause")
-	yield_processor :: proc() ---
-}
-
-Semaphore :: struct {
-	_handle: win32.Handle,
-}
-
+// A lock that can only be held by one thread at once.
 Mutex :: struct {
 	_critical_section: win32.Critical_Section,
 }
 
+// Blocks until signalled.
+// When signalled, awakens exactly one waiting thread.
 Condition :: struct {
 	event: win32.Handle,
 }
 
-Ticket_Mutex :: struct {
-	ticket:  u64,
-	serving: u64,
+// When waited upon, blocks until the internal count is greater than zero, then subtracts one.
+// Posting to the semaphore increases the count by one, or the provided amount.
+Semaphore :: struct {
+	_handle: win32.Handle,
 }
 
 
-current_thread_id :: proc() -> i32 {
-	return i32(win32.get_current_thread_id());
-}
-
-semaphore_init :: proc(s: ^Semaphore) {
-	s._handle = win32.create_semaphore_w(nil, 0, 1<<31-1, nil);
+semaphore_init :: proc(s: ^Semaphore, initial_count := 0) {
+	s._handle = win32.create_semaphore_w(nil, i32(initial_count), 1<<31-1, nil);
 }
 
 semaphore_destroy :: proc(s: ^Semaphore) {
 	win32.close_handle(s._handle);
 }
 
-semaphore_post :: proc(s: ^Semaphore, count: int) {
+semaphore_post :: proc(s: ^Semaphore, count := 1) {
 	win32.release_semaphore(s._handle, i32(count), nil);
 }
 
-semaphore_release :: inline proc(s: ^Semaphore) {
-	semaphore_post(s, 1);
-}
-
-semaphore_wait :: proc(s: ^Semaphore) {
+semaphore_wait_for :: proc(s: ^Semaphore) {
+	// NOTE(tetra, 2019-10-30): wait_for_single_object decrements the count before it returns.
 	result := win32.wait_for_single_object(s._handle, win32.INFINITE);
 	assert(result != win32.WAIT_FAILED);
 }
@@ -73,39 +62,25 @@ mutex_unlock :: proc(m: ^Mutex) {
 
 
 condition_init :: proc(using c: ^Condition) {
+	// create an auto-reset event.
+	// NOTE(tetra, 2019-10-30): this will, when signalled, signal exactly one waiting thread
+	// and then reset itself automatically.
 	event = win32.create_event_w(nil, false, false, nil);
 	assert(event != nil);
 }
 
-condition_signal :: proc(using c: ^Condition) {
-	ok := win32.set_event(event);
-	assert(bool(ok));
-}
-
-condition_wait_for :: proc(using c: ^Condition) {
-	result := win32.wait_for_single_object(event, win32.INFINITE);
-	assert(result != win32.WAIT_FAILED);
-}
-
 condition_destroy :: proc(using c: ^Condition) {
 	if event != nil {
 		win32.close_handle(event);
 	}
 }
 
-
-ticket_mutex_init :: proc(m: ^Ticket_Mutex) {
-	atomic_store(&m.ticket,  0, Ordering.Relaxed);
-	atomic_store(&m.serving, 0, Ordering.Relaxed);
-}
-
-ticket_mutex_lock :: inline proc(m: ^Ticket_Mutex) {
-	ticket := atomic_add(&m.ticket, 1, Ordering.Relaxed);
-	for ticket != m.serving {
-		yield_processor();
-	}
+condition_signal :: proc(using c: ^Condition) {
+	ok := win32.set_event(event);
+	assert(bool(ok));
 }
 
-ticket_mutex_unlock :: inline proc(m: ^Ticket_Mutex) {
-	atomic_add(&m.serving, 1, Ordering.Relaxed);
-}
+condition_wait_for :: proc(using c: ^Condition) {
+	result := win32.wait_for_single_object(event, win32.INFINITE);
+	assert(result != win32.WAIT_FAILED);
+}

+ 29 - 0
core/sys/darwin/mach_darwin.odin

@@ -0,0 +1,29 @@
+package darwin;
+
+foreign import "system:pthread"
+
+import "core:c"
+
+// NOTE(tetra): Unclear whether these should be aligned 16 or not.
+// However all other sync primitives are aligned for robustness.
+// I cannot currently align these though.
+// See core/sys/unix/pthread_linux.odin/pthread_t.
+task_t :: distinct u64;
+semaphore_t :: distinct u64;
+
+kern_return_t :: distinct u64;
+thread_act_t :: distinct u64;
+
+@(default_calling_convention="c")
+foreign pthread {
+	mach_task_self :: proc() -> task_t ---;
+
+	semaphore_create :: proc(task: task_t, semaphore: ^semaphore_t, policy, value: c.int) -> kern_return_t ---;
+	semaphore_destroy :: proc(task: task_t, semaphore: semaphore_t) -> kern_return_t ---;
+
+	semaphore_signal :: proc(semaphore: semaphore_t) -> kern_return_t ---;
+	semaphore_signal_all :: proc(semaphore: semaphore_t) -> kern_return_t ---;
+	semaphore_signal_thread :: proc(semaphore: semaphore_t, thread: thread_act_t) -> kern_return_t ---;
+	
+	semaphore_wait :: proc(semaphore: semaphore_t) -> kern_return_t ---;
+}

+ 80 - 0
core/sys/unix/pthread_darwin.odin

@@ -0,0 +1,80 @@
+package unix;
+
+import "core:c"
+
+// NOTE(tetra): No 32-bit Macs.
+// Source: _pthread_types.h on my Mac.
+PTHREAD_SIZE           :: 8176;
+PTHREAD_ATTR_SIZE      :: 56;
+PTHREAD_MUTEXATTR_SIZE :: 8;
+PTHREAD_MUTEX_SIZE     :: 56;
+PTHREAD_CONDATTR_SIZE  :: 8;
+PTHREAD_COND_SIZE      :: 40;
+PTHREAD_ONCE_SIZE      :: 8;
+PTHREAD_RWLOCK_SIZE    :: 192;
+PTHREAD_RWLOCKATTR_SIZE :: 16;
+
+pthread_t :: opaque struct #align 16 {
+	sig: c.long,
+	cleanup_stack: rawptr,
+	_: [PTHREAD_SIZE] c.char,
+};
+
+pthread_attr_t :: opaque struct #align 16 {
+	sig: c.long,
+	_: [PTHREAD_ATTR_SIZE] c.char,
+};
+
+pthread_cond_t :: opaque struct #align 16 {
+	sig: c.long,
+	_: [PTHREAD_COND_SIZE] c.char,
+};
+
+pthread_condattr_t :: opaque struct #align 16 {
+	sig: c.long,
+	_: [PTHREAD_CONDATTR_SIZE] c.char,
+};
+
+pthread_mutex_t :: opaque struct #align 16 {
+	sig: c.long,
+	_: [PTHREAD_MUTEX_SIZE] c.char,
+};
+
+pthread_mutexattr_t :: opaque struct #align 16 {
+	sig: c.long,
+	_: [PTHREAD_MUTEXATTR_SIZE] c.char,
+};
+
+pthread_once_t :: opaque struct #align 16 {
+	sig: c.long,
+	_: [PTHREAD_ONCE_SIZE] c.char,
+};
+
+pthread_rwlock_t :: opaque struct #align 16 {
+	sig: c.long,
+	_: [PTHREAD_RWLOCK_SIZE] c.char,
+};
+
+pthread_rwlockattr_t :: opaque struct #align 16 {
+	sig: c.long,
+	_: [PTHREAD_RWLOCKATTR_SIZE] c.char,
+};
+
+SCHED_OTHER :: 1; // Avoid if you are writing portable software.
+SCHED_FIFO  :: 4;
+SCHED_RR :: 2; // Round robin.
+
+SCHED_PARAM_SIZE :: 4;
+
+sched_param :: struct {
+	sched_priority: c.int,
+	_: [SCHED_PARAM_SIZE] c.char,
+};
+
+// Source: https://github.com/apple/darwin-libpthread/blob/03c4628c8940cca6fd6a82957f683af804f62e7f/pthread/pthread.h#L138
+PTHREAD_CREATE_JOINABLE :: 1;
+PTHREAD_CREATE_DETACHED :: 2;
+PTHREAD_INHERIT_SCHED :: 1;
+PTHREAD_EXPLICIT_SCHED :: 2;
+PTHREAD_PROCESS_SHARED :: 1;
+PTHREAD_PROCESS_PRIVATE :: 2;

+ 106 - 0
core/sys/unix/pthread_linux.odin

@@ -0,0 +1,106 @@
+package unix;
+
+import "core:c"
+
+// TODO(tetra): For robustness, I'd like to mark this with align 16.
+// I cannot currently do this.
+// And at the time of writing there is a bug with putting it
+// as the only field in a struct.
+pthread_t :: distinct u64;
+// pthread_t :: struct #align 16 { x: u64 };
+
+// NOTE(tetra): Got all the size constants from pthreadtypes-arch.h on my
+// Linux machine.
+
+PTHREAD_COND_T_SIZE :: 48;
+
+PTHREAD_MUTEXATTR_T_SIZE :: 4;
+PTHREAD_CONDATTR_T_SIZE  :: 4;
+PTHREAD_RWLOCKATTR_T_SIZE  :: 8;
+PTHREAD_BARRIERATTR_T_SIZE :: 4;
+
+// WARNING: The sizes of these things are different yet again
+// on non-X86!
+when size_of(int) == 8 {
+	PTHREAD_ATTR_T_SIZE  :: 56;
+	PTHREAD_MUTEX_T_SIZE :: 40;
+	PTHREAD_RWLOCK_T_SIZE  :: 56;
+	PTHREAD_BARRIER_T_SIZE :: 32;
+} else when size_of(int) == 4 {
+	PTHREAD_ATTR_T_SIZE  :: 32;
+	PTHREAD_MUTEX_T_SIZE :: 32;
+	PTHREAD_RWLOCK_T_SIZE  :: 44;
+	PTHREAD_BARRIER_T_SIZE :: 20;
+}
+
+pthread_cond_t :: opaque struct #align 16 {
+	_: [PTHREAD_COND_T_SIZE] c.char,
+};
+pthread_mutex_t :: opaque struct #align 16 {
+	_: [PTHREAD_MUTEX_T_SIZE] c.char,
+};
+pthread_rwlock_t :: opaque struct #align 16 {
+	_: [PTHREAD_RWLOCK_T_SIZE] c.char,
+};
+pthread_barrier_t :: opaque struct #align 16 {
+	_: [PTHREAD_BARRIER_T_SIZE] c.char,
+};
+
+pthread_attr_t :: opaque struct #align 16 {
+	_: [PTHREAD_ATTR_T_SIZE] c.char,
+};
+pthread_condattr_t :: opaque struct #align 16 {
+	_: [PTHREAD_CONDATTR_T_SIZE] c.char,
+};
+pthread_mutexattr_t :: opaque struct #align 16 {
+	_: [PTHREAD_MUTEXATTR_T_SIZE] c.char,
+};
+pthread_rwlockattr_t :: opaque struct #align 16 {
+	_: [PTHREAD_RWLOCKATTR_T_SIZE] c.char,
+};
+pthread_barrierattr_t :: opaque struct #align 16 {
+	_: [PTHREAD_BARRIERATTR_T_SIZE] c.char,
+};
+
+
+// TODO(tetra, 2019-11-01): Maybe make `enum c.int`s for these?
+PTHREAD_CREATE_JOINABLE :: 0;
+PTHREAD_CREATE_DETACHED :: 1;
+PTHREAD_INHERIT_SCHED :: 0;
+PTHREAD_EXPLICIT_SCHED :: 1;
+PTHREAD_PROCESS_PRIVATE :: 0;
+PTHREAD_PROCESS_SHARED :: 1;
+
+SCHED_OTHER :: 0;
+SCHED_FIFO  :: 1;
+SCHED_RR :: 2; // Round robin.
+
+sched_param :: struct {
+	sched_priority: c.int,
+}
+
+sem_t :: struct #align 16 {
+	_: [SEM_T_SIZE] c.char,
+}
+
+when size_of(int) == 8 {
+	SEM_T_SIZE :: 32;
+} else when size_of(int) == 4 {
+	SEM_T_SIZE :: 16;
+}
+
+foreign import "system:pthread"
+
+@(default_calling_convention="c")
+foreign pthread {
+	// create named semaphore.
+	// used in process-shared semaphores.
+	sem_open :: proc(name: cstring, flags: c.int) -> ^sem_t ---;
+
+	sem_init :: proc(sem: ^sem_t, pshared: c.int, initial_value: c.uint) -> c.int ---;
+	sem_destroy :: proc(sem: ^sem_t) -> c.int ---;
+	sem_post :: proc(sem: ^sem_t) -> c.int ---;
+	sem_wait :: proc(sem: ^sem_t) -> c.int ---;
+	sem_trywait :: proc(sem: ^sem_t) -> c.int ---;
+	// sem_timedwait :: proc(sem: ^sem_t, timeout: time.TimeSpec) -> c.int ---;
+}

+ 107 - 0
core/sys/unix/pthread_unix.odin

@@ -0,0 +1,107 @@
+package unix;
+
+foreign import "system:pthread"
+
+import "core:c"
+import "core:time"
+
+//
+// On success, these functions return 0.
+//
+
+@(default_calling_convention="c")
+foreign pthread {
+	pthread_create :: proc(t: ^pthread_t, attrs: ^pthread_attr_t, routine: proc(data: rawptr) -> rawptr, arg: rawptr) -> c.int ---;
+
+	// retval is a pointer to a location to put the return value of the thread proc.
+	pthread_join :: proc(t: pthread_t, retval: rawptr) -> c.int ---;
+
+	pthread_self :: proc() -> pthread_t ---;
+
+	pthread_equal :: proc(a, b: pthread_t) -> b32 ---;
+
+	sched_get_priority_min :: proc(policy: c.int) -> c.int ---;
+	sched_get_priority_max :: proc(policy: c.int) -> c.int ---;
+
+	// NOTE: POSIX says this can fail with OOM.
+	pthread_attr_init :: proc(attrs: ^pthread_attr_t) -> c.int ---;
+
+	pthread_attr_destroy :: proc(attrs: ^pthread_attr_t) -> c.int ---;
+
+	pthread_attr_getschedparam :: proc(attrs: ^pthread_attr_t, param: ^sched_param) -> c.int ---;
+	pthread_attr_setschedparam :: proc(attrs: ^pthread_attr_t, param: ^sched_param) -> c.int ---;
+
+	pthread_attr_getschedpolicy :: proc(t: ^pthread_attr_t, policy: ^c.int) -> c.int ---;
+	pthread_attr_setschedpolicy :: proc(t: ^pthread_attr_t, policy: c.int) -> c.int ---;
+
+	// states: PTHREAD_CREATE_DETACHED, PTHREAD_CREATE_JOINABLE
+	pthread_attr_setdetachstate :: proc(attrs: ^pthread_attr_t, detach_state: c.int) -> c.int ---;
+
+	// scheds: PTHREAD_INHERIT_SCHED, PTHREAD_EXPLICIT_SCHED
+	pthread_attr_setinheritsched :: proc(attrs: ^pthread_attr_t, sched: c.int) -> c.int ---;
+
+	// NOTE(tetra, 2019-11-06): WARNING: Different systems have different alignment requirements.
+	// For maximum usefulness, use the OS's page size.
+	// ALSO VERY MAJOR WARNING: `stack_ptr` must be the LAST byte of the stack on systems
+	// where the stack grows downwards, which is the common case, so far as I know.
+	// On systems where it grows upwards, give the FIRST byte instead.
+	// ALSO SLIGHTLY LESS MAJOR WARNING: Using this procedure DISABLES automatically-provided
+	// guard pages. If you are using this procedure, YOU must set them up manually.
+	// If you forget to do this, you WILL get stack corruption bugs if you do not EXTREMELY
+	// know what you are doing!
+	pthread_attr_setstack :: proc(attrs: ^pthread_attr_t, stack_ptr: rawptr, stack_size: u64) -> c.int ---;
+	pthread_attr_getstack :: proc(attrs: ^pthread_attr_t, stack_ptr: ^rawptr, stack_size: ^u64) -> c.int ---;
+}
+
+@(default_calling_convention="c")
+foreign pthread {
+	// NOTE: POSIX says this can fail with OOM.
+	pthread_cond_init :: proc(cond: ^pthread_cond_t, attrs: ^pthread_condattr_t) -> c.int ---;
+
+	pthread_cond_destroy :: proc(cond: ^pthread_cond_t) -> c.int ---;
+
+	pthread_cond_signal :: proc(cond: ^pthread_cond_t) -> c.int ---;
+
+	// same as signal, but wakes up _all_ threads that are waiting
+	pthread_cond_broadcast :: proc(cond: ^pthread_cond_t) -> c.int ---;
+
+
+	// assumes the mutex is pre-locked
+	pthread_cond_wait :: proc(cond: ^pthread_cond_t, mutex: ^pthread_mutex_t) -> c.int ---;
+	pthread_cond_timedwait :: proc(cond: ^pthread_cond_t, mutex: ^pthread_mutex_t, timeout: ^time.TimeSpec) -> c.int ---;
+
+	pthread_condattr_init :: proc(attrs: ^pthread_condattr_t) -> c.int ---;
+	pthread_condattr_destroy :: proc(attrs: ^pthread_condattr_t) -> c.int ---;
+
+	// p-shared = "process-shared" - i.e: is this condition shared among multiple processes?
+	// values: PTHREAD_PROCESS_PRIVATE, PTHREAD_PROCESS_SHARED
+	pthread_condattr_setpshared :: proc(attrs: ^pthread_condattr_t, value: c.int) -> c.int ---;
+	pthread_condattr_getpshared :: proc(attrs: ^pthread_condattr_t, result: ^c.int) -> c.int ---;
+
+}
+
+@(default_calling_convention="c")
+foreign pthread {
+	// NOTE: POSIX says this can fail with OOM.
+	pthread_mutex_init :: proc(mutex: ^pthread_mutex_t, attrs: ^pthread_mutexattr_t) -> c.int ---;
+
+	pthread_mutex_destroy :: proc(mutex: ^pthread_mutex_t) -> c.int ---;
+
+	pthread_mutex_trylock :: proc(mutex: ^pthread_mutex_t) -> c.int ---;
+
+	pthread_mutex_lock :: proc(mutex: ^pthread_mutex_t) -> c.int ---;
+
+	pthread_mutex_timedlock :: proc(mutex: ^pthread_mutex_t, timeout: ^time.TimeSpec) -> c.int ---;
+
+	pthread_mutex_unlock :: proc(mutex: ^pthread_mutex_t) -> c.int ---;
+
+
+	pthread_mutexattr_init :: proc(attrs: ^pthread_mutexattr_t) -> c.int ---;
+	pthread_mutexattr_destroy :: proc(attrs: ^pthread_mutexattr_t) -> c.int ---;
+
+	// p-shared = "process-shared" - i.e: is this mutex shared among multiple processes?
+	// values: PTHREAD_PROCESS_PRIVATE, PTHREAD_PROCESS_SHARED
+	pthread_mutexattr_setpshared :: proc(attrs: ^pthread_mutexattr_t, value: c.int) -> c.int ---;
+	pthread_mutexattr_getpshared :: proc(attrs: ^pthread_mutexattr_t, result: ^c.int) -> c.int ---;
+
+}

+ 19 - 0
core/sys/win32/general.odin

@@ -300,6 +300,25 @@ File_Notify_Information :: struct {
   file_name:         [1]u16,
 }
 
+// https://docs.microsoft.com/en-gb/windows/win32/api/sysinfoapi/ns-sysinfoapi-system_info
+System_Info :: struct {
+	using _: struct #raw_union {
+		oem_id: u32,
+		using _: struct #raw_union {
+			processor_architecture: u16,
+			_: u16, // reserved
+		},
+	},
+	page_size: u32,
+	minimum_application_address: rawptr,
+	maximum_application_address: rawptr,
+	active_processor_mask: u32,
+	number_of_processors: u32,
+	processor_type: u32,
+	allocation_granularity: u32,
+	processor_level: u16,
+	processor_revision: u16,
+}
 
 // https://docs.microsoft.com/en-us/windows/desktop/api/winnt/ns-winnt-_osversioninfoexa
 OS_Version_Info_Ex_A :: struct {

+ 1 - 0
core/sys/win32/kernel32.odin

@@ -31,6 +31,7 @@ foreign kernel32 {
 	@(link_name="GetCommandLineA")           get_command_line_a           :: proc() -> cstring ---;
 	@(link_name="GetCommandLineW")           get_command_line_w           :: proc() -> Wstring ---;
 	@(link_name="GetSystemMetrics")          get_system_metrics           :: proc(index: i32) -> i32 ---;
+	@(link_name="GetSystemInfo")             get_system_info              :: proc(info: ^System_Info) ---;
 	@(link_name="GetVersionExA")             get_version                  :: proc(osvi: ^OS_Version_Info_Ex_A) ---;
 	@(link_name="GetCurrentThreadId")        get_current_thread_id        :: proc() -> u32 ---;
 

+ 15 - 0
core/thread/thread.odin

@@ -0,0 +1,15 @@
+package thread;
+
+import "core:runtime";
+
+Thread_Proc :: #type proc(^Thread);
+
+Thread :: struct {
+	using specific:   Thread_Os_Specific,
+	procedure:        Thread_Proc,
+	data:             rawptr,
+	user_index:       int,
+
+	init_context:     runtime.Context,
+	use_init_context: bool,
+}

+ 153 - 0
core/thread/thread_unix.odin

@@ -0,0 +1,153 @@
+// +build linux, darwin
+package thread;
+
+import "core:sys/unix"
+import "core:sync"
+
+// NOTE(tetra): Aligned here because of core/unix/pthread_linux.odin/pthread_t.
+// Also see core/sys/darwin/mach_darwin.odin/semaphore_t.
+Thread_Os_Specific :: struct #align 16 {
+	unix_thread: unix.pthread_t, // NOTE: very large on Darwin, small on Linux.
+
+	// NOTE: pthread has a proc to query this, but it is marked
+	// as non-portable ("np") so we do this instead.
+	done: bool,
+
+	// since libpthread doesn't seem to have a way to create a thread
+	// in a suspended state, we have it wait on this gate, which we
+	// signal to start it.
+	// destroyed after thread is started.
+	start_gate: sync.Condition,
+
+	// if true, the thread has been started and the start_gate has been destroyed.
+	started: bool,
+
+	// NOTE: with pthreads, it is undefined behavior for multiple threads
+	// to call join on the same thread at the same time.
+	// this value is atomically updated to detect this.
+	// See the comment in `join`.
+	already_joined: bool,
+}
+
+Thread_Priority :: enum {
+	Normal,
+	Low,
+	High,
+}
+
+//
+// Creates a thread which will run the given procedure.
+// It then waits for `start` to be called.
+//
+// You may provide a slice of bytes to use as the stack for the new thread,
+// but if you do, you are expected to set up the guard pages yourself.
+//
+// The stack must also be aligned appropriately for the platform.
+// We require it's at least 16 bytes aligned to help robustness; other
+// platforms may require page-size alignment.
+// Note also that pthreads requires the stack is at least 6 OS pages in size:
+// 4 are required by pthreads, and two extra for guards pages that will be applied.
+//
+create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal) -> ^Thread {
+	__linux_thread_entry_proc :: proc "c" (t: rawptr) -> rawptr {
+		t := (^Thread)(t);
+		sync.condition_wait_for(&t.start_gate);
+		sync.condition_destroy(&t.start_gate);
+		t.start_gate = {};
+
+		c := context;
+		if t.use_init_context {
+			c = t.init_context;
+		}
+		context = c;
+
+		t.procedure(t);
+		sync.atomic_store(&t.done, true, .Sequentially_Consistent);
+		return nil;
+	}
+
+	attrs: unix.pthread_attr_t;
+	if unix.pthread_attr_init(&attrs) != 0 do return nil; // NOTE(tetra, 2019-11-01): POSIX OOM.
+	defer unix.pthread_attr_destroy(&attrs);
+
+	// NOTE(tetra, 2019-11-01): These only fail if their argument is invalid.
+	assert(unix.pthread_attr_setdetachstate(&attrs, unix.PTHREAD_CREATE_JOINABLE) == 0);
+	assert(unix.pthread_attr_setinheritsched(&attrs, unix.PTHREAD_EXPLICIT_SCHED) == 0);
+
+	thread := new(Thread);
+	if thread == nil do return nil;
+
+	// Set thread priority.
+	policy: i32;
+	res := unix.pthread_attr_getschedpolicy(&attrs, &policy);
+	assert(res == 0);
+	params: unix.sched_param;
+	res = unix.pthread_attr_getschedparam(&attrs, &params);
+	fmt.println(params.sched_priority);
+	assert(res == 0);
+	low := unix.sched_get_priority_min(policy);
+	high := unix.sched_get_priority_max(policy);
+	switch priority {
+	case .Low:
+		params.sched_priority = low + 1;
+	case .High:
+		params.sched_priority = high;
+	}
+	fmt.println(low, high, params.sched_priority);
+	res = unix.pthread_attr_setschedparam(&attrs, &params);
+	assert(res == 0);
+
+	sync.condition_init(&thread.start_gate);
+	if unix.pthread_create(&thread.unix_thread, &attrs, __linux_thread_entry_proc, thread) != 0 {
+		free(thread);
+		return nil;
+	}
+	thread.procedure = procedure;
+
+	return thread;
+}
+
+start :: proc(t: ^Thread) {
+	if sync.atomic_swap(&t.started, true, .Sequentially_Consistent) do return;
+	sync.condition_signal(&t.start_gate);
+}
+
+is_done :: proc(t: ^Thread) -> bool {
+	return sync.atomic_load(&t.done, .Sequentially_Consistent);
+}
+
+join :: proc(t: ^Thread) {
+	if unix.pthread_equal(unix.pthread_self(), t.unix_thread) do return;
+	// if unix.pthread_self().x == t.unix_thread.x do return;
+
+	// NOTE(tetra): It's apparently UB for multiple threads to join the same thread
+	// at the same time.
+	// If someone else already did, spin until the thread dies.
+	// See note on `already_joined` field.
+	// TODO(tetra): I'm not sure if we should do this, or panic, since I'm not
+	// sure it makes sense to need to join from multiple threads?
+	if sync.atomic_swap(&t.already_joined, true, .Sequentially_Consistent) {
+		for {
+			if sync.atomic_load(&t.done, .Sequentially_Consistent) do return;
+			sync.yield_processor();
+		}
+	}
+
+	// NOTE(tetra): If we're already dead, don't bother calling to pthread_join as that
+	// will just return 3 (ESRCH).
+	// We do this instead because I don't know if there is a danger
+	// that you may join a different thread from the one you called join on,
+	// if the thread handle is reused.
+	if sync.atomic_load(&t.done, .Sequentially_Consistent) do return;
+
+	ret := unix.pthread_join(t.unix_thread, nil);
+	assert(ret == 0, "cannot join thread");
+	assert(sync.atomic_load(&t.done, .Sequentially_Consistent), "thread not done after join");
+}
+
+import "core:fmt"
+destroy :: proc(t: ^Thread) {
+	join(t);
+	t.unix_thread = {};
+	free(t);
+}

+ 26 - 17
core/thread/thread_windows.odin

@@ -1,27 +1,29 @@
 package thread
 
-import "core:runtime"
+import "core:sync"
 import "core:sys/win32"
 
-Thread_Proc :: #type proc(^Thread) -> int;
-
 Thread_Os_Specific :: struct {
 	win32_thread:    win32.Handle,
 	win32_thread_id: u32,
+	done: bool, // see note in `is_done`
 }
 
-Thread :: struct {
-	using specific:   Thread_Os_Specific,
-	procedure:        Thread_Proc,
-	data:             rawptr,
-	user_index:       int,
-
-	init_context:     runtime.Context,
-	use_init_context: bool,
+THREAD_PRIORITY_IDLE   :: -15;
+THREAD_PRIORITY_LOWEST :: -2;
+THREAD_PRIORITY_BELOW_NORMAL :: -1;
+THREAD_PRIORITY_NORMAL :: 0;
+THREAD_PRIORITY_ABOVE_NORMAL :: 1;
+THREAD_PRIORITY_HIGHEST :: 2;
+THREAD_PRIORITY_TIME_CRITICAL :: 15;
+
+Thread_Priority :: enum i32 {
+	Normal = THREAD_PRIORITY_NORMAL,
+	Low = THREAD_PRIORITY_LOWEST,
+	High = THREAD_PRIORITY_HIGHEST,
 }
 
-
-create :: proc(procedure: Thread_Proc) -> ^Thread {
+create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal) -> ^Thread {
 	win32_thread_id: u32;
 
 	__windows_thread_entry_proc :: proc "c" (t: ^Thread) -> i32 {
@@ -31,7 +33,9 @@ create :: proc(procedure: Thread_Proc) -> ^Thread {
 		}
 		context = c;
 
-		return i32(t.procedure(t));
+		t.procedure(t);
+		sync.atomic_store(&t.done, true, .Sequentially_Consistent);
+		return 0;
 	}
 
 
@@ -47,6 +51,9 @@ create :: proc(procedure: Thread_Proc) -> ^Thread {
 	thread.win32_thread    = win32_thread;
 	thread.win32_thread_id = win32_thread_id;
 
+	ok := win32.set_thread_priority(win32_thread, i32(priority));
+	assert(ok == true);
+
 	return thread;
 }
 
@@ -55,8 +62,10 @@ start :: proc(using thread: ^Thread) {
 }
 
 is_done :: proc(using thread: ^Thread) -> bool {
-	res := win32.wait_for_single_object(win32_thread, 0);
-	return res != win32.WAIT_TIMEOUT;
+	// NOTE(tetra, 2019-10-31): Apparently using wait_for_single_object and
+	// checking if it didn't time out immediately, is not good enough,
+	// so we do it this way instead.
+	return sync.atomic_load(&done, .Sequentially_Consistent);
 }
 
 join :: proc(using thread: ^Thread) {
@@ -72,4 +81,4 @@ destroy :: proc(thread: ^Thread) {
 
 terminate :: proc(using thread : ^Thread, exit_code : u32) {
 	win32.terminate_thread(win32_thread, exit_code);
-}
+}

+ 0 - 56
core/time/time_darwin.odin

@@ -1,56 +0,0 @@
-package time
-
-foreign import libc "system:c"
-
-TimeSpec :: struct {
-    tv_sec  : i64,  /* seconds */
-    tv_nsec : i64,  /* nanoseconds */
-};
-
-CLOCK_SYSTEM          :: 0;
-CLOCK_CALENDAR        :: 1;
-
-IS_SUPPORTED :: true;
-
-foreign libc {
-    @(link_name="clock_gettime")    _clock_gettime :: proc(clock_id: u64, timespec: ^TimeSpec) ---;
-    @(link_name="nanosleep")        _nanosleep     :: proc(requested: ^TimeSpec, remaining: ^TimeSpec) -> int ---;
-    @(link_name="sleep")            _sleep         :: proc(seconds: u64) -> int ---;
-}
-
-clock_gettime :: proc(clock_id: u64) -> TimeSpec {
-    ts : TimeSpec;
-    _clock_gettime(clock_id, &ts);
-    return ts;
-}
-
-now :: proc() -> Time {
-
-    time_spec_now := clock_gettime(CLOCK_SYSTEM);
-    ns := time_spec_now.tv_sec * 1e9 + time_spec_now.tv_nsec;
-    return Time{_nsec=ns};
-}
-
-seconds_since_boot :: proc() -> f64 {
-
-    ts_boottime := clock_gettime(CLOCK_SYSTEM);
-    return f64(ts_boottime.tv_sec) + f64(ts_boottime.tv_nsec) / 1e9;
-}
-
-sleep :: proc(d: Duration) {
-
-    ds := duration_seconds(d);
-    seconds := u64(ds);
-    nanoseconds := i64((ds - f64(seconds)) * 1e9);
-
-    if seconds > 0 do _sleep(seconds);
-    if nanoseconds > 0 do nanosleep(nanoseconds);
-}
-
-nanosleep :: proc(nanoseconds: i64) -> int {
-    assert(nanoseconds <= 999999999);
-    requested, remaining : TimeSpec;
-    requested = TimeSpec{tv_nsec = nanoseconds};
-
-    return _nanosleep(&requested, &remaining);
-}

+ 0 - 44
core/time/time_linux.odin

@@ -1,44 +0,0 @@
-package time
-
-import "core:os";
-
-// NOTE(Jeroen): The times returned are in UTC
-IS_SUPPORTED :: true;
-
-now :: proc() -> Time {
-
-    time_spec_now := os.clock_gettime(os.CLOCK_REALTIME);
-    ns := time_spec_now.tv_sec * 1e9 + time_spec_now.tv_nsec;
-    return Time{_nsec=ns};
-}
-
-boot_time :: proc() -> Time {
-
-    ts_now := os.clock_gettime(os.CLOCK_REALTIME);
-    ts_boottime := os.clock_gettime(os.CLOCK_BOOTTIME);
-
-    ns := (ts_now.tv_sec - ts_boottime.tv_sec) * 1e9 + ts_now.tv_nsec - ts_boottime.tv_nsec;
-    return Time{_nsec=ns};
-}
-
-seconds_since_boot :: proc() -> f64 {
-
-    ts_boottime := os.clock_gettime(os.CLOCK_BOOTTIME);
-    return f64(ts_boottime.tv_sec) + f64(ts_boottime.tv_nsec) / 1e9;
-}
-
-sleep :: proc(d: Duration) {
-
-    ds := duration_seconds(d);
-    seconds := u64(ds);
-    nanoseconds := i64((ds - f64(seconds)) * 1e9);
-
-    if seconds > 0 do os.sleep(seconds);
-    if nanoseconds > 0 do os.nanosleep(nanoseconds);
-}
-
-nanosleep :: proc(d: Duration) {
-    // NOTE(Jeroen): os.nanosleep returns -1 on failure, 0 on success
-    // duration needs to be [0, 999999999] nanoseconds.
-    os.nanosleep(i64(d));
-}

+ 80 - 0
core/time/time_unix.odin

@@ -0,0 +1,80 @@
+//+build linux, darwin
+package time
+
+IS_SUPPORTED :: true; // NOTE: Times on Darwin are UTC.
+
+foreign import libc "system:c"
+
+@(default_calling_convention="c")
+foreign libc {
+	@(link_name="clock_gettime") _unix_clock_gettime :: proc(clock_id: u64, timespec: ^TimeSpec) -> i32 ---;
+	@(link_name="sleep")         _unix_sleep         :: proc(seconds: u32) -> i32 ---;
+	@(link_name="nanosleep")     _unix_nanosleep     :: proc(requested: ^TimeSpec, remaining: ^TimeSpec) -> i32 ---;
+}
+
+TimeSpec :: struct {
+	tv_sec  : i64,  /* seconds */
+	tv_nsec : i64,  /* nanoseconds */
+};
+
+CLOCK_REALTIME           :: 0; // NOTE(tetra): May jump in time, when user changes the system time.
+CLOCK_MONOTONIC          :: 1; // NOTE(tetra): May stand still while system is asleep.
+CLOCK_PROCESS_CPUTIME_ID :: 2;
+CLOCK_THREAD_CPUTIME_ID  :: 3;
+CLOCK_MONOTONIC_RAW      :: 4; // NOTE(tetra): "RAW" means: Not adjusted by NTP.
+CLOCK_REALTIME_COARSE    :: 5; // NOTE(tetra): "COARSE" clocks are apparently much faster, but not "fine-grained."
+CLOCK_MONOTONIC_COARSE   :: 6;
+CLOCK_BOOTTIME           :: 7; // NOTE(tetra): Same as MONOTONIC, except also including time system was asleep.
+CLOCK_REALTIME_ALARM     :: 8;
+CLOCK_BOOTTIME_ALARM     :: 9;
+
+// TODO(tetra, 2019-11-05): The original implementation of this package for Darwin used this constants.
+// I do not know if Darwin programmers are used to the existance of these constants or not, so
+// I'm leaving aliases to them for now.
+CLOCK_SYSTEM   :: CLOCK_REALTIME;
+CLOCK_CALENDAR :: CLOCK_MONOTONIC;
+
+
+clock_gettime :: proc(clock_id: u64) -> TimeSpec {
+	ts : TimeSpec; // NOTE(tetra): Do we need to initialize this?
+	_unix_clock_gettime(clock_id, &ts);
+	return ts;
+}
+
+now :: proc() -> Time {
+	time_spec_now := clock_gettime(CLOCK_REALTIME);
+	ns := time_spec_now.tv_sec * 1e9 + time_spec_now.tv_nsec;
+	return Time{_nsec=ns};
+}
+
+boot_time :: proc() -> Time {
+	ts_now := clock_gettime(CLOCK_REALTIME);
+	ts_boottime := clock_gettime(CLOCK_BOOTTIME);
+
+	ns := (ts_now.tv_sec - ts_boottime.tv_sec) * 1e9 + ts_now.tv_nsec - ts_boottime.tv_nsec;
+	return Time{_nsec=ns};
+}
+
+seconds_since_boot :: proc() -> f64 {
+	ts_boottime := clock_gettime(CLOCK_BOOTTIME);
+	return f64(ts_boottime.tv_sec) + f64(ts_boottime.tv_nsec) / 1e9;
+}
+
+
+sleep :: proc(d: Duration) {
+	ds := duration_seconds(d);
+	seconds := u32(ds);
+	nanoseconds := i64((ds - f64(seconds)) * 1e9);
+
+	if seconds > 0 do _unix_sleep(seconds);
+	if nanoseconds > 0 do nanosleep(nanoseconds);
+}
+
+nanosleep :: proc(nanoseconds: i64) -> int {
+	// NOTE(tetra): Should we remove this assert? We are measuring nanoseconds after all...
+	assert(nanoseconds <= 999999999);
+
+	requested := TimeSpec{tv_nsec = nanoseconds};
+	remaining: TimeSpec; // NOTE(tetra): Do we need to initialize this?
+	return int(_unix_nanosleep(&requested, &remaining));
+}

+ 0 - 2
core/time/time_windows.odin

@@ -17,8 +17,6 @@ now :: proc() -> Time {
 	return Time{_nsec=ns};
 }
 
-
-
 sleep :: proc(d: Duration) {
 	win32.sleep(u32(d/Millisecond));
 }

+ 53 - 1
examples/demo/demo.odin

@@ -3,6 +3,7 @@ package main
 import "core:fmt"
 import "core:mem"
 import "core:os"
+import "core:thread"
 import "core:reflect"
 import "intrinsics"
 
@@ -1088,6 +1089,54 @@ parametric_polymorphism :: proc() {
 }
 
 
+prefix_table := [?]string{
+	"White",
+	"Red",
+	"Green",
+	"Blue",
+	"Octarine",
+	"Black",
+};
+
+threading_example :: proc() {
+	fmt.println("\n# threading_example");
+
+	worker_proc :: proc(t: ^thread.Thread) {
+		for iteration in 1..5 {
+			fmt.printf("Thread %d is on iteration %d\n", t.user_index, iteration);
+			fmt.printf("`%s`: iteration %d\n", prefix_table[t.user_index], iteration);
+			// win32.sleep(1);
+		}
+	}
+
+	threads := make([dynamic]^thread.Thread, 0, len(prefix_table));
+	defer delete(threads);
+
+	for in prefix_table {
+		if t := thread.create(worker_proc); t != nil {
+			t.init_context = context;
+			t.use_init_context = true;
+			t.user_index = len(threads);
+			append(&threads, t);
+			thread.start(t);
+		}
+	}
+
+	for len(threads) > 0 {
+		for i := 0; i < len(threads); /**/ {
+			if t := threads[i]; thread.is_done(t) {
+				fmt.printf("Thread %d is done\n", t.user_index);
+				thread.destroy(t);
+
+				ordered_remove(&threads, i);
+			} else {
+				i += 1;
+			}
+		}
+	}
+}
+
+
 array_programming :: proc() {
 	fmt.println("\n# array programming");
 	{
@@ -1783,7 +1832,10 @@ main :: proc() {
 		ranged_fields_for_array_compound_literals();
 		deprecated_attribute();
 		range_statements_with_multiple_return_values();
-		soa_struct_layout();
+		threading_example();
+
+		// TODO(tetra): When bill fixes SOA array comparison to nil in reserve_soa, we can re-enable this.
+		// soa_struct_layout();
 	}
 }