Repository: swiftlang/swift-corelibs-libdispatch
Pull request #861: [FreeBSD] support FreeBSD
Author: login: michael-yuji
Created: 2025-03-17T06:22:47Z | Merged: 2025-05-23T19:06:23Z

This patch adds FreeBSD support to libdispatch.

This patch is partly based on #561 with bug fixes and uses the pipe based mechanism based on #559 to bridge with CFRunLoop, since part of the CFRunLoop changes already landed in CoreFoundation.

In addition to above changes, also implements workqueue and limited DispatchSource support.


Diff:
diff --git a/private/private.h b/private/private.h
index b0ee07022..8b1e19670 100644
--- a/private/private.h
+++ b/private/private.h
@@ -189,9 +189,9 @@ void _dispatch_prohibit_transition_to_multithreaded(bool prohibit);
 
 #if TARGET_OS_MAC
 typedef mach_port_t dispatch_runloop_handle_t;
-#elif defined(__linux__) || defined(__FreeBSD__)
+#elif defined(__linux__)
 typedef int dispatch_runloop_handle_t;
-#elif defined(__unix__) && !defined(__linux__) && !defined(__FreeBSD__)
+#elif defined(__unix__)
 typedef uint64_t dispatch_runloop_handle_t;
 #elif defined(_WIN32)
 typedef void *dispatch_runloop_handle_t;
diff --git a/src/event/workqueue.c b/src/event/workqueue.c
index 6bc65c3b6..07cc17bc5 100644
--- a/src/event/workqueue.c
+++ b/src/event/workqueue.c
@@ -293,6 +293,52 @@ _dispatch_workq_count_runnable_workers(dispatch_workq_monitor_t mon)
 
 	_dispatch_unfair_lock_unlock(&mon->registered_tid_lock);
 }
+#elif defined(__FreeBSD__)
+#include <sys/sysctl.h>
+#include <sys/proc.h>
+#include <sys/user.h>
+
+static void
+_dispatch_workq_count_runnable_workers(dispatch_workq_monitor_t mon)
+{
+	struct kinfo_proc kp[WORKQ_MAX_TRACKED_TIDS];
+	size_t size;
+	int count, runners = 0;
+	int mib[] = {CTL_KERN, KERN_PROC, KERN_PROC_PID | KERN_PROC_INC_THREAD, (int)getpid()};
+
+	// get size we need
+	if (sysctl(mib, 4, NULL, &size, NULL, 0) < 0) {
+		_dispatch_debug("workq: failed to get size for kinfo_proc[] from sysctll");
+		return;
+	}
+
+	// only care about up to WORKQ_MAX_TRACKED_TIDS threads
+	size = MIN(sizeof(kp), size);
+
+	if (sysctl(mib, 4, kp, &size, NULL, 0) < 0) {
+		_dispatch_debug("workq: failed to get kinfo_proc[] from sysctl");
+		return;
+	}
+
+	count = (int)(size / sizeof(struct kinfo_proc));
+
+	_dispatch_unfair_lock_lock(&mon->registered_tid_lock);
+
+	for (int i = 0; i < mon->num_registered_tids; ++i) {
+		dispatch_tid tid = mon->registered_tids[i];
+		for (int j = 0; i < count; ++i) {
+			if ((dispatch_tid)kp[j].ki_tid != tid) { continue; }
+			if (kp[j].ki_stat == SRUN || kp[j].ki_stat == SIDL) {
+				++runners;
+				break;
+			}
+		}
+	}
+
+	mon->num_runnable = runners;
+
+	_dispatch_unfair_lock_unlock(&mon->registered_tid_lock);
+}
 #else
 #error must define _dispatch_workq_count_runnable_workers
 #endif
diff --git a/src/event/workqueue_internal.h b/src/event/workqueue_internal.h
index 118d42db5..cc9474e80 100644
--- a/src/event/workqueue_internal.h
+++ b/src/event/workqueue_internal.h
@@ -30,7 +30,7 @@
 void _dispatch_workq_worker_register(dispatch_queue_global_t root_q);
 void _dispatch_workq_worker_unregister(dispatch_queue_global_t root_q);
 
-#if defined(__linux__) || defined(_WIN32) || defined(__OpenBSD__)
+#if defined(__linux__) || defined(_WIN32) || defined(__OpenBSD__) || defined(__FreeBSD__)
 #define HAVE_DISPATCH_WORKQ_MONITORING 1
 #else
 #define HAVE_DISPATCH_WORKQ_MONITORING 0
