From 2053f2767b86e1d84459648a985ca14ee842b10f Mon Sep 17 00:00:00 2001 From: Christian Buttner Date: Sat, 19 Jul 2025 13:12:14 +0200 Subject: [PATCH] Add `ConditionVariable.wait_until` and `ConditionVariable.wait_for` (#2302) * Add `ConditionVariable.wait_until` and `ConditionVariable.wait_for` * Add "@structlike" for typedefs. --------- Co-authored-by: Christoffer Lerno --- lib/std/core/builtin.c3 | 3 +++ lib/std/core/values.c3 | 1 - lib/std/libc/libc.c3 | 2 +- lib/std/libc/libc_extra.c3 | 12 ++++++++++-- lib/std/net/socket.c3 | 2 +- lib/std/net/tcp.c3 | 4 ++-- lib/std/threads/os/thread_posix.c3 | 28 ++++++++++++++++++++------- lib/std/threads/os/thread_win32.c3 | 22 ++++++++++++++++++++- lib/std/threads/thread.c3 | 16 +++++++++++++-- lib/std/time/os/time_win32.c3 | 4 ++-- lib/std/time/time.c3 | 31 ++++++++++++++++-------------- releasenotes.md | 2 ++ src/compiler/compiler_internal.h | 1 + src/compiler/enums.h | 1 + src/compiler/sema_casts.c | 2 +- src/compiler/sema_decls.c | 4 ++++ src/compiler/symtab.c | 1 + test/unit/stdlib/time/datetime.c3 | 4 ++-- 18 files changed, 104 insertions(+), 36 deletions(-) diff --git a/lib/std/core/builtin.c3 b/lib/std/core/builtin.c3 index a7ed457f6..15ebf833b 100644 --- a/lib/std/core/builtin.c3 +++ b/lib/std/core/builtin.c3 @@ -96,6 +96,8 @@ macro anycast(any v, $Type) @builtin return ($Type*)v.ptr; } +macro bool @assignable_to(#foo, $Type) @const @builtin => $defined(*&&($Type){} = #foo); + macro @addr(#val) @builtin { $if $defined(&#val): @@ -104,6 +106,7 @@ macro @addr(#val) @builtin return &&#val; $endif } + fn bool print_backtrace(String message, int backtraces_to_ignore) @if (env::NATIVE_STACKTRACE) => @stack_mem(0x1100; Allocator smem) { void*[256] buffer; diff --git a/lib/std/core/values.c3 b/lib/std/core/values.c3 index 6e3ecb360..fb020afe3 100644 --- a/lib/std/core/values.c3 +++ b/lib/std/core/values.c3 @@ -20,7 +20,6 @@ macro bool @is_vector(#value) @const => types::is_vector($typeof(#value)); macro bool @is_same_vector_type(#value1, #value2) @const => types::is_same_vector_type($typeof(#value1), $typeof(#value2)); macro bool @assign_to(#value1, #value2) @const => $assignable(#value1, $typeof(#value2)); macro bool @is_lvalue(#value) => $defined(#value = #value); -macro bool @assignable_to(#foo, $Type) @const @builtin => $defined(*&&($Type){} = #foo); macro bool @is_const(#foo) @const @builtin { var $v; diff --git a/lib/std/libc/libc.c3 b/lib/std/libc/libc.c3 index 0dd72778f..0d9c7b038 100644 --- a/lib/std/libc/libc.c3 +++ b/lib/std/libc/libc.c3 @@ -1,4 +1,4 @@ -// Copyright (c) 2021 Christoffer Lerno. All rights reserved. +// Copyright (c) 2021-2025 Christoffer Lerno. All rights reserved. // Use of this source code is governed by the MIT license // a copy of which can be found in the LICENSE_STDLIB file. module libc; diff --git a/lib/std/libc/libc_extra.c3 b/lib/std/libc/libc_extra.c3 index 4c4142513..028fc3b64 100644 --- a/lib/std/libc/libc_extra.c3 +++ b/lib/std/libc/libc_extra.c3 @@ -4,7 +4,7 @@ import std::time; <* Return a "timespec" from a duration. - @require self >= 0 + @require self >= time::NANO_DURATION_ZERO *> fn TimeSpec NanoDuration.to_timespec(self) @inline { @@ -16,7 +16,7 @@ fn TimeSpec NanoDuration.to_timespec(self) @inline <* Convert a duration to a timespec. - @require self >= 0 + @require self >= time::DURATION_ZERO *> fn TimeSpec Duration.to_timespec(self) @inline { @@ -24,3 +24,11 @@ fn TimeSpec Duration.to_timespec(self) @inline Time_t sec = (Time_t)(self / time::SEC); return { .s = sec, .ns = ns }; } + +<* + Convert a timestamp to a timespec. +*> +fn TimeSpec Time.to_timespec(self) @inline +{ + return ((Duration)self).to_timespec(); +} diff --git a/lib/std/net/socket.c3 b/lib/std/net/socket.c3 index 27b8b1f9b..ab7af7a35 100644 --- a/lib/std/net/socket.c3 +++ b/lib/std/net/socket.c3 @@ -23,7 +23,7 @@ macro void @loop_over_ai(AddrInfo* ai; @body(NativeSocket fd, AddrInfo* ai)) } } -const Duration POLL_FOREVER = -1; +const Duration POLL_FOREVER = (Duration)-1; typedef PollSubscribes = ushort; typedef PollEvents = ushort; diff --git a/lib/std/net/tcp.c3 b/lib/std/net/tcp.c3 index d800efe04..33ce6c60c 100644 --- a/lib/std/net/tcp.c3 +++ b/lib/std/net/tcp.c3 @@ -5,11 +5,11 @@ import std::time, libc; typedef TcpSocket = inline Socket; typedef TcpServerSocket = inline Socket; -fn TcpSocket? connect(String host, uint port, Duration timeout = 0, SocketOption... options, IpProtocol ip_protocol = UNSPECIFIED) +fn TcpSocket? connect(String host, uint port, Duration timeout = time::DURATION_ZERO, SocketOption... options, IpProtocol ip_protocol = UNSPECIFIED) { AddrInfo* ai = net::addrinfo(host, port, ip_protocol.ai_family, os::SOCK_STREAM)!; defer os::freeaddrinfo(ai); - if (timeout > 0) + if (timeout > time::DURATION_ZERO) { return (TcpSocket)net::connect_with_timeout_from_addrinfo(ai, options, timeout)!; } diff --git a/lib/std/threads/os/thread_posix.c3 b/lib/std/threads/os/thread_posix.c3 index 752914c3f..b30740860 100644 --- a/lib/std/threads/os/thread_posix.c3 +++ b/lib/std/threads/os/thread_posix.c3 @@ -142,12 +142,26 @@ fn void? NativeConditionVariable.wait(&cond, NativeMutex* mtx) *> fn void? NativeConditionVariable.wait_timeout(&cond, NativeMutex* mtx, ulong ms) { - TimeSpec now; - if (libc::timespec_get(&now, libc::TIME_UTC) != libc::TIME_UTC) return thread::WAIT_FAILED?; - now.ns += (CLong)((ms % 1000) * 1000_000); - now.s += (Time_t)(ms / 1000 + now.ns / 1000_000_000); - now.ns = now.ns % 1000_000_000; - switch (posix::pthread_cond_timedwait(cond, &mtx.mutex, &now)) + Time time = time::now() + time::ms(ms); + return cond.wait_until(mtx, time) @inline; +} + +<* + @require mtx.is_initialized() +*> +fn void? NativeConditionVariable.wait_timeout_duration(&cond, NativeMutex* mtx, Duration duration) +{ + if (duration < time::DURATION_ZERO) return thread::WAIT_TIMEOUT?; + Time time = time::now() + duration; + return cond.wait_until(mtx, time) @inline; +} + +<* + @require mtx.is_initialized() +*> +fn void? NativeConditionVariable.wait_until(&cond, NativeMutex* mtx, Time time) +{ + switch (posix::pthread_cond_timedwait(cond, &mtx.mutex, &&time.to_timespec())) { case errno::ETIMEDOUT: return thread::WAIT_TIMEOUT?; @@ -216,7 +230,7 @@ fn void native_thread_yield() fn void? native_sleep_nano(NanoDuration nano) { - if (nano <= 0) return; + if (nano <= time::NANO_DURATION_ZERO) return; if (libc::nanosleep(&&nano.to_timespec(), null)) return thread::INTERRUPTED?; } diff --git a/lib/std/threads/os/thread_win32.c3 b/lib/std/threads/os/thread_win32.c3 index e683a8073..cddaa0717 100644 --- a/lib/std/threads/os/thread_win32.c3 +++ b/lib/std/threads/os/thread_win32.c3 @@ -198,7 +198,7 @@ fn void? NativeTimedMutex.lock_timeout(&mtx, ulong ms) NanoDuration duration = time::ms(ms).to_nano(); Clock start = clock::now(); - for (NanoDuration remaining = duration; remaining > 0; remaining = duration - start.to_now()) + for (NanoDuration remaining = duration; remaining > time::NANO_DURATION_ZERO; remaining = duration - start.to_now()) { ulong remaining_ms = remaining.to_ms(); if (remaining_ms > uint.max) remaining_ms = uint.max; @@ -318,6 +318,26 @@ fn void? NativeConditionVariable.wait_timeout(&cond, NativeMutex* mtx, ulong ms) return timedwait(cond, mtx, (uint)ms) @inline; } +<* + @require mtx.initialized : "Mutex was not initialized" +*> +fn void? NativeConditionVariable.wait_timeout_duration(&cond, NativeMutex* mtx, Duration duration) @inline +{ + if (duration < time::DURATION_ZERO) return thread::WAIT_TIMEOUT?; + long ms = duration.to_ms(); + if (ms > uint.max) ms = uint.max; + return timedwait(cond, mtx, (uint)ms) @inline; +} + +<* + @require mtx.initialized : "Mutex was not initialized" +*> +fn void? NativeConditionVariable.wait_until(&cond, NativeMutex* mtx, Time time) @inline +{ + Duration duration = time - time::now(); + return cond.wait_timeout_duration(mtx, duration); +} + fn void? NativeThread.create(&thread, ThreadFn func, void* args) { if (!(*thread = (NativeThread)win32::createThread(null, 0, func, args, 0, null))) return thread::INIT_FAILED?; diff --git a/lib/std/threads/thread.c3 b/lib/std/threads/thread.c3 index 96fd39bfd..a6193f1eb 100644 --- a/lib/std/threads/thread.c3 +++ b/lib/std/threads/thread.c3 @@ -63,9 +63,21 @@ macro void? ConditionVariable.wait(&cond, Mutex* mutex) { return NativeConditionVariable.wait((NativeConditionVariable*)cond, (NativeMutex*)mutex); } -macro void? ConditionVariable.wait_timeout(&cond, Mutex* mutex, ulong ms) +<* + @require @assignable_to(#ms_or_duration, Duration) || @assignable_to(#ms_or_duration, ulong) +*> +macro void? ConditionVariable.wait_timeout(&cond, Mutex* mutex, #ms_or_duration) @safemacro { - return NativeConditionVariable.wait_timeout((NativeConditionVariable*)cond, (NativeMutex*)mutex, ms); + $if @implicit_to(#ms_or_duration): + return NativeConditionVariable.wait_timeout_duration((NativeConditionVariable*)cond, (NativeMutex*)mutex, #ms_or_duration); + $else + return NativeConditionVariable.wait_timeout((NativeConditionVariable*)cond, (NativeMutex*)mutex, #ms_or_duration); + $endif +} + +macro void? ConditionVariable.wait_until(&cond, Mutex* mutex, Time time) +{ + return NativeConditionVariable.wait_until((NativeConditionVariable*)cond, (NativeMutex*)mutex, time); } <* diff --git a/lib/std/time/os/time_win32.c3 b/lib/std/time/os/time_win32.c3 index 0750694a4..c3f5f51bf 100644 --- a/lib/std/time/os/time_win32.c3 +++ b/lib/std/time/os/time_win32.c3 @@ -12,10 +12,10 @@ fn Clock native_clock() ulong mult = 0; if (!freq.quadPart) { - if (!win32::queryPerformanceFrequency(&freq)) return 0; + if (!win32::queryPerformanceFrequency(&freq)) return (Clock)0; } Win32_LARGE_INTEGER counter @noinit; - if (!win32::queryPerformanceCounter(&counter)) return 0; + if (!win32::queryPerformanceCounter(&counter)) return (Clock)0; return (Clock)counter.quadPart.muldiv(1_000_000_000, freq.quadPart); } diff --git a/lib/std/time/time.c3 b/lib/std/time/time.c3 index 57cf40256..1aa5c12f9 100644 --- a/lib/std/time/time.c3 +++ b/lib/std/time/time.c3 @@ -1,13 +1,15 @@ module std::time; import std::io, std::time::os; -typedef Time = long; -typedef Duration = long; -typedef Clock = ulong; -typedef NanoDuration (Printable) = long; +typedef Time @structlike = long; +typedef Duration @structlike = long; +typedef Clock @structlike = ulong; +typedef NanoDuration (Printable) @structlike = long; const Time FAR_FUTURE = long.max; const Time FAR_PAST = long.min; + +const NanoDuration NANO_DURATION_ZERO = 0; const Duration US = 1; const Duration MS = 1_000; const Duration SEC = 1_000_000; @@ -18,6 +20,7 @@ const Duration WEEK = 7 * DAY; const Duration MONTH = 30 * DAY; const Duration YEAR = 36525 * DAY / 100; const Duration FOREVER = long.max; +const Duration DURATION_ZERO = 0; fn Duration us(long l) @inline => l * US; fn Duration ms(long l) @inline => l * MS; @@ -78,7 +81,7 @@ fn Time now() $if $defined(native_timestamp): return os::native_timestamp(); $else - return 0; + return (Time)0; $endif } @@ -114,31 +117,31 @@ macro Duration Duration.mult(#td, long #val) @operator_s(*) @safemacro => (Durat fn usz? NanoDuration.to_format(&self, Formatter* formatter) @dynamic { NanoDuration nd = *self; - if (nd == 0) + if (nd == NANO_DURATION_ZERO) { return formatter.printf("0s")!; } - bool neg = nd < 0; + bool neg = nd < NANO_DURATION_ZERO; if (neg) nd = -nd; DString str = dstring::temp_with_capacity(64); - if (nd < 1_000_000_000) + if (nd < (NanoDuration)1_000_000_000) { // Less than 1s: print milliseconds, microseconds and nanoseconds. NanoDuration ms = nd / 1_000_000; - if (ms > 0) + if (ms > NANO_DURATION_ZERO) { str.appendf("%dms", ms); nd -= ms * 1_000_000; } NanoDuration us = nd / 1000; - if (us > 0) + if (us > NANO_DURATION_ZERO) { str.appendf("%dµs", us); nd -= us * 1000; } - if (nd > 0) + if (nd > NANO_DURATION_ZERO) { str.appendf("%dns", nd); } @@ -150,19 +153,19 @@ fn usz? NanoDuration.to_format(&self, Formatter* formatter) @dynamic nd /= 1_000_000_000; NanoDuration hour = nd / 3600; - if (hour > 0) + if (hour > NANO_DURATION_ZERO) { str.appendf("%dh", hour); nd -= hour * 3600; } NanoDuration min = nd / 60; - if (min > 0) + if (min > NANO_DURATION_ZERO) { str.appendf("%dm", min); nd -= min * 60; } NanoDuration sec = nd; - if (ms > 0) + if (ms > NANO_DURATION_ZERO) { // Ignore trailing zeroes. while (ms / 10 * 10 == ms) ms /= 10; diff --git a/releasenotes.md b/releasenotes.md index d3a007ede..3933b90ae 100644 --- a/releasenotes.md +++ b/releasenotes.md @@ -24,6 +24,7 @@ - Implicit linking of libc math when libc math functions are used. - Allow even smaller memory limits. - Check unaligned array access. +- Add "@structlike" for typedefs. ### Fixes - mkdir/rmdir would not work properly with substring paths on non-windows platforms. @@ -74,6 +75,7 @@ - New virtual emory arena allocator. - Added `WString.len`. - Added `@addr` macro. +- Add `ConditionVariable.wait_until` and `ConditionVariable.wait_for` ## 0.7.3 Change list diff --git a/src/compiler/compiler_internal.h b/src/compiler/compiler_internal.h index ca245f3e3..f1cae1ba4 100644 --- a/src/compiler/compiler_internal.h +++ b/src/compiler/compiler_internal.h @@ -646,6 +646,7 @@ typedef struct Decl_ bool attr_compact : 1; bool resolved_attributes : 1; bool allow_deprecated : 1; + bool attr_structlike : 1; union { void *backend_ref; diff --git a/src/compiler/enums.h b/src/compiler/enums.h index 2fa391b9a..d6e251443 100644 --- a/src/compiler/enums.h +++ b/src/compiler/enums.h @@ -309,6 +309,7 @@ typedef enum ATTRIBUTE_REFLECT, ATTRIBUTE_SAFEMACRO, ATTRIBUTE_SECTION, + ATTRIBUTE_STRUCTLIKE, ATTRIBUTE_TAG, ATTRIBUTE_TEST, ATTRIBUTE_UNUSED, diff --git a/src/compiler/sema_casts.c b/src/compiler/sema_casts.c index 9ad6d3dcf..6cc67503b 100644 --- a/src/compiler/sema_casts.c +++ b/src/compiler/sema_casts.c @@ -1393,7 +1393,7 @@ static bool rule_to_distinct(CastContext *cc, bool is_explicit, bool is_silent) { is_const = true; } - if (is_const) + if (is_const && (cc->is_binary_conversion || !cc->to->decl->attr_structlike)) { cc->to = flat; cc->to_group = flat_group; diff --git a/src/compiler/sema_decls.c b/src/compiler/sema_decls.c index 1b20d58eb..3f028106e 100755 --- a/src/compiler/sema_decls.c +++ b/src/compiler/sema_decls.c @@ -3112,6 +3112,7 @@ static bool sema_analyse_attribute(SemaContext *context, ResolvedAttrData *attr_ [ATTRIBUTE_REFLECT] = ATTR_FUNC | ATTR_GLOBAL | ATTR_CONST | USER_DEFINED_TYPES, [ATTRIBUTE_SAFEMACRO] = ATTR_MACRO, [ATTRIBUTE_SECTION] = ATTR_FUNC | ATTR_CONST | ATTR_GLOBAL, + [ATTRIBUTE_STRUCTLIKE] = ATTR_DISTINCT, [ATTRIBUTE_TAG] = ATTR_BITSTRUCT_MEMBER | ATTR_MEMBER | USER_DEFINED_TYPES | CALLABLE_TYPE, [ATTRIBUTE_TEST] = ATTR_FUNC, [ATTRIBUTE_UNUSED] = (AttributeDomain)~(ATTR_CALL), @@ -3433,6 +3434,9 @@ static bool sema_analyse_attribute(SemaContext *context, ResolvedAttrData *attr_ } if (!decl->func_decl.priority) decl->func_decl.priority = MAX_PRIORITY; return true; + case ATTRIBUTE_STRUCTLIKE: + decl->attr_structlike = true; + return true; case ATTRIBUTE_SECTION: case ATTRIBUTE_EXTERN: if (context->unit->module->is_generic) diff --git a/src/compiler/symtab.c b/src/compiler/symtab.c index a696a426b..5fa856a9c 100644 --- a/src/compiler/symtab.c +++ b/src/compiler/symtab.c @@ -373,6 +373,7 @@ void symtab_init(uint32_t capacity) attribute_list[ATTRIBUTE_REFLECT] = KW_DEF("@reflect"); attribute_list[ATTRIBUTE_SAFEMACRO] = KW_DEF("@safemacro"); attribute_list[ATTRIBUTE_SECTION] = KW_DEF("@section"); + attribute_list[ATTRIBUTE_STRUCTLIKE] = KW_DEF("@structlike"); attribute_list[ATTRIBUTE_TEST] = KW_DEF("@test"); attribute_list[ATTRIBUTE_TAG] = KW_DEF("@tag"); attribute_list[ATTRIBUTE_UNUSED] = KW_DEF("@unused"); diff --git a/test/unit/stdlib/time/datetime.c3 b/test/unit/stdlib/time/datetime.c3 index a2bdfffcf..e154d3562 100644 --- a/test/unit/stdlib/time/datetime.c3 +++ b/test/unit/stdlib/time/datetime.c3 @@ -66,8 +66,8 @@ fn void test_timezone() Time t1 = d1.time; Time t2 = d2.time; - assert(t1 == 1665792000000000); - assert(t2 == 1665766800000000); + assert(t1 == (Time)1665792000000000); + assert(t2 == (Time)1665766800000000); // to_gmt_offset should keep the timesampt value tz = d1.to_gmt_offset(offset);