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/encoding/json/marshal.odin b/core/encoding/json/marshal.odin index f7ce7a120..985de6880 100644 --- a/core/encoding/json/marshal.odin +++ b/core/encoding/json/marshal.odin @@ -367,23 +367,36 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err: opt_write_end(w, opt, '}') or_return case runtime.Type_Info_Struct: - opt_write_start(w, opt, '{') or_return - - for name, i in info.names { - json_name := reflect.struct_tag_get(reflect.Struct_Tag(info.tags[i]), "json") + marshal_struct_fields :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err: Marshal_Error) { + ti := runtime.type_info_base(type_info_of(v.id)) + info := ti.variant.(runtime.Type_Info_Struct) + for name, i in info.names { + json_name := reflect.struct_tag_get(reflect.Struct_Tag(info.tags[i]), "json") - opt_write_iteration(w, opt, i) or_return - if json_name != "" { - opt_write_key(w, opt, json_name) or_return - } else { - opt_write_key(w, opt, name) or_return + opt_write_iteration(w, opt, i) or_return + if json_name != "" { + opt_write_key(w, opt, json_name) or_return + } else { + // Marshal the fields of 'using _: T' fields directly into the parent struct + if info.usings[i] && name == "_" { + id := info.types[i].id + data := rawptr(uintptr(v.data) + info.offsets[i]) + marshal_struct_fields(w, any{data, id}, opt) or_return + continue + } else { + opt_write_key(w, opt, name) or_return + } + } + + id := info.types[i].id + data := rawptr(uintptr(v.data) + info.offsets[i]) + marshal_to_writer(w, any{data, id}, opt) or_return } - - id := info.types[i].id - data := rawptr(uintptr(v.data) + info.offsets[i]) - marshal_to_writer(w, any{data, id}, opt) or_return + return } - + + opt_write_start(w, opt, '{') or_return + marshal_struct_fields(w, v, opt) or_return opt_write_end(w, opt, '}') or_return case runtime.Type_Info_Union: 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/core/time/time.odin b/core/time/time.odin index 10b71ee0d..4807af840 100644 --- a/core/time/time.odin +++ b/core/time/time.odin @@ -238,8 +238,9 @@ time_add :: proc "contextless" (t: Time, d: Duration) -> Time { // // Accuracy seems to be pretty good out of the box on Linux, to within around 4µs worst case. // On Windows it depends but is comparable with regular sleep in the worst case. -// To get the same kind of accuracy as on Linux, have your program call `win32.time_begin_period(1)` to +// To get the same kind of accuracy as on Linux, have your program call `windows.timeBeginPeriod(1)` to // tell Windows to use a more accurate timer for your process. +// Additionally your program should call `windows.timeEndPeriod(1)` once you're done with `accurate_sleep`. accurate_sleep :: proc "contextless" (d: Duration) { to_sleep, estimate, mean, m2, count: Duration diff --git a/src/build_settings.cpp b/src/build_settings.cpp index 19006ab2c..3a9951cb2 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -436,7 +436,9 @@ struct BuildContext { BlockingMutex target_features_mutex; StringSet target_features_set; String target_features_string; + String minimum_os_version_string; + bool minimum_os_version_string_given; }; gb_global BuildContext build_context = {0}; @@ -1419,7 +1421,7 @@ gb_internal void init_build_context(TargetMetrics *cross_target, Subtarget subta bc->metrics = *metrics; if (metrics->os == TargetOs_darwin) { - if (bc->minimum_os_version_string.len == 0) { + if (!bc->minimum_os_version_string_given) { bc->minimum_os_version_string = str_lit("11.0.0"); } diff --git a/src/check_expr.cpp b/src/check_expr.cpp index a34b425c2..3a275729f 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -122,6 +122,8 @@ gb_internal bool is_expr_inferred_fixed_array(Ast *type_expr); gb_internal Entity *find_polymorphic_record_entity(GenTypesData *found_gen_types, isize param_count, Array const &ordered_operands); +gb_internal bool complete_soa_type(Checker *checker, Type *t, bool wait_to_finish); + enum LoadDirectiveResult { LoadDirective_Success = 0, LoadDirective_Error = 1, @@ -5031,6 +5033,9 @@ gb_internal Entity *check_selector(CheckerContext *c, Operand *operand, Ast *nod } } + if (operand->type && is_type_soa_struct(type_deref(operand->type))) { + complete_soa_type(c->checker, type_deref(operand->type), false); + } if (entity == nullptr && selector->kind == Ast_Ident) { String field_name = selector->Ident.token.string; diff --git a/src/check_type.cpp b/src/check_type.cpp index 0f63d8bcb..40a7ec947 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -2683,6 +2683,118 @@ type_assign:; return; } +struct SoaTypeWorkerData { + CheckerContext ctx; + Type * type; + bool wait_to_finish; +}; + + +gb_internal bool complete_soa_type(Checker *checker, Type *t, bool wait_to_finish) { + Type *original_type = t; + gb_unused(original_type); + + t = base_type(t); + if (t == nullptr || !is_type_soa_struct(t)) { + return true; + } + + MUTEX_GUARD(&t->Struct.soa_mutex); + + if (t->Struct.fields_wait_signal.futex.load()) { + return true; + } + + isize field_count = 0; + i32 extra_field_count = 0; + switch (t->Struct.soa_kind) { + case StructSoa_Fixed: extra_field_count = 0; break; + case StructSoa_Slice: extra_field_count = 1; break; + case StructSoa_Dynamic: extra_field_count = 3; break; + } + + Scope *scope = t->Struct.scope; + i64 soa_count = t->Struct.soa_count; + Type *elem = t->Struct.soa_elem; + Type *old_struct = base_type(elem); + GB_ASSERT(old_struct->kind == Type_Struct); + + if (wait_to_finish) { + wait_signal_until_available(&old_struct->Struct.fields_wait_signal); + } else { + GB_ASSERT(old_struct->Struct.fields_wait_signal.futex.load()); + } + + field_count = old_struct->Struct.fields.count; + + t->Struct.fields = slice_make(permanent_allocator(), field_count+extra_field_count); + t->Struct.tags = gb_alloc_array(permanent_allocator(), String, field_count+extra_field_count); + + + auto const &add_entity = [](Scope *scope, Entity *entity) { + String name = entity->token.string; + if (!is_blank_ident(name)) { + Entity *ie = scope_insert(scope, entity); + if (ie != nullptr) { + redeclaration_error(name, entity, ie); + } + } + }; + + + for_array(i, old_struct->Struct.fields) { + Entity *old_field = old_struct->Struct.fields[i]; + if (old_field->kind == Entity_Variable) { + Type *field_type = nullptr; + if (t->Struct.soa_kind == StructSoa_Fixed) { + GB_ASSERT(soa_count >= 0); + field_type = alloc_type_array(old_field->type, soa_count); + } else { + field_type = alloc_type_pointer(old_field->type); + } + Entity *new_field = alloc_entity_field(scope, old_field->token, field_type, false, old_field->Variable.field_index); + t->Struct.fields[i] = new_field; + add_entity(scope, new_field); + new_field->flags |= EntityFlag_Used; + } else { + t->Struct.fields[i] = old_field; + } + + t->Struct.tags[i] = old_struct->Struct.tags[i]; + } + + if (t->Struct.soa_kind != StructSoa_Fixed) { + Entity *len_field = alloc_entity_field(scope, make_token_ident("__$len"), t_int, false, cast(i32)field_count+0); + t->Struct.fields[field_count+0] = len_field; + add_entity(scope, len_field); + len_field->flags |= EntityFlag_Used; + + if (t->Struct.soa_kind == StructSoa_Dynamic) { + Entity *cap_field = alloc_entity_field(scope, make_token_ident("__$cap"), t_int, false, cast(i32)field_count+1); + t->Struct.fields[field_count+1] = cap_field; + add_entity(scope, cap_field); + cap_field->flags |= EntityFlag_Used; + + init_mem_allocator(checker); + Entity *allocator_field = alloc_entity_field(scope, make_token_ident("allocator"), t_allocator, false, cast(i32)field_count+2); + t->Struct.fields[field_count+2] = allocator_field; + add_entity(scope, allocator_field); + allocator_field->flags |= EntityFlag_Used; + } + } + + // add_type_info_type(ctx, original_type); + + wait_signal_set(&t->Struct.fields_wait_signal); + return true; +} + +gb_internal WORKER_TASK_PROC(complete_soa_type_worker) { + SoaTypeWorkerData *wd = cast(SoaTypeWorkerData *)data; + complete_soa_type(wd->ctx.checker, wd->type, wd->wait_to_finish); + return 0; +} + gb_internal Type *make_soa_struct_internal(CheckerContext *ctx, Ast *array_typ_expr, Ast *elem_expr, Type *elem, i64 count, Type *generic_type, StructSoaKind soa_kind) { @@ -2697,8 +2809,9 @@ gb_internal Type *make_soa_struct_internal(CheckerContext *ctx, Ast *array_typ_e return alloc_type_array(elem, count, generic_type); } - Type *soa_struct = nullptr; - Scope *scope = nullptr; + Type * soa_struct = nullptr; + Scope *scope = nullptr; + bool is_complete = false; isize field_count = 0; i32 extra_field_count = 0; @@ -2707,39 +2820,43 @@ gb_internal Type *make_soa_struct_internal(CheckerContext *ctx, Ast *array_typ_e case StructSoa_Slice: extra_field_count = 1; break; case StructSoa_Dynamic: extra_field_count = 3; break; } + + soa_struct = alloc_type_struct(); + soa_struct->Struct.soa_kind = soa_kind; + soa_struct->Struct.soa_elem = elem; + soa_struct->Struct.is_polymorphic = is_polymorphic; + soa_struct->Struct.node = array_typ_expr; + + if (count > I32_MAX) { + count = I32_MAX; + error(array_typ_expr, "Array count too large for an #soa struct, got %lld", cast(long long)count); + } + soa_struct->Struct.soa_count = cast(i32)count; + + scope = create_scope(ctx->info, ctx->scope); + soa_struct->Struct.scope = scope; + + if (elem && elem->kind == Type_Named) { + add_declaration_dependency(ctx, elem->Named.type_name); + } + if (is_polymorphic) { field_count = 0; - soa_struct = alloc_type_struct(); soa_struct->Struct.fields = slice_make(permanent_allocator(), field_count+extra_field_count); soa_struct->Struct.tags = gb_alloc_array(permanent_allocator(), String, field_count+extra_field_count); - soa_struct->Struct.node = array_typ_expr; - soa_struct->Struct.soa_kind = soa_kind; - soa_struct->Struct.soa_elem = elem; soa_struct->Struct.soa_count = 0; - soa_struct->Struct.is_polymorphic = true; - scope = create_scope(ctx->info, ctx->scope); - soa_struct->Struct.scope = scope; + is_complete = true; + } else if (is_type_array(elem)) { Type *old_array = base_type(elem); field_count = cast(isize)old_array->Array.count; - soa_struct = alloc_type_struct(); soa_struct->Struct.fields = slice_make(permanent_allocator(), field_count+extra_field_count); soa_struct->Struct.tags = gb_alloc_array(permanent_allocator(), String, field_count+extra_field_count); - soa_struct->Struct.node = array_typ_expr; - soa_struct->Struct.soa_kind = soa_kind; - soa_struct->Struct.soa_elem = elem; - if (count > I32_MAX) { - count = I32_MAX; - error(array_typ_expr, "Array count too large for an #soa struct, got %lld", cast(long long)count); - } - soa_struct->Struct.soa_count = cast(i32)count; - scope = create_scope(ctx->info, ctx->scope); string_map_init(&scope->elements, 8); - soa_struct->Struct.scope = scope; String params_xyzw[4] = { str_lit("x"), @@ -2765,52 +2882,44 @@ gb_internal Type *make_soa_struct_internal(CheckerContext *ctx, Ast *array_typ_e add_entity_use(ctx, nullptr, new_field); } + is_complete = true; + } else { GB_ASSERT(is_type_struct(elem)); Type *old_struct = base_type(elem); - wait_signal_until_available(&old_struct->Struct.fields_wait_signal); - field_count = old_struct->Struct.fields.count; + if (old_struct->Struct.fields_wait_signal.futex.load()) { + field_count = old_struct->Struct.fields.count; - soa_struct = alloc_type_struct(); - soa_struct->Struct.fields = slice_make(permanent_allocator(), field_count+extra_field_count); - soa_struct->Struct.tags = gb_alloc_array(permanent_allocator(), String, field_count+extra_field_count); - soa_struct->Struct.node = array_typ_expr; - soa_struct->Struct.soa_kind = soa_kind; - soa_struct->Struct.soa_elem = elem; - if (count > I32_MAX) { - count = I32_MAX; - error(array_typ_expr, "Array count too large for an #soa struct, got %lld", cast(long long)count); - } - soa_struct->Struct.soa_count = cast(i32)count; + soa_struct->Struct.fields = slice_make(permanent_allocator(), field_count+extra_field_count); + soa_struct->Struct.tags = gb_alloc_array(permanent_allocator(), String, field_count+extra_field_count); - scope = create_scope(ctx->info, old_struct->Struct.scope->parent); - soa_struct->Struct.scope = scope; - - for_array(i, old_struct->Struct.fields) { - Entity *old_field = old_struct->Struct.fields[i]; - if (old_field->kind == Entity_Variable) { - Type *field_type = nullptr; - if (soa_kind == StructSoa_Fixed) { - GB_ASSERT(count >= 0); - field_type = alloc_type_array(old_field->type, count); + for_array(i, old_struct->Struct.fields) { + Entity *old_field = old_struct->Struct.fields[i]; + if (old_field->kind == Entity_Variable) { + Type *field_type = nullptr; + if (soa_kind == StructSoa_Fixed) { + GB_ASSERT(count >= 0); + field_type = alloc_type_array(old_field->type, count); + } else { + field_type = alloc_type_pointer(old_field->type); + } + Entity *new_field = alloc_entity_field(scope, old_field->token, field_type, false, old_field->Variable.field_index); + soa_struct->Struct.fields[i] = new_field; + add_entity(ctx, scope, nullptr, new_field); + add_entity_use(ctx, nullptr, new_field); } else { - field_type = alloc_type_pointer(old_field->type); + soa_struct->Struct.fields[i] = old_field; } - Entity *new_field = alloc_entity_field(scope, old_field->token, field_type, false, old_field->Variable.field_index); - soa_struct->Struct.fields[i] = new_field; - add_entity(ctx, scope, nullptr, new_field); - add_entity_use(ctx, nullptr, new_field); - } else { - soa_struct->Struct.fields[i] = old_field; - } - soa_struct->Struct.tags[i] = old_struct->Struct.tags[i]; + soa_struct->Struct.tags[i] = old_struct->Struct.tags[i]; + } + is_complete = true; } } - if (soa_kind != StructSoa_Fixed) { + if (is_complete && soa_kind != StructSoa_Fixed) { Entity *len_field = alloc_entity_field(scope, make_token_ident("__$len"), t_int, false, cast(i32)field_count+0); soa_struct->Struct.fields[field_count+0] = len_field; add_entity(ctx, scope, nullptr, len_field); @@ -2835,8 +2944,18 @@ gb_internal Type *make_soa_struct_internal(CheckerContext *ctx, Ast *array_typ_e Entity *base_type_entity = alloc_entity_type_name(scope, token, elem, EntityState_Resolved); add_entity(ctx, scope, nullptr, base_type_entity); - add_type_info_type(ctx, soa_struct); - wait_signal_set(&soa_struct->Struct.fields_wait_signal); + if (is_complete) { + add_type_info_type(ctx, soa_struct); + wait_signal_set(&soa_struct->Struct.fields_wait_signal); + } else { + SoaTypeWorkerData *wd = gb_alloc_item(permanent_allocator(), SoaTypeWorkerData); + wd->ctx = *ctx; + wd->type = soa_struct; + wd->wait_to_finish = true; + + mpsc_enqueue(&ctx->checker->soa_types_to_complete, soa_struct); + thread_pool_add_task(complete_soa_type_worker, wd); + } return soa_struct; } diff --git a/src/checker.cpp b/src/checker.cpp index e7d0ad9cb..100b53315 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); @@ -1227,9 +1236,9 @@ gb_internal void init_universal(void) { // intrinsics types for objective-c stuff { - t_objc_object = add_global_type_name(intrinsics_pkg->scope, str_lit("objc_object"), alloc_type_struct()); - t_objc_selector = add_global_type_name(intrinsics_pkg->scope, str_lit("objc_selector"), alloc_type_struct()); - t_objc_class = add_global_type_name(intrinsics_pkg->scope, str_lit("objc_class"), alloc_type_struct()); + t_objc_object = add_global_type_name(intrinsics_pkg->scope, str_lit("objc_object"), alloc_type_struct_complete()); + t_objc_selector = add_global_type_name(intrinsics_pkg->scope, str_lit("objc_selector"), alloc_type_struct_complete()); + t_objc_class = add_global_type_name(intrinsics_pkg->scope, str_lit("objc_class"), alloc_type_struct_complete()); t_objc_id = alloc_type_pointer(t_objc_object); t_objc_SEL = alloc_type_pointer(t_objc_selector); @@ -1371,6 +1380,7 @@ gb_internal void init_checker(Checker *c) { array_init(&c->nested_proc_lits, heap_allocator(), 0, 1<<20); mpsc_init(&c->global_untyped_queue, a); // , 1<<20); + mpsc_init(&c->soa_types_to_complete, a); // , 1<<20); c->builtin_ctx = make_checker_context(c); } @@ -1383,6 +1393,7 @@ gb_internal void destroy_checker(Checker *c) { array_free(&c->nested_proc_lits); array_free(&c->procs_to_check); mpsc_destroy(&c->global_untyped_queue); + mpsc_destroy(&c->soa_types_to_complete); } @@ -1682,6 +1693,26 @@ gb_internal bool add_entity_with_name(CheckerContext *c, Scope *scope, Ast *iden } return true; } + +gb_internal bool add_entity_with_name(CheckerInfo *info, Scope *scope, Ast *identifier, Entity *entity, String name) { + if (scope == nullptr) { + return false; + } + + + if (!is_blank_ident(name)) { + Entity *ie = scope_insert(scope, entity); + if (ie != nullptr) { + return redeclaration_error(name, entity, ie); + } + } + if (identifier != nullptr) { + GB_ASSERT(entity->file != nullptr); + add_entity_definition(info, identifier, entity); + } + return true; +} + gb_internal bool add_entity(CheckerContext *c, Scope *scope, Ast *identifier, Entity *entity) { return add_entity_with_name(c, scope, identifier, entity, entity->token.string); } @@ -2006,6 +2037,8 @@ gb_internal void add_type_info_type_internal(CheckerContext *c, Type *t) { break; case Type_Struct: + if (bt->Struct.fields_wait_signal.futex.load() == 0) + return; if (bt->Struct.scope != nullptr) { for (auto const &entry : bt->Struct.scope->elements) { Entity *e = entry.value; @@ -2026,7 +2059,9 @@ gb_internal void add_type_info_type_internal(CheckerContext *c, Type *t) { add_type_info_type_internal(c, bt->Struct.polymorphic_params); for_array(i, bt->Struct.fields) { Entity *f = bt->Struct.fields[i]; - add_type_info_type_internal(c, f->type); + if (f && f->type) { + add_type_info_type_internal(c, f->type); + } } add_comparison_procedures_for_fields(c, bt); break; @@ -4440,6 +4475,10 @@ gb_internal void check_all_global_entities(Checker *c) { DeclInfo *d = e->decl_info; check_single_global_entity(c, e, d); if (e->type != nullptr && is_type_typed(e->type)) { + for (Type *t = nullptr; mpsc_dequeue(&c->soa_types_to_complete, &t); /**/) { + complete_soa_type(c, t, false); + } + (void)type_size_of(e->type); (void)type_align_of(e->type); } @@ -6108,6 +6147,9 @@ gb_internal void check_add_definitions_from_queues(Checker *c) { } gb_internal void check_merge_queues_into_arrays(Checker *c) { + for (Type *t = nullptr; mpsc_dequeue(&c->soa_types_to_complete, &t); /**/) { + complete_soa_type(c, t, false); + } check_add_entities_from_queues(c); check_add_definitions_from_queues(c); } @@ -6318,6 +6360,8 @@ gb_internal void check_parsed_files(Checker *c) { TIME_SECTION("check bodies have all been checked"); check_unchecked_bodies(c); + TIME_SECTION("check #soa types"); + check_merge_queues_into_arrays(c); thread_pool_wait(); diff --git a/src/checker.hpp b/src/checker.hpp index e0dc54a87..1701da58d 100644 --- a/src/checker.hpp +++ b/src/checker.hpp @@ -500,6 +500,7 @@ struct Checker { MPSCQueue global_untyped_queue; + MPSCQueue soa_types_to_complete; }; diff --git a/src/linker.cpp b/src/linker.cpp index 6699c9cb8..0e3169b22 100644 --- a/src/linker.cpp +++ b/src/linker.cpp @@ -502,11 +502,16 @@ gb_internal i32 linker_stage(LinkerData *gen) { platform_lib_str = gb_string_appendc(platform_lib_str, "-L/opt/local/lib "); } - if (build_context.minimum_os_version_string.len) { + // Only specify this flag if the user has given a minimum version to target. + // This will cause warnings to show up for mismatched libraries. + if (build_context.minimum_os_version_string_given) { 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/llvm_abi.cpp b/src/llvm_abi.cpp index f976d99b3..5f5f734fb 100644 --- a/src/llvm_abi.cpp +++ b/src/llvm_abi.cpp @@ -1343,7 +1343,7 @@ namespace lbAbiWasm { // ignore padding LLVMStructGetTypeAtIndex(type, 2) }; - LLVMTypeRef new_type = LLVMStructTypeInContext(c, types, gb_count_of(types), true); + LLVMTypeRef new_type = LLVMStructTypeInContext(c, types, gb_count_of(types), false); return lb_arg_type_direct(type, new_type, nullptr, nullptr); } else { return is_struct(c, type, calling_convention); diff --git a/src/llvm_backend_type.cpp b/src/llvm_backend_type.cpp index de83f5058..93e2874a5 100644 --- a/src/llvm_backend_type.cpp +++ b/src/llvm_backend_type.cpp @@ -205,7 +205,7 @@ gb_internal LLVMTypeRef *lb_setup_modified_types_for_type_info(lbModule *m, isiz stypes[1] = lb_type(m, tibt->Struct.fields[1]->type); stypes[2] = lb_type(m, tibt->Struct.fields[2]->type); isize variant_index = 0; - if (build_context.int_size == 8) { + if (build_context.ptr_size == 8) { stypes[3] = lb_type(m, t_i32); // padding stypes[4] = lb_type(m, tibt->Struct.fields[3]->type); variant_index = 5; @@ -385,7 +385,7 @@ gb_internal void lb_setup_type_info_data_giant_array(lbModule *m, i64 global_typ small_const_values[2] = type_info_flags.value; unsigned variant_index = 0; - if (build_context.int_size == 8) { + if (build_context.ptr_size == 8) { small_const_values[3] = LLVMConstNull(LLVMStructGetTypeAtIndex(stype, 3)); small_const_values[4] = id.value; variant_index = 5; @@ -860,7 +860,7 @@ gb_internal void lb_setup_type_info_data_giant_array(lbModule *m, i64 global_typ Entity *f = t->Struct.fields[source_index]; i64 foffset = 0; if (!t->Struct.is_raw_union) { - GB_ASSERT(t->Struct.offsets != nullptr); + GB_ASSERT_MSG(t->Struct.offsets != nullptr, "%s", type_to_string(t)); GB_ASSERT(0 <= f->Variable.field_index && f->Variable.field_index < count); foffset = t->Struct.offsets[source_index]; } diff --git a/src/llvm_backend_utility.cpp b/src/llvm_backend_utility.cpp index fb61c41c3..865c3f1ec 100644 --- a/src/llvm_backend_utility.cpp +++ b/src/llvm_backend_utility.cpp @@ -124,7 +124,7 @@ gb_internal lbValue lb_emit_select(lbProcedure *p, lbValue cond, lbValue x, lbVa gb_internal lbValue lb_emit_min(lbProcedure *p, Type *t, lbValue x, lbValue y) { x = lb_emit_conv(p, x, t); y = lb_emit_conv(p, y, t); - bool use_llvm_intrinsic = is_type_float(t) || (is_type_simd_vector(t) && is_type_float(base_array_type(t))); + bool use_llvm_intrinsic = !is_arch_wasm() && (is_type_float(t) || (is_type_simd_vector(t) && is_type_float(base_array_type(t)))); if (use_llvm_intrinsic) { LLVMValueRef args[2] = {x.value, y.value}; LLVMTypeRef types[1] = {lb_type(p->module, t)}; @@ -140,7 +140,7 @@ gb_internal lbValue lb_emit_min(lbProcedure *p, Type *t, lbValue x, lbValue y) { gb_internal lbValue lb_emit_max(lbProcedure *p, Type *t, lbValue x, lbValue y) { x = lb_emit_conv(p, x, t); y = lb_emit_conv(p, y, t); - bool use_llvm_intrinsic = is_type_float(t) || (is_type_simd_vector(t) && is_type_float(base_array_type(t))); + bool use_llvm_intrinsic = !is_arch_wasm() && (is_type_float(t) || (is_type_simd_vector(t) && is_type_float(base_array_type(t)))); if (use_llvm_intrinsic) { LLVMValueRef args[2] = {x.value, y.value}; LLVMTypeRef types[1] = {lb_type(p->module, t)}; diff --git a/src/main.cpp b/src/main.cpp index 9c353653f..79c3a1670 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1066,6 +1066,7 @@ gb_internal bool parse_build_flags(Array args) { case BuildFlag_MinimumOSVersion: { GB_ASSERT(value.kind == ExactValue_String); build_context.minimum_os_version_string = value.value_string; + build_context.minimum_os_version_string_given = true; break; } case BuildFlag_RelocMode: { @@ -1926,7 +1927,7 @@ gb_internal void print_show_help(String const arg0, String const &command) { print_usage_line(1, "-minimum-os-version:"); print_usage_line(2, "Sets the minimum OS version targeted by the application."); print_usage_line(2, "Default: -minimum-os-version:11.0.0"); - print_usage_line(2, "(Only used when target is Darwin.)"); + print_usage_line(2, "Only used when target is Darwin, if given, linking mismatched versions will emit a warning."); print_usage_line(0, ""); print_usage_line(1, "-extra-linker-flags:"); 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) { diff --git a/src/types.cpp b/src/types.cpp index c2358056b..256c654ac 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -145,6 +145,7 @@ struct TypeStruct { i32 soa_count; StructSoaKind soa_kind; Wait_Signal fields_wait_signal; + BlockingMutex soa_mutex; BlockingMutex offset_mutex; // for settings offsets bool is_polymorphic; @@ -1060,6 +1061,13 @@ gb_internal Type *alloc_type_struct() { return t; } +gb_internal Type *alloc_type_struct_complete() { + Type *t = alloc_type(Type_Struct); + wait_signal_set(&t->Struct.fields_wait_signal); + return t; +} + + gb_internal Type *alloc_type_union() { Type *t = alloc_type(Type_Union); return t; @@ -3771,6 +3779,8 @@ gb_internal i64 type_align_of_internal(Type *t, TypePath *path) { return 1; } + type_set_offsets(t); + i64 max = 1; for_array(i, t->Struct.fields) { Type *field_type = t->Struct.fields[i]->type;