diff --git a/src/init.c b/src/init.c
index 1640cbab8..2f2efb0ae 100644
--- a/src/init.c
+++ b/src/init.c
@@ -1049,7 +1049,11 @@ _dispatch_bug_kevent_vanished(dispatch_unote_t du)
 			"{ %p[%s], ident: %" PRIdPTR " / 0x%" PRIxPTR ", handler: %p }",
 			dux_type(du._du)->dst_kind, dou._dq,
 			dou._dq->dq_label ? dou._dq->dq_label : "<unknown>",
+#if defined(__FreeBSD__)
+			(intptr_t)du._du->du_ident, (intptr_t)du._du->du_ident, func);
+#else
 			(intptr_t)du._du->du_ident, (uintptr_t)du._du->du_ident, func);
+#endif
 }
 
 #endif // RDAR_49023449
diff --git a/src/internal.h b/src/internal.h
index 9f74f2c19..a67a771dc 100644
--- a/src/internal.h
+++ b/src/internal.h
@@ -277,6 +277,9 @@ upcast(dispatch_object_t dou)
 #include <sys/sysctl.h>
 #include <sys/queue.h>
 #endif
+#if defined(__FreeBSD__)
+#include <sys/eventfd.h>
+#endif // __FreeBSD__
 #include <sys/socket.h>
 #include <sys/time.h>
 #include <sys/mman.h>
diff --git a/src/io.c b/src/io.c
index 8b03e2934..95c9d2d60 100644
--- a/src/io.c
+++ b/src/io.c
@@ -22,7 +22,6 @@
 
 #if defined(__FreeBSD__)
 #include <fcntl.h>
-#define F_RDADVISE F_RDAHEAD
 #endif
 
 #ifndef DISPATCH_IO_DEBUG
diff --git a/src/queue.c b/src/queue.c
index 356fef19b..1d948e947 100644
--- a/src/queue.c
+++ b/src/queue.c
@@ -6490,7 +6490,7 @@ _dispatch_runloop_queue_get_handle(dispatch_lane_t dq)
 #elif defined(__linux__)
 	// decode: 0 is a valid fd, so offset by 1 to distinguish from NULL
 	return ((dispatch_runloop_handle_t)(uintptr_t)dq->do_ctxt) - 1;
-#elif defined(__unix__) && !defined(__linux__)
+#elif defined(__unix__)
 	return ((dispatch_runloop_handle_t)(uintptr_t)dq->do_ctxt);
 #elif defined(_WIN32)
 	return ((dispatch_runloop_handle_t)(uintptr_t)dq->do_ctxt);
@@ -6509,7 +6509,7 @@ _dispatch_runloop_queue_set_handle(dispatch_lane_t dq,
 #elif defined(__linux__)
 	// encode: 0 is a valid fd, so offset by 1 to distinguish from NULL
 	dq->do_ctxt = (void *)(uintptr_t)(handle + 1);
-#elif defined(__unix__) && !defined(__linux__)
+#elif defined(__unix__)
 	dq->do_ctxt = (void *)(uintptr_t)handle;
 #elif defined(_WIN32)
 	dq->do_ctxt = (void *)(uintptr_t)handle;
@@ -6574,6 +6574,7 @@ _dispatch_runloop_queue_handle_init(void *ctxt)
 	}
 	handle = fd;
 #elif defined(__unix__) && !defined(__linux__)
