Merge pull request #458 from Tetralux/linux-threads

Implement core:thread and core:sync on Unix using pthreads
This commit is contained in:
gingerBill
2019-12-01 11:33:23 +00:00
committed by GitHub
25 changed files with 1251 additions and 348 deletions

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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__));

View File

@@ -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__));

View File

@@ -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
View 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);
}

View 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);
}

View File

@@ -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
View 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;
}
}

View File

@@ -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);
}
}

View 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 ---;
}

View 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;

View 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 ---;
}

View 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 ---;
}

View File

@@ -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 {

View File

@@ -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
View 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,
}

View 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, &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);
}

View File

@@ -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);
}
}

View File

@@ -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);
}

View File

@@ -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
View 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));
}

View File

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

View File

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