diff --git a/core/mem/alloc.odin b/core/mem/alloc.odin index 4c60ef4a1..38eda7a11 100644 --- a/core/mem/alloc.odin +++ b/core/mem/alloc.odin @@ -84,7 +84,10 @@ delete :: proc{ new :: inline proc($T: typeid, allocator := context.allocator, loc := #caller_location) -> ^T { - ptr := (^T)(alloc(size_of(T), align_of(T), allocator, loc)); + return new_aligned(T, align_of(T), allocator, loc); +} +new_aligned :: inline proc($T: typeid, alignment: int, allocator := context.allocator, loc := #caller_location) -> ^T { + ptr := (^T)(alloc(size_of(T), alignment, allocator, loc)); if ptr != nil do ptr^ = T{}; return ptr; } @@ -95,9 +98,12 @@ new_clone :: inline proc(data: $T, allocator := context.allocator, loc := #calle } -make_slice :: proc($T: typeid/[]$E, auto_cast len: int, allocator := context.allocator, loc := #caller_location) -> T { +make_slice :: inline proc($T: typeid/[]$E, auto_cast len: int, allocator := context.allocator, loc := #caller_location) -> T { + return make_aligned(T, len, align_of(E), allocator, loc); +} +make_aligned :: proc($T: typeid/[]$E, auto_cast len: int, alignment: int, allocator := context.allocator, loc := #caller_location) -> T { runtime.make_slice_error_loc(loc, len); - data := alloc(size_of(E)*len, align_of(E), allocator, loc); + data := alloc(size_of(E)*len, alignment, allocator, loc); s := Raw_Slice{data, len}; return transmute(T)s; } diff --git a/core/mem/mem.odin b/core/mem/mem.odin index 1c833c4d1..78be94560 100644 --- a/core/mem/mem.odin +++ b/core/mem/mem.odin @@ -151,27 +151,19 @@ is_power_of_two :: inline proc(x: uintptr) -> bool { return (x & (x-1)) == 0; } -align_forward :: proc(ptr: rawptr, align: uintptr) -> rawptr { - assert(is_power_of_two(align)); - - a := uintptr(align); - p := uintptr(ptr); - modulo := p & (a-1); - if modulo != 0 do p += a - modulo; - return rawptr(p); +align_forward :: inline proc(ptr: rawptr, align: uintptr) -> rawptr { + return rawptr(align_forward_uintptr(uintptr(ptr), align)); } align_forward_uintptr :: proc(ptr, align: uintptr) -> uintptr { assert(is_power_of_two(align)); - a := uintptr(align); - p := uintptr(ptr); - modulo := p & (a-1); - if modulo != 0 do p += a - modulo; - return uintptr(p); + p := ptr; + modulo := p & (align-1); + if modulo != 0 do p += align - modulo; + return p; } - align_forward_int :: inline proc(ptr, align: int) -> int { return int(align_forward_uintptr(uintptr(ptr), uintptr(align))); } @@ -179,6 +171,24 @@ align_forward_uint :: inline proc(ptr, align: uint) -> uint { return uint(align_forward_uintptr(uintptr(ptr), uintptr(align))); } +align_backward :: inline proc(ptr: rawptr, align: uintptr) -> rawptr { + return rawptr(align_backward_uintptr(uintptr(ptr), align)); +} + +align_backward_uintptr :: proc(ptr, align: uintptr) -> uintptr { + assert(is_power_of_two(align)); + + ptr := rawptr(ptr - align); + return uintptr(align_forward(ptr, align)); +} + +align_backward_int :: inline proc(ptr, align: int) -> int { + return int(align_backward_uintptr(uintptr(ptr), uintptr(align))); +} +align_backward_uint :: inline proc(ptr, align: uint) -> uint { + return uint(align_backward_uintptr(uintptr(ptr), uintptr(align))); +} + context_from_allocator :: proc(a: Allocator) -> type_of(context) { context.allocator = a; return context; @@ -226,5 +236,3 @@ calc_padding_with_header :: proc(ptr: uintptr, align: uintptr, header_size: int) return int(padding); } - - diff --git a/core/os/os.odin b/core/os/os.odin index d9bb318c4..7d6f4451d 100644 --- a/core/os/os.odin +++ b/core/os/os.odin @@ -119,16 +119,54 @@ read_ptr :: proc(fd: Handle, data: rawptr, len: int) -> (int, Errno) { return read(fd, s); } - heap_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode, size, alignment: int, old_memory: rawptr, old_size: int, flags: u64 = 0, loc := #caller_location) -> rawptr { + // + // NOTE(tetra, 2019-11-10): The heap doesn't respect alignment. + // HACK: Overallocate, align forwards, and then use the two bytes immediately before + // the address we return, to store the padding we inserted. + // This allows us to pass the original pointer we got back from the heap to `free` later. + // + + align_and_store_padding :: proc(ptr: rawptr, alignment: int) -> rawptr { + ptr := mem.ptr_offset(cast(^u8) ptr, 2); + new_ptr := cast(^u8) mem.align_forward(ptr, uintptr(alignment)); + offset := mem.ptr_sub(new_ptr, cast(^u8) ptr) + 2; + assert(offset < int(max(u16))); + (^[2]u8)(mem.ptr_offset(new_ptr, -2))^ = transmute([2]u8) u16(offset); + return new_ptr; + } + + recover_original_pointer :: proc(ptr: rawptr) -> rawptr { + ptr := cast(^u8) ptr; + offset := transmute(u16) (^[2]u8)(mem.ptr_offset(ptr, -2))^; + ptr = mem.ptr_offset(ptr, -int(offset)); + return ptr; + } + + aligned_heap_alloc :: proc(size: int, alignment: int) -> rawptr { + // NOTE(tetra): Alignment 1 will mean we only have one extra byte. + // This is not enough for a u16 - so we ensure there is at least two bytes extra. + // This also means that the pointer is always aligned to at least 2. + extra := alignment; + if extra <= 1 do extra = 2; + + orig := cast(^u8) heap_alloc(size + extra); + if orig == nil do return nil; + ptr := align_and_store_padding(orig, alignment); + assert(recover_original_pointer(ptr) == orig); + return ptr; + } + switch mode { case .Alloc: - return heap_alloc(size); + return aligned_heap_alloc(size, alignment); case .Free: - heap_free(old_memory); + assert(old_memory != nil); + ptr := recover_original_pointer(old_memory); + heap_free(ptr); return nil; case .Free_All: @@ -136,11 +174,12 @@ heap_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode, case .Resize: if old_memory == nil { - return heap_alloc(size); + return aligned_heap_alloc(size, alignment); } - ptr := heap_resize(old_memory, size); + ptr := recover_original_pointer(old_memory); + ptr = heap_resize(ptr, size); assert(ptr != nil); - return ptr; + return align_and_store_padding(ptr, alignment); } return nil; diff --git a/core/os/os_darwin.odin b/core/os/os_darwin.odin index c252cf05c..3bca732ec 100644 --- a/core/os/os_darwin.odin +++ b/core/os/os_darwin.odin @@ -14,6 +14,142 @@ Errno :: distinct int; INVALID_HANDLE :: ~Handle(0); +ERROR_NONE: Errno : 0; +EPERM: Errno : 1; /* Operation not permitted */ +ENOENT: Errno : 2; /* No such file or directory */ +ESRCH: Errno : 3; /* No such process */ +EINTR: Errno : 4; /* Interrupted system call */ +EIO: Errno : 5; /* Input/output error */ +ENXIO: Errno : 6; /* Device not configured */ +E2BIG: Errno : 7; /* Argument list too long */ +ENOEXEC: Errno : 8; /* Exec format error */ +EBADF: Errno : 9; /* Bad file descriptor */ +ECHILD: Errno : 10; /* No child processes */ +EDEADLK: Errno : 11; /* Resource deadlock avoided */ +ENOMEM: Errno : 12; /* Cannot allocate memory */ +EACCES: Errno : 13; /* Permission denied */ +EFAULT: Errno : 14; /* Bad address */ +ENOTBLK: Errno : 15; /* Block device required */ +EBUSY: Errno : 16; /* Device / Resource busy */ +EEXIST: Errno : 17; /* File exists */ +EXDEV: Errno : 18; /* Cross-device link */ +ENODEV: Errno : 19; /* Operation not supported by device */ +ENOTDIR: Errno : 20; /* Not a directory */ +EISDIR: Errno : 21; /* Is a directory */ +EINVAL: Errno : 22; /* Invalid argument */ +ENFILE: Errno : 23; /* Too many open files in system */ +EMFILE: Errno : 24; /* Too many open files */ +ENOTTY: Errno : 25; /* Inappropriate ioctl for device */ +ETXTBSY: Errno : 26; /* Text file busy */ +EFBIG: Errno : 27; /* File too large */ +ENOSPC: Errno : 28; /* No space left on device */ +ESPIPE: Errno : 29; /* Illegal seek */ +EROFS: Errno : 30; /* Read-only file system */ +EMLINK: Errno : 31; /* Too many links */ +EPIPE: Errno : 32; /* Broken pipe */ + +/* math software */ +EDOM: Errno : 33; /* Numerical argument out of domain */ +ERANGE: Errno : 34; /* Result too large */ + +/* non-blocking and interrupt i/o */ +EAGAIN: Errno : 35; /* Resource temporarily unavailable */ +EWOULDBLOCK: Errno : EAGAIN; /* Operation would block */ +EINPROGRESS: Errno : 36; /* Operation now in progress */ +EALREADY: Errno : 37; /* Operation already in progress */ + +/* ipc/network software -- argument errors */ +ENOTSOCK: Errno : 38; /* Socket operation on non-socket */ +EDESTADDRREQ: Errno : 39; /* Destination address required */ +EMSGSIZE: Errno : 40; /* Message too long */ +EPROTOTYPE: Errno : 41; /* Protocol wrong type for socket */ +ENOPROTOOPT: Errno : 42; /* Protocol not available */ +EPROTONOSUPPORT: Errno : 43; /* Protocol not supported */ +ESOCKTNOSUPPORT: Errno : 44; /* Socket type not supported */ +ENOTSUP: Errno : 45; /* Operation not supported */ +EPFNOSUPPORT: Errno : 46; /* Protocol family not supported */ +EAFNOSUPPORT: Errno : 47; /* Address family not supported by protocol family */ +EADDRINUSE: Errno : 48; /* Address already in use */ +EADDRNOTAVAIL: Errno : 49; /* Can't assign requested address */ + +/* ipc/network software -- operational errors */ +ENETDOWN: Errno : 50; /* Network is down */ +ENETUNREACH: Errno : 51; /* Network is unreachable */ +ENETRESET: Errno : 52; /* Network dropped connection on reset */ +ECONNABORTED: Errno : 53; /* Software caused connection abort */ +ECONNRESET: Errno : 54; /* Connection reset by peer */ +ENOBUFS: Errno : 55; /* No buffer space available */ +EISCONN: Errno : 56; /* Socket is already connected */ +ENOTCONN: Errno : 57; /* Socket is not connected */ +ESHUTDOWN: Errno : 58; /* Can't send after socket shutdown */ +ETOOMANYREFS: Errno : 59; /* Too many references: can't splice */ +ETIMEDOUT: Errno : 60; /* Operation timed out */ +ECONNREFUSED: Errno : 61; /* Connection refused */ + +ELOOP: Errno : 62; /* Too many levels of symbolic links */ +ENAMETOOLONG: Errno : 63; /* File name too long */ + +/* should be rearranged */ +EHOSTDOWN: Errno : 64; /* Host is down */ +EHOSTUNREACH: Errno : 65; /* No route to host */ +ENOTEMPTY: Errno : 66; /* Directory not empty */ + +/* quotas & mush */ +EPROCLIM: Errno : 67; /* Too many processes */ +EUSERS: Errno : 68; /* Too many users */ +EDQUOT: Errno : 69; /* Disc quota exceeded */ + +/* Network File System */ +ESTALE: Errno : 70; /* Stale NFS file handle */ +EREMOTE: Errno : 71; /* Too many levels of remote in path */ +EBADRPC: Errno : 72; /* RPC struct is bad */ +ERPCMISMATCH: Errno : 73; /* RPC version wrong */ +EPROGUNAVAIL: Errno : 74; /* RPC prog. not avail */ +EPROGMISMATCH: Errno : 75; /* Program version wrong */ +EPROCUNAVAIL: Errno : 76; /* Bad procedure for program */ + +ENOLCK: Errno : 77; /* No locks available */ +ENOSYS: Errno : 78; /* Function not implemented */ + +EFTYPE: Errno : 79; /* Inappropriate file type or format */ +EAUTH: Errno : 80; /* Authentication error */ +ENEEDAUTH: Errno : 81; /* Need authenticator */ + +/* Intelligent device errors */ +EPWROFF: Errno : 82; /* Device power is off */ +EDEVERR: Errno : 83; /* Device error, e.g. paper out */ +EOVERFLOW: Errno : 84; /* Value too large to be stored in data type */ + +/* Program loading errors */ +EBADEXEC: Errno : 85; /* Bad executable */ +EBADARCH: Errno : 86; /* Bad CPU type in executable */ +ESHLIBVERS: Errno : 87; /* Shared library version mismatch */ +EBADMACHO: Errno : 88; /* Malformed Macho file */ + +ECANCELED: Errno : 89; /* Operation canceled */ + +EIDRM: Errno : 90; /* Identifier removed */ +ENOMSG: Errno : 91; /* No message of desired type */ +EILSEQ: Errno : 92; /* Illegal byte sequence */ +ENOATTR: Errno : 93; /* Attribute not found */ + +EBADMSG: Errno : 94; /* Bad message */ +EMULTIHOP: Errno : 95; /* Reserved */ +ENODATA: Errno : 96; /* No message available on STREAM */ +ENOLINK: Errno : 97; /* Reserved */ +ENOSR: Errno : 98; /* No STREAM resources */ +ENOSTR: Errno : 99; /* Not a STREAM */ +EPROTO: Errno : 100; /* Protocol error */ +ETIME: Errno : 101; /* STREAM ioctl timeout */ + +ENOPOLICY: Errno : 103; /* No such policy registered */ + +ENOTRECOVERABLE: Errno : 104; /* State not recoverable */ +EOWNERDEAD: Errno : 105; /* Previous owner died */ + +EQFULL: Errno : 106; /* Interface output queue is full */ +ELAST: Errno : 106; /* Must be equal largest errno */ + O_RDONLY :: 0x00000; O_WRONLY :: 0x00001; O_RDWR :: 0x00002; @@ -133,6 +269,7 @@ foreign libc { @(link_name="write") _unix_write :: proc(handle: Handle, buffer: rawptr, count: int) -> int ---; @(link_name="lseek") _unix_lseek :: proc(fs: Handle, offset: int, whence: int) -> int ---; @(link_name="gettid") _unix_gettid :: proc() -> u64 ---; + @(link_name="getpagesize") _unix_getpagesize :: proc() -> i32 ---; @(link_name="stat") _unix_stat :: proc(path: cstring, stat: ^Stat) -> int ---; @(link_name="access") _unix_access :: proc(path: cstring, mask: int) -> int ---; @@ -287,6 +424,16 @@ dlerror :: proc() -> string { return string(_unix_dlerror()); } +get_page_size :: proc() -> int { + // NOTE(tetra): The page size never changes, so why do anything complicated + // if we don't have to. + @static page_size := -1; + if page_size != -1 do return page_size; + + page_size = int(_unix_getpagesize()); + return page_size; +} + _alloc_command_line_arguments :: proc() -> []string { res := make([]string, len(runtime.args__)); diff --git a/core/os/os_linux.odin b/core/os/os_linux.odin index dc3cdde93..cf03dac71 100644 --- a/core/os/os_linux.odin +++ b/core/os/os_linux.odin @@ -15,37 +15,138 @@ Syscall :: distinct int; INVALID_HANDLE :: ~Handle(0); -ERROR_NONE: Errno : 0; -EPERM: Errno : 1; -ENOENT: Errno : 2; -EINTR: Errno : 4; -EIO: Errno : 5; -ENXIO: Errno : 6; -EBADF: Errno : 9; -EAGAIN: Errno : 11; -EWOULDBLOCK: Errno : EAGAIN; -ENOMEM: Errno : 12; -EACCES: Errno : 13; -EFAULT: Errno : 14; -EEXIST: Errno : 17; -ENODEV: Errno : 19; -ENOTDIR: Errno : 20; -EISDIR: Errno : 21; -EINVAL: Errno : 22; -ENFILE: Errno : 23; -EMFILE: Errno : 24; -ETXTBSY: Errno : 26; -EFBIG: Errno : 27; -ENOSPC: Errno : 28; -ESPIPE: Errno : 29; -EROFS: Errno : 30; -EPIPE: Errno : 32; -ENAMETOOLONG: Errno : 36; -ELOOP: Errno : 40; -EOVERFLOW: Errno : 75; -EDESTADDRREQ: Errno : 89; -EOPNOTSUPP: Errno : 95; -EDQUOT: Errno : 122; +ERROR_NONE: Errno : 0; +EPERM: Errno : 1; +ENOENT: Errno : 2; +ESRCH: Errno : 3; +EINTR: Errno : 4; +EIO: Errno : 5; +ENXIO: Errno : 6; +EBADF: Errno : 9; +EAGAIN: Errno : 11; +ENOMEM: Errno : 12; +EACCES: Errno : 13; +EFAULT: Errno : 14; +EEXIST: Errno : 17; +ENODEV: Errno : 19; +ENOTDIR: Errno : 20; +EISDIR: Errno : 21; +EINVAL: Errno : 22; +ENFILE: Errno : 23; +EMFILE: Errno : 24; +ETXTBSY: Errno : 26; +EFBIG: Errno : 27; +ENOSPC: Errno : 28; +ESPIPE: Errno : 29; +EROFS: Errno : 30; +EPIPE: Errno : 32; + +EDEADLK: Errno : 35; /* Resource deadlock would occur */ +ENAMETOOLONG: Errno : 36; /* File name too long */ +ENOLCK: Errno : 37; /* No record locks available */ + +ENOSYS: Errno : 38; /* Invalid system call number */ + +ENOTEMPTY: Errno : 39; /* Directory not empty */ +ELOOP: Errno : 40; /* Too many symbolic links encountered */ +EWOULDBLOCK: Errno : EAGAIN; /* Operation would block */ +ENOMSG: Errno : 42; /* No message of desired type */ +EIDRM: Errno : 43; /* Identifier removed */ +ECHRNG: Errno : 44; /* Channel number out of range */ +EL2NSYNC: Errno : 45; /* Level 2 not synchronized */ +EL3HLT: Errno : 46; /* Level 3 halted */ +EL3RST: Errno : 47; /* Level 3 reset */ +ELNRNG: Errno : 48; /* Link number out of range */ +EUNATCH: Errno : 49; /* Protocol driver not attached */ +ENOCSI: Errno : 50; /* No CSI structure available */ +EL2HLT: Errno : 51; /* Level 2 halted */ +EBADE: Errno : 52; /* Invalid exchange */ +EBADR: Errno : 53; /* Invalid request descriptor */ +EXFULL: Errno : 54; /* Exchange full */ +ENOANO: Errno : 55; /* No anode */ +EBADRQC: Errno : 56; /* Invalid request code */ +EBADSLT: Errno : 57; /* Invalid slot */ +EDEADLOCK: Errno : EDEADLK; +EBFONT: Errno : 59; /* Bad font file format */ +ENOSTR: Errno : 60; /* Device not a stream */ +ENODATA: Errno : 61; /* No data available */ +ETIME: Errno : 62; /* Timer expired */ +ENOSR: Errno : 63; /* Out of streams resources */ +ENONET: Errno : 64; /* Machine is not on the network */ +ENOPKG: Errno : 65; /* Package not installed */ +EREMOTE: Errno : 66; /* Object is remote */ +ENOLINK: Errno : 67; /* Link has been severed */ +EADV: Errno : 68; /* Advertise error */ +ESRMNT: Errno : 69; /* Srmount error */ +ECOMM: Errno : 70; /* Communication error on send */ +EPROTO: Errno : 71; /* Protocol error */ +EMULTIHOP: Errno : 72; /* Multihop attempted */ +EDOTDOT: Errno : 73; /* RFS specific error */ +EBADMSG: Errno : 74; /* Not a data message */ +EOVERFLOW: Errno : 75; /* Value too large for defined data type */ +ENOTUNIQ: Errno : 76; /* Name not unique on network */ +EBADFD: Errno : 77; /* File descriptor in bad state */ +EREMCHG: Errno : 78; /* Remote address changed */ +ELIBACC: Errno : 79; /* Can not access a needed shared library */ +ELIBBAD: Errno : 80; /* Accessing a corrupted shared library */ +ELIBSCN: Errno : 81; /* .lib section in a.out corrupted */ +ELIBMAX: Errno : 82; /* Attempting to link in too many shared libraries */ +ELIBEXEC: Errno : 83; /* Cannot exec a shared library directly */ +EILSEQ: Errno : 84; /* Illegal byte sequence */ +ERESTART: Errno : 85; /* Interrupted system call should be restarted */ +ESTRPIPE: Errno : 86; /* Streams pipe error */ +EUSERS: Errno : 87; /* Too many users */ +ENOTSOCK: Errno : 88; /* Socket operation on non-socket */ +EDESTADDRREQ: Errno : 89; /* Destination address required */ +EMSGSIZE: Errno : 90; /* Message too long */ +EPROTOTYPE: Errno : 91; /* Protocol wrong type for socket */ +ENOPROTOOPT: Errno : 92; /* Protocol not available */ +EPROTONOSUPPORT: Errno : 93; /* Protocol not supported */ +ESOCKTNOSUPPORT: Errno : 94; /* Socket type not supported */ +EOPNOTSUPP: Errno : 95; /* Operation not supported on transport endpoint */ +EPFNOSUPPORT: Errno : 96; /* Protocol family not supported */ +EAFNOSUPPORT: Errno : 97; /* Address family not supported by protocol */ +EADDRINUSE: Errno : 98; /* Address already in use */ +EADDRNOTAVAIL: Errno : 99; /* Cannot assign requested address */ +ENETDOWN: Errno : 100; /* Network is down */ +ENETUNREACH: Errno : 101; /* Network is unreachable */ +ENETRESET: Errno : 102; /* Network dropped connection because of reset */ +ECONNABORTED: Errno : 103; /* Software caused connection abort */ +ECONNRESET: Errno : 104; /* Connection reset by peer */ +ENOBUFS: Errno : 105; /* No buffer space available */ +EISCONN: Errno : 106; /* Transport endpoint is already connected */ +ENOTCONN: Errno : 107; /* Transport endpoint is not connected */ +ESHUTDOWN: Errno : 108; /* Cannot send after transport endpoint shutdown */ +ETOOMANYREFS: Errno : 109; /* Too many references: cannot splice */ +ETIMEDOUT: Errno : 110; /* Connection timed out */ +ECONNREFUSED: Errno : 111; /* Connection refused */ +EHOSTDOWN: Errno : 112; /* Host is down */ +EHOSTUNREACH: Errno : 113; /* No route to host */ +EALREADY: Errno : 114; /* Operation already in progress */ +EINPROGRESS: Errno : 115; /* Operation now in progress */ +ESTALE: Errno : 116; /* Stale file handle */ +EUCLEAN: Errno : 117; /* Structure needs cleaning */ +ENOTNAM: Errno : 118; /* Not a XENIX named type file */ +ENAVAIL: Errno : 119; /* No XENIX semaphores available */ +EISNAM: Errno : 120; /* Is a named type file */ +EREMOTEIO: Errno : 121; /* Remote I/O error */ +EDQUOT: Errno : 122; /* Quota exceeded */ + +ENOMEDIUM: Errno : 123; /* No medium found */ +EMEDIUMTYPE: Errno : 124; /* Wrong medium type */ +ECANCELED: Errno : 125; /* Operation Canceled */ +ENOKEY: Errno : 126; /* Required key not available */ +EKEYEXPIRED: Errno : 127; /* Key has expired */ +EKEYREVOKED: Errno : 128; /* Key has been revoked */ +EKEYREJECTED: Errno : 129; /* Key was rejected by service */ + +/* for robust mutexes */ +EOWNERDEAD: Errno : 130; /* Owner died */ +ENOTRECOVERABLE: Errno : 131; /* State not recoverable */ + +ERFKILL: Errno : 132; /* Operation not possible due to RF-kill */ + +EHWPOISON: Errno : 133; /* Memory page has hardware error */ O_RDONLY :: 0x00000; O_WRONLY :: 0x00001; @@ -152,22 +253,6 @@ X_OK :: 1; // Test for execute permission W_OK :: 2; // Test for write permission R_OK :: 4; // Test for read permission -TimeSpec :: struct { - tv_sec : i64, /* seconds */ - tv_nsec : i64, /* nanoseconds */ -}; - -CLOCK_REALTIME :: 0; -CLOCK_MONOTONIC :: 1; -CLOCK_PROCESS_CPUTIME_ID :: 2; -CLOCK_THREAD_CPUTIME_ID :: 3; -CLOCK_MONOTONIC_RAW :: 4; -CLOCK_REALTIME_COARSE :: 5; -CLOCK_MONOTONIC_COARSE :: 6; -CLOCK_BOOTTIME :: 7; -CLOCK_REALTIME_ALARM :: 8; -CLOCK_BOOTTIME_ALARM :: 9; - SYS_GETTID: Syscall : 186; foreign libc { @@ -180,6 +265,7 @@ foreign libc { @(link_name="write") _unix_write :: proc(fd: Handle, buf: rawptr, size: int) -> int ---; @(link_name="lseek64") _unix_seek :: proc(fd: Handle, offset: i64, whence: i32) -> i64 ---; @(link_name="gettid") _unix_gettid :: proc() -> u64 ---; + @(link_name="getpagesize") _unix_getpagesize :: proc() -> i32 ---; @(link_name="stat") _unix_stat :: proc(path: cstring, stat: ^Stat) -> int ---; @(link_name="fstat") _unix_fstat :: proc(fd: Handle, stat: ^Stat) -> int ---; @(link_name="access") _unix_access :: proc(path: cstring, mask: int) -> int ---; @@ -190,10 +276,6 @@ foreign libc { @(link_name="realloc") _unix_realloc :: proc(ptr: rawptr, size: int) -> rawptr ---; @(link_name="getenv") _unix_getenv :: proc(cstring) -> cstring ---; - @(link_name="clock_gettime") _unix_clock_gettime :: proc(clock_id: u64, timespec: ^TimeSpec) ---; - @(link_name="nanosleep") _unix_nanosleep :: proc(requested: ^TimeSpec, remaining: ^TimeSpec) -> int ---; - @(link_name="sleep") _unix_sleep :: proc(seconds: u64) -> int ---; - @(link_name="exit") _unix_exit :: proc(status: int) -> ! ---; } foreign dl { @@ -349,25 +431,6 @@ exit :: proc(code: int) -> ! { _unix_exit(code); } -clock_gettime :: proc(clock_id: u64) -> TimeSpec { - ts : TimeSpec; - _unix_clock_gettime(clock_id, &ts); - return ts; -} - -sleep :: proc(seconds: u64) -> int { - - return _unix_sleep(seconds); -} - -nanosleep :: proc(nanoseconds: i64) -> int { - assert(nanoseconds <= 999999999); - requested, remaining : TimeSpec; - requested = TimeSpec{tv_nsec = nanoseconds}; - - return _unix_nanosleep(&requested, &remaining); -} - current_thread_id :: proc "contextless" () -> int { return syscall(SYS_GETTID); } @@ -393,6 +456,16 @@ dlerror :: proc() -> string { return string(_unix_dlerror()); } +get_page_size :: proc() -> int { + // NOTE(tetra): The page size never changes, so why do anything complicated + // if we don't have to. + @static page_size := -1; + if page_size != -1 do return page_size; + + page_size = int(_unix_getpagesize()); + return page_size; +} + _alloc_command_line_arguments :: proc() -> []string { res := make([]string, len(runtime.args__)); diff --git a/core/os/os_windows.odin b/core/os/os_windows.odin index e45cf9f5f..f433a2517 100644 --- a/core/os/os_windows.odin +++ b/core/os/os_windows.odin @@ -210,7 +210,6 @@ get_std_handle :: proc(h: int) -> Handle { - last_write_time :: proc(fd: Handle) -> (File_Time, Errno) { file_info: win32.By_Handle_File_Information; if !win32.get_file_information_by_handle(win32.Handle(fd), &file_info) { @@ -253,6 +252,18 @@ heap_free :: proc(ptr: rawptr) { win32.heap_free(win32.get_process_heap(), 0, ptr); } +get_page_size :: proc() -> int { + // NOTE(tetra): The page size never changes, so why do anything complicated + // if we don't have to. + @static page_size := -1; + if page_size != -1 do return page_size; + + info: win32.System_Info; + win32.get_system_info(&info); + page_size = int(info.page_size); + return page_size; +} + exit :: proc(code: int) -> ! { win32.exit_process(u32(code)); diff --git a/core/sync/sync.odin b/core/sync/sync.odin new file mode 100644 index 000000000..5a0512275 --- /dev/null +++ b/core/sync/sync.odin @@ -0,0 +1,27 @@ +package sync + +foreign { + @(link_name="llvm.x86.sse2.pause") + yield_processor :: proc() --- +} + +Ticket_Mutex :: struct { + ticket: u64, + serving: u64, +} + +ticket_mutex_init :: proc(m: ^Ticket_Mutex) { + atomic_store(&m.ticket, 0, .Relaxed); + atomic_store(&m.serving, 0, .Relaxed); +} + +ticket_mutex_lock :: inline proc(m: ^Ticket_Mutex) { + ticket := atomic_add(&m.ticket, 1, .Relaxed); + for ticket != m.serving { + yield_processor(); + } +} + +ticket_mutex_unlock :: inline proc(m: ^Ticket_Mutex) { + atomic_add(&m.serving, 1, .Relaxed); +} diff --git a/core/sync/sync_darwin.odin b/core/sync/sync_darwin.odin new file mode 100644 index 000000000..2c86e21db --- /dev/null +++ b/core/sync/sync_darwin.odin @@ -0,0 +1,39 @@ +package sync + +import "core:sys/darwin" + +import "core:c" + +// The Darwin docs say it best: +// A semaphore is much like a lock, except that a finite number of threads can hold it simultaneously. +// Semaphores can be thought of as being much like piles of tokens; multiple threads can take these tokens, +// but when there are none left, a thread must wait until another thread returns one. +Semaphore :: struct #align 16 { + handle: darwin.semaphore_t, +} +// TODO(tetra): Only marked with alignment because we cannot mark distinct integers with alignments. +// See core/sys/unix/pthread_linux.odin/pthread_t. + +semaphore_init :: proc(s: ^Semaphore, initial_count := 0) { + ct := darwin.mach_task_self(); + res := darwin.semaphore_create(ct, &s.handle, 0, c.int(initial_count)); + assert(res == 0); +} + +semaphore_destroy :: proc(s: ^Semaphore) { + ct := darwin.mach_task_self(); + res := darwin.semaphore_destroy(ct, s.handle); + assert(res == 0); + s.handle = {}; +} + +semaphore_post :: proc(s: ^Semaphore, count := 1) { + assert(count == 1); + res := darwin.semaphore_signal(s.handle); + assert(res == 0); +} + +semaphore_wait_for :: proc(s: ^Semaphore) { + res := darwin.semaphore_wait(s.handle); + assert(res == 0); +} diff --git a/core/sync/sync_linux.odin b/core/sync/sync_linux.odin index dcb2ee8e9..dc761f6aa 100644 --- a/core/sync/sync_linux.odin +++ b/core/sync/sync_linux.odin @@ -1,98 +1,28 @@ package sync -/* +import "core:sys/unix" -import "core:atomics" -import "core:os" - -Semaphore :: struct { - // _handle: win32.Handle, +// 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); - } - } -} - -*/ diff --git a/core/sync/sync_unix.odin b/core/sync/sync_unix.odin new file mode 100644 index 000000000..71ba54128 --- /dev/null +++ b/core/sync/sync_unix.odin @@ -0,0 +1,99 @@ +// +build linux, darwin +package sync + +import "core:sys/unix" + +// A lock that can only be held by one thread at once. +Mutex :: struct { + handle: unix.pthread_mutex_t, +} + +// Blocks until signalled, and then lets past exactly +// one thread. +Condition :: struct { + handle: unix.pthread_cond_t, + + // NOTE(tetra, 2019-11-11): Used to mimic the more sane behavior of Windows' AutoResetEvent. + // This means that you may signal the condition before anyone is waiting to cause the + // next thread that tries to wait to just pass by uninterrupted, without sleeping. + // Without this, signalling a condition will only wake up a thread which is already waiting, + // but not one that is about to wait, which can cause your program to become out of sync in + // ways that are hard to debug or fix. + flag: bool, // atomically mutated + + mutex: Mutex, +} + + + +mutex_init :: proc(m: ^Mutex) { + // NOTE(tetra, 2019-11-01): POSIX OOM if we cannot init the attrs or the mutex. + attrs: unix.pthread_mutexattr_t; + assert(unix.pthread_mutexattr_init(&attrs) == 0); + defer unix.pthread_mutexattr_destroy(&attrs); // ignores destruction error + + assert(unix.pthread_mutex_init(&m.handle, &attrs) == 0); +} + +mutex_destroy :: proc(m: ^Mutex) { + assert(unix.pthread_mutex_destroy(&m.handle) == 0); + m.handle = {}; +} + +mutex_lock :: proc(m: ^Mutex) { + assert(unix.pthread_mutex_lock(&m.handle) == 0); +} + +// Returns false if someone else holds the lock. +mutex_try_lock :: proc(m: ^Mutex) -> bool { + return unix.pthread_mutex_trylock(&m.handle) == 0; +} + +mutex_unlock :: proc(m: ^Mutex) { + assert(unix.pthread_mutex_unlock(&m.handle) == 0); +} + + +condition_init :: proc(c: ^Condition) { + // NOTE(tetra, 2019-11-01): POSIX OOM if we cannot init the attrs or the condition. + attrs: unix.pthread_condattr_t; + assert(unix.pthread_condattr_init(&attrs) == 0); + defer unix.pthread_condattr_destroy(&attrs); // ignores destruction error + + assert(unix.pthread_cond_init(&c.handle, &attrs) == 0); + + mutex_init(&c.mutex); + c.flag = false; +} + +condition_destroy :: proc(c: ^Condition) { + assert(unix.pthread_cond_destroy(&c.handle) == 0); + mutex_destroy(&c.mutex); + c.handle = {}; +} + +// Awaken exactly one thread who is waiting on the condition. +condition_signal :: proc(c: ^Condition) { + mutex_lock(&c.mutex); + defer mutex_unlock(&c.mutex); + atomic_swap(&c.flag, true, .Sequentially_Consistent); + assert(unix.pthread_cond_signal(&c.handle) == 0); +} + +// Wait for the condition to be signalled. +// Does not block if the condition has been signalled and no one +// has waited on it yet. +condition_wait_for :: proc(c: ^Condition) { + mutex_lock(&c.mutex); + defer mutex_unlock(&c.mutex); + // NOTE(tetra): If a thread comes by and steals the flag immediately after the signal occurs, + // the thread that gets signalled and wakes up, discovers that the flag was taken and goes + // back to sleep. + // Though this overall behavior is the most sane, there may be a better way to do this that means that + // the first thread to wait, gets the flag first. + if atomic_swap(&c.flag, false, .Sequentially_Consistent) do return; + for { + assert(unix.pthread_cond_wait(&c.handle, &c.mutex.handle) == 0); + if atomic_swap(&c.flag, false, .Sequentially_Consistent) do break; + } +} diff --git a/core/sync/sync_windows.odin b/core/sync/sync_windows.odin index b0a9d944c..a99ac8497 100644 --- a/core/sync/sync_windows.odin +++ b/core/sync/sync_windows.odin @@ -1,51 +1,40 @@ +// +build windows package sync import "core:sys/win32" -foreign { - @(link_name="llvm.x86.sse2.pause") - yield_processor :: proc() --- -} - -Semaphore :: struct { - _handle: win32.Handle, -} - +// A lock that can only be held by one thread at once. Mutex :: struct { _critical_section: win32.Critical_Section, } +// Blocks until signalled. +// When signalled, awakens exactly one waiting thread. Condition :: struct { event: win32.Handle, } -Ticket_Mutex :: struct { - ticket: u64, - serving: u64, +// When waited upon, blocks until the internal count is greater than zero, then subtracts one. +// Posting to the semaphore increases the count by one, or the provided amount. +Semaphore :: struct { + _handle: win32.Handle, } -current_thread_id :: proc() -> i32 { - return i32(win32.get_current_thread_id()); -} - -semaphore_init :: proc(s: ^Semaphore) { - s._handle = win32.create_semaphore_w(nil, 0, 1<<31-1, nil); +semaphore_init :: proc(s: ^Semaphore, initial_count := 0) { + s._handle = win32.create_semaphore_w(nil, i32(initial_count), 1<<31-1, nil); } semaphore_destroy :: proc(s: ^Semaphore) { win32.close_handle(s._handle); } -semaphore_post :: proc(s: ^Semaphore, count: int) { +semaphore_post :: proc(s: ^Semaphore, count := 1) { win32.release_semaphore(s._handle, i32(count), nil); } -semaphore_release :: inline proc(s: ^Semaphore) { - semaphore_post(s, 1); -} - -semaphore_wait :: proc(s: ^Semaphore) { +semaphore_wait_for :: proc(s: ^Semaphore) { + // NOTE(tetra, 2019-10-30): wait_for_single_object decrements the count before it returns. result := win32.wait_for_single_object(s._handle, win32.INFINITE); assert(result != win32.WAIT_FAILED); } @@ -73,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); -} +} \ No newline at end of file diff --git a/core/sys/darwin/mach_darwin.odin b/core/sys/darwin/mach_darwin.odin new file mode 100644 index 000000000..52a145507 --- /dev/null +++ b/core/sys/darwin/mach_darwin.odin @@ -0,0 +1,29 @@ +package darwin; + +foreign import "system:pthread" + +import "core:c" + +// NOTE(tetra): Unclear whether these should be aligned 16 or not. +// However all other sync primitives are aligned for robustness. +// I cannot currently align these though. +// See core/sys/unix/pthread_linux.odin/pthread_t. +task_t :: distinct u64; +semaphore_t :: distinct u64; + +kern_return_t :: distinct u64; +thread_act_t :: distinct u64; + +@(default_calling_convention="c") +foreign pthread { + mach_task_self :: proc() -> task_t ---; + + semaphore_create :: proc(task: task_t, semaphore: ^semaphore_t, policy, value: c.int) -> kern_return_t ---; + semaphore_destroy :: proc(task: task_t, semaphore: semaphore_t) -> kern_return_t ---; + + semaphore_signal :: proc(semaphore: semaphore_t) -> kern_return_t ---; + semaphore_signal_all :: proc(semaphore: semaphore_t) -> kern_return_t ---; + semaphore_signal_thread :: proc(semaphore: semaphore_t, thread: thread_act_t) -> kern_return_t ---; + + semaphore_wait :: proc(semaphore: semaphore_t) -> kern_return_t ---; +} diff --git a/core/sys/unix/pthread_darwin.odin b/core/sys/unix/pthread_darwin.odin new file mode 100644 index 000000000..97a750b9b --- /dev/null +++ b/core/sys/unix/pthread_darwin.odin @@ -0,0 +1,80 @@ +package unix; + +import "core:c" + +// NOTE(tetra): No 32-bit Macs. +// Source: _pthread_types.h on my Mac. +PTHREAD_SIZE :: 8176; +PTHREAD_ATTR_SIZE :: 56; +PTHREAD_MUTEXATTR_SIZE :: 8; +PTHREAD_MUTEX_SIZE :: 56; +PTHREAD_CONDATTR_SIZE :: 8; +PTHREAD_COND_SIZE :: 40; +PTHREAD_ONCE_SIZE :: 8; +PTHREAD_RWLOCK_SIZE :: 192; +PTHREAD_RWLOCKATTR_SIZE :: 16; + +pthread_t :: opaque struct #align 16 { + sig: c.long, + cleanup_stack: rawptr, + _: [PTHREAD_SIZE] c.char, +}; + +pthread_attr_t :: opaque struct #align 16 { + sig: c.long, + _: [PTHREAD_ATTR_SIZE] c.char, +}; + +pthread_cond_t :: opaque struct #align 16 { + sig: c.long, + _: [PTHREAD_COND_SIZE] c.char, +}; + +pthread_condattr_t :: opaque struct #align 16 { + sig: c.long, + _: [PTHREAD_CONDATTR_SIZE] c.char, +}; + +pthread_mutex_t :: opaque struct #align 16 { + sig: c.long, + _: [PTHREAD_MUTEX_SIZE] c.char, +}; + +pthread_mutexattr_t :: opaque struct #align 16 { + sig: c.long, + _: [PTHREAD_MUTEXATTR_SIZE] c.char, +}; + +pthread_once_t :: opaque struct #align 16 { + sig: c.long, + _: [PTHREAD_ONCE_SIZE] c.char, +}; + +pthread_rwlock_t :: opaque struct #align 16 { + sig: c.long, + _: [PTHREAD_RWLOCK_SIZE] c.char, +}; + +pthread_rwlockattr_t :: opaque struct #align 16 { + sig: c.long, + _: [PTHREAD_RWLOCKATTR_SIZE] c.char, +}; + +SCHED_OTHER :: 1; // Avoid if you are writing portable software. +SCHED_FIFO :: 4; +SCHED_RR :: 2; // Round robin. + +SCHED_PARAM_SIZE :: 4; + +sched_param :: struct { + sched_priority: c.int, + _: [SCHED_PARAM_SIZE] c.char, +}; + +// Source: https://github.com/apple/darwin-libpthread/blob/03c4628c8940cca6fd6a82957f683af804f62e7f/pthread/pthread.h#L138 +PTHREAD_CREATE_JOINABLE :: 1; +PTHREAD_CREATE_DETACHED :: 2; +PTHREAD_INHERIT_SCHED :: 1; +PTHREAD_EXPLICIT_SCHED :: 2; +PTHREAD_PROCESS_SHARED :: 1; +PTHREAD_PROCESS_PRIVATE :: 2; diff --git a/core/sys/unix/pthread_linux.odin b/core/sys/unix/pthread_linux.odin new file mode 100644 index 000000000..18ef09a69 --- /dev/null +++ b/core/sys/unix/pthread_linux.odin @@ -0,0 +1,106 @@ +package unix; + +import "core:c" + +// TODO(tetra): For robustness, I'd like to mark this with align 16. +// I cannot currently do this. +// And at the time of writing there is a bug with putting it +// as the only field in a struct. +pthread_t :: distinct u64; +// pthread_t :: struct #align 16 { x: u64 }; + +// NOTE(tetra): Got all the size constants from pthreadtypes-arch.h on my +// Linux machine. + +PTHREAD_COND_T_SIZE :: 48; + +PTHREAD_MUTEXATTR_T_SIZE :: 4; +PTHREAD_CONDATTR_T_SIZE :: 4; +PTHREAD_RWLOCKATTR_T_SIZE :: 8; +PTHREAD_BARRIERATTR_T_SIZE :: 4; + +// WARNING: The sizes of these things are different yet again +// on non-X86! +when size_of(int) == 8 { + PTHREAD_ATTR_T_SIZE :: 56; + PTHREAD_MUTEX_T_SIZE :: 40; + PTHREAD_RWLOCK_T_SIZE :: 56; + PTHREAD_BARRIER_T_SIZE :: 32; +} else when size_of(int) == 4 { + PTHREAD_ATTR_T_SIZE :: 32; + PTHREAD_MUTEX_T_SIZE :: 32; + PTHREAD_RWLOCK_T_SIZE :: 44; + PTHREAD_BARRIER_T_SIZE :: 20; +} + +pthread_cond_t :: opaque struct #align 16 { + _: [PTHREAD_COND_T_SIZE] c.char, +}; +pthread_mutex_t :: opaque struct #align 16 { + _: [PTHREAD_MUTEX_T_SIZE] c.char, +}; +pthread_rwlock_t :: opaque struct #align 16 { + _: [PTHREAD_RWLOCK_T_SIZE] c.char, +}; +pthread_barrier_t :: opaque struct #align 16 { + _: [PTHREAD_BARRIER_T_SIZE] c.char, +}; + +pthread_attr_t :: opaque struct #align 16 { + _: [PTHREAD_ATTR_T_SIZE] c.char, +}; +pthread_condattr_t :: opaque struct #align 16 { + _: [PTHREAD_CONDATTR_T_SIZE] c.char, +}; +pthread_mutexattr_t :: opaque struct #align 16 { + _: [PTHREAD_MUTEXATTR_T_SIZE] c.char, +}; +pthread_rwlockattr_t :: opaque struct #align 16 { + _: [PTHREAD_RWLOCKATTR_T_SIZE] c.char, +}; +pthread_barrierattr_t :: opaque struct #align 16 { + _: [PTHREAD_BARRIERATTR_T_SIZE] c.char, +}; + + +// TODO(tetra, 2019-11-01): Maybe make `enum c.int`s for these? +PTHREAD_CREATE_JOINABLE :: 0; +PTHREAD_CREATE_DETACHED :: 1; +PTHREAD_INHERIT_SCHED :: 0; +PTHREAD_EXPLICIT_SCHED :: 1; +PTHREAD_PROCESS_PRIVATE :: 0; +PTHREAD_PROCESS_SHARED :: 1; + +SCHED_OTHER :: 0; +SCHED_FIFO :: 1; +SCHED_RR :: 2; // Round robin. + +sched_param :: struct { + sched_priority: c.int, +} + +sem_t :: struct #align 16 { + _: [SEM_T_SIZE] c.char, +} + +when size_of(int) == 8 { + SEM_T_SIZE :: 32; +} else when size_of(int) == 4 { + SEM_T_SIZE :: 16; +} + +foreign import "system:pthread" + +@(default_calling_convention="c") +foreign pthread { + // create named semaphore. + // used in process-shared semaphores. + sem_open :: proc(name: cstring, flags: c.int) -> ^sem_t ---; + + sem_init :: proc(sem: ^sem_t, pshared: c.int, initial_value: c.uint) -> c.int ---; + sem_destroy :: proc(sem: ^sem_t) -> c.int ---; + sem_post :: proc(sem: ^sem_t) -> c.int ---; + sem_wait :: proc(sem: ^sem_t) -> c.int ---; + sem_trywait :: proc(sem: ^sem_t) -> c.int ---; + // sem_timedwait :: proc(sem: ^sem_t, timeout: time.TimeSpec) -> c.int ---; +} diff --git a/core/sys/unix/pthread_unix.odin b/core/sys/unix/pthread_unix.odin new file mode 100644 index 000000000..885048a59 --- /dev/null +++ b/core/sys/unix/pthread_unix.odin @@ -0,0 +1,107 @@ +package unix; + +foreign import "system:pthread" + +import "core:c" +import "core:time" + +// +// On success, these functions return 0. +// + +@(default_calling_convention="c") +foreign pthread { + pthread_create :: proc(t: ^pthread_t, attrs: ^pthread_attr_t, routine: proc(data: rawptr) -> rawptr, arg: rawptr) -> c.int ---; + + // retval is a pointer to a location to put the return value of the thread proc. + pthread_join :: proc(t: pthread_t, retval: rawptr) -> c.int ---; + + pthread_self :: proc() -> pthread_t ---; + + pthread_equal :: proc(a, b: pthread_t) -> b32 ---; + + sched_get_priority_min :: proc(policy: c.int) -> c.int ---; + sched_get_priority_max :: proc(policy: c.int) -> c.int ---; + + // NOTE: POSIX says this can fail with OOM. + pthread_attr_init :: proc(attrs: ^pthread_attr_t) -> c.int ---; + + pthread_attr_destroy :: proc(attrs: ^pthread_attr_t) -> c.int ---; + + pthread_attr_getschedparam :: proc(attrs: ^pthread_attr_t, param: ^sched_param) -> c.int ---; + pthread_attr_setschedparam :: proc(attrs: ^pthread_attr_t, param: ^sched_param) -> c.int ---; + + pthread_attr_getschedpolicy :: proc(t: ^pthread_attr_t, policy: ^c.int) -> c.int ---; + pthread_attr_setschedpolicy :: proc(t: ^pthread_attr_t, policy: c.int) -> c.int ---; + + // states: PTHREAD_CREATE_DETACHED, PTHREAD_CREATE_JOINABLE + pthread_attr_setdetachstate :: proc(attrs: ^pthread_attr_t, detach_state: c.int) -> c.int ---; + + // scheds: PTHREAD_INHERIT_SCHED, PTHREAD_EXPLICIT_SCHED + pthread_attr_setinheritsched :: proc(attrs: ^pthread_attr_t, sched: c.int) -> c.int ---; + + // NOTE(tetra, 2019-11-06): WARNING: Different systems have different alignment requirements. + // For maximum usefulness, use the OS's page size. + // ALSO VERY MAJOR WARNING: `stack_ptr` must be the LAST byte of the stack on systems + // where the stack grows downwards, which is the common case, so far as I know. + // On systems where it grows upwards, give the FIRST byte instead. + // ALSO SLIGHTLY LESS MAJOR WARNING: Using this procedure DISABLES automatically-provided + // guard pages. If you are using this procedure, YOU must set them up manually. + // If you forget to do this, you WILL get stack corruption bugs if you do not EXTREMELY + // know what you are doing! + pthread_attr_setstack :: proc(attrs: ^pthread_attr_t, stack_ptr: rawptr, stack_size: u64) -> c.int ---; + pthread_attr_getstack :: proc(attrs: ^pthread_attr_t, stack_ptr: ^rawptr, stack_size: ^u64) -> c.int ---; +} + +@(default_calling_convention="c") +foreign pthread { + // NOTE: POSIX says this can fail with OOM. + pthread_cond_init :: proc(cond: ^pthread_cond_t, attrs: ^pthread_condattr_t) -> c.int ---; + + pthread_cond_destroy :: proc(cond: ^pthread_cond_t) -> c.int ---; + + pthread_cond_signal :: proc(cond: ^pthread_cond_t) -> c.int ---; + + // same as signal, but wakes up _all_ threads that are waiting + pthread_cond_broadcast :: proc(cond: ^pthread_cond_t) -> c.int ---; + + + // assumes the mutex is pre-locked + pthread_cond_wait :: proc(cond: ^pthread_cond_t, mutex: ^pthread_mutex_t) -> c.int ---; + pthread_cond_timedwait :: proc(cond: ^pthread_cond_t, mutex: ^pthread_mutex_t, timeout: ^time.TimeSpec) -> c.int ---; + + pthread_condattr_init :: proc(attrs: ^pthread_condattr_t) -> c.int ---; + pthread_condattr_destroy :: proc(attrs: ^pthread_condattr_t) -> c.int ---; + + // p-shared = "process-shared" - i.e: is this condition shared among multiple processes? + // values: PTHREAD_PROCESS_PRIVATE, PTHREAD_PROCESS_SHARED + pthread_condattr_setpshared :: proc(attrs: ^pthread_condattr_t, value: c.int) -> c.int ---; + pthread_condattr_getpshared :: proc(attrs: ^pthread_condattr_t, result: ^c.int) -> c.int ---; + +} + +@(default_calling_convention="c") +foreign pthread { + // NOTE: POSIX says this can fail with OOM. + pthread_mutex_init :: proc(mutex: ^pthread_mutex_t, attrs: ^pthread_mutexattr_t) -> c.int ---; + + pthread_mutex_destroy :: proc(mutex: ^pthread_mutex_t) -> c.int ---; + + pthread_mutex_trylock :: proc(mutex: ^pthread_mutex_t) -> c.int ---; + + pthread_mutex_lock :: proc(mutex: ^pthread_mutex_t) -> c.int ---; + + pthread_mutex_timedlock :: proc(mutex: ^pthread_mutex_t, timeout: ^time.TimeSpec) -> c.int ---; + + pthread_mutex_unlock :: proc(mutex: ^pthread_mutex_t) -> c.int ---; + + + pthread_mutexattr_init :: proc(attrs: ^pthread_mutexattr_t) -> c.int ---; + pthread_mutexattr_destroy :: proc(attrs: ^pthread_mutexattr_t) -> c.int ---; + + // p-shared = "process-shared" - i.e: is this mutex shared among multiple processes? + // values: PTHREAD_PROCESS_PRIVATE, PTHREAD_PROCESS_SHARED + pthread_mutexattr_setpshared :: proc(attrs: ^pthread_mutexattr_t, value: c.int) -> c.int ---; + pthread_mutexattr_getpshared :: proc(attrs: ^pthread_mutexattr_t, result: ^c.int) -> c.int ---; + +} \ No newline at end of file diff --git a/core/sys/win32/general.odin b/core/sys/win32/general.odin index 16241de05..16853c5cd 100644 --- a/core/sys/win32/general.odin +++ b/core/sys/win32/general.odin @@ -300,6 +300,25 @@ File_Notify_Information :: struct { file_name: [1]u16, } +// https://docs.microsoft.com/en-gb/windows/win32/api/sysinfoapi/ns-sysinfoapi-system_info +System_Info :: struct { + using _: struct #raw_union { + oem_id: u32, + using _: struct #raw_union { + processor_architecture: u16, + _: u16, // reserved + }, + }, + page_size: u32, + minimum_application_address: rawptr, + maximum_application_address: rawptr, + active_processor_mask: u32, + number_of_processors: u32, + processor_type: u32, + allocation_granularity: u32, + processor_level: u16, + processor_revision: u16, +} // https://docs.microsoft.com/en-us/windows/desktop/api/winnt/ns-winnt-_osversioninfoexa OS_Version_Info_Ex_A :: struct { diff --git a/core/sys/win32/kernel32.odin b/core/sys/win32/kernel32.odin index f647ab7e0..3caeb4963 100644 --- a/core/sys/win32/kernel32.odin +++ b/core/sys/win32/kernel32.odin @@ -31,6 +31,7 @@ foreign kernel32 { @(link_name="GetCommandLineA") get_command_line_a :: proc() -> cstring ---; @(link_name="GetCommandLineW") get_command_line_w :: proc() -> Wstring ---; @(link_name="GetSystemMetrics") get_system_metrics :: proc(index: i32) -> i32 ---; + @(link_name="GetSystemInfo") get_system_info :: proc(info: ^System_Info) ---; @(link_name="GetVersionExA") get_version :: proc(osvi: ^OS_Version_Info_Ex_A) ---; @(link_name="GetCurrentThreadId") get_current_thread_id :: proc() -> u32 ---; diff --git a/core/thread/thread.odin b/core/thread/thread.odin new file mode 100644 index 000000000..c326b30f1 --- /dev/null +++ b/core/thread/thread.odin @@ -0,0 +1,15 @@ +package thread; + +import "core:runtime"; + +Thread_Proc :: #type proc(^Thread); + +Thread :: struct { + using specific: Thread_Os_Specific, + procedure: Thread_Proc, + data: rawptr, + user_index: int, + + init_context: runtime.Context, + use_init_context: bool, +} \ No newline at end of file diff --git a/core/thread/thread_unix.odin b/core/thread/thread_unix.odin new file mode 100644 index 000000000..a05c8c2af --- /dev/null +++ b/core/thread/thread_unix.odin @@ -0,0 +1,153 @@ +// +build linux, darwin +package thread; + +import "core:sys/unix" +import "core:sync" + +// NOTE(tetra): Aligned here because of core/unix/pthread_linux.odin/pthread_t. +// Also see core/sys/darwin/mach_darwin.odin/semaphore_t. +Thread_Os_Specific :: struct #align 16 { + unix_thread: unix.pthread_t, // NOTE: very large on Darwin, small on Linux. + + // NOTE: pthread has a proc to query this, but it is marked + // as non-portable ("np") so we do this instead. + done: bool, + + // since libpthread doesn't seem to have a way to create a thread + // in a suspended state, we have it wait on this gate, which we + // signal to start it. + // destroyed after thread is started. + start_gate: sync.Condition, + + // if true, the thread has been started and the start_gate has been destroyed. + started: bool, + + // NOTE: with pthreads, it is undefined behavior for multiple threads + // to call join on the same thread at the same time. + // this value is atomically updated to detect this. + // See the comment in `join`. + already_joined: bool, +} + +Thread_Priority :: enum { + Normal, + Low, + High, +} + +// +// Creates a thread which will run the given procedure. +// It then waits for `start` to be called. +// +// You may provide a slice of bytes to use as the stack for the new thread, +// but if you do, you are expected to set up the guard pages yourself. +// +// The stack must also be aligned appropriately for the platform. +// We require it's at least 16 bytes aligned to help robustness; other +// platforms may require page-size alignment. +// Note also that pthreads requires the stack is at least 6 OS pages in size: +// 4 are required by pthreads, and two extra for guards pages that will be applied. +// +create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal) -> ^Thread { + __linux_thread_entry_proc :: proc "c" (t: rawptr) -> rawptr { + t := (^Thread)(t); + sync.condition_wait_for(&t.start_gate); + sync.condition_destroy(&t.start_gate); + t.start_gate = {}; + + c := context; + if t.use_init_context { + c = t.init_context; + } + context = c; + + t.procedure(t); + sync.atomic_store(&t.done, true, .Sequentially_Consistent); + return nil; + } + + attrs: unix.pthread_attr_t; + if unix.pthread_attr_init(&attrs) != 0 do return nil; // NOTE(tetra, 2019-11-01): POSIX OOM. + defer unix.pthread_attr_destroy(&attrs); + + // NOTE(tetra, 2019-11-01): These only fail if their argument is invalid. + assert(unix.pthread_attr_setdetachstate(&attrs, unix.PTHREAD_CREATE_JOINABLE) == 0); + assert(unix.pthread_attr_setinheritsched(&attrs, unix.PTHREAD_EXPLICIT_SCHED) == 0); + + thread := new(Thread); + if thread == nil do return nil; + + // Set thread priority. + policy: i32; + res := unix.pthread_attr_getschedpolicy(&attrs, &policy); + assert(res == 0); + params: unix.sched_param; + res = unix.pthread_attr_getschedparam(&attrs, ¶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); +} diff --git a/core/thread/thread_windows.odin b/core/thread/thread_windows.odin index 743f0fec8..5b956940f 100644 --- a/core/thread/thread_windows.odin +++ b/core/thread/thread_windows.odin @@ -1,27 +1,29 @@ package thread -import "core:runtime" +import "core:sync" import "core:sys/win32" -Thread_Proc :: #type proc(^Thread) -> int; - Thread_Os_Specific :: struct { win32_thread: win32.Handle, win32_thread_id: u32, + done: bool, // see note in `is_done` } -Thread :: struct { - using specific: Thread_Os_Specific, - procedure: Thread_Proc, - data: rawptr, - user_index: int, +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); -} +} \ No newline at end of file diff --git a/core/time/time_darwin.odin b/core/time/time_darwin.odin deleted file mode 100644 index 91acd8ae7..000000000 --- a/core/time/time_darwin.odin +++ /dev/null @@ -1,56 +0,0 @@ -package time - -foreign import libc "system:c" - -TimeSpec :: struct { - tv_sec : i64, /* seconds */ - tv_nsec : i64, /* nanoseconds */ -}; - -CLOCK_SYSTEM :: 0; -CLOCK_CALENDAR :: 1; - -IS_SUPPORTED :: true; - -foreign libc { - @(link_name="clock_gettime") _clock_gettime :: proc(clock_id: u64, timespec: ^TimeSpec) ---; - @(link_name="nanosleep") _nanosleep :: proc(requested: ^TimeSpec, remaining: ^TimeSpec) -> int ---; - @(link_name="sleep") _sleep :: proc(seconds: u64) -> int ---; -} - -clock_gettime :: proc(clock_id: u64) -> TimeSpec { - ts : TimeSpec; - _clock_gettime(clock_id, &ts); - return ts; -} - -now :: proc() -> Time { - - time_spec_now := clock_gettime(CLOCK_SYSTEM); - ns := time_spec_now.tv_sec * 1e9 + time_spec_now.tv_nsec; - return Time{_nsec=ns}; -} - -seconds_since_boot :: proc() -> f64 { - - ts_boottime := clock_gettime(CLOCK_SYSTEM); - return f64(ts_boottime.tv_sec) + f64(ts_boottime.tv_nsec) / 1e9; -} - -sleep :: proc(d: Duration) { - - ds := duration_seconds(d); - seconds := u64(ds); - nanoseconds := i64((ds - f64(seconds)) * 1e9); - - if seconds > 0 do _sleep(seconds); - if nanoseconds > 0 do nanosleep(nanoseconds); -} - -nanosleep :: proc(nanoseconds: i64) -> int { - assert(nanoseconds <= 999999999); - requested, remaining : TimeSpec; - requested = TimeSpec{tv_nsec = nanoseconds}; - - return _nanosleep(&requested, &remaining); -} \ No newline at end of file diff --git a/core/time/time_linux.odin b/core/time/time_linux.odin deleted file mode 100644 index d83d719fb..000000000 --- a/core/time/time_linux.odin +++ /dev/null @@ -1,44 +0,0 @@ -package time - -import "core:os"; - -// NOTE(Jeroen): The times returned are in UTC -IS_SUPPORTED :: true; - -now :: proc() -> Time { - - time_spec_now := os.clock_gettime(os.CLOCK_REALTIME); - ns := time_spec_now.tv_sec * 1e9 + time_spec_now.tv_nsec; - return Time{_nsec=ns}; -} - -boot_time :: proc() -> Time { - - ts_now := os.clock_gettime(os.CLOCK_REALTIME); - ts_boottime := os.clock_gettime(os.CLOCK_BOOTTIME); - - ns := (ts_now.tv_sec - ts_boottime.tv_sec) * 1e9 + ts_now.tv_nsec - ts_boottime.tv_nsec; - return Time{_nsec=ns}; -} - -seconds_since_boot :: proc() -> f64 { - - ts_boottime := os.clock_gettime(os.CLOCK_BOOTTIME); - return f64(ts_boottime.tv_sec) + f64(ts_boottime.tv_nsec) / 1e9; -} - -sleep :: proc(d: Duration) { - - ds := duration_seconds(d); - seconds := u64(ds); - nanoseconds := i64((ds - f64(seconds)) * 1e9); - - if seconds > 0 do os.sleep(seconds); - if nanoseconds > 0 do os.nanosleep(nanoseconds); -} - -nanosleep :: proc(d: Duration) { - // NOTE(Jeroen): os.nanosleep returns -1 on failure, 0 on success - // duration needs to be [0, 999999999] nanoseconds. - os.nanosleep(i64(d)); -} diff --git a/core/time/time_unix.odin b/core/time/time_unix.odin new file mode 100644 index 000000000..28a6d6a9f --- /dev/null +++ b/core/time/time_unix.odin @@ -0,0 +1,80 @@ +//+build linux, darwin +package time + +IS_SUPPORTED :: true; // NOTE: Times on Darwin are UTC. + +foreign import libc "system:c" + +@(default_calling_convention="c") +foreign libc { + @(link_name="clock_gettime") _unix_clock_gettime :: proc(clock_id: u64, timespec: ^TimeSpec) -> i32 ---; + @(link_name="sleep") _unix_sleep :: proc(seconds: u32) -> i32 ---; + @(link_name="nanosleep") _unix_nanosleep :: proc(requested: ^TimeSpec, remaining: ^TimeSpec) -> i32 ---; +} + +TimeSpec :: struct { + tv_sec : i64, /* seconds */ + tv_nsec : i64, /* nanoseconds */ +}; + +CLOCK_REALTIME :: 0; // NOTE(tetra): May jump in time, when user changes the system time. +CLOCK_MONOTONIC :: 1; // NOTE(tetra): May stand still while system is asleep. +CLOCK_PROCESS_CPUTIME_ID :: 2; +CLOCK_THREAD_CPUTIME_ID :: 3; +CLOCK_MONOTONIC_RAW :: 4; // NOTE(tetra): "RAW" means: Not adjusted by NTP. +CLOCK_REALTIME_COARSE :: 5; // NOTE(tetra): "COARSE" clocks are apparently much faster, but not "fine-grained." +CLOCK_MONOTONIC_COARSE :: 6; +CLOCK_BOOTTIME :: 7; // NOTE(tetra): Same as MONOTONIC, except also including time system was asleep. +CLOCK_REALTIME_ALARM :: 8; +CLOCK_BOOTTIME_ALARM :: 9; + +// TODO(tetra, 2019-11-05): The original implementation of this package for Darwin used this constants. +// I do not know if Darwin programmers are used to the existance of these constants or not, so +// I'm leaving aliases to them for now. +CLOCK_SYSTEM :: CLOCK_REALTIME; +CLOCK_CALENDAR :: CLOCK_MONOTONIC; + + +clock_gettime :: proc(clock_id: u64) -> TimeSpec { + ts : TimeSpec; // NOTE(tetra): Do we need to initialize this? + _unix_clock_gettime(clock_id, &ts); + return ts; +} + +now :: proc() -> Time { + time_spec_now := clock_gettime(CLOCK_REALTIME); + ns := time_spec_now.tv_sec * 1e9 + time_spec_now.tv_nsec; + return Time{_nsec=ns}; +} + +boot_time :: proc() -> Time { + ts_now := clock_gettime(CLOCK_REALTIME); + ts_boottime := clock_gettime(CLOCK_BOOTTIME); + + ns := (ts_now.tv_sec - ts_boottime.tv_sec) * 1e9 + ts_now.tv_nsec - ts_boottime.tv_nsec; + return Time{_nsec=ns}; +} + +seconds_since_boot :: proc() -> f64 { + ts_boottime := clock_gettime(CLOCK_BOOTTIME); + return f64(ts_boottime.tv_sec) + f64(ts_boottime.tv_nsec) / 1e9; +} + + +sleep :: proc(d: Duration) { + ds := duration_seconds(d); + seconds := u32(ds); + nanoseconds := i64((ds - f64(seconds)) * 1e9); + + if seconds > 0 do _unix_sleep(seconds); + if nanoseconds > 0 do nanosleep(nanoseconds); +} + +nanosleep :: proc(nanoseconds: i64) -> int { + // NOTE(tetra): Should we remove this assert? We are measuring nanoseconds after all... + assert(nanoseconds <= 999999999); + + requested := TimeSpec{tv_nsec = nanoseconds}; + remaining: TimeSpec; // NOTE(tetra): Do we need to initialize this? + return int(_unix_nanosleep(&requested, &remaining)); +} \ No newline at end of file diff --git a/core/time/time_windows.odin b/core/time/time_windows.odin index f6bfdd678..c1d766aee 100644 --- a/core/time/time_windows.odin +++ b/core/time/time_windows.odin @@ -17,8 +17,6 @@ now :: proc() -> Time { return Time{_nsec=ns}; } - - sleep :: proc(d: Duration) { win32.sleep(u32(d/Millisecond)); } diff --git a/examples/demo/demo.odin b/examples/demo/demo.odin index 604d56695..67435f4ea 100644 --- a/examples/demo/demo.odin +++ b/examples/demo/demo.odin @@ -3,6 +3,7 @@ package main import "core:fmt" import "core:mem" import "core:os" +import "core:thread" import "core:reflect" import "intrinsics" @@ -1088,6 +1089,54 @@ parametric_polymorphism :: proc() { } +prefix_table := [?]string{ + "White", + "Red", + "Green", + "Blue", + "Octarine", + "Black", +}; + +threading_example :: proc() { + fmt.println("\n# threading_example"); + + worker_proc :: proc(t: ^thread.Thread) { + for iteration in 1..5 { + fmt.printf("Thread %d is on iteration %d\n", t.user_index, iteration); + fmt.printf("`%s`: iteration %d\n", prefix_table[t.user_index], iteration); + // win32.sleep(1); + } + } + + threads := make([dynamic]^thread.Thread, 0, len(prefix_table)); + defer delete(threads); + + for in prefix_table { + if t := thread.create(worker_proc); t != nil { + t.init_context = context; + t.use_init_context = true; + t.user_index = len(threads); + append(&threads, t); + thread.start(t); + } + } + + for len(threads) > 0 { + for i := 0; i < len(threads); /**/ { + if t := threads[i]; thread.is_done(t) { + fmt.printf("Thread %d is done\n", t.user_index); + thread.destroy(t); + + ordered_remove(&threads, i); + } else { + i += 1; + } + } + } +} + + array_programming :: proc() { fmt.println("\n# array programming"); { @@ -1783,7 +1832,10 @@ main :: proc() { ranged_fields_for_array_compound_literals(); deprecated_attribute(); range_statements_with_multiple_return_values(); - soa_struct_layout(); + threading_example(); + + // TODO(tetra): When bill fixes SOA array comparison to nil in reserve_soa, we can re-enable this. + // soa_struct_layout(); } }