+	// swift-corelib-foundation PR #3004 implemented a pipe based queue handle
 	int fds[2];
 	int r = pipe2(fds, O_CLOEXEC | O_NONBLOCK);
 	if (r == -1) {
diff --git a/src/shims/lock.c b/src/shims/lock.c
index 8102c877c..6d0ab8764 100644
--- a/src/shims/lock.c
+++ b/src/shims/lock.c
@@ -54,9 +54,20 @@ _dispatch_thread_switch(dispatch_lock value, dispatch_lock_options_t flags,
 	SwitchToThread();
 }
 #endif
-#endif
-
-#if defined(__unix__)
+#elif defined(__FreeBSD__)
+#if !HAVE_UL_UNFAIR_LOCK
+DISPATCH_ALWAYS_INLINE
+static inline void
+_dispatch_thread_switch(dispatch_lock value, dispatch_lock_options_t flags,
+		uint32_t timeout)
+{
+  (void)value;
+  (void)flags;
+  (void)timeout;
+  sched_yield();
+}
+#endif // HAVE_UL_UNFAIR_LOCK
+#elif defined(__unix__)
 #if !HAVE_UL_UNFAIR_LOCK && !HAVE_FUTEX_PI
 DISPATCH_ALWAYS_INLINE
 static inline void
@@ -539,6 +550,16 @@ _dispatch_wait_on_address(uint32_t volatile *_address, uint32_t value,
 			? INFINITE : ((nsecs + 1000000) / 1000000);
 	if (dwMilliseconds == 0) return ETIMEDOUT;
 	return WaitOnAddress(address, &value, sizeof(value), dwMilliseconds) == TRUE;
+#elif defined(__FreeBSD__)
+	(void)flags;
+	if (nsecs != DISPATCH_TIME_FOREVER) {
+		struct timespec ts = {
+			.tv_sec = (__typeof__(ts.tv_sec))(nsecs / NSEC_PER_SEC),
+			.tv_nsec = (__typeof__(ts.tv_nsec))(nsecs % NSEC_PER_SEC),
+		};
+		return _umtx_op((void*)address, UMTX_OP_WAIT_UINT, value, (void*)(uintptr_t)sizeof(struct timespec), (void*)&ts);
+	}
+	return _umtx_op((void*)address, UMTX_OP_WAIT_UINT, value, 0, 0);
 #else
 #error _dispatch_wait_on_address unimplemented for this platform
 #endif
@@ -553,6 +574,8 @@ _dispatch_wake_by_address(uint32_t volatile *address)
 	_dispatch_futex_wake((uint32_t *)address, INT_MAX, FUTEX_PRIVATE_FLAG);
 #elif defined(_WIN32)
 	WakeByAddressAll((uint32_t *)address);
+#elif defined(__FreeBSD__)
+	_umtx_op((void*)address, UMTX_OP_WAKE, INT_MAX, 0, 0);
 #else
 	(void)address;
 #endif
@@ -712,7 +735,7 @@ _dispatch_once_wait(dispatch_once_gate_t dgo)
 		_dispatch_futex_wait(lock, (dispatch_lock)new_v, NULL,
 				FUTEX_PRIVATE_FLAG);
 #else
-		_dispatch_thread_switch(new_v, 0, timeout++);
+		_dispatch_thread_switch((dispatch_lock)new_v, 0, timeout++);
 #endif
 		(void)timeout;
 	}
diff --git a/src/shims/lock.h b/src/shims/lock.h
index 0fcb65eb6..36ccc9920 100644
--- a/src/shims/lock.h
+++ b/src/shims/lock.h
@@ -79,6 +79,28 @@ _dispatch_lock_owner(dispatch_lock lock_value)
 	return lock_value & DLOCK_OWNER_MASK;
 }
 
+#elif defined(__FreeBSD__)
+
+#include <sys/types.h>
+#include <sys/umtx.h>
+#include <sched.h>
+
+typedef uint32_t dispatch_tid;
+typedef uint32_t dispatch_lock;
+
+#define DLOCK_OWNER_NULL			((dispatch_tid)0)
+#define DLOCK_OWNER_MASK			((dispatch_lock)0xfffffffc)
+#define DLOCK_WAITERS_BIT			((dispatch_lock)0x00000001)
+#define DLOCK_FAILED_TRYLOCK_BIT		((dispatch_lock)0x00000002)
+#define _dispatch_tid_self()			((dispatch_tid)(_dispatch_get_tsd_base()->tid << 2))
+
+DISPATCH_ALWAYS_INLINE
+static inline dispatch_tid
+_dispatch_lock_owner(dispatch_lock lock_value)
+{
+	return lock_value & DLOCK_OWNER_MASK;
+}
+
 #elif defined(_WIN32)
 
 #include <Windows.h>
diff --git a/src/swift/Source.swift b/src/swift/Source.swift
index f9c24280d..92201f8e0 100644
--- a/src/swift/Source.swift
+++ b/src/swift/Source.swift
@@ -124,8 +124,17 @@ extension DispatchSource {
 		public static let exit = ProcessEvent(rawValue: 0x80000000)
 		public static let fork = ProcessEvent(rawValue: 0x40000000)
 		public static let exec = ProcessEvent(rawValue: 0x20000000)
+#if os(FreeBSD)
+		public static let track = ProcessEvent(rawValue: 0x00000001)
+#else
 		public static let signal = ProcessEvent(rawValue: 0x08000000)
+#endif
+
+#if os(FreeBSD)
+		public static let all: ProcessEvent = [.exit, .fork, .exec, .track]
+#else
 		public static let all: ProcessEvent = [.exit, .fork, .exec, .signal]
+#endif
 	}
 #endif
 
@@ -224,7 +233,7 @@ extension DispatchSource {
 		return DispatchSource(source: source) as DispatchSourceUserDataReplace
 	}
 
