From f596e790ecbbd30c9a1ef16bed2edacb9b10685f Mon Sep 17 00:00:00 2001 From: ahwayakchih Date: Sat, 16 May 2026 19:56:25 +0200 Subject: [PATCH 01/10] Initial port to Haiku. Tested with GCC only. Known issues: - networking example keeps running forever without closing TCP server - string to float test fails on "NaN(123)" --- BUILD.md | 25 + benchmark/libponyc/CMakeLists.txt | 2 + benchmark/libponyrt/CMakeLists.txt | 2 + examples/ifdef/ifdef.pony | 3 + packages/files/_file_des.pony | 2 + packages/files/directory.pony | 34 +- packages/files/file.pony | 12 +- packages/net/_test.pony | 4 +- packages/process/_pipe.pony | 17 +- packages/process/_process.pony | 41 +- packages/process/process.pony | 4 +- packages/signals/sig.pony | 26 +- packages/signals/signals.pony | 2 +- packages/term/ansi_term.pony | 2 + packages/time/time.pony | 22 +- src/common/platform.h | 7 +- src/libponyc/CMakeLists.txt | 27 + src/libponyc/codegen/genexe.cc | 17 +- src/libponyc/codegen/genopt.cc | 9 +- src/libponyc/codegen/genopt.h | 1 + src/libponyc/codegen/genprim.c | 12 + src/libponyc/pkg/buildflagset.c | 1 + src/libponyc/pkg/ifdef.c | 4 +- src/libponyc/pkg/platformfuns.c | 6 + src/libponyc/pkg/platformfuns.h | 1 + src/libponyc/platform/paths.c | 5 +- src/libponyrt/CMakeLists.txt | 3 + src/libponyrt/asio/asio.h | 4 + src/libponyrt/asio/event.c | 6 + src/libponyrt/asio/event.h | 5 + src/libponyrt/asio/wfo.c | 928 ++++++++++++++++++++++++++++ src/libponyrt/lang/directory.c | 2 +- src/libponyrt/lang/io.c | 4 +- src/libponyrt/lang/socket.c | 7 +- src/libponyrt/lang/stat.c | 2 +- src/libponyrt/mem/alloc.c | 3 + src/libponyrt/platform/ponyassert.c | 4 +- src/libponyrt/sched/cpu.c | 24 + src/libponyrt/sched/scheduler.h | 1 + src/ponyc/CMakeLists.txt | 2 + test/libponyc/CMakeLists.txt | 2 + test/libponyc/sugar.cc | 6 +- test/libponyrt/CMakeLists.txt | 2 + test/libponyrt/lang/error.cc | 3 + 44 files changed, 1222 insertions(+), 74 deletions(-) create mode 100644 src/libponyrt/asio/wfo.c diff --git a/BUILD.md b/BUILD.md index 29b3363920..6f35ef703f 100644 --- a/BUILD.md +++ b/BUILD.md @@ -144,6 +144,31 @@ sudo make install Note that you only need to run `make libs` once the first time you build (or if the version of LLVM in the `lib/llvm/src` Git submodule changes). +## Haiku + +You'll need additional, non-default software packages on Haiku: + +- cmake +- python3.14 +- libexecinfo_devel + +A quick way to install those is to run following command in the Terminal: + +```bash +pkgman install cmake python3.14 libexecinfo_devel +``` + +Once that's done, rest of the steps are similar to other operating systems: + +```bash +make libs +make configure CC=gcc CXX=g++ +make build +make install +``` + +Note that you only need to run `make libs` once the first time you build (or if the version of LLVM in the `lib/llvm/src` Git submodule changes). + ## Windows Building on Windows requires the following: diff --git a/benchmark/libponyc/CMakeLists.txt b/benchmark/libponyc/CMakeLists.txt index 27248aa93e..20e03aa82a 100644 --- a/benchmark/libponyc/CMakeLists.txt +++ b/benchmark/libponyc/CMakeLists.txt @@ -38,6 +38,8 @@ elseif(${CMAKE_HOST_SYSTEM_NAME} MATCHES "BSD") target_link_libraries(libponyc.benchmarks PRIVATE execinfo) elseif(${CMAKE_HOST_SYSTEM_NAME} MATCHES "DragonFly") target_link_libraries(libponyc.benchmarks PRIVATE execinfo atomic) +elseif(${CMAKE_HOST_SYSTEM_NAME} MATCHES "Haiku") + target_link_libraries(libponyc.benchmarks PRIVATE execinfo atomic) else() target_link_libraries(libponyc.benchmarks PRIVATE atomic dl) target_link_options(libponyc.benchmarks PRIVATE "-static-libstdc++") diff --git a/benchmark/libponyrt/CMakeLists.txt b/benchmark/libponyrt/CMakeLists.txt index d8e8ce7633..dfc06951fb 100644 --- a/benchmark/libponyrt/CMakeLists.txt +++ b/benchmark/libponyrt/CMakeLists.txt @@ -32,6 +32,8 @@ elseif(${CMAKE_HOST_SYSTEM_NAME} MATCHES "BSD") elseif(${CMAKE_HOST_SYSTEM_NAME} MATCHES "DragonFly") target_link_libraries(libponyrt.benchmarks PRIVATE execinfo atomic) target_link_options(libponyrt.benchmarks PRIVATE "-static-libstdc++") +elseif(${CMAKE_HOST_SYSTEM_NAME} MATCHES "Haiku") + target_link_libraries(libponyrt.benchmarks PRIVATE execinfo atomic) else() target_link_libraries(libponyrt.benchmarks PRIVATE atomic dl) target_link_options(libponyrt.benchmarks PRIVATE "-static-libgcc") diff --git a/examples/ifdef/ifdef.pony b/examples/ifdef/ifdef.pony index 29395ac169..b0211102a9 100644 --- a/examples/ifdef/ifdef.pony +++ b/examples/ifdef/ifdef.pony @@ -17,6 +17,9 @@ actor Main elseif linux then // Again we know which FFI declaration to use here. @printf("Hello Linux\n".cstring()) + elseif haiku then + // Again we know which FFI declaration to use here. + @printf("Hello Haiku\n".cstring()) else // And again we know which FFI declaration to use here. @printf("Hello everyone else\n".cstring()) diff --git a/packages/files/_file_des.pony b/packages/files/_file_des.pony index 039e993944..41d2b4af1d 100644 --- a/packages/files/_file_des.pony +++ b/packages/files/_file_des.pony @@ -60,6 +60,8 @@ primitive _FileDes ifdef windows then path.set_time(atime, mtime) + elseif haiku then + path.set_time(atime, mtime) else var tv: (ILong, ILong, ILong, ILong) = ( atime._1.ilong(), atime._2.ilong() / 1000, diff --git a/packages/files/directory.pony b/packages/files/directory.pony index 579f7ec1a9..97d8a52110 100644 --- a/packages/files/directory.pony +++ b/packages/files/directory.pony @@ -8,17 +8,17 @@ use @ponyint_o_directory[I32]() use @ponyint_o_cloexec[I32]() use @ponyint_at_removedir[I32]() use @openat[I32](fd: I32, path: Pointer[U8] tag, flags: I32, ...) - if linux or bsd + if linux or bsd or haiku use @unlinkat[I32](fd: I32, target: Pointer[U8] tag, flags: I32) use @futimes[I32](fildes: I32, tv: Pointer[(ILong, ILong, ILong, ILong)]) - if not windows + if not windows and not haiku use @renameat[I32](fd: I32, from: Pointer[U8] tag, tofd: I32, to_path: Pointer[U8] tag) - if linux or bsd -use @symlinkat[I32](source: Pointer[U8] tag, fd: I32, dst: Pointer[U8] tag) if linux or bsd + if linux or bsd or haiku +use @symlinkat[I32](source: Pointer[U8] tag, fd: I32, dst: Pointer[U8] tag) if linux or bsd or haiku use @futimesat[I32](fd: I32, path: Pointer[U8] tag, timeval: Pointer[(ILong, ILong, ILong, ILong)]) if linux or bsd -use @fchownat[I32](fd: I32, path: Pointer[U8] tag, uid: U32, gid: U32, flags: I32) if linux or bsd -use @fchmodat[I32](fd: I32, path: Pointer[U8] tag, mode: U32, flag: I32) if linux or bsd +use @fchownat[I32](fd: I32, path: Pointer[U8] tag, uid: U32, gid: U32, flags: I32) if linux or bsd or haiku +use @fchmodat[I32](fd: I32, path: Pointer[U8] tag, mode: U32, flag: I32) if linux or bsd or haiku use @mkdirat[I32](fd: I32, path: Pointer[U8] tag, mode: U32) use @fdopendir[Pointer[_DirectoryHandle]](fd: I32) if posix use @opendir[Pointer[_DirectoryHandle]](name: Pointer[U8] tag) if posix @@ -124,7 +124,7 @@ class Directory end let h = - ifdef linux or bsd then + ifdef linux or bsd or haiku then let fd = @openat(fd', ".".cstring(), @ponyint_o_rdonly() @@ -183,7 +183,7 @@ class Directory let path' = FilePath.from(path, target, path.caps)? - ifdef linux or bsd then + ifdef linux or bsd or haiku then let fd' = @openat(_fd, target.cstring(), @ponyint_o_rdonly() @@ -210,7 +210,7 @@ class Directory try let path' = FilePath.from(path, target, path.caps)? - ifdef linux or bsd then + ifdef linux or bsd or haiku then var offset: ISize = 0 repeat @@ -249,7 +249,7 @@ class Directory let path' = FilePath.from(path, target, path.caps)? - ifdef linux or bsd then + ifdef linux or bsd or haiku then let fd' = @openat(_fd, target.cstring(), @ponyint_o_rdwr() @@ -274,7 +274,7 @@ class Directory let path' = FilePath.from(path, target, path.caps - FileWrite)? - ifdef linux or bsd then + ifdef linux or bsd or haiku then let fd' = @openat(_fd, target.cstring(), @ponyint_o_rdonly() or @ponyint_o_cloexec(), @@ -330,7 +330,7 @@ class Directory let path' = FilePath.from(path, target, path.caps)? - ifdef linux or bsd then + ifdef linux or bsd or haiku then FileInfo._relative(_fd, path', target)? else FileInfo(path')? @@ -351,7 +351,7 @@ class Directory try let path' = FilePath.from(path, target, path.caps)? - ifdef linux or bsd then + ifdef linux or bsd or haiku then 0 == @fchmodat(_fd, target.cstring(), mode.u32(), I32(0)) else path'.chmod(mode) @@ -375,7 +375,7 @@ class Directory try let path' = FilePath.from(path, target, path.caps)? - ifdef linux or bsd then + ifdef linux or bsd or haiku then 0 == @fchownat(_fd, target.cstring(), uid, gid, I32(0)) else path'.chown(uid, gid) @@ -442,7 +442,7 @@ class Directory try let path' = FilePath.from(path, link_name, path.caps)? - ifdef linux or bsd then + ifdef linux or bsd or haiku then 0 == @symlinkat(source.path.cstring(), _fd, link_name.cstring()) else source.symlink(path') @@ -467,7 +467,7 @@ class Directory try let path' = FilePath.from(path, target, path.caps)? - ifdef linux or bsd then + ifdef linux or bsd or haiku then let fi = FileInfo(path')? if fi.directory and not fi.symlink then @@ -510,7 +510,7 @@ class Directory let path' = FilePath.from(path, source, path.caps)? let path'' = FilePath.from(to.path, target, to.path.caps)? - ifdef linux or bsd then + ifdef linux or bsd or haiku then 0 == @renameat(_fd, source.cstring(), to._fd, target.cstring()) else path'.rename(path'') diff --git a/packages/files/file.pony b/packages/files/file.pony index c2cb8ccbf6..b2847e47a2 100644 --- a/packages/files/file.pony +++ b/packages/files/file.pony @@ -27,13 +27,19 @@ primitive FileExists primitive FilePermissionDenied primitive _EBADF - fun apply(): I32 => 9 + fun apply(): I32 => + ifdef haiku then -2147459072 // 0x80006000 + else 9 end primitive _EEXIST - fun apply(): I32 => 17 + fun apply(): I32 => + ifdef haiku then -2147459070 // 0x80006002 + else 17 end primitive _EACCES - fun apply(): I32 => 13 + fun apply(): I32 => + ifdef haiku then -2147483646 // 0x80000002 + else 13 end type FileErrNo is ( FileOK diff --git a/packages/net/_test.pony b/packages/net/_test.pony index 1d7b2d6fbe..932b6ea3ed 100644 --- a/packages/net/_test.pony +++ b/packages/net/_test.pony @@ -51,13 +51,13 @@ class \nodoc\ _TestPing is UDPNotify (_, let service) = ip.name()? let list = if ip.ip4() then - ifdef bsd then + ifdef bsd or haiku then DNS.ip4(auth, "", service) else DNS.broadcast_ip4(auth, service) end else - ifdef bsd then + ifdef bsd or haiku then DNS.ip6(auth, "", service) else DNS.broadcast_ip6(auth, service) diff --git a/packages/process/_pipe.pony b/packages/process/_pipe.pony index 5842540d76..ea58e07373 100644 --- a/packages/process/_pipe.pony +++ b/packages/process/_pipe.pony @@ -21,16 +21,24 @@ use @WriteFile[Bool](handle: Pointer[None], buffer: Pointer[None], use @GetLastError[I32]() if not posix primitive _FSETFL - fun apply(): I32 => 4 + fun apply(): I32 => + ifdef haiku then 16 + else 4 end primitive _FGETFL - fun apply(): I32 => 3 + fun apply(): I32 => + ifdef haiku then 8 + else 3 end primitive _FSETFD - fun apply(): I32 => 2 + fun apply(): I32 => + ifdef haiku then 4 + else 2 end primitive _FGETFD - fun apply(): I32 => 1 + fun apply(): I32 => + ifdef haiku then 2 + else 1 end primitive _FDCLOEXEC fun apply(): I32 => 1 @@ -40,6 +48,7 @@ primitive _ONONBLOCK fun apply(): I32 => ifdef bsd or osx then 4 elseif linux then 2048 + elseif haiku then 128 else compile_error "no O_NONBLOCK" end // The pipe has been ended. diff --git a/packages/process/_process.pony b/packages/process/_process.pony index 93d31d459b..89aca5d6d4 100644 --- a/packages/process/_process.pony +++ b/packages/process/_process.pony @@ -45,27 +45,36 @@ primitive _STDERRFILENO // Operation not permitted primitive _EPERM - fun apply(): I32 => 1 + fun apply(): I32 => + ifdef haiku then -2147483633 // 0x8000000f + else 1 end // No such process primitive _ESRCH - fun apply(): I32 => 3 + fun apply(): I32 => + ifdef haiku then -2147454963 // 0x8000700d + else 3 end // Interrupted function primitive _EINTR - fun apply(): I32 => 4 + fun apply(): I32 => + ifdef haiku then -2147483638 // 0x8000000a + else 4 end // Try again primitive _EAGAIN fun apply(): I32 => ifdef bsd or osx then 35 elseif linux then 11 + elseif haiku then -2147483637 // 0x8000000b elseif windows then 22 else compile_error "no EAGAIN" end // Invalid argument primitive _EINVAL - fun apply(): I32 => 22 + fun apply(): I32 => + ifdef haiku then -2147483643 // 0x80000005 + else 22 end primitive _EXOSERR fun apply(): I32 => 71 @@ -320,28 +329,36 @@ primitive _WaitPidStatus """ fun exited(wstatus: I32): Bool => - termsig(wstatus) == 0 + ifdef haiku then (wstatus and (not 0xff)) == 0 + else termsig(wstatus) == 0 end fun exit_code(wstatus: I32): I32 => - (wstatus and 0xff00) >> 8 + ifdef haiku then (wstatus and 0xff) + else (wstatus and 0xff00) >> 8 end fun signaled(wstatus: I32): Bool => - ((termsig(wstatus) + 1) >> 1).i8() > 0 + ifdef haiku then ((wstatus >> 8) and 0xff) != 0 + else ((termsig(wstatus) + 1) >> 1).i8() > 0 end fun termsig(wstatus: I32): I32 => - (wstatus and 0x7f) + ifdef haiku then ((wstatus >> 8) and 0xff) + else (wstatus and 0x7f) end fun stopped(wstatus: I32): Bool => - (wstatus and 0xff) == 0x7f + ifdef haiku then ((wstatus >> 16) and 0xff) != 0 + else (wstatus and 0xff) == 0x7f end fun stopsig(wstatus: I32): I32 => - exit_code(wstatus) + ifdef haiku then ((wstatus >> 16) and 0xff) + else exit_code(wstatus) end fun coredumped(wstatus: I32): Bool => - (wstatus and 0x80) != 0 + ifdef haiku then (wstatus and 0x10000) != 0 + else (wstatus and 0x80) != 0 end fun continued(wstatus: I32): Bool => - wstatus == 0xffff + ifdef haiku then (wstatus and 0x20000) != 0 + else wstatus == 0xffff end class _ProcessWindows is _Process diff --git a/packages/process/process.pony b/packages/process/process.pony index f68976a52d..621758126a 100644 --- a/packages/process/process.pony +++ b/packages/process/process.pony @@ -67,8 +67,8 @@ class ProcessClient is ProcessNotify ## Process portability -The ProcessMonitor supports spawning processes on Linux, FreeBSD, OSX and -Windows. +The ProcessMonitor supports spawning processes on Linux, FreeBSD, OSX, +Haiku and Windows. ## Shutting down ProcessMonitor and external process diff --git a/packages/signals/sig.pony b/packages/signals/sig.pony index 9032dee558..ea317963cc 100644 --- a/packages/signals/sig.pony +++ b/packages/signals/sig.pony @@ -8,12 +8,13 @@ primitive Sig fun quit(): U32 => 3 fun ill(): U32 => - ifdef linux or bsd or osx then 4 + ifdef linux or bsd or osx or haiku then 4 else compile_error "no SIGINT" end fun trap(): U32 => ifdef linux or bsd or osx then 5 + elseif haiku then 22 else compile_error "no SIGTRAP" end @@ -25,7 +26,7 @@ primitive Sig end fun fpe(): U32 => - ifdef linux or bsd or osx then 8 + ifdef linux or bsd or osx or haiku then 8 else compile_error "no SIGFPE" end @@ -34,22 +35,25 @@ primitive Sig fun bus(): U32 => ifdef bsd or osx then 10 elseif linux then 7 + elseif haiku then 30 else compile_error "no SIGBUS" end fun segv(): U32 => - ifdef linux or bsd or osx then 11 + ifdef linux or bsd or osx or haiku then 11 else compile_error "no SIGSEGV" end fun sys(): U32 => ifdef bsd or osx then 12 elseif linux then 31 + elseif haiku then 25 else compile_error "no SIGSYS" end fun pipe(): U32 => ifdef linux or bsd or osx then 13 + elseif haiku then 7 else compile_error "no SIGPIPE" end @@ -59,6 +63,7 @@ primitive Sig fun urg(): U32 => ifdef bsd or osx then 16 elseif linux then 23 + elseif haiku then 26 else compile_error "no SIGURG" end @@ -70,34 +75,40 @@ primitive Sig fun stop(): U32 => ifdef bsd or osx then 17 elseif linux then 19 + elseif haiku then 10 else compile_error "no SIGSTOP" end fun tstp(): U32 => ifdef bsd or osx then 18 elseif linux then 20 + elseif haiku then 13 else compile_error "no SIGTSTP" end fun cont(): U32 => ifdef bsd or osx then 19 elseif linux then 18 + elseif haiku then 12 else compile_error "no SIGCONT" end fun chld(): U32 => ifdef bsd or osx then 20 elseif linux then 17 + elseif haiku then 5 else compile_error "no SIGCHLD" end fun ttin(): U32 => ifdef linux or bsd or osx then 21 + elseif haiku then 16 else compile_error "no SIGTTIN" end fun ttou(): U32 => ifdef linux or bsd or osx then 22 + elseif haiku then 17 else compile_error "no SIGTTOU" end @@ -109,26 +120,31 @@ primitive Sig fun xcpu(): U32 => ifdef linux or bsd or osx then 24 + elseif haiku then 28 else compile_error "no SIGXCPU" end fun xfsz(): U32 => ifdef linux or bsd or osx then 25 + elseif haiku then 29 else compile_error "no SIGXFSZ" end fun vtalrm(): U32 => ifdef linux or bsd or osx then 26 + elseif haiku then 27 else compile_error "no SIGVTALRM" end fun prof(): U32 => ifdef linux or bsd or osx then 27 + elseif haiku then 24 else compile_error "no SIGPROF" end fun winch(): U32 => ifdef linux or bsd or osx then 28 + elseif haiku then 20 else compile_error "no SIGWINCH" end @@ -145,6 +161,7 @@ primitive Sig fun usr1(): U32 => ifdef bsd or osx then 30 elseif linux then 10 + elseif haiku then 18 else compile_error "no SIGUSR1" end @@ -152,10 +169,11 @@ primitive Sig ifdef not "scheduler_scaling_pthreads" then ifdef bsd or osx then 31 elseif linux then 12 + elseif haiku then 19 else compile_error "no SIGUSR2" end else - ifdef linux or bsd or osx then + ifdef linux or bsd or osx or haiku then compile_error "SIGUSR2 reserved for runtime use" else compile_error "no SIGUSR2" diff --git a/packages/signals/signals.pony b/packages/signals/signals.pony index b1aa1d4a16..b7381aeab6 100644 --- a/packages/signals/signals.pony +++ b/packages/signals/signals.pony @@ -36,7 +36,7 @@ class TermHandler is SignalNotify ## Signal portability The `Sig` primitive provides support for portable signal handling across Linux, -FreeBSD and OSX. Signals are not supported on Windows and attempting to use +FreeBSD, Haiku and OSX. Signals are not supported on Windows and attempting to use them will cause a compilation error. ## Shutting down handlers diff --git a/packages/term/ansi_term.pony b/packages/term/ansi_term.pony index 2a2bb5b950..f3e8d9ec19 100644 --- a/packages/term/ansi_term.pony +++ b/packages/term/ansi_term.pony @@ -38,6 +38,8 @@ primitive _TIOCGWINSZ 21523 elseif osx or bsd then 1074295912 + elseif haiku then + 32780 else 0 end diff --git a/packages/time/time.pony b/packages/time/time.pony index 5c213078f3..60d22c3dc1 100644 --- a/packages/time/time.pony +++ b/packages/time/time.pony @@ -12,9 +12,9 @@ use @"llvm.x86.rdtsc"[U64]() use @"llvm.readcyclecounter"[U64]() use @time[I64](tloc: Pointer[None]) use @clock_gettime[I32](clock: U32, ts: Pointer[(I64, I64)]) - if lp64 and (linux or bsd) + if lp64 and (linux or bsd or haiku) use @clock_gettime[I32](clock: U32, ts: Pointer[(I32, I32)]) - if ilp32 and (linux or bsd) + if ilp32 and (linux or bsd or haiku) use @clock_gettime_nsec_np[U64](clock: U32) if osx use @gettimeofday[I32](tp: Pointer[(I64, I64)], tzp: Pointer[None]) if osx @@ -29,6 +29,8 @@ primitive _ClockRealtime fun apply(): U32 => ifdef linux or bsd then 0 + elseif haiku then + -1 else compile_error "no clock_gettime realtime clock" end @@ -41,6 +43,8 @@ primitive _ClockMonotonic 3 elseif bsd then 4 + elseif haiku then + 0 else compile_error "no clock_gettime monotonic clock" end @@ -66,7 +70,7 @@ primitive Time var ts: (I64, I64) = (0, 0) @gettimeofday(addressof ts, USize(0)) (ts._1, ts._2 * 1000) - elseif linux or bsd then + elseif linux or bsd or haiku then _clock_gettime(_ClockRealtime) elseif windows then var ft: (U32, U32) = (0, 0) @@ -92,7 +96,7 @@ primitive Time """ ifdef osx then @clock_gettime_nsec_np(_ClockUptimeRaw()) / 1000000 - elseif linux or bsd then + elseif linux or bsd or haiku then var ts = _clock_gettime(_ClockMonotonic) ((ts._1 * 1000) + (ts._2 / 1000000)).u64() elseif windows then @@ -107,7 +111,7 @@ primitive Time """ ifdef osx then @clock_gettime_nsec_np(_ClockUptimeRaw()) / 1000 - elseif linux or bsd then + elseif linux or bsd or haiku then var ts = _clock_gettime(_ClockMonotonic) ((ts._1 * 1000000) + (ts._2 / 1000)).u64() elseif windows then @@ -122,7 +126,7 @@ primitive Time """ ifdef osx then @clock_gettime_nsec_np(_ClockUptimeRaw()) - elseif linux or bsd then + elseif linux or bsd or haiku then var ts = _clock_gettime(_ClockMonotonic) ((ts._1 * 1000000000) + ts._2).u64() elseif windows then @@ -168,13 +172,13 @@ primitive Time fun _clock_gettime(clock: _Clock): (I64, I64) => """ - Return a clock time on linux and bsd. + Return a clock time on linux, bsd and haiku. """ - ifdef lp64 and (linux or bsd) then + ifdef lp64 and (linux or bsd or haiku) then var ts: (I64, I64) = (0, 0) @clock_gettime(clock(), addressof ts) ts - elseif ilp32 and (linux or bsd) then + elseif ilp32 and (linux or bsd or haiku) then var ts: (I32, I32) = (0, 0) @clock_gettime(clock(), addressof ts) (ts._1.i64(), ts._2.i64()) diff --git a/src/common/platform.h b/src/common/platform.h index a3f7126f88..4beaf76990 100644 --- a/src/common/platform.h +++ b/src/common/platform.h @@ -37,6 +37,10 @@ #elif defined(__OpenBSD__) # define PLATFORM_IS_BSD # define PLATFORM_IS_OPENBSD +#elif defined(__HAIKU__) +# define PLATFORM_IS_HAIKU +# define _DEFAULT_SOURCE +# define _BSD_SOURCE #elif defined(__EMSCRIPTEN__) # define PLATFORM_IS_EMSCRIPTEN # define USE_SCHEDULER_SCALING_PTHREADS @@ -109,7 +113,8 @@ #endif #if defined(PLATFORM_IS_MACOSX) || defined(PLATFORM_IS_LINUX) \ - || defined(PLATFORM_IS_BSD) || defined(PLATFORM_IS_EMSCRIPTEN) + || defined(PLATFORM_IS_BSD) || defined(PLATFORM_IS_HAIKU) \ + || defined(PLATFORM_IS_EMSCRIPTEN) # define PLATFORM_IS_POSIX_BASED #endif diff --git a/src/libponyc/CMakeLists.txt b/src/libponyc/CMakeLists.txt index 47e0ff60ce..834ada4769 100644 --- a/src/libponyc/CMakeLists.txt +++ b/src/libponyc/CMakeLists.txt @@ -242,6 +242,33 @@ elseif(${CMAKE_HOST_SYSTEM_NAME} MATCHES "DragonFly") COMMAND $<$:${CMAKE_COMMAND}> ARGS -E copy libponyc-standalone.a ${CMAKE_BINARY_DIR}/../relwithdebinfo${PONY_OUTPUT_SUFFIX}/ COMMAND $<$:${CMAKE_COMMAND}> ARGS -E copy libponyc-standalone.a ${CMAKE_BINARY_DIR}/../minsizerel${PONY_OUTPUT_SUFFIX}/ ) +elseif(${CMAKE_HOST_SYSTEM_NAME} MATCHES "Haiku") + # add a rule to generate the standalone library if needed + # there's no static libstdc++, so it needs to be linked separately + add_custom_command(OUTPUT libponyc-standalone.a + COMMAND echo "create libponyc-standalone.a" > standalone.mri + COMMAND echo "addlib ${PROJECT_SOURCE_DIR}/../../build/libs/lib/libblake2.a" >> standalone.mri + COMMAND find ${PROJECT_SOURCE_DIR}/../../build/libs/ -name "libLLVM*.a" | xargs -I % -n 1 echo 'addlib %' >> standalone.mri + COMMAND find ${PROJECT_SOURCE_DIR}/../../build/libs/ -name "liblld*.a" | xargs -I % -n 1 echo 'addlib %' >> standalone.mri + COMMAND echo "addlib $" >> standalone.mri + COMMAND echo "save" >> standalone.mri + COMMAND echo "end" >> standalone.mri + COMMAND ${CMAKE_AR} -M < standalone.mri + COMMAND ranlib libponyc-standalone.a + DEPENDS $ ${STANDALONE_ARCHIVES} + ) + # add a separate target that depends on the standalone library file + add_custom_target(libponyc-standalone ALL + DEPENDS libponyc + SOURCES libponyc-standalone.a + ) + # copy the generated file after it is built + add_custom_command(TARGET libponyc-standalone POST_BUILD + COMMAND $<$:${CMAKE_COMMAND}> ARGS -E copy libponyc-standalone.a ${CMAKE_BINARY_DIR}/../debug${PONY_OUTPUT_SUFFIX}/ + COMMAND $<$:${CMAKE_COMMAND}> ARGS -E copy libponyc-standalone.a ${CMAKE_BINARY_DIR}/../release${PONY_OUTPUT_SUFFIX}/ + COMMAND $<$:${CMAKE_COMMAND}> ARGS -E copy libponyc-standalone.a ${CMAKE_BINARY_DIR}/../relwithdebinfo${PONY_OUTPUT_SUFFIX}/ + COMMAND $<$:${CMAKE_COMMAND}> ARGS -E copy libponyc-standalone.a ${CMAKE_BINARY_DIR}/../minsizerel${PONY_OUTPUT_SUFFIX}/ + ) else() execute_process(COMMAND ${CMAKE_CXX_COMPILER} --print-file-name=libstdc++.a OUTPUT_VARIABLE LIBSTDCXX_PATH diff --git a/src/libponyc/codegen/genexe.cc b/src/libponyc/codegen/genexe.cc index 48f975789b..e5d101eb67 100644 --- a/src/libponyc/codegen/genexe.cc +++ b/src/libponyc/codegen/genexe.cc @@ -261,7 +261,7 @@ LLVMValueRef gen_main(compile_t* c, reach_type_t* t_main, reach_type_t* t_env) return func; } -#if defined(PLATFORM_IS_LINUX) || defined(PLATFORM_IS_BSD) +#if defined(PLATFORM_IS_LINUX) || defined(PLATFORM_IS_BSD) || defined(PLATFORM_IS_HAIKU) static const char* env_cc_or_pony_compiler(bool* out_fallback_linker) { const char* cc = getenv("CC"); @@ -1559,7 +1559,7 @@ static bool link_exe(compile_t* c, ast_t* program, ponyint_pool_free_size(dsym_len, dsym_cmd); } -#elif defined(PLATFORM_IS_LINUX) || defined(PLATFORM_IS_BSD) +#elif defined(PLATFORM_IS_LINUX) || defined(PLATFORM_IS_BSD) || defined(PLATFORM_IS_HAIKU) const char* file_exe = suffix_filename(c, c->opt->output, "", c->filename, ""); @@ -1581,7 +1581,7 @@ static bool link_exe(compile_t* c, ast_t* program, const char* fuseld = strlen(fuseldcmd) ? "-fuse-ld=" : ""; const char* ldl = target_is_linux(c->opt->triple) ? "-ldl" : ""; const char* atomic = - (target_is_linux(c->opt->triple) || target_is_dragonfly(c->opt->triple)) + (target_is_linux(c->opt->triple) || target_is_dragonfly(c->opt->triple) || target_is_haiku(c->opt->triple)) ? "-latomic" : ""; const char* staticbin = c->opt->staticbin ? "-static" : ""; const char* dtrace_args = @@ -1591,11 +1591,17 @@ static bool link_exe(compile_t* c, ast_t* program, ""; #endif const char* lexecinfo = -#if (defined(PLATFORM_IS_BSD)) +#if (defined(PLATFORM_IS_BSD) || defined(PLATFORM_IS_HAIKU)) "-lexecinfo"; #else ""; #endif + const char* lplatform = +#if (defined(PLATFORM_IS_HAIKU)) + "-lroot -lnetwork -lstdc++ -lbsd"; +#else + ""; +#endif const char* sanitizer_arg = #if defined(PONY_SANITIZER) @@ -1610,6 +1616,7 @@ static bool link_exe(compile_t* c, ast_t* program, size_t ld_len = 512 + strlen(file_exe) + strlen(file_o) + strlen(lib_args) + strlen(arch) + strlen(mcx16_arg) + strlen(fuseld) + + strlen(lplatform) + strlen(ldl) + strlen(atomic) + strlen(staticbin) + strlen(dtrace_args) + strlen(lexecinfo) + strlen(fuseldcmd) + strlen(sanitizer_arg) + strlen(arm_linker_args); @@ -1640,7 +1647,7 @@ static bool link_exe(compile_t* c, ast_t* program, "%s", linker, file_exe, arch, mcx16_arg, staticbin, fuseld, fuseldcmd, file_o, arm_linker_args, - lib_args, dtrace_args, ponyrt, ldl, lexecinfo, atomic, sanitizer_arg + lib_args, dtrace_args, ponyrt, lplatform, ldl, lexecinfo, atomic, sanitizer_arg ); if(c->opt->verbosity >= VERBOSITY_TOOL_INFO) diff --git a/src/libponyc/codegen/genopt.cc b/src/libponyc/codegen/genopt.cc index 71983608ef..7bd0751938 100644 --- a/src/libponyc/codegen/genopt.cc +++ b/src/libponyc/codegen/genopt.cc @@ -1157,12 +1157,19 @@ bool target_is_windows(char* t) return triple.isOSWindows(); } +bool target_is_haiku(char* t) +{ + Triple triple = Triple(t); + + return triple.isOSHaiku(); +} + bool target_is_posix(char* t) { Triple triple = Triple(t); return triple.isMacOSX() || triple.isOSFreeBSD() || triple.isOSLinux() - || triple.isOSDragonFly() || triple.isOSOpenBSD(); + || triple.isOSDragonFly() || triple.isOSOpenBSD() || triple.isOSHaiku(); } bool target_is_x86(char* t) diff --git a/src/libponyc/codegen/genopt.h b/src/libponyc/codegen/genopt.h index e4c5847fda..9029052d0c 100644 --- a/src/libponyc/codegen/genopt.h +++ b/src/libponyc/codegen/genopt.h @@ -14,6 +14,7 @@ bool target_is_dragonfly(char* triple); bool target_is_openbsd(char* triple); bool target_is_macosx(char* triple); bool target_is_windows(char* triple); +bool target_is_haiku(char* triple); bool target_is_posix(char* triple); bool target_is_x86(char* triple); bool target_is_arm(char* triple); diff --git a/src/libponyc/codegen/genprim.c b/src/libponyc/codegen/genprim.c index 5d2f5cc80b..14fc55dbf9 100644 --- a/src/libponyc/codegen/genprim.c +++ b/src/libponyc/codegen/genprim.c @@ -1131,6 +1131,17 @@ static void platform_linux(compile_t* c, reach_type_t* t, token_id cap) codegen_finishfun(c); } +static void platform_haiku(compile_t* c, reach_type_t* t, token_id cap) +{ + FIND_METHOD("haiku", cap); + start_function(c, t, m, c->i1, &c_t->use_type, 1); + + LLVMValueRef result = + LLVMConstInt(c->i1, target_is_haiku(c->opt->triple), false); + genfun_build_ret(c, result); + codegen_finishfun(c); +} + static void platform_osx(compile_t* c, reach_type_t* t, token_id cap) { FIND_METHOD("osx", cap); @@ -1289,6 +1300,7 @@ void genprim_platform_methods(compile_t* c, reach_type_t* t) BOX_FUNCTION(platform_dragonfly, t); BOX_FUNCTION(platform_openbsd, t); BOX_FUNCTION(platform_linux, t); + BOX_FUNCTION(platform_haiku, t); BOX_FUNCTION(platform_osx, t); BOX_FUNCTION(platform_windows, t); BOX_FUNCTION(platform_x86, t); diff --git a/src/libponyc/pkg/buildflagset.c b/src/libponyc/pkg/buildflagset.c index c99765c1d0..220f266c8a 100644 --- a/src/libponyc/pkg/buildflagset.c +++ b/src/libponyc/pkg/buildflagset.c @@ -16,6 +16,7 @@ static const char* _os_flags[] = OS_DRAGONFLY_NAME, OS_OPENBSD_NAME, OS_LINUX_NAME, + OS_HAIKU_NAME, OS_MACOSX_NAME, OS_WINDOWS_NAME, "unknown_OS", diff --git a/src/libponyc/pkg/ifdef.c b/src/libponyc/pkg/ifdef.c index e5a00b6b1b..1486c67bd5 100644 --- a/src/libponyc/pkg/ifdef.c +++ b/src/libponyc/pkg/ifdef.c @@ -75,7 +75,9 @@ static void cond_normalise(ast_t** astp) NODE(TK_IFDEFOR, NODE(TK_IFDEFFLAG, ID(OS_LINUX_NAME)) NODE(TK_IFDEFFLAG, ID(OS_MACOSX_NAME))) - NODE(TK_IFDEFFLAG, ID(OS_BSD_NAME)))); + NODE(TK_IFDEFOR, + NODE(TK_IFDEFFLAG, ID(OS_BSD_NAME)) + NODE(TK_IFDEFFLAG, ID(OS_HAIKU_NAME))))); break; } diff --git a/src/libponyc/pkg/platformfuns.c b/src/libponyc/pkg/platformfuns.c index 76022ecb35..9638e5a4f7 100644 --- a/src/libponyc/pkg/platformfuns.c +++ b/src/libponyc/pkg/platformfuns.c @@ -59,6 +59,12 @@ bool os_is_target(const char* attribute, bool release, bool* out_is_target, pass return true; } + if(!strcmp(attribute, OS_HAIKU_NAME)) + { + *out_is_target = target_is_haiku(options->triple); + return true; + } + if(!strcmp(attribute, OS_X86_NAME)) { *out_is_target = target_is_x86(options->triple); diff --git a/src/libponyc/pkg/platformfuns.h b/src/libponyc/pkg/platformfuns.h index bb168e44c5..204a78604e 100644 --- a/src/libponyc/pkg/platformfuns.h +++ b/src/libponyc/pkg/platformfuns.h @@ -15,6 +15,7 @@ PONY_EXTERN_C_BEGIN #define OS_MACOSX_NAME "osx" #define OS_WINDOWS_NAME "windows" #define OS_POSIX_NAME "posix" +#define OS_HAIKU_NAME "haiku" #define OS_X86_NAME "x86" #define OS_ARM_NAME "arm" #define OS_LP64_NAME "lp64" diff --git a/src/libponyc/platform/paths.c b/src/libponyc/platform/paths.c index 4dbfe84c54..41fc72e265 100644 --- a/src/libponyc/platform/paths.c +++ b/src/libponyc/platform/paths.c @@ -12,6 +12,9 @@ #include #include #include +#elif defined(PLATFORM_IS_HAIKU) +#include +#include #endif #ifdef PLATFORM_IS_WINDOWS @@ -228,7 +231,7 @@ bool get_compiler_exe_path(char* output_path, const char* argv0) if(success) output_path[r] = '\0'; -#elif defined PLATFORM_IS_OPENBSD +#elif defined PLATFORM_IS_OPENBSD || defined PLATFORM_IS_HAIKU if (argv0 != NULL && (*argv0 == '/' || *argv0 == '.')) { if (pony_realpath(argv0, output_path) != NULL) diff --git a/src/libponyrt/CMakeLists.txt b/src/libponyrt/CMakeLists.txt index 05e274ee62..0d9814cd36 100644 --- a/src/libponyrt/CMakeLists.txt +++ b/src/libponyrt/CMakeLists.txt @@ -11,6 +11,7 @@ set(_c_src asio/event.c asio/iocp.c asio/kqueue.c + asio/wfo.c ds/fun.c ds/hash.c ds/list.c @@ -125,6 +126,8 @@ else() set(LIBPONYRT_ARCHIVE libponyrt.a) elseif(${CMAKE_HOST_SYSTEM_NAME} MATCHES "BSD|DragonFly") set(LIBPONYRT_ARCHIVE libponyrt.a) + elseif(${CMAKE_HOST_SYSTEM_NAME} MATCHES "Haiku") + set(LIBPONYRT_ARCHIVE libponyrt.a) else() set(LIBPONYRT_ARCHIVE libponyrt-pic.a) endif() diff --git a/src/libponyrt/asio/asio.h b/src/libponyrt/asio/asio.h index c9129968c5..4a5b227a3a 100644 --- a/src/libponyrt/asio/asio.h +++ b/src/libponyrt/asio/asio.h @@ -14,6 +14,9 @@ #elif defined(PLATFORM_IS_WINDOWS) # define ASIO_USE_IOCP # define PONY_ASIO_SCHEDULER_INDEX PONY_IOCP_SCHEDULER_INDEX +#elif defined(PLATFORM_IS_HAIKU) +# define ASIO_USE_WFO +# define PONY_ASIO_SCHEDULER_INDEX PONY_WFO_SCHEDULER_INDEX #elif defined(PLATFORM_IS_EMSCRIPTEN) # define ASIO_USE_EMSCRIPTEN # define PONY_ASIO_SCHEDULER_INDEX PONY_EPOLL_SCHEDULER_INDEX @@ -67,6 +70,7 @@ bool ponyint_asio_start(); * ASIO base. * * The concrete mechanism is platform specific: + * Haiku: wait_for_objects * Linux: epoll * MacOSX/BSD: kqueue * Windows: I/O completion ports - to be implemented. diff --git a/src/libponyrt/asio/event.c b/src/libponyrt/asio/event.c index 4ee01f6cab..98860dffd2 100644 --- a/src/libponyrt/asio/event.c +++ b/src/libponyrt/asio/event.c @@ -41,6 +41,12 @@ PONY_API asio_event_t* pony_asio_event_create(pony_actor_t* owner, int fd, atomic_store_explicit(&ev->iocp_token->refcount, 1, memory_order_relaxed); #endif + +#ifdef ASIO_USE_WFO + ev->wfo_id = -1; + ev->timerID = NULL; +#endif + owner->live_asio_events = owner->live_asio_events + 1; // The event is effectively being sent to another thread, so mark it here. diff --git a/src/libponyrt/asio/event.h b/src/libponyrt/asio/event.h index ccef03f351..448029189d 100644 --- a/src/libponyrt/asio/event.h +++ b/src/libponyrt/asio/event.h @@ -50,6 +50,11 @@ typedef struct asio_event_t HANDLE timer; /* timer handle */ iocp_token_t* iocp_token; /* shared liveness token for IOCP callbacks */ #endif +#ifdef PLATFORM_IS_HAIKU + int32_t wfo_id; /* object_wait_info id, controlled only by wfo */ + timer_t timerID; /* timer handle */ +#endif + } asio_event_t; /// Message that carries an event and event flags. diff --git a/src/libponyrt/asio/wfo.c b/src/libponyrt/asio/wfo.c new file mode 100644 index 0000000000..b2bd34b43b --- /dev/null +++ b/src/libponyrt/asio/wfo.c @@ -0,0 +1,928 @@ +#define PONY_WANT_ATOMIC_DEFS + +#include "asio.h" +#include "event.h" +#ifdef ASIO_USE_WFO + +#include "../mem/pool.h" +#include "../sched/cpu.h" +#include "../sched/scheduler.h" +#include "../sched/systematic_testing.h" +#include "../tracing/tracing.h" +#include "ponyassert.h" +#include +#include +#include +#include +#include +#include +#include + +#if defined(USE_SYSTEMATIC_TESTING) && !defined(USE_LOGGER_THREAD) + #define USE_LOGGER_THREAD + #include +#endif + +#define MAX_SIGNAL __MAX_SIGNO + 1 + +enum // Internal WFO event requests +{ +//ASIO_DISPOSABLE = 0, + WFO_WAKEUP = 1, + WFO_TIMER_FIRED = 2, + WFO_SUBSCRIBE = 3, + WFO_RESUBSCRIBE = 4, + WFO_UNSUBSCRIBE = 5, +}; + +const char* const wfo_event_names[] = { + "ASIO_DISPOSABLE", + "WFO_WAKEUP", + "WFO_TIMER_FIRED", + "WFO_SUBSCRIBE", + "WFO_RESUBSCRIBE", + "WFO_UNSUBSCRIBE", +}; + +struct asio_backend_t +{ + port_id port; + + object_wait_info wait_list[MAX_EVENTS]; + uint16 wait_events[MAX_EVENTS]; + asio_event_t* events[MAX_EVENTS]; + int32 wait_list_count; + + PONY_ATOMIC(asio_event_t*) sighandlers[MAX_SIGNAL]; + +#if defined(USE_LOGGER_THREAD) + thread_id logger; +#endif +}; + +struct wfo_message_t +{ + asio_event_t* ev; + uint32_t flags; +}; + +#if defined(USE_LOGGER_THREAD) +# define LOG_MSG_SIZE 128 +# define LOG(format, ...) if(true){ \ + asio_backend_t* b = ponyint_asio_get_backend(); \ + if(b->logger > 0) \ + { \ + char log_msg[LOG_MSG_SIZE]; \ + memset(log_msg, 0, LOG_MSG_SIZE); \ + snprintf(log_msg, LOG_MSG_SIZE, format __VA_OPT__(,) __VA_ARGS__); \ + send_data(b->logger, B_OK, log_msg, LOG_MSG_SIZE); \ + } \ + } \ + + static status_t logger(void* data) + { + (void)data; + while(true) + { + thread_id sender; + char msg[LOG_MSG_SIZE]; + int32 code = receive_data(&sender, &msg, LOG_MSG_SIZE); + if(code == B_SHUTTING_DOWN) + return B_OK; + printf("wfo: %d: %s\n", sender, msg); + fflush(stdout); + } + return B_OK; + } + + static void dump_wait_list(asio_backend_t* b, const char* prefix) + { + for(int32 i = 0; i < b->wait_list_count; i++) + { + LOG("%s: wait_list[%d] = type %d, id %d, read? %d, write? %d, oneshot? %d", + prefix, + i, + b->wait_list[i].type, + b->wait_list[i].object, + (b->wait_list[i].events & B_EVENT_READ) > 0, + (b->wait_list[i].events & B_EVENT_WRITE) > 0, + (b->wait_list[i].events & (B_EVENT_HIGH_PRIORITY_READ | B_EVENT_HIGH_PRIORITY_WRITE)) > 0); + } + } +# define LOGLIST(prefix) dump_wait_list(ponyint_asio_get_backend(), prefix) +#else +# define LOG(...) +# define LOGLIST(...) +#endif + +static void send_request(asio_event_t* ev, int32 req) +{ + asio_backend_t* b = ponyint_asio_get_backend(); + if(b == NULL || b->port < 0) + { + LOG("tried to send request %s (req = %d) after backend finalization already happened", wfo_event_names[req], req); + return; + } + + struct wfo_message_t msg; + msg.ev = ev; + // We store event flags as they are at moment of sending message, + // becuase handle_queue might be called after ev->flags are changed in meantime. + msg.flags = ev->flags; + + LOG("sending request %s (req = %d)", wfo_event_names[req], req); + status_t result = write_port(b->port, req, &msg, sizeof(msg)); + if(result < B_OK) + LOG("ERROR: sending request failed: %d", result); + else + LOG("sending request result: %d", result); +} + +static void signal_handler(int sig, void* userData) +{ + if(sig >= MAX_SIGNAL) + return; + + asio_event_t* ev = (asio_event_t*)userData; + + if(ev == NULL) + return; + + asio_backend_t* b = ponyint_asio_get_backend(); + if(b == NULL || b->port < 0) + return; + + pony_asio_event_send(ev, ASIO_SIGNAL, 1); +} + +static void timer_handler(union sigval val) +{ + LOG("timer_handler"); + asio_event_t* ev = (asio_event_t*)val.sival_ptr; + + if(ev == NULL || ev->timerID == NULL) + return; + + asio_backend_t* b = ponyint_asio_get_backend(); + if(b == NULL || b->port < 0) + return; + + LOG("handling event for timer %p", ev->timerID); + // We're making Haiku kernel call our `timer_handler` from a "new thread" + // (or same one for all timer calls, but still not owned by us AFAIK). + // When it calls us, and we try to call pony_asio_event_send, pony context is not set. + // When we try to create one, by calling `pony_register_thread`, we may crash, + // because `this_scheduler` variable does not seem to exist! Like if thread local storage + // was not set at all? + // This might be a Haiku-only problem, but still prevents us from simply sending pony event. + // With examples/timers it worked fine, but with examples/under_pressure it consistently crashes. +// pony_asio_event_send(ev, ASIO_TIMER, 0); + // That's why we send request to our own thread, which will in turn send the pony event. + send_request(ev, WFO_TIMER_FIRED); + LOG("done event for timer %p", ev->timerID); +} + +#if !defined(USE_SCHEDULER_SCALING_PTHREADS) +static void empty_signal_handler(int sig) +{ + (void) sig; +} +#endif + +int32 event_add(asio_backend_t* b, asio_event_t* ev, int32 object, uint16 type, uint16 events) +{ + pony_assert(b != NULL); + + int32 index = b->wait_list_count; + + if(index >= (int32)B_COUNT_OF(b->wait_list)) + { + LOG("ERROR: cannot add any more events"); + return -1; + } + + if(ev != NULL && ev->wfo_id != -1) + { + LOG("ERROR: event already subscribed, unsubscribe it first!"); + return -1; + } + + if(object < 0) + { + LOG("ERROR: Invalid object: %d", object); + return -1; + } + + b->wait_list_count += 1; + + b->wait_list[index].object = object; + b->wait_list[index].type = type; + b->wait_list[index].events = events; + + b->wait_events[index] = events; + + b->events[index] = ev; + + if(ev != NULL) + ev->wfo_id = index; + + return index; +} + +int32 event_update(asio_backend_t* b, asio_event_t* ev, int32 index, int32 object, uint16 type, uint16 events) +{ + pony_assert(b != NULL); + + if(index >= MAX_EVENTS || index < 0) + return event_add(b, ev, object, type, events); + + object_wait_info* info = &b->wait_list[index]; + + info->object = object; + info->type = type; + info->events = events; + + b->wait_events[index] = events; + + b->events[index] = ev; + + if(ev != NULL) + ev->wfo_id = index; + + return index; +} + +int32 event_remove(asio_backend_t* b, asio_event_t* ev) +{ + pony_assert(b != NULL); + pony_assert(ev != NULL); + + int32 index = ev->wfo_id; + if(index < 0) + return -1; + + LOG("REMOVE %d, wfo_id = %d", ev->fd, index); + LOGLIST("before removal"); + + ev->wfo_id = -1; + + if(b->events[index] != ev) + { + LOG("ERROR: event mismatch"); + return -1; + } + + int32 last = b->wait_list_count - 1; + + if(b->wait_list_count > 0) + --b->wait_list_count; + + if(last <= index) + { + LOGLIST("after trimming"); + return 0; + } + + b->wait_list[index] = b->wait_list[last]; + b->wait_events[index] = b->wait_events[last]; + b->events[index] = b->events[last]; + + // We could be called after wait_for_objects modified events, before they were refreshed, + // so refresh them here. + b->wait_list[index].events = b->wait_events[index]; + + asio_event_t* ev2 = b->events[index]; + if(ev2 != NULL && ev2->wfo_id >= 0) + ev2->wfo_id = index; + + LOGLIST("after removal"); + + return 0; +} + +static uint16 asio_flags_to_wfo_events(uint32_t flags) +{ + uint16 events = 0; + + // WARNING: This is a hack. + // Haiku, AFAIK, does not trigger B_EVENT_PRIORITY_* and B_EVENT_HIGH_PRIORITY_* events. + // So we use one of them to keep ONESHOT event valid, when we want to ignore it for now. + // Which means that we'll be using either both READ and HIGHT_PRIORITY_READ, + // or just the HIGH_PRIORITY_READ. + // This allows us to emulate ONESHOT, without a need for dispatch function to + // accessing asio_event_t's readable/writeable in a race condition manner, + // and without a need to remove and re-add the event to our wait_list. + if(flags & ASIO_READ) + { + events |= B_EVENT_READ; + if(flags & ASIO_ONESHOT) + { + events |= B_EVENT_HIGH_PRIORITY_READ; + } + } + if(flags & ASIO_WRITE) + { + events |= B_EVENT_WRITE; + if(flags & ASIO_ONESHOT) + { + events |= B_EVENT_HIGH_PRIORITY_WRITE; + } + } + + return events; +} + +static void handle_queue(asio_backend_t* b) +{ + int32 req; + struct wfo_message_t msg; + ssize_t read_size = 0; + + while((read_size = read_port_etc(b->port, &req, &msg, sizeof(msg), B_TIMEOUT, 0)) >= 0) + { + LOG("handle_queue got message: %s (req = %d)", wfo_event_names[req], req); + if(read_size != sizeof(msg)) + { + // 0 means it was just a wakeup-type message + if(read_size > 0) + { + LOG("ERROR: handle_queue got invalid size of data for a msg: %ld != %ld", read_size, sizeof(msg)); + } + continue; + } + + asio_event_t* ev = msg.ev; + uint32_t flags = msg.flags; + uint16 events = 0; + + pony_assert(req < (int32)B_COUNT_OF(wfo_event_names)); + LOG("handle_queue message targets event with ev->fd set to %d", ev->fd); + + switch(req) + { + case ASIO_DISPOSABLE: + LOG("DISPOSE %d", ev->fd); + pony_asio_event_send(ev, ASIO_DISPOSABLE, 0); + break; + + case WFO_TIMER_FIRED: + LOG("handling WFO_TIMER_FIRED for timer %p", ev->timerID); + if(ev->timerID != NULL) + pony_asio_event_send(ev, ASIO_TIMER, 0); + LOG("done handling WFO_TIMER_FIRED for timer %p", ev->timerID); + break; + + case WFO_SUBSCRIBE: + if(ev->fd < 0) continue; + + events = asio_flags_to_wfo_events(flags); + + LOG("SUBSCRIBE %d for: READ %d, WRITE %d, ONESHOT %d", ev->fd, (flags & ASIO_READ) > 0, (flags & ASIO_WRITE) > 0, (flags & ASIO_ONESHOT) > 1); + LOG("ev->wfo_id was %d", ev->wfo_id); + LOG("ev is %p", ev); + + if(ev->wfo_id >= 0 && event_update(b, ev, ev->wfo_id, ev->fd, B_OBJECT_TYPE_FD, events) < B_OK) + pony_asio_event_send(ev, ASIO_ERROR, 0); + else if(event_add(b, ev, ev->fd, B_OBJECT_TYPE_FD, events) < B_OK) + pony_asio_event_send(ev, ASIO_ERROR, 0); + break; + + case WFO_RESUBSCRIBE: + if(ev->fd < 0) continue; + + events = asio_flags_to_wfo_events(flags); + + LOG("RESUBSCRIBE %d for: READ %d, WRITE %d, ONESHOT %d", ev->fd, (flags & ASIO_READ) > 0, (flags & ASIO_WRITE) > 0, (flags & ASIO_ONESHOT) > 1); + LOG("ev->wfo_id was %d", ev->wfo_id); + LOG("ev is %p", ev); + + if(event_update(b, ev, ev->wfo_id, ev->fd, B_OBJECT_TYPE_FD, events) < B_OK) + pony_asio_event_send(ev, ASIO_ERROR, 0); + break; + + case WFO_UNSUBSCRIBE: + LOG("UNSUBSCRIBE %d", ev->fd); + // Don't send ASIO_ERROR on delete failure — the actor is tearing down, + // and ENOENT/EBADF is expected (FD already closed or never registered). + event_remove(b, ev); + break; + + default: {} + } + } +} + +asio_backend_t* ponyint_asio_backend_init() +{ + asio_backend_t* b = POOL_ALLOC(asio_backend_t); + memset(b, 0, sizeof(asio_backend_t)); + + b->port = create_port(MAX_EVENTS * 2, "libponyrt asio backend queue"); + if(b->port < B_OK) + { + POOL_FREE(asio_backend_t, b); + return NULL; + } + + b->wait_list_count = 0; + event_add(b, NULL, b->port, B_OBJECT_TYPE_PORT, B_EVENT_READ); + +#if !defined(USE_SCHEDULER_SCALING_PTHREADS) + // Make sure we ignore signals related to scheduler sleeping/waking + // as the default for those signals is termination. + struct sigaction new_action; + new_action.sa_handler = empty_signal_handler; + new_action.sa_userdata = NULL; + sigemptyset (&new_action.sa_mask); + + // ask to restart interrupted syscalls to match `signal` behavior + new_action.sa_flags = SA_RESTART; + + sigaction(PONY_SCHED_SLEEP_WAKE_SIGNAL, &new_action, NULL); +#endif + +#if defined(USE_LOGGER_THREAD) + b->logger = spawn_thread(logger, "pony wfo logger", B_DISPLAY_PRIORITY, NULL); + resume_thread(b->logger); +#endif + + return b; +} + +void ponyint_asio_backend_final(asio_backend_t* b) +{ + delete_port(b->port); +} + +// Single function for resubscribing to both reads and writes for an event +PONY_API void pony_asio_event_resubscribe(asio_event_t* ev) +{ + // needs to be a valid event that is one shot enabled + if((ev == NULL) || + (ev->flags == ASIO_DISPOSABLE) || + (ev->flags == ASIO_DESTROYED) || + !(ev->flags & ASIO_ONESHOT)) + { + pony_assert(0); + return; + } + + send_request(ev, WFO_RESUBSCRIBE); +} + +// Kept to maintain backwards compatibility so folks don't +// have to change their code to use `pony_asio_event_resubscribe` +// immediately +PONY_API void pony_asio_event_resubscribe_write(asio_event_t* ev) +{ + pony_asio_event_resubscribe(ev); +} + +// Kept to maintain backwards compatibility so folks don't +// have to change their code to use `pony_asio_event_resubscribe` +// immediately +PONY_API void pony_asio_event_resubscribe_read(asio_event_t* ev) +{ + pony_asio_event_resubscribe(ev); +} + +DECLARE_THREAD_FN(ponyint_asio_backend_dispatch) +{ + ponyint_cpu_affinity(ponyint_asio_get_cpu()); + ponyint_register_asio_thread(); + asio_backend_t* b = arg; + pony_assert(b != NULL); + + rename_thread(get_pthread_thread_id(pthread_self()), "wfo_asio_backend_dispatch"); +// set_thread_priority(get_pthread_thread_id(pthread_self()), B_DISPLAY_PRIORITY); + +#if !defined(USE_SCHEDULER_SCALING_PTHREADS) + // Make sure we block signals related to scheduler sleeping/waking + // so they queue up to avoid race conditions + sigset_t set; + sigemptyset(&set); + sigaddset(&set, PONY_SCHED_SLEEP_WAKE_SIGNAL); + pthread_sigmask(SIG_BLOCK, &set, NULL); +#endif + +#if defined(USE_SYSTEMATIC_TESTING) + // sleep thread until we're ready to start processing + SYSTEMATIC_TESTING_WAIT_START(ponyint_asio_get_backend_tid(), ponyint_asio_get_backend_sleep_object()); +#endif + + bool should_refresh_events = false; + + while(true) + { + int32 wait_count = b->wait_list_count; + + if(should_refresh_events) + { + for(int i = 0; i < wait_count; i++) b->wait_list[i].events = b->wait_events[i]; + should_refresh_events = false; + } + + LOGLIST("before wait"); + +#if defined(USE_SYSTEMATIC_TESTING) + // wait only for 10 milliseconds + ssize_t result = wait_for_objects_etc(b->wait_list, wait_count, B_RELATIVE_TIMEOUT, 10000); +#else + // wait indefinitely + ssize_t result = wait_for_objects(b->wait_list, wait_count); +#endif + + SYSTEMATIC_TESTING_YIELD(); + LOGLIST("results"); + + if(result == B_TIMED_OUT || result == B_WOULD_BLOCK) + { + ssize_t count = port_count(b->port); + if(count == B_BAD_PORT_ID) + { + // `ponyint_asio_backend_final` simply deletes port, so we know it's time to quit. + LOG("dispatch: shutdown requested"); + break; + } + else if(count < 0) + { + LOG("ERROR: dispatch port_count returned %ld", count); + } + else if(count > 0) + { + LOG("dispatch: timed out, has %ld messages in queue, calling handle_queue...", count); + handle_queue(b); + } + + should_refresh_events = true; + continue; + } + else if(result < B_OK) + { + LOG("dispatch: ERROR: %ld", result); + break; + } + + bool should_handle_queue = false; + bool should_quit = false; + + for(int32 i = 0; i < wait_count; i++) + { + object_wait_info* info = &(b->wait_list[i]); + + uint16 events = info->events; + info->events = b->wait_events[i]; + + asio_event_t* ev = b->events[i]; + + if(ev == NULL) + { + if(info->type == B_OBJECT_TYPE_PORT && info->object == b->port) + { + should_handle_queue = ((events & B_EVENT_READ) == B_EVENT_READ) && port_count(b->port) > 0; + should_quit = (events & B_EVENT_INVALID) == B_EVENT_INVALID; + LOG("dispatch port update: has messages %d, shutting down %d", should_handle_queue, should_quit); + } + + if(should_handle_queue || should_quit) + { + should_refresh_events = true; + break; + } + else + continue; + } + + if(events == 0) + { + continue; + } + + if(ev->flags == ASIO_DESTROYED) + { + continue; + } + + uint32_t flags = 0; + uint32_t count = 0; + + if(events & B_EVENT_READ) + { + flags |= ASIO_READ; + ev->readable = true; + + if(info->events & B_EVENT_HIGH_PRIORITY_READ) + { + // Since this is an ASIO_ONESHOT event, set only high priority read (which is not triggered by Haiku), + // to keep it a valid object info, but not trigger again until a call to resubscribe and B_EVENT_READ is set. + info->events = info->events & ~(B_EVENT_READ); + b->wait_events[i] = info->events; + } + } + + if(events & B_EVENT_WRITE) + { + flags |= ASIO_WRITE; + ev->writeable = true; + + if(info->events & B_EVENT_HIGH_PRIORITY_WRITE) + { + // Same as above, only for a write. + info->events = info->events & ~(B_EVENT_WRITE); + b->wait_events[i] = info->events; + } + } + + if((events & (B_EVENT_DISCONNECTED | B_EVENT_INVALID | B_EVENT_ERROR)) > 0) + { + if(events & B_EVENT_DISCONNECTED) + { + LOG("B_EVENT_DISCONNECTED info->object %d, ev->fd %d", info->object, ev->fd); + if(!(info->events & B_EVENT_HIGH_PRIORITY_READ) || !ev->readable) + { + ev->readable = true; + flags |= ASIO_READ; + } + } + if(events & B_EVENT_ERROR) + { + LOG("B_EVENT_ERROR info->object %d, ev->fd %d", info->object, ev->fd); + } + if(events & B_EVENT_INVALID) + { + LOG("B_EVENT_INVALID info->object %d, ev->fd %d", info->object, ev->fd); + } + } + + // if we had a valid event of some type that needs to be sent + // to an actor + if(flags != 0) + { + LOG("SEND event for info->object %d, ev->fd %d", info->object, ev->fd); + // send the event to the actor + pony_asio_event_send(ev, flags, count); + } + } + + if(should_handle_queue) + { + LOG("Handling queue..."); + handle_queue(b); + } + + if(should_quit) break; + } + + LOG("Shutting down... handling queue for the last time..."); + handle_queue(b); + + delete_port(b->port); + b->port = B_BAD_PORT_ID; + +#if defined(USE_LOGGER_THREAD) + send_data(b->logger, B_SHUTTING_DOWN, NULL, 0); + wait_for_thread(b->logger, NULL); + b->logger = -1; +#endif + + POOL_FREE(asio_backend_t, b); + + SYSTEMATIC_TESTING_STOP_THREAD(); + TRACING_THREAD_STOP(); + + pony_unregister_thread(); + return NULL; +} + +static bool timer_set_nsec(asio_event_t* ev) +{ + struct itimerspec ts; + + pony_assert(ev != NULL); + + uint64_t nsec = ev->nsec; + timer_t timerID = ev->timerID; + + ts.it_interval.tv_sec = 0; + ts.it_interval.tv_nsec = 0; + ts.it_value.tv_sec = (time_t)(nsec / 1000000000); + ts.it_value.tv_nsec = (long)(nsec - (ts.it_value.tv_sec * 1000000000)); + + LOG("setting timer %p", timerID); + + return timer_settime(timerID, 0, &ts, NULL) == B_OK; +} + +static bool wfo_remove_timer(asio_event_t* ev) +{ + if (ev->timerID != NULL) + { + timer_delete(ev->timerID); + LOG("timer %p" " removed", ev->timerID); + } + ev->timerID = NULL; + return true; +} + +static bool wfo_set_timer(asio_event_t* ev) +{ + LOG("creating timer"); + struct sigevent event; + event.sigev_notify = SIGEV_THREAD; + event.sigev_value.sival_ptr = (void*)ev; + event.sigev_notify_function = timer_handler; + event.sigev_notify_attributes = NULL; + + if(timer_create(CLOCK_MONOTONIC, &event, &ev->timerID) != B_OK) + { + goto failure; + } + + LOG("timer %p" " created", ev->timerID); + + if(!timer_set_nsec(ev)) + { + goto failure; + } + + LOG("timer %p" " set", ev->timerID); + +//success: + return true; + + failure: + LOG("ERROR: failed to create timer"); + wfo_remove_timer(ev); + return false; +} + +static bool wfo_remove_signal(asio_event_t* ev, int sig) +{ + struct sigaction new_action; + + (void)ev; + +#if !defined(USE_SCHEDULER_SCALING_PTHREADS) + // Make sure we ignore signals related to scheduler sleeping/waking + // as the default for those signals is termination. + if(sig == PONY_SCHED_SLEEP_WAKE_SIGNAL) + { + new_action.sa_handler = empty_signal_handler; + new_action.sa_userdata = NULL; + } + else +#endif + new_action.sa_handler = SIG_DFL; + + sigemptyset (&new_action.sa_mask); + + // ask to restart interrupted syscalls to match `signal` behavior + new_action.sa_flags = SA_RESTART; + + sigaction(sig, &new_action, NULL); + + return true; +} + +static bool wfo_set_signal(asio_event_t* ev, int sig) +{ + struct sigaction new_action; + new_action.sa_handler = (__sighandler_t)(void*)signal_handler; + new_action.sa_userdata = (void*)ev; + sigemptyset (&new_action.sa_mask); + + // ask to restart interrupted syscalls to match `signal` behavior + new_action.sa_flags = SA_RESTART; + + sigaction(sig, &new_action, NULL); + + return true; +} + +PONY_API void pony_asio_event_subscribe(asio_event_t* ev) +{ + if((ev == NULL) || + (ev->flags == ASIO_DISPOSABLE) || + (ev->flags == ASIO_DESTROYED)) + { + pony_assert(0); + return; + } + + asio_backend_t* b = ponyint_asio_get_backend(); + pony_assert(b != NULL); + + if(ev->noisy) + { + uint64_t old_count = ponyint_asio_noisy_add(); + // tell scheduler threads that asio has at least one noisy actor + // if the old_count was 0 + if (old_count == 0) + ponyint_sched_noisy_asio(pony_scheduler_index()); + } + + if(ev->flags & ASIO_TIMER) + { + LOG("init timer event"); + if (!wfo_set_timer(ev)) + { + LOG("failed to init timer event"); + pony_asio_event_send(ev, ASIO_ERROR, 0); + return; + } + LOG("subscribe timer event"); + return; + } + + if(ev->flags & ASIO_SIGNAL) + { + int sig = (int)ev->nsec; + asio_event_t* prev = NULL; + + // TODO: somehow warn about this? + if(sig == SIGKILL || sig == SIGSTOP) + { + // KILL and STOP are not catchable + } + + if((sig < MAX_SIGNAL) && + atomic_compare_exchange_strong_explicit(&b->sighandlers[sig], &prev, NULL, + memory_order_acq_rel, memory_order_acquire)) + { + if (!wfo_set_signal(ev, sig)) + { + pony_asio_event_send(ev, ASIO_ERROR, 0); + return; + } + } else { + return; + } + } + + if(ev->flags & (ASIO_READ | ASIO_WRITE)) + { + send_request(ev, WFO_SUBSCRIBE); + } +} + +PONY_API void pony_asio_event_setnsec(asio_event_t* ev, uint64_t nsec) +{ + if((ev == NULL) || + (ev->flags == ASIO_DISPOSABLE) || + (ev->flags == ASIO_DESTROYED)) + { + pony_assert(0); + return; + } + + if(ev->flags & ASIO_TIMER) + { + ev->nsec = nsec; + if(!timer_set_nsec(ev)) + pony_asio_event_send(ev, ASIO_ERROR, 0); + } +} + +PONY_API void pony_asio_event_unsubscribe(asio_event_t* ev) +{ + if((ev == NULL) || + (ev->flags == ASIO_DISPOSABLE) || + (ev->flags == ASIO_DESTROYED)) + { + pony_assert(0); + return; + } + + asio_backend_t* b = ponyint_asio_get_backend(); + pony_assert(b != NULL); + + if(ev->flags & ASIO_TIMER) + { + LOG("removing timer..."); + wfo_remove_timer(ev); + LOG("... done removing timer"); + } + + if(ev->flags & ASIO_SIGNAL) + { + int sig = (int)ev->nsec; + asio_event_t* prev = ev; + + if((sig < MAX_SIGNAL) && + atomic_compare_exchange_strong_explicit(&b->sighandlers[sig], &prev, NULL, + memory_order_acq_rel, memory_order_acquire)) + { + wfo_remove_signal(ev, sig); + } + } + + if(ev->flags & (ASIO_READ | ASIO_WRITE)) + { + send_request(ev, WFO_UNSUBSCRIBE); + } + + ev->flags = ASIO_DISPOSABLE; + send_request(ev, ASIO_DISPOSABLE); +} + +#endif diff --git a/src/libponyrt/lang/directory.c b/src/libponyrt/lang/directory.c index dab6db5429..e8364de527 100644 --- a/src/libponyrt/lang/directory.c +++ b/src/libponyrt/lang/directory.c @@ -176,7 +176,7 @@ PONY_API const char* ponyint_unix_readdir(DIR* dir) if(d == NULL) break; -#if defined(PLATFORM_IS_LINUX) || defined(PLATFORM_IS_EMSCRIPTEN) +#if defined(PLATFORM_IS_LINUX) || defined(PLATFORM_IS_EMSCRIPTEN) || defined(PLATFORM_IS_HAIKU) size_t len = strlen(d->d_name); #elif defined(PLATFORM_IS_BSD) || defined(PLATFORM_IS_MACOSX) size_t len = d->d_namlen; diff --git a/src/libponyrt/lang/io.c b/src/libponyrt/lang/io.c index bde869e344..71ce61bc2c 100644 --- a/src/libponyrt/lang/io.c +++ b/src/libponyrt/lang/io.c @@ -30,7 +30,7 @@ PONY_API int64_t pony_os_lseek(int fd, int64_t offset, int whence) #else return lseek(fd, offset, whence); #endif -#elif defined(PLATFORM_IS_MACOSX) || defined(PLATFORM_IS_BSD) +#elif defined(PLATFORM_IS_MACOSX) || defined(PLATFORM_IS_BSD) || defined(PLATFORM_IS_HAIKU) return lseek(fd, offset, whence); #elif defined(PLATFORM_IS_WINDOWS) return _lseeki64(fd, offset, whence); @@ -45,7 +45,7 @@ PONY_API int pony_os_ftruncate(int fd, int64_t length) #else return ftruncate(fd, length); #endif -#elif defined(PLATFORM_IS_MACOSX) || defined(PLATFORM_IS_BSD) +#elif defined(PLATFORM_IS_MACOSX) || defined(PLATFORM_IS_BSD) || defined(PLATFORM_IS_HAIKU) return ftruncate(fd, length); #elif defined(PLATFORM_IS_WINDOWS) return _chsize_s(fd, length); diff --git a/src/libponyrt/lang/socket.c b/src/libponyrt/lang/socket.c index 558f71ff86..caf1fcaa01 100644 --- a/src/libponyrt/lang/socket.c +++ b/src/libponyrt/lang/socket.c @@ -85,6 +85,9 @@ typedef int SOCKET; #ifdef PLATFORM_IS_WINDOWS // TODO #endif +#ifdef PLATFORM_IS_HAIKU +#include +#endif PONY_EXTERN_C_BEGIN @@ -170,7 +173,7 @@ static struct addrinfo* os_addrinfo_intern(int family, int socktype, return result; } -#if defined(PLATFORM_IS_MACOSX) || defined(PLATFORM_IS_BSD) +#if defined(PLATFORM_IS_MACOSX) || defined(PLATFORM_IS_BSD) || defined(PLATFORM_IS_HAIKU) static int set_nonblocking(int s) { int flags = fcntl(s, F_GETFL, 0); @@ -533,7 +536,7 @@ static int socket_from_addrinfo(struct addrinfo* p, bool reuse) (const char*)&reuseaddr, sizeof(int)); } -#if defined(PLATFORM_IS_MACOSX) || defined(PLATFORM_IS_BSD) +#if defined(PLATFORM_IS_MACOSX) || defined(PLATFORM_IS_BSD) || defined(PLATFORM_IS_HAIKU) r |= set_nonblocking(fd); #endif diff --git a/src/libponyrt/lang/stat.c b/src/libponyrt/lang/stat.c index ab17d44255..c39e345c90 100644 --- a/src/libponyrt/lang/stat.c +++ b/src/libponyrt/lang/stat.c @@ -145,7 +145,7 @@ static void unix_stat(pony_stat_t* p, struct stat* st) p->modified_time = st->st_mtime; p->change_time = st->st_ctime; -#if defined(PLATFORM_IS_LINUX) || defined(PLATFORM_IS_EMSCRIPTEN) +#if defined(PLATFORM_IS_LINUX) || defined(PLATFORM_IS_EMSCRIPTEN) || defined(PLATFORM_IS_HAIKU) p->access_time_nsec = st->st_atim.tv_nsec; p->modified_time_nsec = st->st_mtim.tv_nsec; p->change_time_nsec = st->st_ctim.tv_nsec; diff --git a/src/libponyrt/mem/alloc.c b/src/libponyrt/mem/alloc.c index db3cc35312..967951c799 100644 --- a/src/libponyrt/mem/alloc.c +++ b/src/libponyrt/mem/alloc.c @@ -37,6 +37,9 @@ void* ponyint_virt_alloc(size_t bytes) #elif defined(PLATFORM_IS_OPENBSD) p = mmap(0, bytes, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); +#elif defined(PLATFORM_IS_HAIKU) + p = mmap(0, bytes, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0); #elif defined(PLATFORM_IS_BSD) #ifndef MAP_ALIGNED_SUPER #define MAP_ALIGNED_SUPER 0 diff --git a/src/libponyrt/platform/ponyassert.c b/src/libponyrt/platform/ponyassert.c index 86f7076d20..681ce60ed7 100644 --- a/src/libponyrt/platform/ponyassert.c +++ b/src/libponyrt/platform/ponyassert.c @@ -5,7 +5,7 @@ #include #include #ifdef PLATFORM_IS_POSIX_BASED -#if defined(__GLIBC__) || defined(PLATFORM_IS_BSD) || defined(ALPINE_LINUX) || defined(PLATFORM_IS_MACOSX) +#if defined(__GLIBC__) || defined(PLATFORM_IS_BSD) || defined(ALPINE_LINUX) || defined(PLATFORM_IS_MACOSX) || defined(PLATFORM_IS_HAIKU) # include #endif # include @@ -40,7 +40,7 @@ void ponyint_assert_fail(const char* expr, const char* file, size_t line, fprintf(stderr, "%s:" __zu ": %s: Assertion `%s` failed.\n\n", file, line, func, expr); -#if defined(__GLIBC__) || defined(PLATFORM_IS_BSD) || defined(ALPINE_LINUX) || defined(PLATFORM_IS_MACOSX) +#if defined(__GLIBC__) || defined(PLATFORM_IS_BSD) || defined(ALPINE_LINUX) || defined(PLATFORM_IS_MACOSX) || defined(PLATFORM_IS_HAIKU) void* addrs[256]; stack_depth_t depth = backtrace(addrs, 256); char** strings = backtrace_symbols(addrs, depth); diff --git a/src/libponyrt/sched/cpu.c b/src/libponyrt/sched/cpu.c index 6ce47c29ed..3ba3fb6f2c 100644 --- a/src/libponyrt/sched/cpu.c +++ b/src/libponyrt/sched/cpu.c @@ -21,6 +21,8 @@ #elif defined(PLATFORM_IS_EMSCRIPTEN) #include #include +#elif defined(PLATFORM_IS_HAIKU) + #include #endif #include "cpu.h" @@ -56,6 +58,21 @@ static uint32_t cpus_online(void) #endif +#if defined(PLATFORM_IS_HAIKU) + +static uint32_t haiku_cpu_count(void) +{ + system_info info; + if(get_system_info(&info) < B_OK) + { + return 1; + } + + return info.cpu_count; +} + +#endif + // Number of cores static uint32_t hw_cpu_count; @@ -173,6 +190,8 @@ void ponyint_cpu_init() hw_cpu_count = property("hw.physicalcpu"); #elif defined(PLATFORM_IS_EMSCRIPTEN) hw_cpu_count = emscripten_num_logical_cores(); +#elif defined(PLATFORM_IS_HAIKU) + hw_cpu_count = haiku_cpu_count(); #elif defined(PLATFORM_IS_WINDOWS) PSYSTEM_LOGICAL_PROCESSOR_INFORMATION info = NULL; PSYSTEM_LOGICAL_PROCESSOR_INFORMATION ptr = NULL; @@ -218,6 +237,9 @@ void ponyint_cpu_init() if(ptr != NULL) ponyint_pool_free_size(len, ptr); +#else + #warning Missing hw_cpu_count on unsupported platform + hw_cpu_count = 1; #endif } @@ -326,6 +348,8 @@ void ponyint_cpu_affinity(uint32_t cpu) sched_setaffinity(0, sizeof(cpu_set_t), &set); #elif defined(PLATFORM_IS_BSD) // No pinning, since we cannot yet determine hyperthreads vs physical cores. +#elif defined(PLATFORM_IS_HAIKU) + // TODO: Not implemented yet! #elif defined(PLATFORM_IS_MACOSX) thread_affinity_policy_data_t policy; policy.affinity_tag = cpu; diff --git a/src/libponyrt/sched/scheduler.h b/src/libponyrt/sched/scheduler.h index 1c9bb4a98f..12b3ab2421 100644 --- a/src/libponyrt/sched/scheduler.h +++ b/src/libponyrt/sched/scheduler.h @@ -18,6 +18,7 @@ typedef struct scheduler_t scheduler_t; #define PONY_KQUEUE_SCHEDULER_INDEX -10 #define PONY_IOCP_SCHEDULER_INDEX -11 #define PONY_EPOLL_SCHEDULER_INDEX -12 +#define PONY_WFO_SCHEDULER_INDEX -13 #define PONY_UNKNOWN_SCHEDULER_INDEX -1 // the `-999` constant is the same value that is hardcoded in `actor_pinning.pony` diff --git a/src/ponyc/CMakeLists.txt b/src/ponyc/CMakeLists.txt index d15122d815..196aab2dd4 100644 --- a/src/ponyc/CMakeLists.txt +++ b/src/ponyc/CMakeLists.txt @@ -40,6 +40,8 @@ else() target_link_libraries(ponyc PRIVATE execinfo) elseif(${CMAKE_HOST_SYSTEM_NAME} MATCHES "DragonFly") target_link_libraries(ponyc PRIVATE execinfo atomic) + elseif(${CMAKE_HOST_SYSTEM_NAME} MATCHES "Haiku") + target_link_libraries(ponyc PRIVATE execinfo atomic stdc++ root) else() execute_process(COMMAND ${CMAKE_CXX_COMPILER} --print-file-name=libstdc++.a OUTPUT_VARIABLE LIBSTDCXX_PATH diff --git a/test/libponyc/CMakeLists.txt b/test/libponyc/CMakeLists.txt index c40cb65fee..97f1559808 100644 --- a/test/libponyc/CMakeLists.txt +++ b/test/libponyc/CMakeLists.txt @@ -121,6 +121,8 @@ else() target_link_libraries(libponyc.tests PRIVATE execinfo) elseif(${CMAKE_HOST_SYSTEM_NAME} MATCHES "DragonFly") target_link_libraries(libponyc.tests PRIVATE execinfo atomic) + elseif(${CMAKE_HOST_SYSTEM_NAME} MATCHES "Haiku") + target_link_libraries(libponyc.tests PRIVATE execinfo atomic stdc++ root) else() target_link_libraries(libponyc.tests PRIVATE atomic dl) target_link_options(libponyc.tests PRIVATE "-static-libstdc++") diff --git a/test/libponyc/sugar.cc b/test/libponyc/sugar.cc index a288d546e9..63a8780d95 100644 --- a/test/libponyc/sugar.cc +++ b/test/libponyc/sugar.cc @@ -2084,9 +2084,9 @@ TEST_F(SugarTest, IfdefPosix) "class ref Foo\n" " var create: U32\n" " fun box f(): None =>\n" - " ifdef $flag linux $ifdefor $flag osx $ifdefor $flag bsd\n" - " $extra $ifdefnot $noseq($flag linux $ifdefor $flag osx $ifdefor\n" - " $flag bsd) then\n" + " ifdef $noseq($flag linux $ifdefor $flag osx) $ifdefor $noseq($flag bsd $ifdefor $flag haiku)\n" + " $extra $ifdefnot $noseq($noseq($flag linux $ifdefor $flag osx) $ifdefor\n" + " $noseq($flag bsd $ifdefor $flag haiku)) then\n" " 3\n" " else\n" " 4\n" diff --git a/test/libponyrt/CMakeLists.txt b/test/libponyrt/CMakeLists.txt index 3bae3205f4..933b4f0365 100644 --- a/test/libponyrt/CMakeLists.txt +++ b/test/libponyrt/CMakeLists.txt @@ -32,6 +32,8 @@ elseif(${CMAKE_HOST_SYSTEM_NAME} MATCHES "BSD") target_link_libraries(libponyrt.tests PRIVATE execinfo) elseif(${CMAKE_HOST_SYSTEM_NAME} MATCHES "DragonFly") target_link_libraries(libponyrt.tests PRIVATE execinfo atomic) +elseif(${CMAKE_HOST_SYSTEM_NAME} MATCHES "Haiku") + target_link_libraries(libponyrt.tests PRIVATE execinfo atomic stdc++ root) else() target_link_libraries(libponyrt.tests PRIVATE atomic dl) target_link_options(libponyrt.tests PRIVATE "-static-libstdc++") diff --git a/test/libponyrt/lang/error.cc b/test/libponyrt/lang/error.cc index 01ecec502c..5db5fad920 100644 --- a/test/libponyrt/lang/error.cc +++ b/test/libponyrt/lang/error.cc @@ -19,6 +19,8 @@ TEST(ErrorTest, PonyTry) // LLVM exception code generation interact. // See https://github.com/ponylang/ponyc/issues/2455 for more details. #ifndef PLATFORM_IS_WINDOWS +// TODO: it currently crashes on Haiku +#ifndef PLATFORM_IS_HAIKU TEST(ErrorTest, PonyTryCantCatchCppExcept) { auto cb_cppthrow = [](void*){ throw std::exception{}; }; @@ -26,3 +28,4 @@ TEST(ErrorTest, PonyTryCantCatchCppExcept) ASSERT_THROW((pony_try(cb_cppthrow, nullptr)), std::exception); } #endif +#endif From 6bc90f19a3fdec53d9344fbe0ab005b1ae663c77 Mon Sep 17 00:00:00 2001 From: ahwayakchih Date: Sat, 16 May 2026 19:59:17 +0200 Subject: [PATCH 02/10] Workaround Haiku problem with shutting down sockets. Without this, sockets stay in wait-time state and example/net runs forever waiting for TCP server to close. TODO: prepare minimal C-only test and make sure it's not a problem with wfo.c or something else on my side. If it's a Haiku problem, report test to them. --- packages/net/tcp_connection.pony | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/net/tcp_connection.pony b/packages/net/tcp_connection.pony index 5a7b654a24..addd3035da 100644 --- a/packages/net/tcp_connection.pony +++ b/packages/net/tcp_connection.pony @@ -1088,7 +1088,14 @@ actor TCPConnection is AsioEventNotify _shutdown = true if _connected then - @pony_os_socket_shutdown(_fd) + // There's some bug in shutdown on Haiku, which never closes socket (it stays in timed wait forever). + // So just force hard_close there. + // TODO: remove it once Haiku OS bug is fixed. + ifdef haiku then + _shutdown_peer = true + else + @pony_os_socket_shutdown(_fd) + end else _shutdown_peer = true end From d66a7d376c8877b937f6c3ac6a5890638c6dfd29 Mon Sep 17 00:00:00 2001 From: ahwayakchih Date: Sat, 16 May 2026 20:03:43 +0200 Subject: [PATCH 03/10] Workaround Haiku's strto(f/d/ld) bug When parsing "nan(123)", Haiku sets `endp` to point to the closing parenthesis ")", instead of next char. Reported at https://dev.haiku-os.org/ticket/20092 This adds a lot of lines to workaround the problem, so not sure if it should be included at all. Especially when pure pony implementation (https://github.com/ponylang/ponyc/pull/5328) is in the works anyway. --- packages/builtin/string.pony | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/packages/builtin/string.pony b/packages/builtin/string.pony index d37443c97c..dbf9bb7f50 100644 --- a/packages/builtin/string.pony +++ b/packages/builtin/string.pony @@ -1613,7 +1613,16 @@ actor Main let res = @strtof(ptr.offset(index), addressof endp) let errno: I32 = @pony_os_errno() if (errno != 0) or (endp != ptr.offset(_size)) then - error + ifdef haiku then + // Workaround haiku bug with parsing "nan(123)": https://dev.haiku-os.org/ticket/20092 + if (errno == 0) and (endp == ptr.offset(_size - 1)) and (this.at_offset(_size.isize() - 1)? == 0x29) then + res + else + error + end + else + error + end else res end @@ -1647,7 +1656,16 @@ actor Main let res = @strtod(ptr.offset(index), addressof endp) let errno: I32 = @pony_os_errno() if (errno != 0) or (endp != ptr.offset(_size)) then - error + ifdef haiku then + // Workaround haiku bug with parsing "nan(123)": https://dev.haiku-os.org/ticket/20092 + if (errno == 0) and (endp == ptr.offset(_size - 1)) and (this.at_offset(_size.isize() - 1)? == 0x29) then + res + else + error + end + else + error + end else res end From e7b9f196f3eb4855f4fec97cbcc31809d8d98f1a Mon Sep 17 00:00:00 2001 From: ahwayakchih Date: Sat, 16 May 2026 20:10:21 +0200 Subject: [PATCH 04/10] Support .rdef files on Haiku On Haiku, there's a .rdef format to define application's mime type, icon, version numbers, and any other info that one might to add there. Rdef file then is compiled into .rsrc file. That .rsrc file then is "injected" into elf executable file. Last `mimeset` is run to set file attributes (Haiku uses BFS file system, where things like icon and mimetype are set). This change implements all of that, assuming that there is a .rdef file named the same as generated executable file. So, for example, when compiling `examples/circle`, it will look for `circle.rdef` file in the same directory. If found it will use it, if not, executable file will be created anyway, as usual - without custom icon and/or info :). --- src/libponyc/codegen/genexe.cc | 112 +++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) diff --git a/src/libponyc/codegen/genexe.cc b/src/libponyc/codegen/genexe.cc index e5d101eb67..138fb37f1c 100644 --- a/src/libponyc/codegen/genexe.cc +++ b/src/libponyc/codegen/genexe.cc @@ -1747,6 +1747,105 @@ static bool link_exe(compile_t* c, ast_t* program, return true; } +#ifdef PLATFORM_IS_HAIKU +const char* genrsrc(compile_t* c) +{ + errors_t* errors = c->opt->check.errors; + + const char* file_rdef = + suffix_filename(c, c->opt->output, "", c->filename, ".rdef"); + + if (!file_exists(file_rdef)) + return NULL; + + const char* file_rsrc = + suffix_filename(c, c->opt->output, "", c->filename, ".rsrc"); + + if(c->opt->verbosity >= VERBOSITY_MINIMAL) + fprintf(stderr, "Generating %s\n", file_rsrc); + + const char* rc_format = "rc -o %s %s"; + size_t rc_len = strlen(rc_format) + strlen(file_rdef) + strlen(file_rsrc); + char* rc_cmd = (char*)ponyint_pool_alloc_size(rc_len); + + if (snprintf(rc_cmd, rc_len, rc_format, file_rsrc, file_rdef) >= rc_len) + { + ponyint_pool_free_size(rc_len, rc_cmd); + return NULL; + } + + int result = system(rc_cmd); + if (result != 0) + { + errorf(errors, NULL, "unable to generate rsrc file: %s: %d", rc_cmd, result); + ponyint_pool_free_size(rc_len, rc_cmd); + return NULL; + } + + ponyint_pool_free_size(rc_len, rc_cmd); + return file_rsrc; +} + +bool rsrc_exe(compile_t* c, const char* file_rsrc) +{ + errors_t* errors = c->opt->check.errors; + + if (!file_exists(file_rsrc)) + return false; + + if(c->opt->verbosity >= VERBOSITY_MINIMAL) + fprintf(stderr, "Applying %s\n", file_rsrc); + + const char* xres_format = "xres -o %s %s"; + size_t xres_len = strlen(xres_format) + strlen(c->filename) + strlen(file_rsrc); + char* xres_cmd = (char*)ponyint_pool_alloc_size(xres_len); + + if (snprintf(xres_cmd, xres_len, xres_format, c->filename, file_rsrc) >= xres_len) + { + ponyint_pool_free_size(xres_len, xres_cmd); + return false; + } + + int result = system(xres_cmd); + if (result != 0) + errorf(errors, NULL, "unable to apply rsrc file: %s: %d", xres_cmd, result); + + ponyint_pool_free_size(xres_len, xres_cmd); + return result == 0; +} + +bool mime_exe(compile_t* c) +{ + errors_t* errors = c->opt->check.errors; + + const char* file_exe = + suffix_filename(c, c->opt->output, "", c->filename, ""); + + if (!file_exists(file_exe)) + return false; + + if(c->opt->verbosity >= VERBOSITY_MINIMAL) + fprintf(stderr, "Setting mime of %s\n", file_exe); + + const char* mimeset_format = "mimeset -f %s"; + size_t mimeset_len = strlen(mimeset_format) + strlen(file_exe); + char* mimeset_cmd = (char*)ponyint_pool_alloc_size(mimeset_len); + + if (snprintf(mimeset_cmd, mimeset_len, mimeset_format, file_exe) >= mimeset_len) + { + ponyint_pool_free_size(mimeset_len, mimeset_cmd); + return false; + } + + int result = system(mimeset_cmd); + if (result != 0) + errorf(errors, NULL, "unable to set mime of: %s: %d", mimeset_cmd, result); + + ponyint_pool_free_size(mimeset_len, mimeset_cmd); + return result == 0; +} +#endif + bool genexe(compile_t* c, ast_t* program) { errors_t* errors = c->opt->check.errors; @@ -1865,5 +1964,18 @@ bool genexe(compile_t* c, ast_t* program) unlink(file_o); #endif +#ifdef PLATFORM_IS_HAIKU + // On Haiku, if there's an .rdef file with the same name as "file_exe", + // compile it into a .rsrc file, link it to the exe and then set mime. + // If rsrc file is generated, but applying it to exe fails, + // leave the file for investigation. + const char* file_rsrc = genrsrc(c); + if(file_rsrc != NULL && rsrc_exe(c, file_rsrc)) + { + mime_exe(c); + unlink(file_rsrc); + } +#endif + return true; } From ffc0d8e2cc02697d36221b16099b038f3fb960ad Mon Sep 17 00:00:00 2001 From: ahwayakchih Date: Sun, 17 May 2026 18:51:11 +0200 Subject: [PATCH 05/10] Fix compiling with runtime tracing enabled on Haiku --- src/libponyrt/tracing/tracing.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/libponyrt/tracing/tracing.c b/src/libponyrt/tracing/tracing.c index a64ab62b94..b288e2508d 100644 --- a/src/libponyrt/tracing/tracing.c +++ b/src/libponyrt/tracing/tracing.c @@ -17,9 +17,12 @@ #include "../sched/scheduler.h" #include "../mem/pool.h" #include "../actor/actor.h" -#if defined(__GLIBC__) || defined(PLATFORM_IS_BSD) || defined(ALPINE_LINUX) || defined(PLATFORM_IS_MACOSX) +#if defined(__GLIBC__) || defined(PLATFORM_IS_BSD) || defined(ALPINE_LINUX) || defined(PLATFORM_IS_MACOSX) || defined(PLATFORM_IS_HAIKU) # include #endif +#if defined(PLATFORM_IS_HAIKU) +# include +#endif #define PONY_TRACING_THREAD_INDEX -998 #define PONY_TRACING_MAX_STACK_FRAME_DEPTH 256 @@ -742,7 +745,11 @@ static void flight_recorder_dump_signal_handler(int sig, siginfo_t* siginfo, voi // if we timed out waiting for the flight recorder dump to finish, we // should exit the program immediately. This is a safety measure in case // something went wrong in the tracing thread dump process. +#if defined(PLATFORM_IS_HAIKU) + const char* strsig = strsignal(sig); +#else char* strsig = strsignal(sig); +#endif char* msg = "Flight recorder dump timed out.\n Exiting program without waiting for it to complete.\n Signal encountered: "; write(1, msg, strlen(msg)); write(1, strsig, strlen(strsig)); @@ -1084,6 +1091,8 @@ void ponyint_tracing_thread_start(scheduler_t* sched) #if defined(PLATFORM_IS_LINUX) this_tracing_scheduler->tid = gettid(); +#elif defined(PLATFORM_IS_HAIKU) + this_tracing_scheduler->tid = get_pthread_thread_id(pthread_self()); #else pthread_threadid_np(NULL, &this_tracing_scheduler->tid); #endif @@ -2797,6 +2806,10 @@ static DECLARE_THREAD_FN(run_thread) (void)arg; ponyint_cpu_affinity(tracing_thread.cpu); +#if defined(PLATFORM_IS_HAIKU) + rename_thread(get_pthread_thread_id(pthread_self()), "tracing::run_thread"); +#endif + #if !defined(PLATFORM_IS_WINDOWS) // Make sure we block signals related to scheduler sleeping/waking // so they queue up to avoid race conditions From 1f0051138ab04362b37d6bf2061a0b75cba67bb3 Mon Sep 17 00:00:00 2001 From: ahwayakchih Date: Sun, 17 May 2026 18:52:08 +0200 Subject: [PATCH 06/10] Rename scheduler and wfo threads to follow pattern used for tracing. --- src/libponyrt/asio/wfo.c | 6 +++++- src/libponyrt/sched/scheduler.c | 8 ++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/libponyrt/asio/wfo.c b/src/libponyrt/asio/wfo.c index b2bd34b43b..21fb5b82d6 100644 --- a/src/libponyrt/asio/wfo.c +++ b/src/libponyrt/asio/wfo.c @@ -133,9 +133,13 @@ static void send_request(asio_event_t* ev, int32 req) LOG("sending request %s (req = %d)", wfo_event_names[req], req); status_t result = write_port(b->port, req, &msg, sizeof(msg)); if(result < B_OK) + { LOG("ERROR: sending request failed: %d", result); + } else + { LOG("sending request result: %d", result); + } } static void signal_handler(int sig, void* userData) @@ -493,7 +497,7 @@ DECLARE_THREAD_FN(ponyint_asio_backend_dispatch) asio_backend_t* b = arg; pony_assert(b != NULL); - rename_thread(get_pthread_thread_id(pthread_self()), "wfo_asio_backend_dispatch"); + rename_thread(get_pthread_thread_id(pthread_self()), "wfo::ponyint_asio_backend_dispatch"); // set_thread_priority(get_pthread_thread_id(pthread_self()), B_DISPLAY_PRIORITY); #if !defined(USE_SCHEDULER_SCALING_PTHREADS) diff --git a/src/libponyrt/sched/scheduler.c b/src/libponyrt/sched/scheduler.c index 03c43ab56a..f81a112a3e 100644 --- a/src/libponyrt/sched/scheduler.c +++ b/src/libponyrt/sched/scheduler.c @@ -16,6 +16,10 @@ #include "mutemap.h" #include "../tracing/tracing.h" +#if defined(PLATFORM_IS_HAIKU) +# include +#endif + #ifdef USE_RUNTIMESTATS #include #endif @@ -1304,6 +1308,10 @@ static DECLARE_THREAD_FN(run_thread) ponyint_cpu_affinity(sched->cpu); TRACING_THREAD_START(this_scheduler); +#if defined(PLATFORM_IS_HAIKU) + rename_thread(get_pthread_thread_id(pthread_self()), "scheduler::run_thread"); +#endif + #if !defined(PLATFORM_IS_WINDOWS) && !defined(USE_SCHEDULER_SCALING_PTHREADS) // Make sure we block signals related to scheduler sleeping/waking // so they queue up to avoid race conditions From 5ad1a5d33167c7814dcf34376a38c1d2aa42203d Mon Sep 17 00:00:00 2001 From: ahwayakchih Date: Tue, 19 May 2026 22:35:12 +0200 Subject: [PATCH 07/10] Implement pinning to cpus on Haiku --- benchmark/libponyc/CMakeLists.txt | 2 +- benchmark/libponyrt/CMakeLists.txt | 2 +- src/libponyc/codegen/genexe.cc | 2 +- src/libponyrt/sched/cpu.c | 132 +++++++++++++++++++++++++++-- src/ponyc/CMakeLists.txt | 2 +- test/libponyc/CMakeLists.txt | 2 +- test/libponyrt/CMakeLists.txt | 2 +- 7 files changed, 133 insertions(+), 11 deletions(-) diff --git a/benchmark/libponyc/CMakeLists.txt b/benchmark/libponyc/CMakeLists.txt index 20e03aa82a..dfc2afc124 100644 --- a/benchmark/libponyc/CMakeLists.txt +++ b/benchmark/libponyc/CMakeLists.txt @@ -39,7 +39,7 @@ elseif(${CMAKE_HOST_SYSTEM_NAME} MATCHES "BSD") elseif(${CMAKE_HOST_SYSTEM_NAME} MATCHES "DragonFly") target_link_libraries(libponyc.benchmarks PRIVATE execinfo atomic) elseif(${CMAKE_HOST_SYSTEM_NAME} MATCHES "Haiku") - target_link_libraries(libponyc.benchmarks PRIVATE execinfo atomic) + target_link_libraries(libponyc.benchmarks PRIVATE execinfo atomic gnu) else() target_link_libraries(libponyc.benchmarks PRIVATE atomic dl) target_link_options(libponyc.benchmarks PRIVATE "-static-libstdc++") diff --git a/benchmark/libponyrt/CMakeLists.txt b/benchmark/libponyrt/CMakeLists.txt index dfc06951fb..925d0fcad7 100644 --- a/benchmark/libponyrt/CMakeLists.txt +++ b/benchmark/libponyrt/CMakeLists.txt @@ -33,7 +33,7 @@ elseif(${CMAKE_HOST_SYSTEM_NAME} MATCHES "DragonFly") target_link_libraries(libponyrt.benchmarks PRIVATE execinfo atomic) target_link_options(libponyrt.benchmarks PRIVATE "-static-libstdc++") elseif(${CMAKE_HOST_SYSTEM_NAME} MATCHES "Haiku") - target_link_libraries(libponyrt.benchmarks PRIVATE execinfo atomic) + target_link_libraries(libponyrt.benchmarks PRIVATE execinfo atomic gnu) else() target_link_libraries(libponyrt.benchmarks PRIVATE atomic dl) target_link_options(libponyrt.benchmarks PRIVATE "-static-libgcc") diff --git a/src/libponyc/codegen/genexe.cc b/src/libponyc/codegen/genexe.cc index 138fb37f1c..d4cd84d4ff 100644 --- a/src/libponyc/codegen/genexe.cc +++ b/src/libponyc/codegen/genexe.cc @@ -1598,7 +1598,7 @@ static bool link_exe(compile_t* c, ast_t* program, #endif const char* lplatform = #if (defined(PLATFORM_IS_HAIKU)) - "-lroot -lnetwork -lstdc++ -lbsd"; + "-lroot -lnetwork -lstdc++ -lbsd -lgnu"; #else ""; #endif diff --git a/src/libponyrt/sched/cpu.c b/src/libponyrt/sched/cpu.c index 3ba3fb6f2c..c9e7649691 100644 --- a/src/libponyrt/sched/cpu.c +++ b/src/libponyrt/sched/cpu.c @@ -22,6 +22,7 @@ #include #include #elif defined(PLATFORM_IS_HAIKU) + #include #include #endif @@ -60,15 +61,111 @@ static uint32_t cpus_online(void) #if defined(PLATFORM_IS_HAIKU) +// Haiku supports up to 64 CPUs anyway, so let's make our life easier, +// and void alloc/free multiple times. +typedef unsigned long long cpu_map; +const uint8 MAX_CPU = sizeof(cpu_map) * CHAR_BIT; +static cpu_map cpu_cores = 0; +static cpu_map cpu_smts = 0; + static uint32_t haiku_cpu_count(void) { - system_info info; - if(get_system_info(&info) < B_OK) + system_info system; + if(get_system_info(&system) < B_OK) + { + cpu_cores = 1; + cpu_smts = 1; + return 1; // Bail early, since if this didn't work, everything is broken. + } + + // `system_info.cpu_count` reflects all SMTs, not just cores or physical dies. + // But it does not care if some of them are disabled at the moment. + + // Get info about all cpus to know which ones are enabled at the moment. + cpu_info info[MAX_CPU]; + if(get_cpu_info(0, MAX_CPU, info) != B_OK) + goto failure; + + // Check how many topology nodes there will be. + uint32 topology_count = 0; + if(get_cpu_topology_info(NULL, &topology_count) != B_OK) + goto failure; + + uint32 cpus_size = topology_count * sizeof(cpu_topology_node_info); + cpu_topology_node_info *cpus = + (cpu_topology_node_info*)ponyint_pool_alloc_size(cpus_size); + if(cpus == NULL) + goto failure; + + // Get all topology nodes. + if(get_cpu_topology_info(cpus, &topology_count) != B_OK) + { + ponyint_pool_free_size(cpus_size, cpus); + goto failure; + } + + // Topology goes like this: + // - ROOT + // -- PACKAGE + // --- CORE + // ---- SMT + // PACKAGE is a CPU die, so a "socket" in QEmu. + // Every CORE has at least one SMT node (AFAIK). + // We treat every first SMT's Id of core as a "core_id", others "more_id". + bool next_smt_is_core = false; + for(uint32 i = 0; i < topology_count; i++) + { + if(cpus[i].type == B_TOPOLOGY_CORE) + { + next_smt_is_core = true; + } + else if(cpus[i].type == B_TOPOLOGY_SMT) + { + if(cpus[i].id >= MAX_CPU) + break; + if(!info[cpus[i].id].enabled) + { + // Ignore disabled cpus. + // TODO: find a way to observe dynamically when cpu gets (re)enabled? + } + else if(next_smt_is_core) + { + cpu_cores |= 1L << cpus[i].id; + next_smt_is_core = false; + } + else + { + cpu_smts |= 1L << cpus[i].id; + } + } + } + + ponyint_pool_free_size(cpus_size, cpus); + + // success: + return __builtin_popcountll(cpu_cores); + + failure: + for(uint8 i = 0; i < system.cpu_count; i++) { - return 1; + cpu_cores |= 1LL << i; } + cpu_smts = cpu_cores; + return system.cpu_count; +} - return info.cpu_count; +static uint8 next_cpu_id(uint8 *last_pos, cpu_map *cpus, bool *cpus_are_cores) +{ + if(*cpus == 0) + { + *last_pos = 0; + *cpus_are_cores = !(*cpus_are_cores); + *cpus = *cpus_are_cores ? cpu_smts : cpu_cores; + } + uint8 pos = (uint8)__builtin_ctzll(*cpus) + 1; + *cpus = *cpus >> pos; + *last_pos += pos; + return *last_pos - 1; } #endif @@ -308,6 +405,27 @@ uint32_t ponyint_cpu_assign(uint32_t count, scheduler_t* scheduler, scheduler[i].cpu = i % hw_cpu_count; scheduler[i].node = 0; } +#elif defined(PLATFORM_IS_HAIKU) + uint8 cookie = 0; + cpu_map curr_map = cpu_cores; + bool curr_are_cores = true; + + for(uint32_t i = 0; i < count; i++) + { + scheduler[i].cpu = next_cpu_id(&cookie, &curr_map, &curr_are_cores); + scheduler[i].node = 0; + } + + // If pinning asio thread to a core is requested override the default + // asio_cpu of -1 + if(pinasio) + asio_cpu = next_cpu_id(&cookie, &curr_map, &curr_are_cores); + + if(pinpat) + pat_cpu = next_cpu_id(&cookie, &curr_map, &curr_are_cores); + + if(pin_tracing_thread) + *tracing_cpu = next_cpu_id(&cookie, &curr_map, &curr_are_cores); #else // Affinity groups rather than processor numbers. for(uint32_t i = 0; i < count; i++) @@ -349,7 +467,11 @@ void ponyint_cpu_affinity(uint32_t cpu) #elif defined(PLATFORM_IS_BSD) // No pinning, since we cannot yet determine hyperthreads vs physical cores. #elif defined(PLATFORM_IS_HAIKU) - // TODO: Not implemented yet! + cpu_set_t set; + CPU_ZERO(&set); + CPU_SET(cpu, &set); + + sched_setaffinity(0, sizeof(cpu_set_t), &set); #elif defined(PLATFORM_IS_MACOSX) thread_affinity_policy_data_t policy; policy.affinity_tag = cpu; diff --git a/src/ponyc/CMakeLists.txt b/src/ponyc/CMakeLists.txt index 196aab2dd4..9d4b9a23a3 100644 --- a/src/ponyc/CMakeLists.txt +++ b/src/ponyc/CMakeLists.txt @@ -41,7 +41,7 @@ else() elseif(${CMAKE_HOST_SYSTEM_NAME} MATCHES "DragonFly") target_link_libraries(ponyc PRIVATE execinfo atomic) elseif(${CMAKE_HOST_SYSTEM_NAME} MATCHES "Haiku") - target_link_libraries(ponyc PRIVATE execinfo atomic stdc++ root) + target_link_libraries(ponyc PRIVATE execinfo atomic stdc++ root gnu) else() execute_process(COMMAND ${CMAKE_CXX_COMPILER} --print-file-name=libstdc++.a OUTPUT_VARIABLE LIBSTDCXX_PATH diff --git a/test/libponyc/CMakeLists.txt b/test/libponyc/CMakeLists.txt index 97f1559808..4afa2799fc 100644 --- a/test/libponyc/CMakeLists.txt +++ b/test/libponyc/CMakeLists.txt @@ -122,7 +122,7 @@ else() elseif(${CMAKE_HOST_SYSTEM_NAME} MATCHES "DragonFly") target_link_libraries(libponyc.tests PRIVATE execinfo atomic) elseif(${CMAKE_HOST_SYSTEM_NAME} MATCHES "Haiku") - target_link_libraries(libponyc.tests PRIVATE execinfo atomic stdc++ root) + target_link_libraries(libponyc.tests PRIVATE execinfo atomic stdc++ root gnu) else() target_link_libraries(libponyc.tests PRIVATE atomic dl) target_link_options(libponyc.tests PRIVATE "-static-libstdc++") diff --git a/test/libponyrt/CMakeLists.txt b/test/libponyrt/CMakeLists.txt index 933b4f0365..bbb53f2466 100644 --- a/test/libponyrt/CMakeLists.txt +++ b/test/libponyrt/CMakeLists.txt @@ -33,7 +33,7 @@ elseif(${CMAKE_HOST_SYSTEM_NAME} MATCHES "BSD") elseif(${CMAKE_HOST_SYSTEM_NAME} MATCHES "DragonFly") target_link_libraries(libponyrt.tests PRIVATE execinfo atomic) elseif(${CMAKE_HOST_SYSTEM_NAME} MATCHES "Haiku") - target_link_libraries(libponyrt.tests PRIVATE execinfo atomic stdc++ root) + target_link_libraries(libponyrt.tests PRIVATE execinfo atomic stdc++ root gnu) else() target_link_libraries(libponyrt.tests PRIVATE atomic dl) target_link_options(libponyrt.tests PRIVATE "-static-libstdc++") From 33fae410fd5c99115b6ce5d33b92c825f418ae63 Mon Sep 17 00:00:00 2001 From: ahwayakchih Date: Tue, 19 May 2026 22:36:31 +0200 Subject: [PATCH 08/10] Add Haiku as one of `posix` systems in `platform.pony` --- packages/builtin/platform.pony | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/builtin/platform.pony b/packages/builtin/platform.pony index a7a02f71d0..ea4e264f20 100644 --- a/packages/builtin/platform.pony +++ b/packages/builtin/platform.pony @@ -5,7 +5,8 @@ primitive Platform fun openbsd(): Bool => compile_intrinsic fun linux(): Bool => compile_intrinsic fun osx(): Bool => compile_intrinsic - fun posix(): Bool => bsd() or linux() or osx() + fun haiku(): Bool => compile_intrinsic + fun posix(): Bool => bsd() or linux() or osx() or haiku() fun windows(): Bool => compile_intrinsic fun x86(): Bool => compile_intrinsic From f7da856174d543a210b0cdc4efd10d0f2107c5b8 Mon Sep 17 00:00:00 2001 From: ahwayakchih Date: Tue, 19 May 2026 22:36:56 +0200 Subject: [PATCH 09/10] Fix misspellings in comments. --- src/libponyrt/sched/cpu.c | 2 +- src/libponyrt/sched/scheduler.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libponyrt/sched/cpu.c b/src/libponyrt/sched/cpu.c index c9e7649691..5aa1b14be1 100644 --- a/src/libponyrt/sched/cpu.c +++ b/src/libponyrt/sched/cpu.c @@ -446,7 +446,7 @@ uint32_t ponyint_cpu_assign(uint32_t count, scheduler_t* scheduler, *tracing_cpu = (count + 2); #endif - // set the affinity of the current thread (nain thread) which is the pinned + // set the affinity of the current thread (main thread) which is the pinned // actor thread ponyint_cpu_affinity(pat_cpu); diff --git a/src/libponyrt/sched/scheduler.c b/src/libponyrt/sched/scheduler.c index f81a112a3e..9bc97ba052 100644 --- a/src/libponyrt/sched/scheduler.c +++ b/src/libponyrt/sched/scheduler.c @@ -1706,7 +1706,7 @@ pony_ctx_t* ponyint_sched_init(uint32_t threads, bool noyield, bool pin, uint32_t asio_cpu = ponyint_cpu_assign(scheduler_count, scheduler, pin, pinasio, pinpat, pin_tracing_thread, &tracing_cpu); - // make sure tracing knows how mant schedulers there are + // make sure tracing knows how many schedulers there are TRACING_SCHEDULERS_INIT(scheduler_count, tracing_cpu); #if !defined(PLATFORM_IS_WINDOWS) && defined(USE_SCHEDULER_SCALING_PTHREADS) From 9ca1e5bfd2af23c8fc7f8148362ad4e6224aabf7 Mon Sep 17 00:00:00 2001 From: ahwayakchih Date: Tue, 19 May 2026 22:43:40 +0200 Subject: [PATCH 10/10] Force regression-1118 to run on Haiku with max threads set to 2. Otherwise it keeps timing out. --- test/full-program-tests/regression-1118/main.pony | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/full-program-tests/regression-1118/main.pony b/test/full-program-tests/regression-1118/main.pony index 5498f70f66..a3a9271f9b 100644 --- a/test/full-program-tests/regression-1118/main.pony +++ b/test/full-program-tests/regression-1118/main.pony @@ -36,6 +36,13 @@ actor Main fun @runtime_override_defaults(rto: RuntimeOptions) => rto.ponynoblock = true + // Skip on Haiku: keeps timing out in QEmu with 4 cores enabled. + // It runs fine in QEmu with just 2 cores enabled or without time limit. + // It runs in ~35s on 2 cores and ~60s on 4 cores. + ifdef haiku then + rto.ponymaxthreads = 2 + end + actor Test var c: U64 = 0