mirror of
https://github.com/odin-lang/Odin.git
synced 2026-01-03 11:42:28 +00:00
Merge pull request #458 from Tetralux/linux-threads
Implement core:thread and core:sync on Unix using pthreads
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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__));
|
||||
|
||||
@@ -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__));
|
||||
|
||||
@@ -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
core/sync/sync.odin
Normal file
27
core/sync/sync.odin
Normal file
@@ -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
core/sync/sync_darwin.odin
Normal file
39
core/sync/sync_darwin.odin
Normal file
@@ -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);
|
||||
}
|
||||
@@ -1,98 +1,28 @@
|
||||
package sync
|
||||
|
||||
/*
|
||||
import "core:sys/unix"
|
||||
|
||||
import "core:atomics"
|
||||
import "core:os"
|
||||
|
||||
Semaphore :: struct {
|
||||
// _handle: win32.Handle,
|
||||
// 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,
|
||||
}
|
||||
|
||||
Mutex :: struct {
|
||||
_semaphore: Semaphore,
|
||||
_counter: i32,
|
||||
_owner: i32,
|
||||
_recursion: i32,
|
||||
}
|
||||
|
||||
current_thread_id :: proc() -> i32 {
|
||||
return i32(os.current_thread_id());
|
||||
}
|
||||
|
||||
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
core/sync/sync_unix.odin
Normal file
99
core/sync/sync_unix.odin
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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,10 +62,19 @@ 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_destroy :: proc(using c: ^Condition) {
|
||||
if event != nil {
|
||||
win32.close_handle(event);
|
||||
}
|
||||
}
|
||||
|
||||
condition_signal :: proc(using c: ^Condition) {
|
||||
ok := win32.set_event(event);
|
||||
assert(bool(ok));
|
||||
@@ -85,27 +83,4 @@ condition_signal :: proc(using c: ^Condition) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
ticket_mutex_unlock :: inline proc(m: ^Ticket_Mutex) {
|
||||
atomic_add(&m.serving, 1, Ordering.Relaxed);
|
||||
}
|
||||
}
|
||||
29
core/sys/darwin/mach_darwin.odin
Normal file
29
core/sys/darwin/mach_darwin.odin
Normal file
@@ -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
core/sys/unix/pthread_darwin.odin
Normal file
80
core/sys/unix/pthread_darwin.odin
Normal file
@@ -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
core/sys/unix/pthread_linux.odin
Normal file
106
core/sys/unix/pthread_linux.odin
Normal file
@@ -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
core/sys/unix/pthread_unix.odin
Normal file
107
core/sys/unix/pthread_unix.odin
Normal file
@@ -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 ---;
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
core/thread/thread.odin
Normal file
15
core/thread/thread.odin
Normal file
@@ -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
core/thread/thread_unix.odin
Normal file
153
core/thread/thread_unix.odin
Normal file
@@ -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, ¶ms);
|
||||
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, ¶ms);
|
||||
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);
|
||||
}
|
||||
@@ -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,
|
||||
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;
|
||||
|
||||
init_context: runtime.Context,
|
||||
use_init_context: bool,
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
core/time/time_unix.odin
Normal file
80
core/time/time_unix.odin
Normal file
@@ -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));
|
||||
}
|
||||
@@ -17,8 +17,6 @@ now :: proc() -> Time {
|
||||
return Time{_nsec=ns};
|
||||
}
|
||||
|
||||
|
||||
|
||||
sleep :: proc(d: Duration) {
|
||||
win32.sleep(u32(d/Millisecond));
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user