diff --git a/LICENSE b/LICENSE index 9a87ab8da..4d155def4 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2016-2022 Ginger Bill. All rights reserved. +Copyright (c) 2016-2024 Ginger Bill. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/core/hash/crc.odin b/core/hash/crc.odin index 9c0048a0f..cb3e36881 100644 --- a/core/hash/crc.odin +++ b/core/hash/crc.odin @@ -1,7 +1,7 @@ package hash @(optimization_mode="speed") -crc64_ecma_182 :: proc(data: []byte, seed := u64(0)) -> (result: u64) #no_bounds_check { +crc64_ecma_182 :: proc "contextless" (data: []byte, seed := u64(0)) -> (result: u64) #no_bounds_check { result = seed #no_bounds_check for b in data { result = result<<8 ~ _crc64_table_ecma_182[((result>>56) ~ u64(b)) & 0xff] @@ -15,7 +15,7 @@ crc64_ecma_182 :: proc(data: []byte, seed := u64(0)) -> (result: u64) #no_bounds Based on Mark Adler's v1.4 implementation in C under the ZLIB license. */ @(optimization_mode="speed") -crc64_xz :: proc(data: []byte, seed := u64(0)) -> u64 #no_bounds_check { +crc64_xz :: proc "contextless" (data: []byte, seed := u64(0)) -> u64 #no_bounds_check { data := data result := ~u64le(seed) @@ -53,7 +53,7 @@ crc64_xz :: proc(data: []byte, seed := u64(0)) -> u64 #no_bounds_check { Generator polynomial: x^64 + x^4 + x^3 + x + 1 */ @(optimization_mode="speed") -crc64_iso_3306 :: proc(data: []byte, seed := u64(0)) -> u64 #no_bounds_check { +crc64_iso_3306 :: proc "contextless" (data: []byte, seed := u64(0)) -> u64 #no_bounds_check { result := seed @@ -70,7 +70,7 @@ crc64_iso_3306 :: proc(data: []byte, seed := u64(0)) -> u64 #no_bounds_check { return result } -crc64_iso_3306_inverse :: proc(data: []byte, seed := u64(0)) -> u64 { +crc64_iso_3306_inverse :: proc "contextless" (data: []byte, seed := u64(0)) -> u64 { result := #force_inline crc64_iso_3306(data, ~seed) return ~result } diff --git a/core/hash/crc32.odin b/core/hash/crc32.odin index 761444676..5dde467a7 100644 --- a/core/hash/crc32.odin +++ b/core/hash/crc32.odin @@ -3,7 +3,7 @@ package hash import "base:intrinsics" @(optimization_mode="speed") -crc32 :: proc(data: []byte, seed := u32(0)) -> u32 #no_bounds_check { +crc32 :: proc "contextless" (data: []byte, seed := u32(0)) -> u32 #no_bounds_check { crc := ~seed buffer := raw_data(data) length := len(data) @@ -323,7 +323,7 @@ crc32_table := [8][256]u32{ /* @(optimization_mode="speed") -crc32 :: proc(data: []byte, seed := u32(0)) -> u32 { +crc32 :: proc "contextless" (data: []byte, seed := u32(0)) -> u32 { result := ~u32(seed); #no_bounds_check for b in data { result = result>>8 ~ _crc32_table[(result ~ u32(b)) & 0xff]; diff --git a/core/hash/hash.odin b/core/hash/hash.odin index ea99b630c..fb170bfe4 100644 --- a/core/hash/hash.odin +++ b/core/hash/hash.odin @@ -4,7 +4,7 @@ import "core:mem" import "base:intrinsics" @(optimization_mode="speed") -adler32 :: proc(data: []byte, seed := u32(1)) -> u32 #no_bounds_check { +adler32 :: proc "contextless" (data: []byte, seed := u32(1)) -> u32 #no_bounds_check { ADLER_CONST :: 65521 @@ -47,7 +47,7 @@ adler32 :: proc(data: []byte, seed := u32(1)) -> u32 #no_bounds_check { } @(optimization_mode="speed") -djb2 :: proc(data: []byte, seed := u32(5381)) -> u32 { +djb2 :: proc "contextless" (data: []byte, seed := u32(5381)) -> u32 { hash: u32 = seed for b in data { hash = (hash << 5) + hash + u32(b) // hash * 33 + u32(b) @@ -55,7 +55,7 @@ djb2 :: proc(data: []byte, seed := u32(5381)) -> u32 { return hash } -djbx33a :: proc(data: []byte, seed := u32(5381)) -> (result: [16]byte) #no_bounds_check { +djbx33a :: proc "contextless" (data: []byte, seed := u32(5381)) -> (result: [16]byte) #no_bounds_check { state := [4]u32{seed, seed, seed, seed} s: u32 = 0 @@ -74,7 +74,7 @@ djbx33a :: proc(data: []byte, seed := u32(5381)) -> (result: [16]byte) #no_bound // If you have a choice, prefer fnv32a @(optimization_mode="speed") -fnv32_no_a :: proc(data: []byte, seed := u32(0x811c9dc5)) -> u32 { +fnv32_no_a :: proc "contextless" (data: []byte, seed := u32(0x811c9dc5)) -> u32 { h: u32 = seed for b in data { h = (h * 0x01000193) ~ u32(b) @@ -87,7 +87,7 @@ fnv64 :: fnv64_no_a // NOTE(bill): Not a fan of these aliases but seems necessar // If you have a choice, prefer fnv64a @(optimization_mode="speed") -fnv64_no_a :: proc(data: []byte, seed := u64(0xcbf29ce484222325)) -> u64 { +fnv64_no_a :: proc "contextless" (data: []byte, seed := u64(0xcbf29ce484222325)) -> u64 { h: u64 = seed for b in data { h = (h * 0x100000001b3) ~ u64(b) @@ -95,7 +95,7 @@ fnv64_no_a :: proc(data: []byte, seed := u64(0xcbf29ce484222325)) -> u64 { return h } @(optimization_mode="speed") -fnv32a :: proc(data: []byte, seed := u32(0x811c9dc5)) -> u32 { +fnv32a :: proc "contextless" (data: []byte, seed := u32(0x811c9dc5)) -> u32 { h: u32 = seed for b in data { h = (h ~ u32(b)) * 0x01000193 @@ -104,7 +104,7 @@ fnv32a :: proc(data: []byte, seed := u32(0x811c9dc5)) -> u32 { } @(optimization_mode="speed") -fnv64a :: proc(data: []byte, seed := u64(0xcbf29ce484222325)) -> u64 { +fnv64a :: proc "contextless" (data: []byte, seed := u64(0xcbf29ce484222325)) -> u64 { h: u64 = seed for b in data { h = (h ~ u64(b)) * 0x100000001b3 @@ -113,7 +113,7 @@ fnv64a :: proc(data: []byte, seed := u64(0xcbf29ce484222325)) -> u64 { } @(optimization_mode="speed") -jenkins :: proc(data: []byte, seed := u32(0)) -> u32 { +jenkins :: proc "contextless" (data: []byte, seed := u32(0)) -> u32 { hash: u32 = seed for b in data { hash += u32(b) @@ -127,7 +127,7 @@ jenkins :: proc(data: []byte, seed := u32(0)) -> u32 { } @(optimization_mode="speed") -murmur32 :: proc(data: []byte, seed := u32(0)) -> u32 { +murmur32 :: proc "contextless" (data: []byte, seed := u32(0)) -> u32 { c1_32: u32 : 0xcc9e2d51 c2_32: u32 : 0x1b873593 @@ -178,7 +178,7 @@ murmur32 :: proc(data: []byte, seed := u32(0)) -> u32 { // See https://github.com/aappleby/smhasher/blob/master/src/MurmurHash2.cpp#L96 @(optimization_mode="speed") -murmur64a :: proc(data: []byte, seed := u64(0x9747b28c)) -> u64 { +murmur64a :: proc "contextless" (data: []byte, seed := u64(0x9747b28c)) -> u64 { m :: 0xc6a4a7935bd1e995 r :: 47 @@ -219,7 +219,7 @@ murmur64a :: proc(data: []byte, seed := u64(0x9747b28c)) -> u64 { // See https://github.com/aappleby/smhasher/blob/master/src/MurmurHash2.cpp#L140 @(optimization_mode="speed") -murmur64b :: proc(data: []byte, seed := u64(0x9747b28c)) -> u64 { +murmur64b :: proc "contextless" (data: []byte, seed := u64(0x9747b28c)) -> u64 { m :: 0x5bd1e995 r :: 24 @@ -287,7 +287,7 @@ murmur64b :: proc(data: []byte, seed := u64(0x9747b28c)) -> u64 { } @(optimization_mode="speed") -sdbm :: proc(data: []byte, seed := u32(0)) -> u32 { +sdbm :: proc "contextless" (data: []byte, seed := u32(0)) -> u32 { hash: u32 = seed for b in data { hash = u32(b) + (hash<<6) + (hash<<16) - hash diff --git a/core/hash/mini.odin b/core/hash/mini.odin index 98b1b4ba3..6b476f535 100644 --- a/core/hash/mini.odin +++ b/core/hash/mini.odin @@ -1,6 +1,6 @@ package hash -ginger_hash8 :: proc(x: u8) -> u8 { +ginger_hash8 :: proc "contextless" (x: u8) -> u8 { h := x * 251 h += ~(x << 3) h ~= (x >> 1) @@ -11,7 +11,7 @@ ginger_hash8 :: proc(x: u8) -> u8 { } -ginger_hash16 :: proc(x: u16) -> u16 { +ginger_hash16 :: proc "contextless" (x: u16) -> u16 { z := (x << 8) | (x >> 8) h := z h += ~(z << 5) @@ -24,14 +24,14 @@ ginger_hash16 :: proc(x: u16) -> u16 { } -ginger8 :: proc(data: []byte) -> u8 { +ginger8 :: proc "contextless" (data: []byte) -> u8 { h := ginger_hash8(0) for b in data { h ~= ginger_hash8(b) } return h } -ginger16 :: proc(data: []byte) -> u16 { +ginger16 :: proc "contextless" (data: []byte) -> u16 { h := ginger_hash16(0) for b in data { h ~= ginger_hash16(u16(b)) diff --git a/core/hash/xxhash/streaming.odin b/core/hash/xxhash/streaming.odin index 07744a12b..f68862f67 100644 --- a/core/hash/xxhash/streaming.odin +++ b/core/hash/xxhash/streaming.odin @@ -129,7 +129,7 @@ XXH3_create_state :: proc(allocator := context.allocator) -> (res: ^XXH3_state, } XXH3_destroy_state :: proc(state: ^XXH3_state, allocator := context.allocator) -> (err: Error) { - free(state) + free(state, allocator) return .None } diff --git a/core/hash/xxhash/xxhash_32.odin b/core/hash/xxhash/xxhash_32.odin index 2f27118f2..b0dea305e 100644 --- a/core/hash/xxhash/xxhash_32.odin +++ b/core/hash/xxhash/xxhash_32.odin @@ -19,15 +19,15 @@ xxh_u32 :: u32 XXH32_DEFAULT_SEED :: XXH32_hash(0) XXH32_state :: struct { - total_len_32: XXH32_hash, /*!< Total length hashed, modulo 2^32 */ - large_len: XXH32_hash, /*!< Whether the hash is >= 16 (handles @ref total_len_32 overflow) */ - v1: XXH32_hash, /*!< First accumulator lane */ - v2: XXH32_hash, /*!< Second accumulator lane */ - v3: XXH32_hash, /*!< Third accumulator lane */ - v4: XXH32_hash, /*!< Fourth accumulator lane */ - mem32: [4]XXH32_hash, /*!< Internal buffer for partial reads. Treated as unsigned char[16]. */ - memsize: XXH32_hash, /*!< Amount of data in @ref mem32 */ - reserved: XXH32_hash, /*!< Reserved field. Do not read or write to it, it may be removed. */ + total_len_32: XXH32_hash, /*!< Total length hashed, modulo 2^32 */ + large_len: XXH32_hash, /*!< Whether the hash is >= 16 (handles @ref total_len_32 overflow) */ + v1: XXH32_hash, /*!< First accumulator lane */ + v2: XXH32_hash, /*!< Second accumulator lane */ + v3: XXH32_hash, /*!< Third accumulator lane */ + v4: XXH32_hash, /*!< Fourth accumulator lane */ + mem32: [4]XXH32_hash, /*!< Internal buffer for partial reads. Treated as unsigned char[16]. */ + memsize: XXH32_hash, /*!< Amount of data in @ref mem32 */ + reserved: XXH32_hash, /*!< Reserved field. Do not read or write to it, it may be removed. */ } XXH32_canonical :: struct { diff --git a/core/sync/futex_darwin.odin b/core/sync/futex_darwin.odin index 44746e57b..6ea177d1b 100644 --- a/core/sync/futex_darwin.odin +++ b/core/sync/futex_darwin.odin @@ -3,6 +3,7 @@ package sync import "core:c" +import "core:sys/darwin" import "core:time" foreign import System "system:System.framework" @@ -29,8 +30,29 @@ _futex_wait :: proc "contextless" (f: ^Futex, expected: u32) -> bool { } _futex_wait_with_timeout :: proc "contextless" (f: ^Futex, expected: u32, duration: time.Duration) -> bool { + when darwin.WAIT_ON_ADDRESS_AVAILABLE { + s: i32 + if duration > 0 { + s = darwin.os_sync_wait_on_address_with_timeout(f, u64(expected), size_of(Futex), {}, .MACH_ABSOLUTE_TIME, u64(duration)) + } else { + s = darwin.os_sync_wait_on_address(f, u64(expected), size_of(Futex), {}) + } + + if s >= 0 { + return true + } + + switch darwin.errno() { + case -EINTR, -EFAULT: + return true + case -ETIMEDOUT: + return false + case: + _panic("darwin.os_sync_wait_on_address_with_timeout failure") + } + } else { + timeout_ns := u32(duration) * 1000 - s := __ulock_wait(UL_COMPARE_AND_WAIT | ULF_NO_ERRNO, f, u64(expected), timeout_ns) if s >= 0 { return true @@ -45,9 +67,27 @@ _futex_wait_with_timeout :: proc "contextless" (f: ^Futex, expected: u32, durati } return true + } } _futex_signal :: proc "contextless" (f: ^Futex) { + when darwin.WAIT_ON_ADDRESS_AVAILABLE { + loop: for { + s := darwin.os_sync_wake_by_address_any(f, size_of(Futex), {}) + if s >= 0 { + return + } + switch darwin.errno() { + case -EINTR, -EFAULT: + continue loop + case -ENOENT: + return + case: + _panic("darwin.os_sync_wake_by_address_any failure") + } + } + } else { + loop: for { s := __ulock_wake(UL_COMPARE_AND_WAIT | ULF_NO_ERRNO, f, 0) if s >= 0 { @@ -62,9 +102,28 @@ _futex_signal :: proc "contextless" (f: ^Futex) { _panic("futex_wake_single failure") } } + + } } _futex_broadcast :: proc "contextless" (f: ^Futex) { + when darwin.WAIT_ON_ADDRESS_AVAILABLE { + loop: for { + s := darwin.os_sync_wake_by_address_all(f, size_of(Futex), {}) + if s >= 0 { + return + } + switch darwin.errno() { + case -EINTR, -EFAULT: + continue loop + case -ENOENT: + return + case: + _panic("darwin.os_sync_wake_by_address_all failure") + } + } + } else { + loop: for { s := __ulock_wake(UL_COMPARE_AND_WAIT | ULF_NO_ERRNO | ULF_WAKE_ALL, f, 0) if s >= 0 { @@ -79,5 +138,6 @@ _futex_broadcast :: proc "contextless" (f: ^Futex) { _panic("futex_wake_all failure") } } -} + } +} diff --git a/core/sys/darwin/darwin.odin b/core/sys/darwin/darwin.odin index a3e07277c..ddd25a76c 100644 --- a/core/sys/darwin/darwin.odin +++ b/core/sys/darwin/darwin.odin @@ -3,6 +3,8 @@ package darwin import "core:c" +foreign import system "system:System.framework" + Bool :: b8 RUsage :: struct { @@ -24,3 +26,10 @@ RUsage :: struct { ru_nivcsw: c.long, } +foreign system { + __error :: proc() -> ^i32 --- +} + +errno :: #force_inline proc "contextless" () -> i32 { + return __error()^ +} diff --git a/core/sys/darwin/sync.odin b/core/sys/darwin/sync.odin new file mode 100644 index 000000000..c76b30d6b --- /dev/null +++ b/core/sys/darwin/sync.odin @@ -0,0 +1,309 @@ +package darwin + +foreign import system "system:System.framework" + +// #define OS_WAIT_ON_ADDR_AVAILABILITY \ +// __API_AVAILABLE(macos(14.4), ios(17.4), tvos(17.4), watchos(10.4)) +when ODIN_OS == .Darwin { + when ODIN_PLATFORM_SUBTARGET == .iOS && ODIN_MINIMUM_OS_VERSION > 17_04_00 { + WAIT_ON_ADDRESS_AVAILABLE :: true + } else when ODIN_MINIMUM_OS_VERSION > 14_04_00 { + WAIT_ON_ADDRESS_AVAILABLE :: true + } else { + WAIT_ON_ADDRESS_AVAILABLE :: false + } +} else { + WAIT_ON_ADDRESS_AVAILABLE :: false +} + +os_sync_wait_on_address_flag :: enum u32 { + // This flag should be used as a default flag when no other flags listed below are required. + NONE, + + // This flag should be used when synchronizing among multiple processes by + // placing the @addr passed to os_sync_wait_on_address and its variants + // in a shared memory region. + // + // When using this flag, it is important to pass OS_SYNC_WAKE_BY_ADDRESS_SHARED + // flag along with the exact same @addr to os_sync_wake_by_address_any and + // its variants to correctly find and wake up blocked waiters on the @addr. + // + // This flag should not be used when synchronizing among multiple threads of + // a single process. It allows the kernel to perform performance optimizations + // as the @addr is local to the calling process. + SHARED, +} + +os_sync_wait_on_address_flags :: bit_set[os_sync_wait_on_address_flag; u32] + +os_sync_wake_by_address_flag :: enum u32 { + // This flag should be used as a default flag when no other flags listed below are required. + NONE, + + // This flag should be used when synchronizing among multiple processes by + // placing the @addr passed to os_sync_wake_by_address_any and its variants + // in a shared memory region. + // + // When using this flag, it is important to pass OS_SYNC_WAIT_ON_ADDRESS_SHARED + // flag along with the exact same @addr to os_sync_wait_on_address and + // its variants to correctly find and wake up blocked waiters on the @addr. + // + // This flag should not be used when synchronizing among multiple threads of + // a single process. It allows the kernel to perform performance optimizations + // as the @addr is local the calling process. + SHARED, +} + +os_sync_wake_by_address_flags :: bit_set[os_sync_wake_by_address_flag; u32] + +os_clockid :: enum u32 { + MACH_ABSOLUTE_TIME = 32, +} + +foreign system { + // This function provides an atomic compare-and-wait functionality that + // can be used to implement other higher level synchronization primitives. + // + // It reads a value from @addr, compares it to expected @value and blocks + // the calling thread if they are equal. This sequence of operations is + // done atomically with respect to other concurrent operations that can + // be performed on this @addr by other threads using this same function + // or os_sync_wake_by_addr variants. At this point, the blocked calling + // thread is considered to be a waiter on this @addr, waiting to be woken + // up by a call to os_sync_wake_by_addr variants. If the value at @addr + // turns out to be different than expected, the calling thread returns + // immediately without blocking. + // + // This function is expected to be used for implementing synchronization + // primitives that do not have a sense of ownership (e.g. condition + // variables, semaphores) as it does not provide priority inversion avoidance. + // For locking primitives, it is recommended that you use existing OS + // primitives such as os_unfair_lock API family / pthread mutex or + // std::mutex. + // + // @param addr + // The userspace address to be used for atomic compare-and-wait. + // This address must be aligned to @size. + // + // @param value + // The value expected at @addr. + // + // @param size + // The size of @value, in bytes. This can be either 4 or 8 today. + // For @value of @size 4 bytes, the upper 4 bytes of @value are ignored. + // + // @param flags + // Flags to alter behavior of os_sync_wait_on_address. + // See os_sync_wait_on_address_flags_t. + // + // @return + // If the calling thread is woken up by a call to os_sync_wake_by_addr + // variants or the value at @addr is different than expected, this function + // returns successfully and the return value indicates the number + // of outstanding waiters blocked on this address. + // In the event of an error, returns -1 with errno set to indicate the error. + // + // EINVAL : Invalid flags or size. + // EINVAL : The @addr passed is NULL or misaligned. + // EINVAL : The operation associated with existing kernel state + // at this @addr is inconsistent with what the caller + // has requested. + // It is important to make sure consistent values are + // passed across wait and wake APIs for @addr, @size + // and the shared memory specification + // (See os_sync_wait_on_address_flags_t). + // + // It is possible for the os_sync_wait_on_address and its variants to perform + // an early return in the event of following errors where user may want to + // re-try the wait operation. E.g. low memory conditions could cause such early + // return. + // It is important to read the current value at the @addr before re-trying + // to ensure that the new value still requires waiting on @addr. + // + // ENOMEM : Unable to allocate memory for kernel internal data + // structures. + // EINTR : The syscall was interrupted / spurious wake up. + // EFAULT : Unable to read value from the @addr. Kernel copyin failed. + // It is possible to receive EFAULT error in following cases: + // 1. The @addr is an invalid address. This is a programmer error. + // 2. The @addr is valid; but, this is a transient error such as + // due to low memory conditions. User may want to re-try the wait + // operation. + // Following code snippet illustrates a possible re-try loop. + // + // retry: + // current = atomic_load_explicit(addr, memory_order_relaxed); + // if (current != expected) { + // int ret = os_sync_wait_on_address(addr, current, size, flags); + // if ((ret < 0) && ((errno == EINTR) || (errno == EFAULT))) { + // goto retry; + // } + // } + // + os_sync_wait_on_address :: proc( + addr: rawptr, + value: u64, + size: uint, + flags: os_sync_wait_on_address_flags, + ) -> i32 --- + + // This function is a variant of os_sync_wait_on_address that + // allows the calling thread to specify a deadline + // until which it is willing to block. + // + // @param addr + // The userspace address to be used for atomic compare-and-wait. + // This address must be aligned to @size. + // + // @param value + // The value expected at @addr. + // + // @param size + // The size of @value, in bytes. This can be either 4 or 8 today. + // For @value of @size 4 bytes, the upper 4 bytes of @value are ignored. + // + // @param flags + // Flags to alter behavior of os_sync_wait_on_address_with_deadline. + // See os_sync_wait_on_address_flags_t. + // + // @param clockid + // This value anchors @deadline argument to a specific clock id. + // See os_clockid_t. + // + // @param deadline + // This value is used to specify a deadline until which the calling + // thread is willing to block. + // Passing zero for the @deadline results in an error being returned. + // It is recommended to use os_sync_wait_on_address API to block + // indefinitely until woken up by a call to os_sync_wake_by_address_any + // or os_sync_wake_by_address_all APIs. + // + // @return + // If the calling thread is woken up by a call to os_sync_wake_by_addr + // variants or the value at @addr is different than expected, this function + // returns successfully and the return value indicates the number + // of outstanding waiters blocked on this address. + // In the event of an error, returns -1 with errno set to indicate the error. + // + // In addition to errors returned by os_sync_wait_on_address, this function + // can return the following additional error codes. + // + // EINVAL : Invalid clock id. + // EINVAL : The @deadline passed is 0. + // ETIMEDOUT : Deadline expired. + os_sync_wait_on_address_with_deadline :: proc( + addr: rawptr, + value: u64, + size: uint, + flags: os_sync_wait_on_address_flags, + clockid: os_clockid, + deadline: u64, + ) -> i32 --- + + // This function is a variant of os_sync_wait_on_address that + // allows the calling thread to specify a timeout + // until which it is willing to block. + // + // @param addr + // The userspace address to be used for atomic compare-and-wait. + // This address must be aligned to @size. + // + // @param value + // The value expected at @addr. + // + // @param size + // The size of @value, in bytes. This can be either 4 or 8 today. + // For @value of @size 4 bytes, the upper 4 bytes of @value are ignored. + // + // @param flags + // Flags to alter behavior of os_sync_wait_on_address_with_timeout. + // See os_sync_wait_on_address_flags_t. + // + // @param clockid + // This value anchors @timeout_ns argument to a specific clock id. + // See os_clockid_t. + // + // @param timeout_ns + // This value is used to specify a timeout in nanoseconds until which + // the calling thread is willing to block. + // Passing zero for the @timeout_ns results in an error being returned. + // It is recommended to use os_sync_wait_on_address API to block + // indefinitely until woken up by a call to os_sync_wake_by_address_any + // or os_sync_wake_by_address_all APIs. + // + // @return + // If the calling thread is woken up by a call to os_sync_wake_by_address + // variants or the value at @addr is different than expected, this function + // returns successfully and the return value indicates the number + // of outstanding waiters blocked on this address. + // In the event of an error, returns -1 with errno set to indicate the error. + // + // In addition to errors returned by os_sync_wait_on_address, this function + // can return the following additional error codes. + // + // EINVAL : Invalid clock id. + // EINVAL : The @timeout_ns passed is 0. + // ETIMEDOUT : Timeout expired. + os_sync_wait_on_address_with_timeout :: proc( + addr: rawptr, + value: u64, + size: uint, + flags: os_sync_wait_on_address_flags, + clockid: os_clockid, + timeout_ns: u64, + ) -> i32 --- + + // This function wakes up one waiter out of all those blocked in os_sync_wait_on_address + // or its variants on the @addr. No guarantee is provided about which + // specific waiter is woken up. + // + // @param addr + // The userspace address to be used for waking up the blocked waiter. + // It should be same as what is passed to os_sync_wait_on_address or its variants. + // + // @param size + // The size of lock value, in bytes. This can be either 4 or 8 today. + // It should be same as what is passed to os_sync_wait_on_address or its variants. + // + // @param flags + // Flags to alter behavior of os_sync_wake_by_address_any. + // See os_sync_wake_by_address_flags_t. + // + // @return + // Returns 0 on success. + // In the event of an error, returns -1 with errno set to indicate the error. + // + // EINVAL : Invalid flags or size. + // EINVAL : The @addr passed is NULL. + // EINVAL : The operation associated with existing kernel state + // at this @addr is inconsistent with what caller + // has requested. + // It is important to make sure consistent values are + // passed across wait and wake APIs for @addr, @size + // and the shared memory specification + // (See os_sync_wake_by_address_flags_t). + // ENOENT : No waiter(s) found waiting on the @addr. + os_sync_wake_by_address_any :: proc(addr: rawptr, size: uint, flags: os_sync_wait_on_address_flags) -> i32 --- + + // This function is a variant of os_sync_wake_by_address_any that wakes up all waiters + // blocked in os_sync_wait_on_address or its variants. + // + // @param addr + // The userspace address to be used for waking up the blocked waiters. + // It should be same as what is passed to os_sync_wait_on_address or its variants. + // + // @param size + // The size of lock value, in bytes. This can be either 4 or 8 today. + // It should be same as what is passed to os_sync_wait_on_address or its variants. + // + // @param flags + // Flags to alter behavior of os_sync_wake_by_address_all. + // See os_sync_wake_by_address_flags_t. + // + // @return + // Returns 0 on success. + // In the event of an error, returns -1 with errno set to indicate the error. + // + // This function returns same error codes as returned by os_sync_wait_on_address. + os_sync_wake_by_address_all :: proc(addr: rawptr, size: uint, flags: os_sync_wait_on_address_flags) -> i32 --- +} diff --git a/src/checker.cpp b/src/checker.cpp index a919f4dc5..135a1ab7b 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -1097,6 +1097,15 @@ gb_internal void init_universal(void) { scope_insert(intrinsics_pkg->scope, t_atomic_memory_order->Named.type_name); } + { + int minimum_os_version = 0; + if (build_context.minimum_os_version_string != "") { + int major, minor, revision = 0; + sscanf(cast(const char *)(build_context.minimum_os_version_string.text), "%d.%d.%d", &major, &minor, &revision); + minimum_os_version = (major*10000)+(minor*100)+revision; + } + add_global_constant("ODIN_MINIMUM_OS_VERSION", t_untyped_integer, exact_value_i64(minimum_os_version)); + } add_global_bool_constant("ODIN_DEBUG", bc->ODIN_DEBUG); add_global_bool_constant("ODIN_DISABLE_ASSERT", bc->ODIN_DISABLE_ASSERT); diff --git a/src/linker.cpp b/src/linker.cpp index 63987f9e8..0e3169b22 100644 --- a/src/linker.cpp +++ b/src/linker.cpp @@ -508,8 +508,10 @@ gb_internal i32 linker_stage(LinkerData *gen) { link_settings = gb_string_append_fmt(link_settings, "-mmacosx-version-min=%.*s ", LIT(build_context.minimum_os_version_string)); } - // This points the linker to where the entry point is - link_settings = gb_string_appendc(link_settings, "-e _main "); + if (build_context.build_mode != BuildMode_DynamicLibrary) { + // This points the linker to where the entry point is + link_settings = gb_string_appendc(link_settings, "-e _main "); + } } if (!build_context.no_crt) { diff --git a/src/parser.cpp b/src/parser.cpp index d46079964..13225f622 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -2885,6 +2885,10 @@ gb_internal Ast *parse_operand(AstFile *f, bool lhs) { elem = parse_expr(f, true); f->allow_range = prev_allow_range; + if (elem == nullptr) { + syntax_error(token, "Expected a type or range, got nothing"); + } + if (allow_token(f, Token_Semicolon)) { underlying = parse_type(f); } else if (allow_token(f, Token_Comma)) { @@ -2894,6 +2898,7 @@ gb_internal Ast *parse_operand(AstFile *f, bool lhs) { underlying = parse_type(f); } + expect_token(f, Token_CloseBracket); return ast_bit_set_type(f, token, elem, underlying); } @@ -5009,6 +5014,7 @@ gb_internal Ast *parse_stmt(AstFile *f) { case Token_Xor: case Token_Not: case Token_And: + case Token_Mul: // Used for error handling when people do C-like things s = parse_simple_stmt(f, StmtAllowFlag_Label); expect_semicolon(f); return s; diff --git a/src/threading.cpp b/src/threading.cpp index ab19718cc..fbe8997d1 100644 --- a/src/threading.cpp +++ b/src/threading.cpp @@ -758,6 +758,11 @@ gb_internal void futex_wait(Futex *f, Footex val) { #elif defined(GB_SYSTEM_OSX) +#if __has_include() + #define DARWIN_WAIT_ON_ADDRESS_AVAILABLE + #include +#endif + #define UL_COMPARE_AND_WAIT 0x00000001 #define ULF_NO_ERRNO 0x01000000 @@ -765,6 +770,23 @@ extern "C" int __ulock_wait(uint32_t operation, void *addr, uint64_t value, uint extern "C" int __ulock_wake(uint32_t operation, void *addr, uint64_t wake_value); gb_internal void futex_signal(Futex *f) { + #ifdef DARWIN_WAIT_ON_ADDRESS_AVAILABLE + if (__builtin_available(macOS 14.4, *)) { + for (;;) { + int ret = os_sync_wake_by_address_any(f, sizeof(Futex), OS_SYNC_WAKE_BY_ADDRESS_NONE); + if (ret >= 0) { + return; + } + if (errno == EINTR || errno == EFAULT) { + continue; + } + if (errno == ENOENT) { + return; + } + GB_PANIC("Failed in futex wake %d %d!\n", ret, errno); + } + } else { + #endif for (;;) { int ret = __ulock_wake(UL_COMPARE_AND_WAIT | ULF_NO_ERRNO, f, 0); if (ret >= 0) { @@ -778,9 +800,29 @@ gb_internal void futex_signal(Futex *f) { } GB_PANIC("Failed in futex wake!\n"); } + #ifdef DARWIN_WAIT_ON_ADDRESS_AVAILABLE + } + #endif } gb_internal void futex_broadcast(Futex *f) { + #ifdef DARWIN_WAIT_ON_ADDRESS_AVAILABLE + if (__builtin_available(macOS 14.4, *)) { + for (;;) { + int ret = os_sync_wake_by_address_all(f, sizeof(Footex), OS_SYNC_WAKE_BY_ADDRESS_NONE); + if (ret >= 0) { + return; + } + if (errno == EINTR || errno == EFAULT) { + continue; + } + if (errno == ENOENT) { + return; + } + GB_PANIC("Failed in futext wake %d %d!\n", ret, errno); + } + } else { + #endif for (;;) { enum { ULF_WAKE_ALL = 0x00000100 }; int ret = __ulock_wake(UL_COMPARE_AND_WAIT | ULF_NO_ERRNO | ULF_WAKE_ALL, f, 0); @@ -795,9 +837,32 @@ gb_internal void futex_broadcast(Futex *f) { } GB_PANIC("Failed in futex wake!\n"); } + #ifdef DARWIN_WAIT_ON_ADDRESS_AVAILABLE + } + #endif } gb_internal void futex_wait(Futex *f, Footex val) { + #ifdef DARWIN_WAIT_ON_ADDRESS_AVAILABLE + if (__builtin_available(macOS 14.4, *)) { + for (;;) { + int ret = os_sync_wait_on_address(f, cast(uint64_t)(val), sizeof(Footex), OS_SYNC_WAIT_ON_ADDRESS_NONE); + if (ret >= 0) { + if (*f != val) { + return; + } + continue; + } + if (errno == EINTR || errno == EFAULT) { + continue; + } + if (errno == ENOENT) { + return; + } + GB_PANIC("Failed in futex wait %d %d!\n", ret, errno); + } + } else { + #endif for (;;) { int ret = __ulock_wait(UL_COMPARE_AND_WAIT | ULF_NO_ERRNO, f, val, 0); if (ret >= 0) { @@ -815,7 +880,11 @@ gb_internal void futex_wait(Futex *f, Footex val) { GB_PANIC("Failed in futex wait!\n"); } + #ifdef DARWIN_WAIT_ON_ADDRESS_AVAILABLE + } + #endif } + #elif defined(GB_SYSTEM_WINDOWS) gb_internal void futex_signal(Futex *f) {