aboutsummaryrefslogtreecommitdiff
path: root/core
diff options
context:
space:
mode:
Diffstat (limited to 'core')
-rw-r--r--core/mem/alloc.odin12
-rw-r--r--core/mem/mem.odin40
-rw-r--r--core/os/os.odin51
-rw-r--r--core/os/os_darwin.odin147
-rw-r--r--core/os/os_linux.odin213
-rw-r--r--core/os/os_windows.odin13
-rw-r--r--core/sync/sync.odin27
-rw-r--r--core/sync/sync_darwin.odin39
-rw-r--r--core/sync/sync_linux.odin100
-rw-r--r--core/sync/sync_unix.odin99
-rw-r--r--core/sync/sync_windows.odin71
-rw-r--r--core/sys/darwin/mach_darwin.odin29
-rw-r--r--core/sys/unix/pthread_darwin.odin80
-rw-r--r--core/sys/unix/pthread_linux.odin106
-rw-r--r--core/sys/unix/pthread_unix.odin107
-rw-r--r--core/sys/win32/general.odin19
-rw-r--r--core/sys/win32/kernel32.odin1
-rw-r--r--core/thread/thread.odin15
-rw-r--r--core/thread/thread_unix.odin153
-rw-r--r--core/thread/thread_windows.odin43
-rw-r--r--core/time/time_darwin.odin56
-rw-r--r--core/time/time_linux.odin44
-rw-r--r--core/time/time_unix.odin80
-rw-r--r--core/time/time_windows.odin2
24 files changed, 1199 insertions, 348 deletions
diff --git a/core/mem/alloc.odin b/core/mem/alloc.odin
index 4c60ef4a1..38eda7a11 100644
--- a/core/mem/alloc.odin
+++ b/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;
}
diff --git a/core/mem/mem.odin b/core/mem/mem.odin
index 1c833c4d1..78be94560 100644
--- a/core/mem/mem.odin
+++ b/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);
}
-
-
diff --git a/core/os/os.odin b/core/os/os.odin
index d9bb318c4..7d6f4451d 100644
--- a/core/os/os.odin
+++ b/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;
diff --git a/core/os/os_darwin.odin b/core/os/os_darwin.odin
index c252cf05c..3bca732ec 100644
--- a/core/os/os_darwin.odin
+++ b/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__));
diff --git a/core/os/os_linux.odin b/core/os/os_linux.odin
index dc3cdde93..cf03dac71 100644
--- a/core/os/os_linux.odin
+++ b/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__));
diff --git a/core/os/os_windows.odin b/core/os/os_windows.odin
index e45cf9f5f..f433a2517 100644
--- a/core/os/os_windows.odin
+++ b/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));
diff --git a/core/sync/sync.odin b/core/sync/sync.odin
new file mode 100644
index 000000000..5a0512275
--- /dev/null
+++ b/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);
+}
diff --git a/core/sync/sync_darwin.odin b/core/sync/sync_darwin.odin
new file mode 100644
index 000000000..2c86e21db
--- /dev/null
+++ b/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);
+}
diff --git a/core/sync/sync_linux.odin b/core/sync/sync_linux.odin
index dcb2ee8e9..dc761f6aa 100644
--- a/core/sync/sync_linux.odin
+++ b/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);
- }
- }
-}
-
-*/
diff --git a/core/sync/sync_unix.odin b/core/sync/sync_unix.odin
new file mode 100644
index 000000000..71ba54128
--- /dev/null
+++ b/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;
+ }
+}
diff --git a/core/sync/sync_windows.odin b/core/sync/sync_windows.odin
index b0a9d944c..a99ac8497 100644
--- a/core/sync/sync_windows.odin
+++ b/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);
+} \ No newline at end of file
diff --git a/core/sys/darwin/mach_darwin.odin b/core/sys/darwin/mach_darwin.odin
new file mode 100644
index 000000000..52a145507
--- /dev/null
+++ b/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 ---;
+}
diff --git a/core/sys/unix/pthread_darwin.odin b/core/sys/unix/pthread_darwin.odin
new file mode 100644
index 000000000..97a750b9b
--- /dev/null
+++ b/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;
diff --git a/core/sys/unix/pthread_linux.odin b/core/sys/unix/pthread_linux.odin
new file mode 100644
index 000000000..18ef09a69
--- /dev/null
+++ b/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 ---;
+}
diff --git a/core/sys/unix/pthread_unix.odin b/core/sys/unix/pthread_unix.odin
new file mode 100644
index 000000000..885048a59
--- /dev/null
+++ b/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 ---;
+
+} \ No newline at end of file
diff --git a/core/sys/win32/general.odin b/core/sys/win32/general.odin
index 16241de05..16853c5cd 100644
--- a/core/sys/win32/general.odin
+++ b/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 {
diff --git a/core/sys/win32/kernel32.odin b/core/sys/win32/kernel32.odin
index f647ab7e0..3caeb4963 100644
--- a/core/sys/win32/kernel32.odin
+++ b/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 ---;
diff --git a/core/thread/thread.odin b/core/thread/thread.odin
new file mode 100644
index 000000000..c326b30f1
--- /dev/null
+++ b/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,
+} \ No newline at end of file
diff --git a/core/thread/thread_unix.odin b/core/thread/thread_unix.odin
new file mode 100644
index 000000000..a05c8c2af
--- /dev/null
+++ b/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);
+}
diff --git a/core/thread/thread_windows.odin b/core/thread/thread_windows.odin
index 743f0fec8..5b956940f 100644
--- a/core/thread/thread_windows.odin
+++ b/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);
-}
+} \ No newline at end of file
diff --git a/core/time/time_darwin.odin b/core/time/time_darwin.odin
deleted file mode 100644
index 91acd8ae7..000000000
--- a/core/time/time_darwin.odin
+++ /dev/null
@@ -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);
-} \ No newline at end of file
diff --git a/core/time/time_linux.odin b/core/time/time_linux.odin
deleted file mode 100644
index d83d719fb..000000000
--- a/core/time/time_linux.odin
+++ /dev/null
@@ -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));
-}
diff --git a/core/time/time_unix.odin b/core/time/time_unix.odin
new file mode 100644
index 000000000..28a6d6a9f
--- /dev/null
+++ b/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));
+} \ No newline at end of file
diff --git a/core/time/time_windows.odin b/core/time/time_windows.odin
index f6bfdd678..c1d766aee 100644
--- a/core/time/time_windows.odin
+++ b/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));
}