-#if !os(Linux) && !os(Android) && !os(Windows) && !os(OpenBSD)
+#if !os(Linux) && !os(Android) && !os(Windows) && !os(OpenBSD) && !os(FreeBSD)
 	public class func makeFileSystemObjectSource(fileDescriptor: Int32, eventMask: FileSystemEvent, queue: DispatchQueue? = nil) -> DispatchSourceFileSystemObject {
 		let source = dispatch_source_create(_swift_dispatch_source_type_VNODE(), UInt(fileDescriptor), eventMask.rawValue, queue?.__wrapped)
 		return DispatchSource(source: source) as DispatchSourceFileSystemObject
@@ -293,7 +302,7 @@ extension DispatchSourceMemoryPressure {
 #if !os(Linux) && !os(Android) && !os(Windows) && !os(OpenBSD)
 extension DispatchSourceProcess {
 	public var handle: pid_t {
-		return pid_t(dispatch_source_get_handle(self as! DispatchSource))
+		return pid_t(CDispatch.dispatch_source_get_handle((self as! DispatchSource).__wrapped))
 	}
 
 	public var data: DispatchSource.ProcessEvent {
diff --git a/src/swift/Wrapper.swift b/src/swift/Wrapper.swift
index 63f76bcd9..1118bb6e4 100644
--- a/src/swift/Wrapper.swift
+++ b/src/swift/Wrapper.swift
@@ -183,8 +183,12 @@ extension DispatchSource : DispatchSourceMachSend,
 #endif
 
 #if !os(Linux) && !os(Android) && !os(Windows) && !os(OpenBSD)
-extension DispatchSource : DispatchSourceProcess,
-	DispatchSourceFileSystemObject {
+extension DispatchSource : DispatchSourceProcess {
+}
+#endif
+
+#if !os(Linux) && !os(Android) && !os(Windows) && !os(FreeBSD) && !os(OpenBSD)
+extension DispatchSource : DispatchSourceFileSystemObject {
 }
 #endif
 
diff --git a/src/swift/shims/DispatchOverlayShims.h b/src/swift/shims/DispatchOverlayShims.h
index 80e87a8db..5ed8dd228 100644
--- a/src/swift/shims/DispatchOverlayShims.h
+++ b/src/swift/shims/DispatchOverlayShims.h
@@ -88,6 +88,11 @@ SWIFT_DISPATCH_SOURCE_TYPE(PROC)
 SWIFT_DISPATCH_SOURCE_TYPE(VNODE)
 #endif
 
+#if defined(__FreeBSD__)
+SWIFT_DISPATCH_SOURCE_TYPE(PROC)
+SWIFT_DISPATCH_SOURCE_TYPE(VNODE)
+#endif
+
 extern void
 _swift_dispatch_source_create_abort(void);
 
diff --git a/tests/bsdtestharness.c b/tests/bsdtestharness.c
index 4b66c8956..5014cba37 100644
--- a/tests/bsdtestharness.c
+++ b/tests/bsdtestharness.c
@@ -212,7 +212,7 @@ main(int argc, char *argv[])
 #else
 	dispatch_queue_t main_q = dispatch_get_main_queue();
 
-	dispatch_source_t tmp_ds = dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC, pid, DISPATCH_PROC_EXIT, main_q);
+	dispatch_source_t tmp_ds = dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC, (uintptr_t)pid, DISPATCH_PROC_EXIT, main_q);
 	assert(tmp_ds);
 	dispatch_source_set_event_handler(tmp_ds, ^{
 		int status;
diff --git a/tests/dispatch_apply.c b/tests/dispatch_apply.c
index 1f2dfb470..01b3dfb65 100644
--- a/tests/dispatch_apply.c
+++ b/tests/dispatch_apply.c
@@ -81,7 +81,7 @@ static void busythread(void *ignored)
 static void test_apply_contended(dispatch_queue_t dq)
 {
 	uint32_t activecpu;
-#if defined(__linux__) || defined(__OpenBSD__)
+#if defined(__linux__) || defined(__OpenBSD__) || defined(__FreeBSD__)
 	activecpu = (uint32_t)sysconf(_SC_NPROCESSORS_ONLN);
 #elif defined(_WIN32)
 	SYSTEM_INFO si;
diff --git a/tests/dispatch_io_pipe.c b/tests/dispatch_io_pipe.c
index 50ddc8a87..eb32b6b87 100644
--- a/tests/dispatch_io_pipe.c
+++ b/tests/dispatch_io_pipe.c
@@ -90,6 +90,10 @@ test_get_pipe_buffer_size(int kind)
 		CloseHandle(write_handle);
 	});
 	return size;
+#elif defined(__FreeBSD__)
+	// buffer size of blocking and non-blocking pipes is different on FreeBSD
+	(void)kind;
+	return PIPE_BUF;
 #else
 	(void)kind;
 	static dispatch_once_t once;
diff --git a/tests/dispatch_select.c b/tests/dispatch_select.c
index 352157a6e..8a1147a5b 100644
--- a/tests/dispatch_select.c
+++ b/tests/dispatch_select.c
@@ -39,7 +39,7 @@ void finish(void* cxt);
 void
 stage1(int stage)
 {
-#if defined(_WIN32)
+#if defined(_WIN32) || defined(__FreeBSD__)
 	char *path = dispatch_test_get_large_file();
 	dispatch_fd_t fd = dispatch_test_fd_open(path, O_RDONLY);
 	if (fd == -1)


Reviews:
user: login: compnerd
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-17T15:55:46Z
user: login: michael-yuji
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-17T16:49:20Z
user: login: etcwilde
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-21T23:53:02Z
user: login: etcwilde
body: A few places that look like it's duplicating the `__unix__` case. Otherwise I think this looks pretty good.
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-22T02:42:56Z
user: login: etcwilde
body: LGTM
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-22T15:33:35Z
user: login: CodingCarpincho
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-22T20:10:15Z
user: login: compnerd
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-22T22:05:48Z
user: login: michael-yuji
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-22T22:09:42Z
user: login: compnerd
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-23T00:36:21Z
user: login: michael-yuji
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-23T00:49:33Z
user: login: michael-yuji
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-23T05:22:57Z

Review comments:
diff_hunk: @@ -27,6 +27,8 @@ function(get_swift_host_arch result_var_name)
     set("${result_var_name}" "armv7" PARENT_SCOPE)
   elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "armv7l")
     set("${result_var_name}" "armv7" PARENT_SCOPE)
+  elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "amd64")
+    set("${result_var_name}" "x86_64" PARENT_SCOPE)
path: cmake/modules/SwiftSupport.cmake
user: login: compnerd
body: Depending on land order - I think that https://github.com/swiftlang/swift-corelibs-libdispatch/pull/860 should obviate/clean up this part.
created_at: 2025-03-17T15:50:33Z
author_association: MEMBER
diff_hunk: @@ -189,8 +189,10 @@ void _dispatch_prohibit_transition_to_multithreaded(bool prohibit);
 
 #if TARGET_OS_MAC
 typedef mach_port_t dispatch_runloop_handle_t;
-#elif defined(__linux__) || defined(__FreeBSD__)
+#elif defined(__linux__)
 typedef int dispatch_runloop_handle_t;
+#elif defined(__FreeBSD__)
+typedef uint64_t dispatch_runloop_handle_t;
path: private/private.h
user: login: compnerd
body: Should this be `uint64_t` or `uintptr_t`? (i.e. is the handle pointer shaped or an integer shaped value?)
created_at: 2025-03-17T15:51:15Z
author_association: MEMBER
diff_hunk: @@ -247,6 +247,51 @@ _dispatch_workq_count_runnable_workers(dispatch_workq_monitor_t mon)
 
 	_dispatch_unfair_lock_unlock(&mon->registered_tid_lock);
 }
+#elif defined(__FreeBSD__)
+#include <sys/sysctl.h>
+#include <sys/proc.h>
+#include <sys/user.h>
+
+static void
+_dispatch_workq_count_runnable_workers(dispatch_workq_monitor_t mon)
+{
+	struct kinfo_proc kp[WORKQ_MAX_TRACKED_TIDS];
+	size_t size;
+  int count, runners = 0;
path: src/event/workqueue.c
user: login: compnerd
body: Indentation seems off
created_at: 2025-03-17T15:52:24Z
author_association: MEMBER
diff_hunk: @@ -189,8 +189,10 @@ void _dispatch_prohibit_transition_to_multithreaded(bool prohibit);
 
 #if TARGET_OS_MAC
 typedef mach_port_t dispatch_runloop_handle_t;
-#elif defined(__linux__) || defined(__FreeBSD__)
+#elif defined(__linux__)
 typedef int dispatch_runloop_handle_t;
+#elif defined(__FreeBSD__)
+typedef uint64_t dispatch_runloop_handle_t;
path: private/private.h
user: login: michael-yuji
body: this will be an integer shaped value. This is used to adopt the `pipe` based mechanism to bridge between Foundation https://github.com/swiftlang/swift-corelibs-foundation/pull/3004
created_at: 2025-03-17T16:49:20Z
author_association: MEMBER
diff_hunk: @@ -27,6 +27,8 @@ function(get_swift_host_arch result_var_name)
     set("${result_var_name}" "armv7" PARENT_SCOPE)
   elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "armv7l")
     set("${result_var_name}" "armv7" PARENT_SCOPE)
+  elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "amd64")
+    set("${result_var_name}" "x86_64" PARENT_SCOPE)
path: cmake/modules/SwiftSupport.cmake
user: login: etcwilde
body: https://github.com/swiftlang/swift-corelibs-libdispatch/pull/875 should have made this change unnecessary. It'll just grab the right value from the compiler.
created_at: 2025-05-21T23:53:02Z
author_association: MEMBER
diff_hunk: @@ -189,9 +189,11 @@ void _dispatch_prohibit_transition_to_multithreaded(bool prohibit);
 
 #if TARGET_OS_MAC
 typedef mach_port_t dispatch_runloop_handle_t;
-#elif defined(__linux__) || defined(__FreeBSD__)
+#elif defined(__linux__)
 typedef int dispatch_runloop_handle_t;
-#elif defined(__unix__) && !defined(__linux__) && !defined(__FreeBSD__)
+#elif defined(__FreeBSD__)
+typedef uint64_t dispatch_runloop_handle_t;
path: private/private.h
user: login: etcwilde
body: Do we need this case? The case below looks like it's is typedef'ing the same things and the `defined(__unix__)` should capture on FreeBSD as well.
created_at: 2025-05-22T02:22:09Z
author_association: MEMBER
diff_hunk: @@ -6490,7 +6490,9 @@ _dispatch_runloop_queue_get_handle(dispatch_lane_t dq)
 #elif defined(__linux__)
 	// decode: 0 is a valid fd, so offset by 1 to distinguish from NULL
 	return ((dispatch_runloop_handle_t)(uintptr_t)dq->do_ctxt) - 1;
-#elif defined(__unix__) && !defined(__linux__)
+#elif defined(__FreeBSD__)
+	return ((dispatch_runloop_handle_t)(uintptr_t)dq->do_ctxt);
path: src/queue.c
user: login: etcwilde
body: Do we need this? It looks like it's the same as the `defined(__unix__)` case below, which should capture on FreeBSD.
created_at: 2025-05-22T02:29:53Z
author_association: MEMBER
diff_hunk: @@ -6509,6 +6511,8 @@ _dispatch_runloop_queue_set_handle(dispatch_lane_t dq,
 #elif defined(__linux__)
 	// encode: 0 is a valid fd, so offset by 1 to distinguish from NULL
 	dq->do_ctxt = (void *)(uintptr_t)(handle + 1);
+#elif defined(__FreeBSD__)
path: src/queue.c
user: login: etcwilde
body: Do we need this? It looks like it's the same as the `defined(__unix__)` case below, which should capture on FreeBSD.
created_at: 2025-05-22T02:30:08Z
author_association: MEMBER
diff_hunk: @@ -54,9 +54,20 @@ _dispatch_thread_switch(dispatch_lock value, dispatch_lock_options_t flags,
 	SwitchToThread();
 }
 #endif
-#endif
-
-#if defined(__unix__)
+#elif defined(__FreeBSD__)
+#if !HAVE_UL_UNFAIR_LOCK
+DISPATCH_ALWAYS_INLINE
+static inline void
+_dispatch_thread_switch(dispatch_lock value, dispatch_lock_options_t flags,
+		uint32_t timeout)
+{
+  (void)value;
+  (void)flags;
+  (void)timeout;
+	sched_yield();
path: src/shims/lock.c
user: login: CodingCarpincho
body: This is a nitpick, but the indentation is not consistent here.
created_at: 2025-05-22T20:08:47Z
author_association: CONTRIBUTOR
diff_hunk: @@ -539,6 +550,16 @@ _dispatch_wait_on_address(uint32_t volatile *_address, uint32_t value,
 			? INFINITE : ((nsecs + 1000000) / 1000000);
 	if (dwMilliseconds == 0) return ETIMEDOUT;
 	return WaitOnAddress(address, &value, sizeof(value), dwMilliseconds) == TRUE;
+#elif defined(__FreeBSD__)
+  (void)flags;
+  if (nsecs != DISPATCH_TIME_FOREVER) {
+		struct timespec ts = {
path: src/shims/lock.c
user: login: CodingCarpincho
body: There seems to be more indentation issues here.
created_at: 2025-05-22T20:09:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -293,6 +293,52 @@ _dispatch_workq_count_runnable_workers(dispatch_workq_monitor_t mon)
 
 	_dispatch_unfair_lock_unlock(&mon->registered_tid_lock);
 }
+#elif defined(__FreeBSD__)
+#include <sys/sysctl.h>
+#include <sys/proc.h>
+#include <sys/user.h>
+
+static void
+_dispatch_workq_count_runnable_workers(dispatch_workq_monitor_t mon)
+{
+	struct kinfo_proc kp[WORKQ_MAX_TRACKED_TIDS];
+	size_t size;
+	int count, runners = 0;
+	int mib[] = {CTL_KERN, KERN_PROC, KERN_PROC_PID | KERN_PROC_INC_THREAD, (int)getpid()};
+
+	// get size we need
+	if (sysctl(mib, 4, NULL, &size, NULL, 0) < 0) {
path: src/event/workqueue.c
user: login: compnerd
body: I wonder if we should use `ARRAY_SIZEOF` for the second parameter.

```c
#define ARRAY_SIZEOF(array) (sizeof((array))/sizeof(*(array)))
```
created_at: 2025-05-22T21:59:44Z
author_association: MEMBER
diff_hunk: @@ -293,6 +293,52 @@ _dispatch_workq_count_runnable_workers(dispatch_workq_monitor_t mon)
 
 	_dispatch_unfair_lock_unlock(&mon->registered_tid_lock);
 }
+#elif defined(__FreeBSD__)
+#include <sys/sysctl.h>
+#include <sys/proc.h>
+#include <sys/user.h>
+
+static void
+_dispatch_workq_count_runnable_workers(dispatch_workq_monitor_t mon)
+{
+	struct kinfo_proc kp[WORKQ_MAX_TRACKED_TIDS];
+	size_t size;
+	int count, runners = 0;
+	int mib[] = {CTL_KERN, KERN_PROC, KERN_PROC_PID | KERN_PROC_INC_THREAD, (int)getpid()};
+
+	// get size we need
+	if (sysctl(mib, 4, NULL, &size, NULL, 0) < 0) {
+		_dispatch_debug("workq: failed to get size for kinfo_proc[] from sysctll");
+		return;
+	}
+
+	// only care about up to WORKQ_MAX_TRACKED_TIDS threads
+	size = size > sizeof(kp) ? sizeof(kp) : size;
path: src/event/workqueue.c
user: login: compnerd
body: Why not write this using `MIN`?

```suggestion
	size = MIN(sizeof(kp), size);
```
created_at: 2025-05-22T22:00:52Z
author_association: MEMBER
diff_hunk: @@ -124,8 +124,17 @@ extension DispatchSource {
 		public static let exit = ProcessEvent(rawValue: 0x80000000)
 		public static let fork = ProcessEvent(rawValue: 0x40000000)
 		public static let exec = ProcessEvent(rawValue: 0x20000000)
+#if os(FreeBSD)
+		public static let track = ProcessEvent(rawValue: 0x00000001)
path: src/swift/Source.swift
user: login: compnerd
body: Why the different name? Should we not be using the name names to maintain source consistency?
created_at: 2025-05-22T22:04:41Z
author_association: MEMBER
diff_hunk: @@ -124,8 +124,17 @@ extension DispatchSource {
 		public static let exit = ProcessEvent(rawValue: 0x80000000)
 		public static let fork = ProcessEvent(rawValue: 0x40000000)
 		public static let exec = ProcessEvent(rawValue: 0x20000000)
+#if os(FreeBSD)
+		public static let track = ProcessEvent(rawValue: 0x00000001)
path: src/swift/Source.swift
user: login: michael-yuji
body: This is because `NOTE_TRACK` is only available on FreeBSD but not Darwin
created_at: 2025-05-22T22:09:42Z
author_association: MEMBER
diff_hunk: @@ -124,8 +124,17 @@ extension DispatchSource {
 		public static let exit = ProcessEvent(rawValue: 0x80000000)
 		public static let fork = ProcessEvent(rawValue: 0x40000000)
 		public static let exec = ProcessEvent(rawValue: 0x20000000)
+#if os(FreeBSD)
+		public static let track = ProcessEvent(rawValue: 0x00000001)
path: src/swift/Source.swift
user: login: compnerd
body: Sure, having an extended FreeBSD only option is fine, but this does remove the `signal` note doesn't it? I take it that `NOTE_TRACK` is meant to replace that? If so, why not keep the name incongruous with the underlying note to allow easier porting?
created_at: 2025-05-23T00:36:21Z
author_association: MEMBER
diff_hunk: @@ -124,8 +124,17 @@ extension DispatchSource {
 		public static let exit = ProcessEvent(rawValue: 0x80000000)
 		public static let fork = ProcessEvent(rawValue: 0x40000000)
 		public static let exec = ProcessEvent(rawValue: 0x20000000)
+#if os(FreeBSD)
+		public static let track = ProcessEvent(rawValue: 0x00000001)
path: src/swift/Source.swift
user: login: michael-yuji
body: Sorry, I should've add more context in my previous reply. `NOTE_SIGNAL` on Darwin and `NOTE_TRACK` on FreeBSD are not interchangeable and each are platform specific.

`NOTE_SIGNAL` on Darwin allow users to monitor if the process was sent a signal,

on the other hand, `NOTE_TRACK` on FreeBSD cause kqueue to follow the target process across fork and automatically register events with same monitor. For example, `NOTE_TRACK | NOTE_EXIT` on a pid cause exit event of the tracing process, and all its decedents to be reported.
created_at: 2025-05-23T00:49:33Z
author_association: MEMBER
diff_hunk: @@ -293,6 +293,52 @@ _dispatch_workq_count_runnable_workers(dispatch_workq_monitor_t mon)
 
 	_dispatch_unfair_lock_unlock(&mon->registered_tid_lock);
 }
+#elif defined(__FreeBSD__)
+#include <sys/sysctl.h>
+#include <sys/proc.h>
+#include <sys/user.h>
+
+static void
+_dispatch_workq_count_runnable_workers(dispatch_workq_monitor_t mon)
+{
+	struct kinfo_proc kp[WORKQ_MAX_TRACKED_TIDS];
+	size_t size;
+	int count, runners = 0;
+	int mib[] = {CTL_KERN, KERN_PROC, KERN_PROC_PID | KERN_PROC_INC_THREAD, (int)getpid()};
+
+	// get size we need
+	if (sysctl(mib, 4, NULL, &size, NULL, 0) < 0) {
path: src/event/workqueue.c
user: login: michael-yuji
body: I don't really think it's a good idea to introduce a macro, especially just for one place.

Although it makes it more readable, but I think it is reasonable to expect the next person who need to change this line are already familiar with the correct usage of `sysctl`
created_at: 2025-05-23T05:22:57Z
author_association: MEMBER

PR comments:
user: login: etcwilde
created_at: 2025-05-22T15:33:04Z
body: @swift-ci please test
author_association: MEMBER
user: login: michael-yuji
created_at: 2025-05-22T21:15:46Z
body: @swift-ci please test
author_association: MEMBER
user: login: etcwilde
created_at: 2025-05-23T05:55:30Z
body: It's not in this PR, but I just came across an additional build failure in the tests:
https://github.com/swiftlang/swift-corelibs-libdispatch/blob/beb137b99c92c54ae6903483f40dd1a3d3b61008/tests/dispatch_workqueue.c#L33-L47

I think we need to adjust the macro to `#if defined(__linux__) || defined(__OpenBSD__) || defined(__FreeBSD__)`.
I know that FreeBSD has `sysctlbyname`, but `hw.activecpu` returns the physical core count, while `dwNumberOfProcessors` is the logical core count, so `sysconf(_SC_NPROCESSORS_ONLN)` seems to be more consistent. Up to you if you want to add that in this PR or not.

edit: Realizing I didn't add the actual failure.
The error is that we're hitting the `sysctlbyname` call without including `sys/sysctl.h`.
author_association: MEMBER
user: login: etcwilde
created_at: 2025-05-23T05:55:39Z
body: @swift-ci please test
author_association: MEMBER
user: login: etcwilde
created_at: 2025-05-23T05:56:02Z
body: @swift-ci please test Windows
author_association: MEMBER
user: login: michael-yuji
created_at: 2025-05-23T19:06:50Z
body: > It's not in this PR, but I just came across an additional build failure in the tests:
> 
> https://github.com/swiftlang/swift-corelibs-libdispatch/blob/beb137b99c92c54ae6903483f40dd1a3d3b61008/tests/dispatch_workqueue.c#L33-L47
> 
> I think we need to adjust the macro to `#if defined(__linux__) || defined(__OpenBSD__) || defined(__FreeBSD__)`. I know that FreeBSD has `sysctlbyname`, but `hw.activecpu` returns the physical core count, while `dwNumberOfProcessors` is the logical core count, so `sysconf(_SC_NPROCESSORS_ONLN)` seems to be more consistent. Up to you if you want to add that in this PR or not.
> 
> edit: Realizing I didn't add the actual failure. The error is that we're hitting the `sysctlbyname` call without including `sys/sysctl.h`.

Will be addressing this in the next PR

author_association: MEMBER