Răsfoiți Sursa

Merge pull request #458 from Tetralux/linux-threads

Implement core:thread and core:sync on Unix using pthreads
gingerBill 5 ani în urmă
părinte
comite
3fd5c3cd85

+ 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();
 	}
 }