From e9f903bbf9bb58d81f69fc1f80a7598abecb926f Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Sun, 21 Jun 2026 23:57:45 -0700 Subject: [PATCH 01/11] Add WASIp1/2/3 as reserved version identifiers (dlang/dmd!23293) --- dmd/cond.d | 3 ++ tests/dmd/fail_compilation/reserved_version.d | 37 ++++++++++++------- .../reserved_version_switch.d | 9 +++++ 3 files changed, 35 insertions(+), 14 deletions(-) diff --git a/dmd/cond.d b/dmd/cond.d index b1cdbbcade1..c8deb58a0c6 100644 --- a/dmd/cond.d +++ b/dmd/cond.d @@ -442,6 +442,9 @@ extern (C++) final class VersionCondition : DVCondition case "unittest": case "VisionOS": case "WASI": + case "WASIp1": + case "WASIp2": + case "WASIp3": case "WatchOS": case "WebAssembly": case "Win32": diff --git a/tests/dmd/fail_compilation/reserved_version.d b/tests/dmd/fail_compilation/reserved_version.d index b64b80ce90f..cdaaed5f643 100644 --- a/tests/dmd/fail_compilation/reserved_version.d +++ b/tests/dmd/fail_compilation/reserved_version.d @@ -111,20 +111,23 @@ fail_compilation/reserved_version.d(212): Error: version identifier `AsmJS` is r fail_compilation/reserved_version.d(213): Error: version identifier `Emscripten` is reserved and cannot be set fail_compilation/reserved_version.d(214): Error: version identifier `WebAssembly` is reserved and cannot be set fail_compilation/reserved_version.d(215): Error: version identifier `WASI` is reserved and cannot be set -fail_compilation/reserved_version.d(216): Error: version identifier `CppRuntime_LLVM` is reserved and cannot be set -fail_compilation/reserved_version.d(217): Error: version identifier `CppRuntime_DigitalMars` is reserved and cannot be set -fail_compilation/reserved_version.d(218): Error: version identifier `CppRuntime_GNU` is reserved and cannot be set -fail_compilation/reserved_version.d(219): Error: version identifier `CppRuntime_Microsoft` is reserved and cannot be set -fail_compilation/reserved_version.d(220): Error: version identifier `CppRuntime_Sun` is reserved and cannot be set -fail_compilation/reserved_version.d(221): Error: version identifier `D_PIE` is reserved and cannot be set -fail_compilation/reserved_version.d(222): Error: version identifier `AVR` is reserved and cannot be set -fail_compilation/reserved_version.d(223): Error: version identifier `D_PreConditions` is reserved and cannot be set -fail_compilation/reserved_version.d(224): Error: version identifier `D_PostConditions` is reserved and cannot be set -fail_compilation/reserved_version.d(225): Error: version identifier `D_ProfileGC` is reserved and cannot be set -fail_compilation/reserved_version.d(226): Error: version identifier `D_Invariants` is reserved and cannot be set -fail_compilation/reserved_version.d(227): Error: version identifier `D_Optimized` is reserved and cannot be set -fail_compilation/reserved_version.d(228): Error: version identifier `VisionOS` is reserved and cannot be set -fail_compilation/reserved_version.d(229): Error: version identifier `D_Profile` is reserved and cannot be set +fail_compilation/reserved_version.d(216): Error: version identifier `WASIp1` is reserved and cannot be set +fail_compilation/reserved_version.d(217): Error: version identifier `WASIp2` is reserved and cannot be set +fail_compilation/reserved_version.d(218): Error: version identifier `WASIp3` is reserved and cannot be set +fail_compilation/reserved_version.d(219): Error: version identifier `CppRuntime_LLVM` is reserved and cannot be set +fail_compilation/reserved_version.d(220): Error: version identifier `CppRuntime_DigitalMars` is reserved and cannot be set +fail_compilation/reserved_version.d(221): Error: version identifier `CppRuntime_GNU` is reserved and cannot be set +fail_compilation/reserved_version.d(222): Error: version identifier `CppRuntime_Microsoft` is reserved and cannot be set +fail_compilation/reserved_version.d(223): Error: version identifier `CppRuntime_Sun` is reserved and cannot be set +fail_compilation/reserved_version.d(224): Error: version identifier `D_PIE` is reserved and cannot be set +fail_compilation/reserved_version.d(225): Error: version identifier `AVR` is reserved and cannot be set +fail_compilation/reserved_version.d(226): Error: version identifier `D_PreConditions` is reserved and cannot be set +fail_compilation/reserved_version.d(227): Error: version identifier `D_PostConditions` is reserved and cannot be set +fail_compilation/reserved_version.d(228): Error: version identifier `D_ProfileGC` is reserved and cannot be set +fail_compilation/reserved_version.d(229): Error: version identifier `D_Invariants` is reserved and cannot be set +fail_compilation/reserved_version.d(230): Error: version identifier `D_Optimized` is reserved and cannot be set +fail_compilation/reserved_version.d(231): Error: version identifier `VisionOS` is reserved and cannot be set +fail_compilation/reserved_version.d(232): Error: version identifier `D_Profile` is reserved and cannot be set --- */ @@ -241,6 +244,9 @@ version = AsmJS; version = Emscripten; version = WebAssembly; version = WASI; +version = WASIp1; +version = WASIp2; +version = WASIp3; version = CppRuntime_LLVM; version = CppRuntime_DigitalMars; version = CppRuntime_GNU; @@ -323,6 +329,9 @@ debug = HPPA64; debug = SH; debug = WebAssembly; debug = WASI; +debug = WASIp1; +debug = WASIp2; +debug = WASIp3; debug = Alpha; debug = Alpha_SoftFloat; debug = Alpha_HardFloat; diff --git a/tests/dmd/fail_compilation/reserved_version_switch.d b/tests/dmd/fail_compilation/reserved_version_switch.d index 1d82b22f944..492fc16e47b 100644 --- a/tests/dmd/fail_compilation/reserved_version_switch.d +++ b/tests/dmd/fail_compilation/reserved_version_switch.d @@ -67,6 +67,9 @@ // REQUIRED_ARGS: -version=SH // REQUIRED_ARGS: -version=WebAssembly // REQUIRED_ARGS: -version=WASI +// REQUIRED_ARGS: -version=WASIp1 +// REQUIRED_ARGS: -version=WASIp2 +// REQUIRED_ARGS: -version=WASIp3 // REQUIRED_ARGS: -version=Alpha // REQUIRED_ARGS: -version=Alpha_SoftFloat // REQUIRED_ARGS: -version=Alpha_HardFloat @@ -178,6 +181,9 @@ // REQUIRED_ARGS: -debug=SH // REQUIRED_ARGS: -debug=WebAssembly // REQUIRED_ARGS: -debug=WASI +// REQUIRED_ARGS: -debug=WASIp1 +// REQUIRED_ARGS: -debug=WASIp2 +// REQUIRED_ARGS: -debug=WASIp3 // REQUIRED_ARGS: -debug=Alpha // REQUIRED_ARGS: -debug=Alpha_SoftFloat // REQUIRED_ARGS: -debug=Alpha_HardFloat @@ -294,6 +300,9 @@ Error: version identifier `HPPA64` is reserved and cannot be set Error: version identifier `SH` is reserved and cannot be set Error: version identifier `WebAssembly` is reserved and cannot be set Error: version identifier `WASI` is reserved and cannot be set +Error: version identifier `WASIp1` is reserved and cannot be set +Error: version identifier `WASIp2` is reserved and cannot be set +Error: version identifier `WASIp3` is reserved and cannot be set Error: version identifier `Alpha` is reserved and cannot be set Error: version identifier `Alpha_SoftFloat` is reserved and cannot be set Error: version identifier `Alpha_HardFloat` is reserved and cannot be set From b00ce0523e586f8aab6e58313ccb483dce2dca2c Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Tue, 23 Jun 2026 10:36:27 -0700 Subject: [PATCH 02/11] Mangle C `main` on Wasm --- dmd/mangle/package.d | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/dmd/mangle/package.d b/dmd/mangle/package.d index 8a3f59cae9d..0e1e4bec1f2 100644 --- a/dmd/mangle/package.d +++ b/dmd/mangle/package.d @@ -727,6 +727,23 @@ public: buf.writestring(fd.ident.toString()); return; } + + version (IN_LLVM) + { + import gen.llvmhelpers : isTargetWasm; + bool isWasm = isTargetWasm(); + } + else bool isWasm = false; + + if (fd.isCMain() && isWasm) + { + if (fd.parameters) + buf.writestring("__main_argc_argv"); + else + buf.writestring("__main_void"); + return; + } + visit(cast(Declaration)fd); } From ffae691cd4be8be723059063c07243a3489dc9ba Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Tue, 23 Jun 2026 22:11:55 -0700 Subject: [PATCH 03/11] druntime: Add `wasi-libc` support to `core.stdc` (dlang/dmd!23294) --- runtime/druntime/src/core/stdc/assert_.d | 7 + runtime/druntime/src/core/stdc/errno.d | 221 +++++++----------- runtime/druntime/src/core/stdc/fenv.d | 23 ++ runtime/druntime/src/core/stdc/limits.d | 13 ++ runtime/druntime/src/core/stdc/locale.d | 17 ++ runtime/druntime/src/core/stdc/math.d | 74 ++++++ runtime/druntime/src/core/stdc/signal.d | 33 ++- runtime/druntime/src/core/stdc/stdarg.d | 8 + runtime/druntime/src/core/stdc/stdio.d | 36 ++- runtime/druntime/src/core/stdc/stdlib.d | 2 +- runtime/druntime/src/core/stdc/time.d | 4 +- .../src/core/sys/wasi/posix/stdc/time.d | 29 +++ .../druntime/src/core/sys/wasi/posix/time.d | 37 +++ 13 files changed, 347 insertions(+), 157 deletions(-) create mode 100644 runtime/druntime/src/core/sys/wasi/posix/stdc/time.d create mode 100644 runtime/druntime/src/core/sys/wasi/posix/time.d diff --git a/runtime/druntime/src/core/stdc/assert_.d b/runtime/druntime/src/core/stdc/assert_.d index ac237d9c897..e1d1709d6f6 100644 --- a/runtime/druntime/src/core/stdc/assert_.d +++ b/runtime/druntime/src/core/stdc/assert_.d @@ -106,6 +106,13 @@ else version (CRuntime_Musl) */ noreturn __assert_fail(const(char)* exp, const(char)* file, uint line, const(char)* func); } +else version (CRuntime_WASI) +{ + /*** + * Assert failure function in the WASI C library. + */ + noreturn __assert_fail(const(char)* exp, const(char)* file, uint line, const(char)* func); +} else version (CRuntime_Newlib) { /*** diff --git a/runtime/druntime/src/core/stdc/errno.d b/runtime/druntime/src/core/stdc/errno.d index db347e404b4..8a956a71687 100644 --- a/runtime/druntime/src/core/stdc/errno.d +++ b/runtime/druntime/src/core/stdc/errno.d @@ -67,6 +67,14 @@ else version (CRuntime_Musl) alias errno = __errno_location; } } +else version (CRuntime_WASI) +{ + extern (C) + { + ref int __errno_location(); + alias errno = __errno_location; + } +} else version (CRuntime_Newlib) { extern (C) @@ -2320,142 +2328,87 @@ else version (Haiku) enum B_NO_TRANSLATOR = (B_TRANSLATION_ERROR_BASE + 1); enum B_ILLEGAL_DATA = (B_TRANSLATION_ERROR_BASE + 2); } -else version (WASI) +else version (CRuntime_WASI) { - enum EPERM = 1; - enum ENOENT = 2; - enum ESRCH = 3; - enum EINTR = 4; - enum EIO = 5; - enum ENXIO = 6; - enum E2BIG = 7; - enum ENOEXEC = 8; - enum EBADF = 9; - enum ECHILD = 10; - enum EAGAIN = 11; - enum ENOMEM = 12; - enum EACCES = 13; - enum EFAULT = 14; - enum ENOTBLK = 15; - enum EBUSY = 16; - enum EEXIST = 17; - enum EXDEV = 18; - enum ENODEV = 19; - enum ENOTDIR = 20; - enum EISDIR = 21; - enum EINVAL = 22; - enum ENFILE = 23; - enum EMFILE = 24; - enum ENOTTY = 25; - enum ETXTBSY = 26; - enum EFBIG = 27; - enum ENOSPC = 28; - enum ESPIPE = 29; - enum EROFS = 30; - enum EMLINK = 31; - enum EPIPE = 32; - enum EDOM = 33; - enum ERANGE = 34; - enum EDEADLK = 35; - enum ENAMETOOLONG = 36; - enum ENOLCK = 37; - enum ENOSYS = 38; - enum ENOTEMPTY = 39; - enum ELOOP = 40; + enum E2BIG = 1; + enum EACCES = 2; + enum EADDRINUSE = 3; + enum EADDRNOTAVAIL = 4; + enum EAFNOSUPPORT = 5; + enum EAGAIN = 6; + enum EALREADY = 7; + enum EBADF = 8; + enum EBADMSG = 9; + enum EBUSY = 10; + enum ECANCELED = 11; + enum ECHILD = 12; + enum ECONNABORTED = 13; + enum ECONNREFUSED = 14; + enum ECONNRESET = 15; + enum EDEADLK = 16; + enum EDESTADDRREQ = 17; + enum EDOM = 18; + enum EDQUOT = 19; + enum EEXIST = 20; + enum EFAULT = 21; + enum EFBIG = 22; + enum EHOSTUNREACH = 23; + enum EIDRM = 24; + enum EILSEQ = 25; + enum EINPROGRESS = 26; + enum EINTR = 27; + enum EINVAL = 28; + enum EIO = 29; + enum EISCONN = 30; + enum EISDIR = 31; + enum ELOOP = 32; + enum EMFILE = 33; + enum EMLINK = 34; + enum EMSGSIZE = 35; + enum EMULTIHOP = 36; + enum ENAMETOOLONG = 37; + enum ENETDOWN = 38; + enum ENETRESET = 39; + enum ENETUNREACH = 40; + enum ENFILE = 41; + enum ENOBUFS = 42; + enum ENODEV = 43; + enum ENOENT = 44; + enum ENOEXEC = 45; + enum ENOLCK = 46; + enum ENOLINK = 47; + enum ENOMEM = 48; + enum ENOMSG = 49; + enum ENOPROTOOPT = 50; + enum ENOSPC = 51; + enum ENOSYS = 52; + enum ENOTCONN = 53; + enum ENOTDIR = 54; + enum ENOTEMPTY = 55; + enum ENOTRECOVERABLE = 56; + enum ENOTSOCK = 57; + enum ENOTSUP = 58; + enum ENOTTY = 59; + enum ENXIO = 60; + enum EOVERFLOW = 61; + enum EOWNERDEAD = 62; + enum EPERM = 63; + enum EPIPE = 64; + enum EPROTO = 65; + enum EPROTONOSUPPORT = 66; + enum EPROTOTYPE = 67; + enum ERANGE = 68; + enum EROFS = 69; + enum ESPIPE = 70; + enum ESRCH = 71; + enum ESTALE = 72; + enum ETIMEDOUT = 73; + enum ETXTBSY = 74; + enum EXDEV = 75; + enum ENOTCAPABLE = 76; + + enum EOPNOTSUPP = ENOTSUP; enum EWOULDBLOCK = EAGAIN; - enum ENOMSG = 42; - enum EIDRM = 43; - enum ECHRNG = 44; - enum EL2NSYNC = 45; - enum EL3HLT = 46; - enum EL3RST = 47; - enum ELNRNG = 48; - enum EUNATCH = 49; - enum ENOCSI = 50; - enum EL2HLT = 51; - enum EBADE = 52; - enum EBADR = 53; - enum EXFULL = 54; - enum ENOANO = 55; - enum EBADRQC = 56; - enum EBADSLT = 57; - enum EDEADLOCK = EDEADLK; - enum EBFONT = 59; - enum ENOSTR = 60; - enum ENODATA = 61; - enum ETIME = 62; - enum ENOSR = 63; - enum ENONET = 64; - enum ENOPKG = 65; - enum EREMOTE = 66; - enum ENOLINK = 67; - enum EADV = 68; - enum ESRMNT = 69; - enum ECOMM = 70; - enum EPROTO = 71; - enum EMULTIHOP = 72; - enum EDOTDOT = 73; - enum EBADMSG = 74; - enum EOVERFLOW = 75; - enum ENOTUNIQ = 76; - enum EBADFD = 77; - enum EREMCHG = 78; - enum ELIBACC = 79; - enum ELIBBAD = 80; - enum ELIBSCN = 81; - enum ELIBMAX = 82; - enum ELIBEXEC = 83; - enum EILSEQ = 84; - enum ERESTART = 85; - enum ESTRPIPE = 86; - enum EUSERS = 87; - enum ENOTSOCK = 88; - enum EDESTADDRREQ = 89; - enum EMSGSIZE = 90; - enum EPROTOTYPE = 91; - enum ENOPROTOOPT = 92; - enum EPROTONOSUPPORT = 93; - enum ESOCKTNOSUPPORT = 94; - enum EOPNOTSUPP = 95; - enum ENOTSUP = EOPNOTSUPP; - enum EPFNOSUPPORT = 96; - enum EAFNOSUPPORT = 97; - enum EADDRINUSE = 98; - enum EADDRNOTAVAIL = 99; - enum ENETDOWN = 100; - enum ENETUNREACH = 101; - enum ENETRESET = 102; - enum ECONNABORTED = 103; - enum ECONNRESET = 104; - enum ENOBUFS = 105; - enum EISCONN = 106; - enum ENOTCONN = 107; - enum ESHUTDOWN = 108; - enum ETOOMANYREFS = 109; - enum ETIMEDOUT = 110; - enum ECONNREFUSED = 111; - enum EHOSTDOWN = 112; - enum EHOSTUNREACH = 113; - enum EALREADY = 114; - enum EINPROGRESS = 115; - enum ESTALE = 116; - enum EUCLEAN = 117; - enum ENOTNAM = 118; - enum ENAVAIL = 119; - enum EISNAM = 120; - enum EREMOTEIO = 121; - enum EDQUOT = 122; - enum ENOMEDIUM = 123; - enum EMEDIUMTYPE = 124; - enum ECANCELED = 125; - enum ENOKEY = 126; - enum EKEYEXPIRED = 127; - enum EKEYREVOKED = 128; - enum EKEYREJECTED = 129; - enum EOWNERDEAD = 130; - enum ENOTRECOVERABLE = 131; - enum ERFKILL = 132; - enum EHWPOISON = 133; } else { diff --git a/runtime/druntime/src/core/stdc/fenv.d b/runtime/druntime/src/core/stdc/fenv.d index 1c49d84906e..4e4b0317aa6 100644 --- a/runtime/druntime/src/core/stdc/fenv.d +++ b/runtime/druntime/src/core/stdc/fenv.d @@ -450,6 +450,16 @@ else version (CRuntime_Musl) static assert(false, "Architecture not supported."); } } +else version (CRuntime_WASI) +{ + import core.stdc.config : c_ulong; + struct fenv_t + { + c_ulong __cw; + } + + alias c_ulong fexcept_t; +} else version (CRuntime_Newlib) { version (AArch64) @@ -628,6 +638,14 @@ else version (Solaris) static assert(0, "Unimplemented architecture"); } } +else version (CRuntime_WASI) +{ + enum + { + FE_ALL_EXCEPT = 0, /// + FE_TONEAREST = 0, /// + } +} else { version (X86) @@ -927,6 +945,11 @@ else version (CRuntime_Musl) /// enum FE_DFL_ENV = cast(fenv_t*)(-1); } +else version (CRuntime_WASI) +{ + /// + enum FE_DFL_ENV = cast(fenv_t*)(-1); +} else version (CRuntime_UClibc) { /// diff --git a/runtime/druntime/src/core/stdc/limits.d b/runtime/druntime/src/core/stdc/limits.d index 2684b22bfcd..c5df0cd65b4 100644 --- a/runtime/druntime/src/core/stdc/limits.d +++ b/runtime/druntime/src/core/stdc/limits.d @@ -181,5 +181,18 @@ else version (Windows) /// enum PIPE_BUF = 5120; } +else version (CRuntime_WASI) +{ + /// + enum MAX_CANON = 255; + /// + enum MAX_INPUT = 255; + /// + enum NAME_MAX = 255; + /// + enum PATH_MAX = 256; + /// + enum PIPE_BUF = 512; +} else static assert(0, "unsupported OS"); diff --git a/runtime/druntime/src/core/stdc/locale.d b/runtime/druntime/src/core/stdc/locale.d index 50b45980dc3..23ad077f9a0 100644 --- a/runtime/druntime/src/core/stdc/locale.d +++ b/runtime/druntime/src/core/stdc/locale.d @@ -290,6 +290,23 @@ else version (CRuntime_Musl) /// enum LC_ALL = 6; } +else version (CRuntime_WASI) +{ + /// + enum LC_CTYPE = 0; + /// + enum LC_NUMERIC = 1; + /// + enum LC_TIME = 2; + /// + enum LC_COLLATE = 3; + /// + enum LC_MONETARY = 4; + /// + enum LC_MESSAGES = 5; + /// + enum LC_ALL = 6; +} else version (CRuntime_Newlib) { /// diff --git a/runtime/druntime/src/core/stdc/math.d b/runtime/druntime/src/core/stdc/math.d index 665037f1af3..e7ae9d58673 100644 --- a/runtime/druntime/src/core/stdc/math.d +++ b/runtime/druntime/src/core/stdc/math.d @@ -736,6 +736,80 @@ else version (CRuntime_Musl) pragma(mangle, real.sizeof == double.sizeof ? "__signbit" : "__signbitl") pure int signbit(real x); } +else version (CRuntime_WASI) +{ + enum + { + /// + FP_NAN, + /// + FP_INFINITE, + /// + FP_ZERO, + /// + FP_SUBNORMAL, + /// + FP_NORMAL, + } + + enum + { + /// + FP_FAST_FMA = 0, + /// + FP_FAST_FMAF = 0, + /// + FP_FAST_FMAL = 0, + } + + // In `wasi_libc`, these are defined as macros redirecting to Clang builtins + // We approximate the builtins based on their definitions in + // https://github.com/llvm/llvm-project/blob/main/clang/lib/CodeGen/CGBuiltin.cpp + version(LDC) + extern (D) pure pragma(inline, true) { + // __builtin_fpclassify(FP_NAN, FP_INFINITE, FP_NORMAL, FP_SUBNORMAL, FP_ZERO, val) + // Codegen only matches at -O1 or greater + int fpclassify(T)(T val) { + import ldc.llvmasm : __ir_pure; + import ldc.intrinsics : llvm_fabs; + + if (val == T(0.0)) return FP_ZERO; + if (val != val) return FP_NAN; + + auto fabsVal = llvm_fabs(val); + + if (fabsVal == T.infinity) return FP_INFINITE; + + // Work around to deal with https://wiki.dlang.org/Cross-compiling_with_LDC#Limitations + // real.min_normal ends up underflowing to zero + static if (is(T == real)) + return __ir_pure!(` + %cmp = fcmp uge fp128 %0, f0x00010000000000000000000000000000 + ret i1 %cmp + `, bool)(val) ? FP_NORMAL : FP_SUBNORMAL; + else return fabsVal >= T.min_normal ? FP_NORMAL : FP_SUBNORMAL; + } + + import ldc.intrinsics : llvm_is_fpclass; + bool isinf(T)(T val) pure => llvm_is_fpclass(val, (1 << 2) | (1 << 9)); // __builtin_isinf + bool isnan(T)(T val) pure => llvm_is_fpclass(val, (1 << 0) | (1 << 1)); // __builtin_isnan + bool isnormal(T)(T val) pure => llvm_is_fpclass(val, (1 << 3) | (1 << 8)); // __builtin_isnormal + bool isfinite(T)(T val) pure => llvm_is_fpclass(val, (1 << 3) | (1 << 4) | (1 << 5) | (1 << 6) | (1 << 7) | (1 << 8)); // __builtin_isfinite + + bool signbit(float val) pure => cast(int)val < 0; // __builtin_signbit + bool signbit(double val) pure => cast(long)val < 0; // __builtin_signbit + + // __builtin_signbit + // Uses LLVM IR to access i128 ops directly + // approximately: cast(cent)val < 0 + bool signbit(real val) pure => __ir_pure!(` + %cast = bitcast fp128 %0 to i128 + %cmp = icmp slt i128 %cast, 0 + ret i1 %cmp + `, bool)(val); + } + else static assert(0, "Unknown D compiler for WASI"); +} else version (CRuntime_UClibc) { enum diff --git a/runtime/druntime/src/core/stdc/signal.d b/runtime/druntime/src/core/stdc/signal.d index acd5ff9cbbe..7bc59a5be44 100644 --- a/runtime/druntime/src/core/stdc/signal.d +++ b/runtime/druntime/src/core/stdc/signal.d @@ -20,7 +20,12 @@ nothrow: // this should be volatile /// -alias sig_atomic_t = int; +version (CRuntime_WASI) { + import core.stdc.config : c_long; + alias c_long sig_atomic_t; +} else { + alias int sig_atomic_t; +} private alias sigfn_t = void function(int); @@ -70,6 +75,32 @@ else version (Windows) /// enum SIGTERM = 15; // Termination } +else version (CRuntime_WASI) +{ + extern(C) void __SIG_ERR(int); + extern(C) void __SIG_IGN(int); + + /// + enum SIG_ERR = cast(sigfn_t)&__SIG_ERR; + /// + enum SIG_DFL = cast(sigfn_t)0; + /// + enum SIG_IGN = cast(sigfn_t)&__SIG_IGN; + + // standard C signals + /// + enum SIGABRT = 6; // Abnormal termination + /// + enum SIGFPE = 8; // Floating-point error + /// + enum SIGILL = 4; // Illegal hardware instruction + /// + enum SIGINT = 2; // Terminal interrupt character + /// + enum SIGSEGV = 11; // Invalid memory reference + /// + enum SIGTERM = 15; // Termination +} /// sigfn_t signal(int sig, sigfn_t func); diff --git a/runtime/druntime/src/core/stdc/stdarg.d b/runtime/druntime/src/core/stdc/stdarg.d index 112766c8384..5c6f9bce806 100644 --- a/runtime/druntime/src/core/stdc/stdarg.d +++ b/runtime/druntime/src/core/stdc/stdarg.d @@ -261,6 +261,14 @@ T va_arg(T)(ref va_list ap) ap += T.sizeof.alignUp; return *p; } + else version (WebAssembly) + { + ap = ap.alignUp!(T.alignof); + auto p = cast(T*) ap; + ap += T.sizeof.alignUp!4; // align up to int (4-bytes, even on wasm64) + + return *p; + } else static assert(0, "Unsupported platform"); } diff --git a/runtime/druntime/src/core/stdc/stdio.d b/runtime/druntime/src/core/stdc/stdio.d index adb9f9d9a00..7e4c045d698 100644 --- a/runtime/druntime/src/core/stdc/stdio.d +++ b/runtime/druntime/src/core/stdc/stdio.d @@ -115,6 +115,20 @@ else version (CRuntime_Musl) L_tmpnam = 20 } } +else version (CRuntime_WASI) +{ + enum + { + /// + BUFSIZ = 1024, + /// + EOF = -1, + /// + FOPEN_MAX = 1000, + /// + FILENAME_MAX = 4096, + } +} else version (Darwin) { enum @@ -337,24 +351,6 @@ else version (CRuntime_UClibc) L_tmpnam = 20 } } -else version (WASI) -{ - enum - { - /// - BUFSIZ = 1024, - /// - EOF = -1, - /// - FOPEN_MAX = 1000, - /// - FILENAME_MAX = 4096, - /// - TMP_MAX = 10000, - /// - L_tmpnam = 20 - } -} else { static assert( false, "Unsupported platform" ); @@ -436,7 +432,7 @@ else version (CRuntime_Glibc) /// alias FILE = shared(_IO_FILE); } -else version (WASI) +else version (CRuntime_WASI) { union fpos_t { @@ -1148,7 +1144,7 @@ else version (CRuntime_UClibc) /// extern shared FILE* stderr; } -else version (WASI) +else version (CRuntime_WASI) { // needs tail const extern shared FILE* stdin; diff --git a/runtime/druntime/src/core/stdc/stdlib.d b/runtime/druntime/src/core/stdc/stdlib.d index fba91609f1e..d4195b9c4f8 100644 --- a/runtime/druntime/src/core/stdc/stdlib.d +++ b/runtime/druntime/src/core/stdc/stdlib.d @@ -99,9 +99,9 @@ else version (DragonFlyBSD) enum RAND_MAX = 0x7fffffff; else version (Solaris) enum RAND_MAX = 0x7fff; else version (CRuntime_Bionic) enum RAND_MAX = 0x7fffffff; else version (CRuntime_Musl) enum RAND_MAX = 0x7fffffff; +else version (CRuntime_WASI) enum RAND_MAX = 0x7fffffff; else version (CRuntime_Newlib) enum RAND_MAX = 0x7fffffff; else version (CRuntime_UClibc) enum RAND_MAX = 0x7fffffff; -else version (WASI) enum RAND_MAX = 0x7fffffff; else static assert( false, "Unsupported platform" ); /// diff --git a/runtime/druntime/src/core/stdc/time.d b/runtime/druntime/src/core/stdc/time.d index ead533718b7..6e2caa5e496 100644 --- a/runtime/druntime/src/core/stdc/time.d +++ b/runtime/druntime/src/core/stdc/time.d @@ -19,7 +19,9 @@ version (Posix) public import core.sys.posix.stdc.time; else version (Windows) public import core.sys.windows.stdc.time; -else +else version (WASI) { + public import core.sys.wasi.posix.stdc.time; +} else static assert(0, "unsupported system"); import core.stdc.config; diff --git a/runtime/druntime/src/core/sys/wasi/posix/stdc/time.d b/runtime/druntime/src/core/sys/wasi/posix/stdc/time.d new file mode 100644 index 00000000000..13439651f5d --- /dev/null +++ b/runtime/druntime/src/core/sys/wasi/posix/stdc/time.d @@ -0,0 +1,29 @@ +module core.sys.wasi.posix.stdc.time; + +extern (C): +@trusted: +nothrow: +@nogc: + +alias clock_t = long; // signed 64-bit +alias time_t = long; // signed 64-bit + +/// +struct tm +{ + int tm_sec; /// seconds after the minute [0-60] + int tm_min; /// minutes after the hour [0-59] + int tm_hour; /// hours since midnight [0-23] + int tm_mday; /// day of the month [1-31] + int tm_mon; /// months since January [0-11] + int tm_year; /// years since 1900 + int tm_wday; /// days since Sunday [0-6] + int tm_yday; /// days since January 1 [0-365] + int tm_isdst; /// Daylight Savings Time flag + int __tm_gmtoff; /// offset from CUT in seconds + const(char)* __tm_zone; /// timezone abbreviation + int __tm_nsec; +} + +enum clock_t CLOCKS_PER_SEC = 1_000_000_000; +clock_t clock(); diff --git a/runtime/druntime/src/core/sys/wasi/posix/time.d b/runtime/druntime/src/core/sys/wasi/posix/time.d new file mode 100644 index 00000000000..3585053801a --- /dev/null +++ b/runtime/druntime/src/core/sys/wasi/posix/time.d @@ -0,0 +1,37 @@ +module core.sys.wasi.posix.time; + +import core.stdc.config : muslRedirTime64Mangle; +import core.sys.wasi.posix.stdc.time : time_t; + +extern(C): +@nogc: +nothrow: +@trusted: + +struct __clockid; +alias clockid_t = __clockid*; + +private extern __gshared __clockid _CLOCK_MONOTONIC; +enum CLOCK_MONOTONIC = &_CLOCK_MONOTONIC; +private extern __gshared __clockid _CLOCK_REALTIME; +enum CLOCK_REALTIME = &_CLOCK_REALTIME; + +struct timespec { + time_t tv_sec; + long tv_nsec; +} + +alias long suseconds_t; + +struct timeval +{ + time_t tv_sec; + suseconds_t tv_usec; +} + +pragma(mangle, muslRedirTime64Mangle!("clock_getres", "__clock_getres_time64")) +int clock_getres(clockid_t, timespec*); +pragma(mangle, muslRedirTime64Mangle!("clock_gettime", "__clock_gettime64")) +int clock_gettime(clockid_t, timespec*); + +int nanosleep(const scope timespec*, timespec*); From 07211c4140800f74fba8c48b8d225a783cf63b6a Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Mon, 22 Jun 2026 22:05:10 -0700 Subject: [PATCH 04/11] Add WASI support to `core.internal` --- runtime/druntime/src/core/internal/abort.d | 81 +++++++++++++++++++ .../druntime/src/core/internal/entrypoint.d | 6 ++ .../core/internal/gc/impl/conservative/gc.d | 12 +++ 3 files changed, 99 insertions(+) diff --git a/runtime/druntime/src/core/internal/abort.d b/runtime/druntime/src/core/internal/abort.d index c0adde99dd2..96fa26bd67b 100644 --- a/runtime/druntime/src/core/internal/abort.d +++ b/runtime/druntime/src/core/internal/abort.d @@ -41,6 +41,87 @@ void abort(scope string msg, scope string filename = __FILE__, size_t line = __L } } } + else version (WASIp1) { + // currently depends on wasi-libc being linked in to provide these "syscalls" + // TODO: detach this from wasi-libc + alias ushort __wasi_errno_t; + alias int __wasi_fd_t; + alias size_t __wasi_size_t; + + extern(C) struct __wasi_ciovec_t { + const(ubyte)* buf; + __wasi_size_t buf_len; + } + + extern(C) __wasi_errno_t + __wasi_fd_write(__wasi_fd_t fd, + const __wasi_ciovec_t *iovs, + size_t iovs_len, __wasi_size_t *retptr0) @nogc nothrow; + + static void writeStr(scope const(char)[][] m...) @nogc nothrow @trusted + { + foreach (s; m) { + __wasi_ciovec_t iovec; + __wasi_size_t ret; + iovec.buf = cast(const(ubyte)*)s.ptr; + iovec.buf_len = s.length; + cast(void)__wasi_fd_write(2, &iovec, 1, &ret); + } + } + } else version (WASIp2) { + // currently depends on wasi-libc being linked in to provide these "syscalls" + // TODO: detach this from wasi-libc + extern(C) struct wasip2_list_u8_t { + ubyte* ptr; + size_t len; + } + extern(C) struct streams_own_output_stream_t { + int __handle; + } + extern(C) struct streams_borrow_output_stream_t { + int __handle; + } + alias streams_own_output_stream_t stderr_own_output_stream_t; + extern(C) struct io_error_own_error_t { + int __handle; + } + alias io_error_own_error_t streams_own_error_t; + + extern(C) struct streams_stream_error_t { + ubyte tag; + + union Val { + streams_own_error_t last_operation_failed; + } + Val val; + } + + extern(C) void streams_output_stream_drop_own(streams_own_output_stream_t handle) @nogc nothrow; + extern(C) streams_borrow_output_stream_t streams_borrow_output_stream(streams_own_output_stream_t handle) @nogc nothrow; + extern(C) stderr_own_output_stream_t stderr_get_stderr() @nogc nothrow; + extern(C) void io_error_error_drop_own(io_error_own_error_t handle) @nogc nothrow; + extern(C) void streams_stream_error_free(streams_stream_error_t *ptr) @nogc nothrow; + extern(C) bool streams_method_output_stream_blocking_write_and_flush(streams_borrow_output_stream_t self, wasip2_list_u8_t *contents, streams_stream_error_t *err) @nogc nothrow; + + static void writeStr(scope const(char)[][] m...) @nogc nothrow @trusted + { + auto stderr_own = stderr_get_stderr(); + scope(exit) streams_output_stream_drop_own(stderr_own); + + auto stderr = streams_borrow_output_stream(stderr_own); + + foreach (s; m) { + wasip2_list_u8_t contents; + contents.ptr = cast(ubyte*)s.ptr; + contents.len = s.length; + streams_stream_error_t err; + bool success = streams_method_output_stream_blocking_write_and_flush(stderr, &contents, &err); + if (!success) { + streams_stream_error_free(&err); + } + } + } + } else static assert(0, "Unsupported OS"); diff --git a/runtime/druntime/src/core/internal/entrypoint.d b/runtime/druntime/src/core/internal/entrypoint.d index 811a3bafbf3..a8bc04b8610 100644 --- a/runtime/druntime/src/core/internal/entrypoint.d +++ b/runtime/druntime/src/core/internal/entrypoint.d @@ -36,6 +36,12 @@ template _d_cmain() { int _d_run_main(int argc, char** argv, void* mainFunc); + version (CRuntime_WASI) + int __main_argc_argv(int argc, char **argv) + { + return _d_run_main(argc, argv, &_Dmain); + } + else int main(int argc, char** argv) { pragma(LDC_profile_instr, false); diff --git a/runtime/druntime/src/core/internal/gc/impl/conservative/gc.d b/runtime/druntime/src/core/internal/gc/impl/conservative/gc.d index 0d30a03f363..76005917e3e 100644 --- a/runtime/druntime/src/core/internal/gc/impl/conservative/gc.d +++ b/runtime/druntime/src/core/internal/gc/impl/conservative/gc.d @@ -33,6 +33,18 @@ version = COLLECT_PARALLEL; // parallel scanning version (Posix) version = COLLECT_FORK; +version (WASI) { + // TODO: get GC working on WASI + + enum PAGESIZE = 65536; + + extern(C) void _d_register_conservative_gc() + { + // no-op + } +} +else: + import core.internal.gc.bits; import core.internal.gc.os; import core.gc.config; From 2f3d041ca312b56a4cf05cc1c84911160da1dfb0 Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Tue, 23 Jun 2026 01:02:34 -0700 Subject: [PATCH 05/11] Undo `_d_cmain` change --- runtime/druntime/src/core/internal/entrypoint.d | 6 ------ 1 file changed, 6 deletions(-) diff --git a/runtime/druntime/src/core/internal/entrypoint.d b/runtime/druntime/src/core/internal/entrypoint.d index a8bc04b8610..811a3bafbf3 100644 --- a/runtime/druntime/src/core/internal/entrypoint.d +++ b/runtime/druntime/src/core/internal/entrypoint.d @@ -36,12 +36,6 @@ template _d_cmain() { int _d_run_main(int argc, char** argv, void* mainFunc); - version (CRuntime_WASI) - int __main_argc_argv(int argc, char **argv) - { - return _d_run_main(argc, argv, &_Dmain); - } - else int main(int argc, char** argv) { pragma(LDC_profile_instr, false); From b10c406b95166de54d7d6368aa6ad5951769b6c4 Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Thu, 2 Jul 2026 00:31:49 -0700 Subject: [PATCH 06/11] druntime: Fix some issues with dlang/dmd!23294 and dlang/dmd!23301 --- runtime/druntime/src/core/internal/abort.d | 21 ++++++++++++++------- runtime/druntime/src/core/stdc/math.d | 1 + 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/runtime/druntime/src/core/internal/abort.d b/runtime/druntime/src/core/internal/abort.d index 96fa26bd67b..2e64e1e13dc 100644 --- a/runtime/druntime/src/core/internal/abort.d +++ b/runtime/druntime/src/core/internal/abort.d @@ -53,7 +53,8 @@ void abort(scope string msg, scope string filename = __FILE__, size_t line = __L __wasi_size_t buf_len; } - extern(C) __wasi_errno_t + pragma(mangle, "__wasi_fd_write") + extern(C) static __wasi_errno_t __wasi_fd_write(__wasi_fd_t fd, const __wasi_ciovec_t *iovs, size_t iovs_len, __wasi_size_t *retptr0) @nogc nothrow; @@ -96,12 +97,18 @@ void abort(scope string msg, scope string filename = __FILE__, size_t line = __L Val val; } - extern(C) void streams_output_stream_drop_own(streams_own_output_stream_t handle) @nogc nothrow; - extern(C) streams_borrow_output_stream_t streams_borrow_output_stream(streams_own_output_stream_t handle) @nogc nothrow; - extern(C) stderr_own_output_stream_t stderr_get_stderr() @nogc nothrow; - extern(C) void io_error_error_drop_own(io_error_own_error_t handle) @nogc nothrow; - extern(C) void streams_stream_error_free(streams_stream_error_t *ptr) @nogc nothrow; - extern(C) bool streams_method_output_stream_blocking_write_and_flush(streams_borrow_output_stream_t self, wasip2_list_u8_t *contents, streams_stream_error_t *err) @nogc nothrow; + pragma(mangle, "streams_output_stream_drop_own") + extern(C) static void streams_output_stream_drop_own(streams_own_output_stream_t handle) @nogc nothrow; + pragma(mangle, "streams_borrow_output_stream") + extern(C) static streams_borrow_output_stream_t streams_borrow_output_stream(streams_own_output_stream_t handle) @nogc nothrow; + pragma(mangle, "stderr_get_stderr") + extern(C) static stderr_own_output_stream_t stderr_get_stderr() @nogc nothrow; + pragma(mangle, "io_error_error_drop_own") + extern(C) static void io_error_error_drop_own(io_error_own_error_t handle) @nogc nothrow; + pragma(mangle, "streams_stream_error_free") + extern(C) static void streams_stream_error_free(streams_stream_error_t *ptr) @nogc nothrow; + pragma(mangle, "streams_method_output_stream_blocking_write_and_flush") + extern(C) static bool streams_method_output_stream_blocking_write_and_flush(streams_borrow_output_stream_t self, wasip2_list_u8_t *contents, streams_stream_error_t *err) @nogc nothrow; static void writeStr(scope const(char)[][] m...) @nogc nothrow @trusted { diff --git a/runtime/druntime/src/core/stdc/math.d b/runtime/druntime/src/core/stdc/math.d index e7ae9d58673..6dadabecfd4 100644 --- a/runtime/druntime/src/core/stdc/math.d +++ b/runtime/druntime/src/core/stdc/math.d @@ -802,6 +802,7 @@ else version (CRuntime_WASI) // __builtin_signbit // Uses LLVM IR to access i128 ops directly // approximately: cast(cent)val < 0 + import ldc.llvmasm : __ir_pure; bool signbit(real val) pure => __ir_pure!(` %cast = bitcast fp128 %0 to i128 %cmp = icmp slt i128 %cast, 0 From 6583824b176cb3a66f55e036561630ac36b23d84 Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Thu, 2 Jul 2026 18:28:43 -0700 Subject: [PATCH 07/11] druntime: DRuntime support for WASI (dlang/dmd!23305) --- runtime/druntime/src/core/gc/config.d | 7 +- runtime/druntime/src/core/memory.d | 4 + runtime/druntime/src/core/sync/condition.d | 24 ++++ runtime/druntime/src/core/sync/event.d | 22 ++++ runtime/druntime/src/core/sync/mutex.d | 8 ++ runtime/druntime/src/core/sync/semaphore.d | 32 +++++ runtime/druntime/src/core/thread/fiber/base.d | 4 + .../druntime/src/core/thread/fiber/package.d | 4 + runtime/druntime/src/core/thread/osthread.d | 101 +++++++++++++++- runtime/druntime/src/core/thread/types.d | 5 + runtime/druntime/src/core/time.d | 110 ++++++++++++++++++ runtime/druntime/src/rt/cover.d | 3 + runtime/druntime/src/rt/dmain2.d | 16 +++ runtime/druntime/src/rt/monitor_.d | 24 ++++ runtime/druntime/src/rt/profilegc.d | 6 + runtime/druntime/src/rt/sections.d | 2 + runtime/druntime/src/rt/sections_wasm.d | 74 ++++++++++++ runtime/druntime/src/rt/trace.d | 4 + 18 files changed, 448 insertions(+), 2 deletions(-) create mode 100644 runtime/druntime/src/rt/sections_wasm.d diff --git a/runtime/druntime/src/core/gc/config.d b/runtime/druntime/src/core/gc/config.d index c3b79e0926b..edede3313c9 100644 --- a/runtime/druntime/src/core/gc/config.d +++ b/runtime/druntime/src/core/gc/config.d @@ -19,7 +19,12 @@ struct Config bool disable; // start disabled bool fork = false; // optional concurrent behaviour ubyte profile; // enable profiling with summary when terminating program - string gc = "conservative"; // select gc implementation conservative|precise|manual + + // TODO: support full `conservative` GC on Wasm + version (WebAssembly) + string gc = "manual"; // select gc implementation conservative|precise|manual + else + string gc = "conservative"; // select gc implementation conservative|precise|manual @MemVal size_t initReserve; // initial reserve (bytes) @MemVal size_t minPoolSize = 1 << 20; // initial and minimum pool size (bytes) diff --git a/runtime/druntime/src/core/memory.d b/runtime/druntime/src/core/memory.d index 8cc63c85c91..a6e3bd20b50 100644 --- a/runtime/druntime/src/core/memory.d +++ b/runtime/druntime/src/core/memory.d @@ -223,6 +223,10 @@ private extern (C) void _initialize() @system GetSystemInfo(&si); (cast() pageSize) = cast(size_t) si.dwPageSize; } + else version (WebAssembly) + { + (cast() pageSize) = cast(size_t) 65536; + } else static assert(false, __FUNCTION__ ~ " is not implemented on this platform"); } diff --git a/runtime/druntime/src/core/sync/condition.d b/runtime/druntime/src/core/sync/condition.d index 9328f6d4886..678f3b1cc11 100644 --- a/runtime/druntime/src/core/sync/condition.d +++ b/runtime/druntime/src/core/sync/condition.d @@ -41,6 +41,10 @@ else version (Posix) pthread_cond_signal, pthread_cond_t, pthread_cond_timedwait, pthread_cond_wait; import core.sys.posix.time : timespec; } +else version (WASI) +{ + // Dummy no-ops +} else { static assert(false, "Platform not supported"); @@ -250,6 +254,10 @@ class Condition if ( rc ) throw staticError!AssertError("Unable to wait for condition", __FILE__, __LINE__); } + else version (WASI) + { + throw staticError!AssertError("Unable to wait for condition", __FILE__, __LINE__); + } } /** @@ -315,6 +323,10 @@ class Condition return false; throw staticError!AssertError("Unable to wait for condition", __FILE__, __LINE__); } + else version (WASI) + { + throw staticError!AssertError("Unable to wait for condition", __FILE__, __LINE__); + } } /** @@ -363,6 +375,10 @@ class Condition if ( rc ) throw staticError!AssertError("Unable to notify condition", __FILE__, __LINE__); } + else version (WASI) + { + throw staticError!AssertError("Unable to notify condition", __FILE__, __LINE__); + } } /** @@ -411,6 +427,10 @@ class Condition if ( rc ) throw staticError!AssertError("Unable to notify condition", __FILE__, __LINE__); } + else version (WASI) + { + throw staticError!AssertError("Unable to notify condition", __FILE__, __LINE__); + } } private: @@ -616,6 +636,10 @@ private: Mutex m_assocMutex; pthread_cond_t m_hndl; } + else version (WASI) + { + Mutex m_assocMutex; + } } diff --git a/runtime/druntime/src/core/sync/event.d b/runtime/druntime/src/core/sync/event.d index 6278218f673..6f80aab1970 100644 --- a/runtime/druntime/src/core/sync/event.d +++ b/runtime/druntime/src/core/sync/event.d @@ -26,6 +26,10 @@ else version (Posix) import core.sys.posix.sys.types : pthread_cond_t, pthread_mutex_t; import core.sys.posix.time : timespec; } +else version (WASI) +{ + // Dummy no-op +} else { static assert(false, "Platform not supported"); @@ -133,6 +137,10 @@ nothrow @nogc: m_manualReset = manualReset; m_initalized = true; } + else version (WASI) + { + abort("Error: Cannot initialize Event on WASI."); + } } // copying not allowed, can produce resource leaks @@ -192,6 +200,10 @@ nothrow @nogc: pthread_mutex_unlock(&m_mutex); } } + else version (WASI) + { + abort("Error: Cannot deinitialize Event on WASI."); + } } /// Reset the event manually @@ -229,6 +241,11 @@ nothrow @nogc: { return wait(Duration.max); } + else version (WASI) + { + abort("Error: Cannot wait for Event on WASI."); + return false; + } } /** @@ -290,6 +307,11 @@ nothrow @nogc: return result == 0; } + else version (WASI) + { + abort("Error: Cannot wait for Event on WASI."); + return false; + } } private: diff --git a/runtime/druntime/src/core/sync/mutex.d b/runtime/druntime/src/core/sync/mutex.d index 86a112119d8..700289c87e9 100644 --- a/runtime/druntime/src/core/sync/mutex.d +++ b/runtime/druntime/src/core/sync/mutex.d @@ -31,6 +31,10 @@ else version (Posix) pthread_mutexattr_init, pthread_mutexattr_settype; import core.sys.posix.sys.types : pthread_mutex_t, pthread_mutexattr_t; } +else version (WASI) +{ + // Dummy no-op +} else { static assert(false, "Platform not supported"); @@ -275,6 +279,10 @@ class Mutex : { return pthread_mutex_trylock(&self.m_hndl) == 0; } + else version (WASI) + { + return true; + } } diff --git a/runtime/druntime/src/core/sync/semaphore.d b/runtime/druntime/src/core/sync/semaphore.d index 44465669b7e..7f85d3ed06b 100644 --- a/runtime/druntime/src/core/sync/semaphore.d +++ b/runtime/druntime/src/core/sync/semaphore.d @@ -50,6 +50,10 @@ else version (Posix) import core.sys.posix.semaphore : sem_destroy, sem_init, sem_post, sem_t, sem_timedwait, sem_trywait, sem_wait; import core.sys.posix.time : clock_gettime, CLOCK_REALTIME, timespec; } +else version (WASI) +{ + // Dummy no-op +} else { static assert(false, "Platform not supported"); @@ -107,6 +111,10 @@ class Semaphore if ( rc ) throw new SyncError( "Unable to create semaphore" ); } + else version (WASI) + { + m_hndl = count; + } } @@ -172,6 +180,12 @@ class Semaphore throw new SyncError( "Unable to wait for semaphore" ); } } + else version (WASI) + { + if (m_hndl == 0) throw new SyncError( "Unable to wait for semaphore" ); + + m_hndl -= 1; + } } @@ -268,6 +282,11 @@ class Semaphore throw new SyncError( "Unable to wait for semaphore" ); } } + else version (WASI) + { + wait(); + return true; + } } @@ -297,6 +316,13 @@ class Semaphore if ( rc ) throw new SyncError( "Unable to notify semaphore" ); } + else version (WASI) + { + if (m_hndl < typeof(m_hndl).max) + m_hndl += 1; + else + throw new SyncError( "Unable to notify semaphore" ); + } } @@ -340,6 +366,11 @@ class Semaphore throw new SyncError( "Unable to wait for semaphore" ); } } + else version (WASI) + { + wait(); + return true; + } } @@ -351,6 +382,7 @@ protected: else version (Darwin) alias Handle = semaphore_t; /// ditto else version (Posix) alias Handle = sem_t; + else version (WASI) alias Handle = uint; /// Handle to the system-specific semaphore. Handle m_hndl; diff --git a/runtime/druntime/src/core/thread/fiber/base.d b/runtime/druntime/src/core/thread/fiber/base.d index 115d876aff4..20bb3c33dff 100644 --- a/runtime/druntime/src/core/thread/fiber/base.d +++ b/runtime/druntime/src/core/thread/fiber/base.d @@ -11,6 +11,10 @@ module core.thread.fiber.base; +// No Fiber support on WebAssembly (arch issue) +// TODO: Investigate Wasm `stack-switching` proposal as solution +version(WebAssembly) {} else: + package: import core.thread.fiber; diff --git a/runtime/druntime/src/core/thread/fiber/package.d b/runtime/druntime/src/core/thread/fiber/package.d index c2c0ecdc249..f99e1eacf24 100644 --- a/runtime/druntime/src/core/thread/fiber/package.d +++ b/runtime/druntime/src/core/thread/fiber/package.d @@ -11,6 +11,10 @@ module core.thread.fiber; +// No Fiber support on WebAssembly (arch issue) +// TODO: Investigate Wasm `stack-switching` proposal as solution +version(WebAssembly) {} else: + import core.thread.context; import core.thread.fiber.base : fiber_entryPoint, FiberBase; import core.thread.threadbase; diff --git a/runtime/druntime/src/core/thread/osthread.d b/runtime/druntime/src/core/thread/osthread.d index d50a58bfc0c..01e25b4beda 100644 --- a/runtime/druntime/src/core/thread/osthread.d +++ b/runtime/druntime/src/core/thread/osthread.d @@ -158,6 +158,14 @@ else version (Posix) // Use POSIX threads for suspend/resume } } +else version (WASI) +{ + // No real threading support + // Just manipulations of the main "thread" + import core.stdc.stdlib : free, malloc, realloc; + import core.stdc.errno : EINTR, errno; + import core.sys.wasi.posix.time : nanosleep, timespec; +} version (GNU) { @@ -518,6 +526,8 @@ class Thread : ThreadBase multiThreadedFlag = false; } + version (WASI) onThreadError("cannot start new threads on WASI"); + version (Windows) {} else version (Posix) { @@ -671,6 +681,11 @@ class Thread : ThreadBase // on object destruction. m_addr = m_addr.init; } + else version (WASI) + { + throw new ThreadException( "Unable to join thread" ); + } + if ( m_unhandled ) { if ( rethrow ) @@ -702,6 +717,23 @@ class Thread : ThreadBase return THREAD_PRIORITY_NORMAL; } } + else version (WASI) + { + @property static int PRIORITY_MIN() @nogc nothrow pure @safe + { + return 0; + } + + @property static const(int) PRIORITY_MAX() @nogc nothrow pure @safe + { + return 0; + } + + @property static int PRIORITY_DEFAULT() @nogc nothrow pure @safe + { + return 0; + } + } else { private struct Priority @@ -886,6 +918,10 @@ class Thread : ThreadBase } return param.sched_priority; } + else version (WASI) + { + return 0; + } } @@ -1026,6 +1062,11 @@ class Thread : ThreadBase { return atomicLoad(m_isRunning); } + else version (WASI) + { + // the "main thread" is the only that will pass super.isRunning(), and is always running + return true; + } } @@ -1088,6 +1129,23 @@ class Thread : ThreadBase timespec tin = void; timespec tout = void; + val.split!("seconds", "nsecs")(tin.tv_sec, tin.tv_nsec); + if ( val.total!"seconds" > tin.tv_sec.max ) + tin.tv_sec = tin.tv_sec.max; + while ( true ) + { + if ( !nanosleep( &tin, &tout ) ) + return; + if ( errno != EINTR ) + assert(0, "Unable to sleep for the specified duration"); + tin = tout; + } + } + else version (WASI) + { + timespec tin = void; + timespec tout = void; + val.split!("seconds", "nsecs")(tin.tv_sec, tin.tv_nsec); if ( val.total!"seconds" > tin.tv_sec.max ) tin.tv_sec = tin.tv_sec.max; @@ -1345,6 +1403,13 @@ private extern (D) ThreadBase attachThread(ThreadBase _thisThread) @nogc nothrow atomicStore!(MemoryOrder.raw)(thisThread.toThread.m_isRunning, true); } + else version (WASI) + { + thisThread.m_addr = 1; // assumes this is done only once + thisContext.bstack = getStackBottom(); + thisContext.tstack = thisContext.bstack; + } + thisThread.m_isDaemon = true; thisThread.tlsRTdataInit(); Thread.setThis( thisThread ); @@ -1690,7 +1755,7 @@ in (fn) }} asm pure nothrow @nogc { ("sd $gp, %0") : "=m" (regs[8]); - ("sd $fp, %0") : "=m" (regs[9]); + ("sd $fp, %0") : "=m" (regs[9]); ("sd $ra, %0") : "=m" (sp); } } @@ -1794,6 +1859,11 @@ in (fn) } assert(0, "implement AArch64 inline assembler for callWithStackShell()"); // TODO AArch64 } + else version (WebAssembly) + { + // Wasm is special in that this isn't really possible + // Spilling has to be done somehow else... + } else { static assert(false, "Architecture not supported."); @@ -1819,6 +1889,10 @@ else version (Windows) { alias getpid = imported!"core.sys.windows.winbase".GetCurrentProcessId; } +else version (WASI) +{ + int getpid() @nogc nothrow @safe => 1; +} extern (C) @nogc nothrow { @@ -2396,6 +2470,10 @@ private extern (D) bool suspend( Thread t ) nothrow @nogc t.m_curr.tstack = getStackTop(); } } + else version (WASI) + { + onThreadError( "Unable to suspend thread" ); + } return true; } @@ -2417,6 +2495,8 @@ extern (C) void thread_preStopTheWorld() nothrow { */ extern (C) void thread_suspendAll() nothrow { + version (WASI) onThreadError( "Unable to suspend all threads" ); + // NOTE: We've got an odd chicken & egg problem here, because while the GC // is required to call thread_init before calling any other thread // routines, thread_init may allocate memory which could in turn @@ -2569,6 +2649,10 @@ private extern (D) void resume(ThreadBase _t) nothrow @nogc t.m_curr.tstack = t.m_curr.bstack; } } + else version (WASI) + { + onThreadError( "Unable to resume thread" ); + } else static assert(false, "Platform not supported."); } @@ -3071,6 +3155,17 @@ else version (Posix) } } } +else version (WASI) +{ + // + // Entry point for WASI threads + // + extern (C) void* thread_entryPoint( void* arg ) nothrow + { + onThreadError("Cannot enter new WASI threads."); + return null; + } +} else { // NOTE: This is the only place threading versions are checked. If a new @@ -3453,6 +3548,10 @@ void joinLowLevelThread(ThreadID tid) nothrow @nogc if (pthread_join(tid, null) != 0) onThreadError("Unable to join thread"); } + else version (WASI) + { + onThreadError("Unable to join thread"); + } } nothrow @nogc unittest diff --git a/runtime/druntime/src/core/thread/types.d b/runtime/druntime/src/core/thread/types.d index e630c39668e..aa482542de0 100644 --- a/runtime/druntime/src/core/thread/types.d +++ b/runtime/druntime/src/core/thread/types.d @@ -24,6 +24,11 @@ version (Posix) alias ThreadID = pthread_t; } +else +version (WASI) +{ + alias ThreadID = ubyte; // dummy; always 1 +} struct ll_ThreadData { diff --git a/runtime/druntime/src/core/time.d b/runtime/druntime/src/core/time.d index beb6963486c..2355f18036a 100644 --- a/runtime/druntime/src/core/time.d +++ b/runtime/druntime/src/core/time.d @@ -92,6 +92,10 @@ else version (Posix) import core.sys.posix.sys.time : gettimeofday, timeval; import core.sys.posix.time : clock_getres, clock_gettime, CLOCK_MONOTONIC, timespec; } +else version (WASI) +{ + import core.sys.wasi.posix.time : clock_getres, clock_gettime, CLOCK_MONOTONIC, timespec; +} version (unittest) import core.stdc.stdio : printf; @@ -337,6 +341,13 @@ else version (Solaris) enum ClockType second = 6, threadCPUTime = 7, } +else version (WASI) enum ClockType +{ + normal = 0, + coarse = 2, + precise = 3, + second = 6 +} else { // It needs to be decided (and implemented in an appropriate version branch @@ -445,6 +456,21 @@ version (Posix) assert(0, "What are the monotonic clock types supported by this system?"); } } +else +version (WASI) +{ + private auto _posixClock(ClockType clockType) + { + import core.sys.wasi.posix.time; + with(ClockType) final switch (clockType) + { + case coarse: return CLOCK_MONOTONIC; + case normal: return CLOCK_MONOTONIC; + case precise: return CLOCK_MONOTONIC; + case second: assert(0); + } + } +} unittest { @@ -2132,6 +2158,10 @@ struct MonoTimeImpl(ClockType clockType) { enum clockArg = _posixClock(clockType); } + else version (WASI) + { + enum clockArg = _posixClock(clockType); + } else static assert(0, "Unsupported platform"); @@ -2197,6 +2227,24 @@ struct MonoTimeImpl(ClockType clockType) 1_000_000_000L, ticksPerSecond)); } + else version (WASI) + { + timespec ts = void; + immutable error = clock_gettime(clockArg, &ts); + // clockArg is supported and if tv_sec is long or larger + // overflow won't happen before 292 billion years A.D. + static if (ts.tv_sec.max < long.max) + { + if (error) + { + import core.internal.abort : abort; + abort("Call to clock_gettime failed."); + } + } + return MonoTimeImpl(convClockFreq(ts.tv_sec * 1_000_000_000L + ts.tv_nsec, + 1_000_000_000L, + ticksPerSecond)); + } } @@ -2592,6 +2640,34 @@ extern(C) void _d_initMonoTime() @nogc nothrow } } } + else version (WASI) + { + timespec ts; + foreach (i, typeStr; __traits(allMembers, ClockType)) + { + static if (typeStr != "second") + { + enum clockArg = _posixClock(__traits(getMember, ClockType, typeStr)); + if (clock_getres(clockArg, &ts) == 0) + { + // ensure we are only writing immutable data once + if (tps[i] != 0) + // should only be called once + assert(0); + + // For some reason, on some systems, clock_getres returns + // a resolution which is clearly wrong: + // - it's a millisecond or worse, but the time is updated + // much more frequently than that. + // - it's negative + // - it's zero + // In such cases, we'll just use nanosecond resolution. + tps[i] = ts.tv_sec != 0 || ts.tv_nsec <= 0 || ts.tv_nsec >= 1000 + ? 1_000_000_000L : 1_000_000_000L / ts.tv_nsec; + } + } + } + } else static assert(0, "Unsupported platform"); } @@ -2893,6 +2969,23 @@ deprecated: else ticksPerSec = 1_000_000; } + else version (WASI) + { + timespec ts; + + if (clock_getres(CLOCK_MONOTONIC, &ts) != 0) + ticksPerSec = 0; + else + { + //For some reason, on some systems, clock_getres returns + //a resolution which is clearly wrong (it's a millisecond + //or worse, but the time is updated much more frequently + //than that). In such cases, we'll just use nanosecond + //resolution. + ticksPerSec = ts.tv_nsec >= 1000 ? 1_000_000_000 + : 1_000_000_000 / ts.tv_nsec; + } + } else static assert(0, "Unsupported platform"); @@ -3462,6 +3555,23 @@ deprecated: tv.tv_usec * TickDuration.ticksPerSec / 1000 / 1000); } } + else version (WASI) + { + timespec ts = void; + immutable error = clock_gettime(CLOCK_MONOTONIC, &ts); + // CLOCK_MONOTONIC is supported and if tv_sec is long or larger + // overflow won't happen before 292 billion years A.D. + static if (ts.tv_sec.max < long.max) + { + if (error) + { + import core.internal.abort : abort; + abort("Call to clock_gettime failed."); + } + } + return TickDuration(ts.tv_sec * TickDuration.ticksPerSec + + ts.tv_nsec * TickDuration.ticksPerSec / 1000 / 1000 / 1000); + } } version (CoreUnittest) @safe nothrow unittest diff --git a/runtime/druntime/src/rt/cover.d b/runtime/druntime/src/rt/cover.d index 86723cfb4bd..d5b3961b21f 100644 --- a/runtime/druntime/src/rt/cover.d +++ b/runtime/druntime/src/rt/cover.d @@ -11,6 +11,9 @@ module rt.cover; +version (WASI) {} +else: + import core.internal.utf; import core.internal.util.math : max, min; import core.stdc.stdio : EOF, fclose, fgetc, FILE, fileno, fprintf, fread, fseek, ftell, printf, SEEK_END, SEEK_SET, diff --git a/runtime/druntime/src/rt/dmain2.d b/runtime/druntime/src/rt/dmain2.d index 156b0764906..2519ae92add 100644 --- a/runtime/druntime/src/rt/dmain2.d +++ b/runtime/druntime/src/rt/dmain2.d @@ -43,6 +43,10 @@ else version (Posix) { import core.stdc.string : strlen; } +else version (WASI) +{ + import core.stdc.string : strlen; +} version (DigitalMars) version (AArch64) version = UseMalloc; // cuz alloca() is not implemented yet @@ -349,6 +353,18 @@ extern (C) int _d_run_main(int argc, char** argv, MainFunc mainFunc) totalArgsLength += arg.length; } } + else version (WASI) + { + // Allocate args[] on the stack + char[][] args = (cast(char[]*) alloca(argc * (char[]).sizeof))[0 .. argc]; + + size_t totalArgsLength = 0; + foreach (i, ref arg; args) + { + arg = argv[i][0 .. strlen(argv[i])]; + totalArgsLength += arg.length; + } + } else static assert(0); diff --git a/runtime/druntime/src/rt/monitor_.d b/runtime/druntime/src/rt/monitor_.d index b63707ee959..643e0e59de5 100644 --- a/runtime/druntime/src/rt/monitor_.d +++ b/runtime/druntime/src/rt/monitor_.d @@ -24,6 +24,10 @@ else version (Posix) pthread_mutexattr_settype; import core.sys.posix.sys.types : pthread_mutex_t, pthread_mutexattr_t; } +else version (WASI) +{ + // dummy no-op +} else { static assert(0, "Unsupported platform"); @@ -225,6 +229,26 @@ else version (Posix) pthread_mutex_unlock(mtx) && assert(0); } } +else version (WASI) { +@nogc: + alias Mutex = ubyte; + + void initMutex(Mutex* mtx) + { + } + + void destroyMutex(Mutex* mtx) + { + } + + void lockMutex(Mutex* mtx) + { + } + + void unlockMutex(Mutex* mtx) + { + } +} struct Monitor { diff --git a/runtime/druntime/src/rt/profilegc.d b/runtime/druntime/src/rt/profilegc.d index dd12995f422..1fd5ba857a8 100644 --- a/runtime/druntime/src/rt/profilegc.d +++ b/runtime/druntime/src/rt/profilegc.d @@ -13,6 +13,12 @@ module rt.profilegc; +// On Wasm, it increases codesize noticably. +// Meanwhile, ldc (ldmd2) does not support `-profile-gc`, so there's no point +// keeping it around on Wasm targets +version (WASI) {} +else: + private: import core.stdc.errno : errno; diff --git a/runtime/druntime/src/rt/sections.d b/runtime/druntime/src/rt/sections.d index 770343540b5..2beeb2e06f5 100644 --- a/runtime/druntime/src/rt/sections.d +++ b/runtime/druntime/src/rt/sections.d @@ -69,6 +69,8 @@ else version (CRuntime_Bionic) public import rt.sections_elf_shared; else version (CRuntime_UClibc) public import rt.sections_elf_shared; +else version (WebAssembly) + public import rt.sections_wasm; else static assert(0, "unimplemented"); diff --git a/runtime/druntime/src/rt/sections_wasm.d b/runtime/druntime/src/rt/sections_wasm.d new file mode 100644 index 00000000000..b054bf855da --- /dev/null +++ b/runtime/druntime/src/rt/sections_wasm.d @@ -0,0 +1,74 @@ +module rt.sections_wasm; + +version (WebAssembly): + +import rt.minfo; + +struct SectionGroup +{ + static int opApply(scope int delegate(ref SectionGroup) dg) + { + return dg(_sections); + } + + static int opApplyReverse(scope int delegate(ref SectionGroup) dg) + { + return dg(_sections); + } + + @property immutable(ModuleInfo*)[] modules() const nothrow @nogc + { + return _moduleGroup.modules; + } + + @property ref inout(ModuleGroup) moduleGroup() inout return nothrow @nogc + { + return _moduleGroup; + } + + @property inout(void[])[] gcRanges() inout return nothrow @nogc + { + return null; + } + +private: + ModuleGroup _moduleGroup; +} + +void initSections() nothrow @nogc +{ + auto mbeg = cast(immutable ModuleInfo**)&__start___minfo; + auto mend = cast(immutable ModuleInfo**)&__stop___minfo; + _sections.moduleGroup = ModuleGroup(mbeg[0 .. mend - mbeg]); +} + +void finiSections() nothrow @nogc +{ +} + +void[] initTLSRanges() nothrow @nogc +{ + return null; +} + +void finiTLSRanges(void[] rng) nothrow @nogc +{ +} + +void scanTLSRanges(void[] rng, scope void delegate(void* pbeg, void* pend) nothrow dg) nothrow +{ + dg(rng.ptr, rng.ptr + rng.length); +} + +private: + +__gshared SectionGroup _sections; + +extern(C) +{ + extern __gshared + { + void* __start___minfo; + void* __stop___minfo; + } +} diff --git a/runtime/druntime/src/rt/trace.d b/runtime/druntime/src/rt/trace.d index 18a37b7a1df..99ee1ad487f 100644 --- a/runtime/druntime/src/rt/trace.d +++ b/runtime/druntime/src/rt/trace.d @@ -11,6 +11,10 @@ module rt.trace; +// TODO(?): support this on Wasm at some point +version (WASI) {} +else: + import core.demangle; import core.stdc.ctype : isalpha, isgraph, isspace; import core.stdc.stdio : EOF, fclose, fgetc, FILE, fopen, fprintf, stderr, stdout; From 5d5e3d1e320fb28054a6cebc963878010efa661b Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Sun, 5 Jul 2026 22:23:39 -0700 Subject: [PATCH 08/11] Enable GC on WASI --- runtime/druntime/src/core/gc/config.d | 7 +------ .../core/internal/gc/impl/conservative/gc.d | 20 ++++++------------- runtime/druntime/src/core/thread/osthread.d | 8 +++++++- runtime/druntime/src/rt/sections_wasm.d | 12 +++++++++-- 4 files changed, 24 insertions(+), 23 deletions(-) diff --git a/runtime/druntime/src/core/gc/config.d b/runtime/druntime/src/core/gc/config.d index edede3313c9..c3b79e0926b 100644 --- a/runtime/druntime/src/core/gc/config.d +++ b/runtime/druntime/src/core/gc/config.d @@ -19,12 +19,7 @@ struct Config bool disable; // start disabled bool fork = false; // optional concurrent behaviour ubyte profile; // enable profiling with summary when terminating program - - // TODO: support full `conservative` GC on Wasm - version (WebAssembly) - string gc = "manual"; // select gc implementation conservative|precise|manual - else - string gc = "conservative"; // select gc implementation conservative|precise|manual + string gc = "conservative"; // select gc implementation conservative|precise|manual @MemVal size_t initReserve; // initial reserve (bytes) @MemVal size_t minPoolSize = 1 << 20; // initial and minimum pool size (bytes) diff --git a/runtime/druntime/src/core/internal/gc/impl/conservative/gc.d b/runtime/druntime/src/core/internal/gc/impl/conservative/gc.d index 76005917e3e..340a4decd09 100644 --- a/runtime/druntime/src/core/internal/gc/impl/conservative/gc.d +++ b/runtime/druntime/src/core/internal/gc/impl/conservative/gc.d @@ -29,22 +29,12 @@ module core.internal.gc.impl.conservative.gc; //debug = VALGRIND; // Valgrind memcheck integration /***************************************************/ -version = COLLECT_PARALLEL; // parallel scanning +version (WASI) {} // WASI is single-threaded +else version = COLLECT_PARALLEL; // parallel scanning + version (Posix) version = COLLECT_FORK; -version (WASI) { - // TODO: get GC working on WASI - - enum PAGESIZE = 65536; - - extern(C) void _d_register_conservative_gc() - { - // no-op - } -} -else: - import core.internal.gc.bits; import core.internal.gc.os; import core.gc.config; @@ -3351,7 +3341,9 @@ Lmark: rangesLock.unlock(); rootsLock.unlock(); } - thread_suspendAll(); + + version (WASI) {} // WASI is single-threaded + else thread_suspendAll(); prepare(); diff --git a/runtime/druntime/src/core/thread/osthread.d b/runtime/druntime/src/core/thread/osthread.d index 01e25b4beda..8936e62c165 100644 --- a/runtime/druntime/src/core/thread/osthread.d +++ b/runtime/druntime/src/core/thread/osthread.d @@ -1862,7 +1862,13 @@ in (fn) else version (WebAssembly) { // Wasm is special in that this isn't really possible - // Spilling has to be done somehow else... + // to dump "registers" (Wasm locals & value stack) onto + // the linear-memory "C" stack easily. + // + // Spilling has to be done somehow else. + + import ldc.intrinsics : llvm_stackaddress; + sp = llvm_stackaddress(); } else { diff --git a/runtime/druntime/src/rt/sections_wasm.d b/runtime/druntime/src/rt/sections_wasm.d index b054bf855da..f7e97d57225 100644 --- a/runtime/druntime/src/rt/sections_wasm.d +++ b/runtime/druntime/src/rt/sections_wasm.d @@ -28,11 +28,11 @@ struct SectionGroup @property inout(void[])[] gcRanges() inout return nothrow @nogc { - return null; + return _gcRanges[]; } - private: ModuleGroup _moduleGroup; + void[][1] _gcRanges; } void initSections() nothrow @nogc @@ -40,6 +40,11 @@ void initSections() nothrow @nogc auto mbeg = cast(immutable ModuleInfo**)&__start___minfo; auto mend = cast(immutable ModuleInfo**)&__stop___minfo; _sections.moduleGroup = ModuleGroup(mbeg[0 .. mend - mbeg]); + + // TODO: PIC support + auto pbeg = cast(void*)&__global_base; + auto pend = cast(void*)&__data_end; + _sections._gcRanges[0] = pbeg[0 .. pend - pbeg]; } void finiSections() nothrow @nogc @@ -70,5 +75,8 @@ extern(C) { void* __start___minfo; void* __stop___minfo; + + void* __global_base; + void* __data_end; } } From 436cbd324f206a568ddedcd565bc28cd4cccb00f Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Tue, 14 Jul 2026 15:46:40 -0700 Subject: [PATCH 09/11] Fix `callWithStackShell` on old LLVM --- runtime/druntime/src/core/thread/osthread.d | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/runtime/druntime/src/core/thread/osthread.d b/runtime/druntime/src/core/thread/osthread.d index 8936e62c165..c84e15fa3df 100644 --- a/runtime/druntime/src/core/thread/osthread.d +++ b/runtime/druntime/src/core/thread/osthread.d @@ -1867,8 +1867,10 @@ in (fn) // // Spilling has to be done somehow else. - import ldc.intrinsics : llvm_stackaddress; - sp = llvm_stackaddress(); + import ldc.intrinsics; + + static if (LLVM_major >= 22) sp = llvm_stackaddress(); + else sp = llvm_stacksave(); } else { From e5150d1a0763f1f9cf8e8e18acc6d4bf87358aa5 Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Wed, 15 Jul 2026 12:53:33 -0700 Subject: [PATCH 10/11] druntime: Use WASIp1 directly instead of POSIX emulation --- runtime/druntime/src/core/internal/abort.d | 27 +- runtime/druntime/src/core/sys/wasi/p1.d | 1703 +++++++++++++++++++ runtime/druntime/src/core/thread/osthread.d | 27 +- runtime/druntime/src/core/time.d | 103 +- 4 files changed, 1829 insertions(+), 31 deletions(-) create mode 100644 runtime/druntime/src/core/sys/wasi/p1.d diff --git a/runtime/druntime/src/core/internal/abort.d b/runtime/druntime/src/core/internal/abort.d index 2e64e1e13dc..60e71679f8b 100644 --- a/runtime/druntime/src/core/internal/abort.d +++ b/runtime/druntime/src/core/internal/abort.d @@ -42,31 +42,16 @@ void abort(scope string msg, scope string filename = __FILE__, size_t line = __L } } else version (WASIp1) { - // currently depends on wasi-libc being linked in to provide these "syscalls" - // TODO: detach this from wasi-libc - alias ushort __wasi_errno_t; - alias int __wasi_fd_t; - alias size_t __wasi_size_t; - - extern(C) struct __wasi_ciovec_t { - const(ubyte)* buf; - __wasi_size_t buf_len; - } - - pragma(mangle, "__wasi_fd_write") - extern(C) static __wasi_errno_t - __wasi_fd_write(__wasi_fd_t fd, - const __wasi_ciovec_t *iovs, - size_t iovs_len, __wasi_size_t *retptr0) @nogc nothrow; + import core.sys.wasi.p1 : CIOVec, fdWrite; static void writeStr(scope const(char)[][] m...) @nogc nothrow @trusted { foreach (s; m) { - __wasi_ciovec_t iovec; - __wasi_size_t ret; - iovec.buf = cast(const(ubyte)*)s.ptr; - iovec.buf_len = s.length; - cast(void)__wasi_fd_write(2, &iovec, 1, &ret); + CIOVec[1] iovecs; + size_t bytesWritten; + iovecs[0].buf = cast(const(ubyte)*)s.ptr; + iovecs[0].bufLen = s.length; + cast(void)fdWrite(2, iovecs[], bytesWritten); } } } else version (WASIp2) { diff --git a/runtime/druntime/src/core/sys/wasi/p1.d b/runtime/druntime/src/core/sys/wasi/p1.d new file mode 100644 index 00000000000..533354873e1 --- /dev/null +++ b/runtime/druntime/src/core/sys/wasi/p1.d @@ -0,0 +1,1703 @@ +/** + * D bindings for WASI preview1. + * + * Based on `wasi-libc`'s `wasi/wasip1.h`, adjusted to be more idiomatic D. + * + * We bind and wrap the "syscalls" directly, rather than using the wrappers + * provided by `wasi-libc`. + * + * Standards: $(LINK2 https://github.com/WebAssembly/WASI/tree/wasi-0.1/, WebAssembly System Interface (WASI) snapshot preview1) + */ +module core.sys.wasi.p1; + +version (WASIp1): + +version (D_LP64) static assert(0, "WASIp1 is only supported on wasm32, not wasm64"); + +version (LDC) { + import ldc.attributes : llvmAttr; + + private alias AliasSeq(T...) = T; + private alias wasiImport(string name) = AliasSeq!( + llvmAttr("wasm-import-module", "wasi_snapshot_preview1"), + llvmAttr("wasm-import-name", name) + ); +} else static assert("Unknown compiler for WASI."); + +@nogc nothrow: + +static assert(byte.alignof == 1, "non-wasi data layout"); +static assert(ubyte.alignof == 1, "non-wasi data layout"); +static assert(short.alignof == 2, "non-wasi data layout"); +static assert(ushort.alignof == 2, "non-wasi data layout"); +static assert(int.alignof == 4, "non-wasi data layout"); +static assert(uint.alignof == 4, "non-wasi data layout"); +static assert(long.alignof == 8, "non-wasi data layout"); +static assert(ulong.alignof == 8, "non-wasi data layout"); +static assert((void*).alignof == 4, "non-wasi data layout"); + +static assert(size_t.sizeof == 4, "witx calculated size"); +static assert(size_t.alignof == 4, "witx calculated align"); + +/// Non-negative file size or length of a region within a file. +alias FileSize = ulong; + +/// Timestamp in nanoseconds. +alias Timestamp = ulong; + +/// Identifiers for clocks. +enum ClockID : uint { + /// The clock measuring real time. + /// Time value zero corresponds with 1970-01-01T00:00:00Z. + realtime, + + /** + * The store-wide monotonic clock, which is defined as a clock measuring + * real time, whose value cannot be adjusted and which cannot have + * negative clock jumps. + * + * The epoch of this clock is undefined. The absolute time value of this + * clock therefore has no meaning. + */ + monotonic, + + /// The CPU-time clock associated with the current process. + processCPUTimeID, + + /// The CPU-time clock associated with the current thread. + threadCPUTimeID +} + +/** + * Error codes returned by functions. Not all of these error codes are + * returned by the functions provided by this API; some are used in + * higher-level library layers, and others are provided merely for + * alignment with POSIX. + */ +enum Errno : ushort { + /// No error occurred. System call completed successfully. + success, + + /// Argument list too long. + _2big, + + /// Permission denied. + acces, + + /// Address in use. + addrinuse, + + /// Address not available. + addrnotavail, + + /// Address family not supported. + afnosupport, + + /// Resource unavailable, or operation would block. + again, + + /// Connection already in progress. + already, + + /// Bad file descriptor. + badf, + + /// Bad message. + badmsg, + + /// Device or resource busy. + busy, + + /// Operation canceled. + canceled, + + /// No child processes. + child, + + /// Connection aborted. + connaborted, + + /// Connection refused. + connrefused, + + /// Connection reset. + connreset, + + /// Resource deadlock would occur. + deadlk, + + /// Destination address required. + destaddrreq, + + /// Mathematics argument out of domain of function. + dom, + + /// Reserved. + dquot, + + /// File exists. + exist, + + /// Bad address. + fault, + + /// File too large. + fbig, + + /// Host is unreachable. + hostunreach, + + /// Identifier removed. + idrm, + + /// Illegal byte sequence. + ilseq, + + /// Operation in progress. + inprogress, + + /// Interrupted function. + intr, + + /// Invalid argument. + inval, + + /// I/O error. + io, + + /// Socket is connected. + isconn, + + /// Is a directory. + isdir, + + /// Too many levels of symbolic links. + loop, + + /// File descriptor value too large. + mfile, + + /// Too many links. + mlink, + + /// Message too large. + msgsize, + + /// Reserved. + multihop, + + /// Filename too long. + nametoolong, + + /// Network is down. + netdown, + + /// Connection aborted by network. + netreset, + + /// Network unreachable. + netunreach, + + /// Too many files open in system. + nfile, + + /// No buffer space available. + nobufs, + + /// No such device. + nodev, + + /// No such file or directory. + noent, + + /// Executable file format error. + noexec, + + /// No locks available. + nolck, + + /// Reserved. + nolink, + + /// Not enough space. + nomem, + + /// No message of the desired type. + nomsg, + + /// Protocol not available. + noprotoopt, + + /// No space left on device. + nospc, + + /// Function not supported. + nosys, + + /// The socket is not connected. + notconn, + + /// Not a directory or a symbolic link to a directory. + notdir, + + /// Directory not empty. + notempty, + + /// State not recoverable. + notrecoverable, + + /// Not a socket. + notsock, + + /// Not supported, or operation not supported on socket. + notsup, + + /// Inappropriate I/O control operation. + notty, + + /// No such device or address. + nxio, + + /// Value too large to be stored in data type. + overflow, + + /// Previous owner died. + ownerdead, + + /// Operation not permitted. + perm, + + /// Broken pipe. + pipe, + + /// Protocol error. + proto, + + /// Protocol not supported. + protonosupport, + + /// Protocol wrong type for socket. + prototype, + + /// Result too large. + range, + + /// Read-only file system. + rofs, + + /// Invalid seek. + spipe, + + /// No such process. + srch, + + /// Reserved. + stale, + + /// Connection timed out. + timedout, + + /// Text file busy. + txtbsy, + + /// Cross-device link. + xdev, + + /// Extension: Capabilities insufficient. + notcapable +} + +/// File descriptor rights, determining which actions may be performed. +enum Rights : ulong { + /// The right to invoke `.fdDataSync`. + /// If `pathOpen` is set, includes the right to invoke + /// `.pathOpen` with `FdFlags.dsync`. + fdDataSync = (1 << 0), + + /// The right to invoke `.fdRead` and `.sockRecv`. + /// If `Rights.fdSeek` is set, includes the right to invoke `.fdPread`. + fdRead = (1 << 1), + + /// The right to invoke `.fdSeek`. + /// This flag implies `fdTell`. + fdSeek = (1 << 2), + + /// The right to invoke `.fdFdstatSetFlags`. + fdFdstatSetFlags = (1 << 3), + + /// The right to invoke `.fdSync`. + /// If `pathOpen` is set, includes the right to invoke + /// `.pathOpen` with `FdFlags.rsync` and `FdFlags.dsync`. + fdSync = (1 << 4), + + /// The right to invoke `.fdSeek` in such a way that the + /// file offset remains unaltered (i.e., `Whence.cur` with offset zero), + /// or to invoke `.fdTell`. + fdTell = (1 << 5), + + /// The right to invoke `.fdWrite` and `.sockSend`. + /// If `fdSeek` is set, includes the right to invoke `.fdPwrite`. + fdWrite = (1 << 6), + + /// The right to invoke `.fdAdvise`. + fdAdvise = (1 << 7), + + /// The right to invoke `.fdAllocate`. + fdAllocate = (1 << 8), + + /// The right to invoke `.pathCreateDirectory`. + pathCreateDirectory = (1 << 9), + + /// If `pathOpen` is set, the right to invoke + /// `.pathOpen` with `OFlags.create`. + pathCreateFile = (1 << 10), + + /// The right to invoke `.pathLink` with the file descriptor + /// as the source directory. + pathLinkSource = (1 << 11), + + /// The right to invoke `.pathLink` with the file descriptor as the target. + pathLinkTarget = (1 << 12), + + /// The right to invoke `.pathOpen`. + pathOpen = (1 << 13), + + /// The right to invoke `.fdReadDir`. + fdReadDir = (1 << 14), + + /// The right to invoke `.pathReadLink`. + pathReadLink = (1 << 15), + + /// The right to invoke `.pathRename` with the source file descriptor. + pathRenameSource = (1 << 16), + + /// The right to invoke `.pathRename` with the target file descriptor. + pathRenameTarget = (1 << 17), + + /// The right to invoke `.pathFilestatGet`. + pathFilestatGet = (1 << 18), + + /** + * The right to change a file's size. If `pathOpen` is set, includes the + * right to invoke `.pathOpen` with `OFlags.trunc`. + * Note: + * There is no function named `pathFilestatSetSize`. + * This follows POSIX design, which only has `ftruncate` + * and does not provide `ftruncateat`. + */ + pathFilestatSetSize = (1 << 19), + + /// The right to invoke `.pathFilestatSetTimes`. + pathFilestatSetTimes = (1 << 20), + + /// The right to invoke `.fdFilestatGet`. + fdFilestatGet = (1 << 21), + + /// The right to invoke `.fdFilestatSetSize`. + fdFilestatSetSize = (1 << 22), + + /// The right to invoke `.fdFilestatSetTimes`. + fdFilestatSetTimes = (1 << 23), + + /// The right to invoke `.pathSymlink`. + pathSymlink = (1 << 24), + + /// The right to invoke `.pathRemoveDirectory`. + pathRemoveDirectory = (1 << 25), + + /// The right to invoke `.pathUnlinkFile`. + pathUnlinkFile = (1 << 26), + + /** + * If `Rights.fdRead` is set, includes the right to invoke `.pollOnOff` + * to subscribe to `EventType.fdRead`. + * If `Rights.fdWrite` is set, includes the right to invoke `.pollOneoff` + * to subscribe to `EventType.fdWrite`. + */ + pollFdReadwrite = (1 << 27), + + /// The right to invoke `.sockShutdown`. + sockShutdown = (1 << 28), + + /// The right to invoke `.sockAccept`. + sockAccept = (1 << 29), +} + +/// A file descriptor handle. +alias Fd = int; + +/// A region of memory for scatter/gather reads. +extern(C) struct IOVec { + ubyte* buf; + size_t bufLen; +} + +/// A region of memory for scatter/gather writes. +extern(C) struct CIOVec { + const(ubyte)* buf; + size_t bufLen; +} + +/// +alias IOVecArray = IOVec[]; + +/// +alias CIOVecArray = CIOVec[]; + +/// Relative offset within a file. +alias FileDelta = long; + +/// The position relative to which to set the offset of the file descriptor. +enum Whence : ubyte { + /// Seek relative to start-of-file. + set, + + /// Seek relative to current position. + cur, + + /// Seek relative to end-of-file. + end +} + +/// A reference to the offset of a directory entry. +alias DirCookie = ulong; +enum DirCookie DIRCOOKIE_START = 0; + +/// File serial number that is unique within its file system. +alias Inode = ulong; + +/// The type of a file descriptor or file. +enum FileType : ubyte { + /// The type of the file descriptor or file is unknown or + /// is different from any of the other types specified. + unknown, + + /// The file descriptor or file refers to a block device inode. + blockDevice, + + /// The file descriptor or file refers to a character device inode. + characterDevice, + + /// The file descriptor or file refers to a directory inode. + directory, + + /// The file descriptor or file refers to a regular file inode. + regularFile, + + /// The file descriptor or file refers to a datagram socket. + socketDgram, + + /// The file descriptor or file refers to a byte-stream socket. + socketStream, + + /// The file refers to a symbolic link inode. + symbolicLink, +} + +/// A directory entry. +extern(C) struct DirEnt { + /// The offset of the next directory entry stored in this directory. + DirCookie next; + + /// The serial number of the file referred to by this directory entry. + Inode inode; + + /// The length of the name of the directory entry. + uint nameLength; + + /// The type of the file referred to by this directory entry. + FileType type; +} + +/// File or memory access pattern advisory information. +enum Advice : ubyte { + /// The application has no advice to give on its behavior + /// with respect to the specified data. + normal, + + /// The application expects to access the specified data sequentially + /// from lower offsets to higher offsets. + sequential, + + /// The application expects to access the specified data + /// in a random order. + random, + + /// The application expects to access the specified data + /// in the near future. + willNeed, + + /// The application expects that it will not access the specified data + /// in the near future. + dontNeed, + + /// The application expects to access the specified data once + /// and then not reuse it thereafter. + noReuse, +} + +/// File descriptor flags. +enum FdFlags : ushort { + /// Append mode: Data written to the file is always appended + /// to the file's end. + append = (1 << 0), + + /** + * Write according to synchronized I/O data integrity completion. + * Only the data stored in the file is synchronized. + * + * This feature is not available on all platforms and therefore + * `.pathOpen` and other such functions which accept `FdFlags` may return + * `Errno.notsup` in the case that this flag is set. + */ + dsync = (1 << 1), + + /// Non-blocking mode. + nonBlock = (1 << 2), + + /** + * Synchronized read I/O operations. + * + * This feature is not available on all platforms and therefore + * `.pathOpen` and other such functions which accept `FdFlags` may return + * `Errno.notsup` in the case that this flag is set. + */ + rsync = (1 << 3), + + /** + * Write according to synchronized I/O file integrity completion. + * In addition to synchronizing the data stored in the file, + * the implementation may also synchronously update the file's metadata. + * + * This feature is not available on all platforms and therefore + * `.pathOpen` and other such functions which accept `FdFlags` may return + * `Errno.notsup` in the case that this flag is set. + */ + sync = (1 << 4), +} + +/// File descriptor attributes. +extern(C) struct FdStat { + /// File type. + FileType fileType; + + /// File descriptor flags. + FdFlags flags; + + /// Rights that apply to this file descriptor. + Rights rightsBase; + + /// Maximum set of rights that may be installed on new file descriptors that + /// are created through this file descriptor, e.g., through `.pathOpen`. + Rights rightsInheriting; +} + +/** + * Identifier for a device containing a file system. Can be used in + * combination with `Inode` to uniquely identify a file or directory. + */ +alias Device = ulong; + +/// Which file time attributes to adjust. +enum FstFlags : ushort { + /// Adjust the last data access timestamp to + /// the value stored in `FileStat.accessTime`. + accessTime = (1 << 0), + + /// Adjust the last data access timestamp to + /// the time of clock `ClockID.realtime`. + accessTimeNow = (1 << 1), + + /// Adjust the last data modification timestamp to + /// the value stored in `FileStat.modifyTime`. + modifyTime = (1 << 2), + + /// Adjust the last data modification timestamp to + /// the time of clock `ClockID.realtime`. + modifyTimeNow = (1 << 3) +} + +/// Flags determining the method of how paths are resolved. +enum LookupFlags : uint { + /// As long as the resolved path corresponds to a symbolic link, + /// it is expanded. + symlinkFollow = (1 << 0) +} + +/// Open flags used by `pathOpen`. +enum OFlags : ushort { + /// Create file if it does not exist. + create = (1 << 0), + + /// Fail if not a directory. + directory = (1 << 1), + + /// Fail if file already exists. + exclusive = (1 << 2), + + /// Truncate file to size 0. + truncate = (1 << 3), +} + +/// Number of hard links to an inode. +alias LinkCount = ulong; + +/// File attributes. +extern(C) struct FileStat { + /// Device ID of device containing the file. + Device device; + + /// File serial number. + Inode inode; + + /// File type. + FileType fileType; + + /// Number of hard links to the file. + LinkCount nLink; + + /// For regular files, the file size in bytes. For symbolic links, the + /// length in bytes of the pathname contained in the symbolic link. + FileSize size; + + /// Last data access timestamp. + Timestamp accessTime; + + /// Last data modification timestamp. + Timestamp modifyTime; + + /// Last file status change timestamp. + Timestamp changeTime; +} + +/// User-provided value that may be attached to objects that is retained when +/// extracted from the implementation. +alias UserData = ulong; + +/// Type of a subscription to an event or its occurrence. +enum EventType : ubyte { + /// The time value of clock `SubscriptionClock.id` has reached + /// timestamp `SubscriptionClock.timeout` + clock, + + /// File descriptor `SubscriptionFdReadwrite.fileDescriptor` has data + // available for reading. This event always triggers for regular files. + fdRead, + + /// File descriptor `SubscriptionFdReadwrite.fileDescriptor` has capacity + /// available for writing. This event always triggers for regular files. + fdWrite +} + +/// The state of the file descriptor subscribed to +/// with `EventType.fdRead` or `EventType.fdWrite` +enum EventRWFlags : ushort { + /// The peer of this socket has closed or disconnected. + fdReadwriteHangup = (1 << 0) +} + +/// The contents of an event when type +/// is `EventType.fdRead` or `EventType.fdWrite` +extern(C) struct EventFdReadwrite { + /// The number of bytes available for reading or writing. + FileSize nBytes; + + /// The state of the file descriptor. + EventRWFlags flags; +} + +/// An event that occurred. +extern(C) struct Event { + /// User-provided value that got attached to `Subscription.userData` + UserData userData; + + /// If non-zero, an error that occurred while processing + /// the subscription request. + Errno error; + + /// The type of event that occurred. + EventType type; + + /// The contents of the event if it + /// is `EventType.fdRead` or `EventType.fdWrite`. + EventFdReadwrite fdReadwrite; +} + +/// Flags determining how to interpret the timestamp +/// provided in `SubscriptionClock.timeout` +enum SubClockFlags : ushort { + subscriptionClockAbstime = (1 << 0) +} + +/// The contents of a subscription when type is `EventType.clock`. +extern(C) struct SubscriptionClock { + /// The clock against which to compare the timestamp. + ClockID id; + + /// The absolute or relative timestamp. + Timestamp timeout; + + /// The amount of time that the implementation may wait additionally + /// to coalesce with other events. + Timestamp precision; + + /// Flags specifying whether the timeout is absolute or relative + SubClockFlags flags; +} + +/// The contents of a subscription when type +/// is `EventType.fdRead` or `EventType.fdWrite`. +extern(C) struct SubscriptionFdReadwrite { + Fd fileDescriptor; +} + +/// The contents of a subscription. +extern(C) struct SubscriptionU { + enum Tag : ubyte { + clock, + fdRead, + fdWrite + } + + Tag tag; + union { + SubscriptionClock clock; + SubscriptionFdReadwrite fdRead; + SubscriptionFdReadwrite fdWrite; + } +} + +/// Subscription to an event. +extern(C) struct Subscription { + /// User-provided value that is attached to the subscription in the + /// implementation and returned through `Event.userData`. + UserData userData; + + /// The type of the event to which to subscribe, and its contents + SubscriptionU u; +} + +/// Exit code generated by a process when exiting. +alias ExitCode = uint; + +/// Signal condition. +enum Signal : ubyte { + /// No signal. Note that POSIX has special semantics for `kill(pid, 0)`, + /// so this value is reserved. + none, + + /// Hangup. Action: Terminates the process. + hup, + + /// Terminate interrupt signal. Action: Terminates the process. + int_, + + /// Terminal quit signal. Action: Terminates the process. + quit, + + /// Illegal instruction. Action: Terminates the process. + ill, + + /// Trace/breakpoint trap. Action: Terminates the process. + trap, + + /// Process abort signal. Action: Terminates the process. + abrt, + + /// Access to an undefined portion of memory. Action: Terminates. + bus, + + /// Erroneous arithmetic operation. Action: Terminates the process. + fpe, + + /// Kill. Action: Terminates the process. + kill, + + /// User-defined signal 1. Action: Terminates the process. + usr1, + + /// Invalid memory reference. Action: Terminates the process. + segv, + + /// User-defined signal 2. Action: Terminates the process. + usr2, + + /// Write on a pipe with no one to read it. Action: Ignored. + pipe, + + /// Alarm clock. Action: Terminates the process. + alrm, + + /// Termination signal. Action: Terminates the process. + term, + + /// Child process terminated, stopped, or continued. Action: Ignored. + chld, + + /// Continue executing, if stopped. Action: Continues executing, + // if stopped. + cont, + + /// Stop executing. Action: Stops executing. + stop, + + /// Terminal stop signal. Action: Stops executing. + tstp, + + /// Background process attempting read. Action: Stops executing. + ttin, + + /// Background process attempting write. Action: Stops executing. + ttou, + + /// High bandwidth data is available at a socket. Action: Ignored. + urg, + + /// CPU time limit exceeded. Action: Terminates the process. + xcpu, + + /// File size limit exceeded. Action: Terminates the process. + xfsz, + + /// Virtual timer expired. Action: Terminates the process. + vtalrm, + + /// Profiling timer expired. Action: Terminates the process. + prof, + + /// Window changed. Action: Ignored. + winch, + + /// I/O possible. Action: Terminates the process. + poll, + + /// Power failure. Action: Terminates the process. + pwr, + + /// Bad system call. Action: Terminates the process. + sys, +} + +/// Flags provided to `sockRecv`. +enum RiFlags : ushort { + /// Returns the message without removing it from + /// the socket's receive queue. + peek = (1 << 0), + + /// On byte-stream sockets, block until the full amount + /// of data can be returned. + waitAll = (1 << 1) +} + +/// Flags returned by `sockRecv`. +enum RoFlags : ushort { + /// Returned by sock_recv: Message data has been truncated. + dataTruncated = (1 << 0), +} + +/// Flags provided to sock_send. +/// As there are currently no flags defined, it must be set to zero. +enum SiFlags : ushort { + /// Zero + none = 0, +} + +/// Which channels on a socket to shut down. +enum SdFlags : ubyte { + /// Disables further receive operations. + rd = (1 << 0), + + /// Disables further send operations. + wr = (1 << 1), +} + +enum PreopenType : ubyte { + /// A pre-opened directory. + dir +} + +/// The contents of a `Prestat` when type is `PreopenType.dir`. +extern(C) struct PrestatDir { + /// The length of the directory name for use with `fdPrestatDirName`. + size_t nameLength; +} + +/// Information about a pre-opened capability. +extern(C) struct Prestat { + enum Tag : ubyte { + dir + } + + Tag tag; + union { + PrestatDir dir; + } +} + +/// Read command-line argument data. The size of the array should match that +/// returned by `argsSizesGet`. Each argument is expected to be `\0` terminated. +Errno argsGet(ubyte** argv, ubyte* argvBuf) { + return cast(Errno)imported_argsGet(cast(int)argv, cast(int)argvBuf); +} +@wasiImport!("args_get") +pragma(mangle, "__imported_wasi_snapshot_preview1_args_get") +extern(C) private int imported_argsGet(int, int); + +/// Return command-line argument data sizes. +Errno argsSizesGet(out size_t argvSize, out size_t argvBufSize) { + return cast(Errno)imported_argsSizesGet(cast(int)&argvSize, cast(int)&argvBufSize); +} +@wasiImport!("args_sizes_get") +pragma(mangle, "__imported_wasi_snapshot_preview1_args_sizes_get") +extern(C) private int imported_argsSizesGet(int, int); + +/** + * Read environment variable data. The sizes of the buffers should match + * that returned by `environSizesGet`. Key/value pairs are expected to be + * joined with `=`s, and terminated with `\0`s. + */ +Errno environGet(ubyte** environ, ubyte* environBuf) { + return cast(Errno)imported_environGet(cast(int)environ, cast(int)environBuf); +} +@wasiImport!("environ_get") +pragma(mangle, "__imported_wasi_snapshot_preview1_environ_get") +extern(C) private int imported_environGet(int, int); + +/// Return environment variable data sizes. +Errno environSizesGet(out size_t environSize, out size_t environBufSize) { + return cast(Errno)imported_environSizesGet( + cast(int)&environSize, + cast(int)&environBufSize + ); +} +@wasiImport!("environ_sizes_get") +pragma(mangle, "__imported_wasi_snapshot_preview1_environ_sizes_get") +extern(C) private int imported_environSizesGet(int, int); + +/** + * Return the resolution of a clock. Implementations are required to provide + * a non-zero value for supported clocks. For unsupported clocks, return + * `Errno.inval`. + * + * Note: + * This is similar to `clock_getres` in POSIX. + */ +Errno clockResGet(ClockID id, out Timestamp resolution) { + return cast(Errno)imported_clockResGet(cast(int)id, cast(int)&resolution); +} +@wasiImport!("clock_res_get") +pragma(mangle, "__imported_wasi_snapshot_preview1_clock_res_get") +extern(C) private int imported_clockResGet(int, int); + +/** + * Return the time value of a clock. + * + * Note: + * This is similar to `clock_gettime` in POSIX. + */ +Errno clockTimeGet(ClockID id, Timestamp precision, out Timestamp time) { + return cast(Errno)imported_clockTimeGet( + cast(int)id, + cast(long)precision, + cast(int)&time + ); +} +@wasiImport!("clock_time_get") +pragma(mangle, "__imported_wasi_snapshot_preview1_clock_time_get") +extern(C) private int imported_clockTimeGet(int, long, int); + +/** + * Provide file advisory information on a file descriptor. + * + * Note: + * This is similar to `posix_fadvise` in POSIX. + */ +Errno fdAdvise(Fd fd, FileSize offset, FileSize len, Advice advice) { + return cast(Errno)imported_fdAdvise( + cast(int)fd, + cast(long)offset, + cast(long)len, + cast(int)advice + ); +} +@wasiImport!("fd_advise") +pragma(mangle, "__imported_wasi_snapshot_preview1_fd_advise") +extern(C) private int imported_fdAdvise(int, long, long, int); + +/** + * Force the allocation of space in a file. + * + * Note: + * This is similar to `posix_fallocate` in POSIX. + */ +Errno fdAllocate(Fd fd, FileSize offset, FileSize len) { + return cast(Errno)imported_fdAllocate(cast(int)fd, cast(long)offset, cast(long)len); +} +@wasiImport!("fd_allocate") +pragma(mangle, "__imported_wasi_snapshot_preview1_fd_allocate") +extern(C) private int imported_fdAllocate(int, long, long); + +/** + * Close a file descriptor. + * + * Note: + * This is similar to `close` in POSIX. + */ +Errno fdClose(Fd fd) { + return cast(Errno)imported_fdClose(cast(int)fd); +} +@wasiImport!("fd_close") +pragma(mangle, "__imported_wasi_snapshot_preview1_fd_close") +extern(C) private int imported_fdClose(int); + +/** + * Synchronize the data of a file to disk. + * + * Note: + * This is similar to `fdatasync` in POSIX. + */ +Errno fdDataSync(Fd fd) { + return cast(Errno)imported_fdDataSync(cast(int)fd); +} +@wasiImport!("fd_datasync") +pragma(mangle, "__imported_wasi_snapshot_preview1_fd_datasync") +extern(C) private int imported_fdDataSync(int); + +/** + * Get the attributes of a file descriptor. + * + * Note: + * This returns similar flags to `fcntl(fd, F_GETFL)` in POSIX, as well as + * additional fields. + */ +Errno fdFdstatGet(Fd fd, out FdStat stat) { + return cast(Errno)imported_fdFdstatGet(cast(int)fd, cast(int)&stat); +} +@wasiImport!("fd_fdstat_get") +pragma(mangle, "__imported_wasi_snapshot_preview1_fd_fdstat_get") +extern(C) private int imported_fdFdstatGet(int, int); + +/** + * Adjust the flags associated with a file descriptor. + * + * Note: + * This is similar to `fcntl(fd, F_SETFL, flags)` in POSIX. + */ +Errno fdFdstatSetFlags(Fd fd, FdFlags flags) { + return cast(Errno)imported_fdFdstatSetFlags(cast(int)fd, cast(int)flags); +} +@wasiImport!("fd_fdstat_set_flags") +pragma(mangle, "__imported_wasi_snapshot_preview1_fd_fdstat_set_flags") +extern(C) private int imported_fdFdstatSetFlags(int, int); + +/** + * Adjust the rights associated with a file descriptor. This can only be used + * to remove rights, and returns `Errno.notcapable` if called in a way that + * would attempt to add rights. + */ +Errno fdFdstatSetRights(Fd fd, Rights rightsBase, Rights rightsInheriting) { + return cast(Errno)imported_fdFdstatSetRights( + cast(int)fd, + cast(long)rightsBase, + cast(long)rightsInheriting + ); +} +@wasiImport!("fd_fdstat_set_rights") +pragma(mangle, "__imported_wasi_snapshot_preview1_fd_fdstat_set_rights") +extern(C) private int imported_fdFdstatSetRights(int, long, long); + +/// Return the attributes of an open file. +Errno fdFilestatGet(Fd fd, out FileStat stat) { + return cast(Errno)imported_fdFilestatGet(cast(int)fd, cast(int)&stat); +} +@wasiImport!("fd_filestat_get") +pragma(mangle, "__imported_wasi_snapshot_preview1_fd_filestat_get") +extern(C) private int imported_fdFilestatGet(int, int); + +/** + * Adjust the size of an open file. If this increases the file's size, the + * extra bytes are filled with zeros. + * + * Note: + * This is similar to `ftruncate` in POSIX. + */ +Errno fdFilestatSetSize(Fd fd, FileSize size) { + return cast(Errno)imported_fdFilestatSetSize(cast(int)fd, cast(long)size); +} +@wasiImport!("fd_filestat_set_size") +pragma(mangle, "__imported_wasi_snapshot_preview1_fd_filestat_set_size") +extern(C) private int imported_fdFilestatSetSize(int, long); + +/** + * Adjust the timestamps of an open file or directory. + * + * Note: + * This is similar to `futimens` in POSIX. + */ +Errno fdFilestatSetTimes( + Fd fd, + Timestamp accessTime, + Timestamp modifyTime, + FstFlags fstFlags +) { + return cast(Errno)imported_fdFilestatSetTimes( + cast(int)fd, + cast(long)accessTime, + cast(long)modifyTime, + cast(int)fstFlags + ); +} +@wasiImport!("fd_filestat_set_times") +pragma(mangle, "__imported_wasi_snapshot_preview1_fd_filestat_set_times") +extern(C) private int imported_fdFilestatSetTimes(int, long, long, int); + +/** + * Read from a file descriptor, without using and updating + * the file descriptor's offset. + * + * Note: + * This is similar to `preadv` in Linux (and other Unix-es). + */ +Errno fdPread(Fd fd, IOVecArray iovs, FileSize offset, out size_t bytesRead) { + return cast(Errno)imported_fdPread( + cast(int)fd, + cast(int)iovs.ptr, + cast(int)iovs.length, + cast(long)offset, + cast(int)&bytesRead + ); +} +@wasiImport!("fd_pread") +pragma(mangle, "__imported_wasi_snapshot_preview1_fd_pread") +extern(C) private int imported_fdPread(int, int, int, long, int); + +/// Return a description of the given preopened file descriptor. +Errno fdPrestatGet(Fd fd, out Prestat prestat) { + return cast(Errno)imported_fdPrestatGet(cast(int)fd, cast(int)&prestat); +} +@wasiImport!("fd_prestat_get") +pragma(mangle, "__imported_wasi_snapshot_preview1_fd_prestat_get") +extern(C) private int imported_fdPrestatGet(int, int); + +/// Return a description of the given preopened file descriptor. +Errno fdPrestatDirName(Fd fd, char[] path) { + return cast(Errno)imported_fdPrestatDirName( + cast(int)fd, + cast(int)path.ptr, + cast(int)path.length + ); +} +@wasiImport!("fd_prestat_dir_name") +pragma(mangle, "__imported_wasi_snapshot_preview1_fd_prestat_dir_name") +extern(C) private int imported_fdPrestatDirName(int, int, int); + +/** + * Write to a file descriptor, without using and updating the file descriptor's + * offset. + * + * Like Linux (and other Unix-es), any calls of `fdPwrite` (and other functions + * to read or write) for a regular file by other threads in the WASI process + * should not be interleaved while `fdPwrite` is executed. + * + * Note: + * This is similar to `pwritev` in Linux (and other Unix-es). + */ +Errno fdPwrite( + Fd fd, + CIOVecArray iovs, + FileSize offset, + out size_t bytesWritten +) { + return cast(Errno)imported_fdPwrite( + cast(int)fd, + cast(int)iovs.ptr, + cast(int)iovs.length, + cast(long)offset, + cast(int)&bytesWritten + ); +} +@wasiImport!("fd_pwrite") +pragma(mangle, "__imported_wasi_snapshot_preview1_fd_pwrite") +extern(C) private int imported_fdPwrite(int, int, int, long, int); + +/** + * Read from a file descriptor. + * + * Note: + * This is similar to `readv` in POSIX. + */ +Errno fdRead(Fd fd, IOVecArray iovs, out size_t bytesRead) { + return cast(Errno)imported_fdRead( + cast(int)fd, + cast(int)iovs.ptr, + cast(int)iovs.length, + cast(int)&bytesRead + ); +} +@wasiImport!("fd_read") +pragma(mangle, "__imported_wasi_snapshot_preview1_fd_read") +extern(C) private int imported_fdRead(int, int, int, int); + +/** + * Read directory entries from a directory. When successful, the contents of + * the output buffer consist of a sequence of directory entries. Each directory + * entry consists of a `DirEnt` object, followed by `DirEnt.nameLength` bytes + * holding the name of the directory entry. This function fills the output + * buffer as much as possible, potentially truncating the last directory entry. + * This allows the caller to grow its read buffer size in case it's too small + * to fit a single large directory entry, or skip the oversized directory + * entry. + */ +Errno fdReadDir(Fd fd, ubyte[] buf, DirCookie cookie, out size_t bytesRead) { + return cast(Errno)imported_fdReadDir( + cast(int)fd, + cast(int)buf.ptr, + cast(int)buf.length, + cast(long)cookie, + cast(int)&bytesRead + ); +} +@wasiImport!("fd_readdir") +pragma(mangle, "__imported_wasi_snapshot_preview1_fd_readdir") +extern(C) private int imported_fdReadDir(int, int, int, long, int); + +/** + * Atomically replace a file descriptor by renumbering another file descriptor. + * Due to the strong focus on thread safety, this environment does not provide + * a mechanism to duplicate or renumber a file descriptor to an arbitrary + * number, like dup2(). This would be prone to race conditions, as an actual + * file descriptor with the same number could be allocated by a different + * thread at the same time. This function provides a way to atomically renumber + * file descriptors, which would disappear if dup2() were to be removed + * entirely. + */ +Errno fdRenumber(Fd fd, Fd to) { + return cast(Errno)imported_fdRenumber(cast(int)fd, cast(int)to); +} +@wasiImport!("fd_renumber") +pragma(mangle, "__imported_wasi_snapshot_preview1_fd_renumber") +extern(C) private int imported_fdRenumber(int, int); + +/** + * Move the offset of a file descriptor. + * + * Note: + * This is similar to `lseek` in POSIX. + */ +Errno fdSeek(Fd fd, FileDelta offset, Whence whence, out FileSize newOffset) { + return cast(Errno)imported_fdSeek( + cast(int)fd, + cast(long)offset, + cast(int)whence, + cast(int)&newOffset + ); +} +@wasiImport!("fd_seek") +pragma(mangle, "__imported_wasi_snapshot_preview1_fd_seek") +extern(C) private int imported_fdSeek(int, long, int, int); + +/** + * Synchronize the data and metadata of a file to disk. + * + * Note: + * This is similar to `fsync` in POSIX. + */ +Errno fdSync(Fd fd) { + return cast(Errno)imported_fdSync(cast(int)fd); +} +@wasiImport!("fd_sync") +pragma(mangle, "__imported_wasi_snapshot_preview1_fd_sync") +extern(C) private int imported_fdSync(int); + +/** + * Return the current offset of a file descriptor. + * + * Note: + * This is similar to `lseek(fd, 0, SEEK_CUR)` in POSIX. + */ +Errno fdTell(Fd fd, out FileSize offset) { + return cast(Errno)imported_fdTell(cast(int)fd, cast(int)&offset); +} +@wasiImport!("fd_tell") +pragma(mangle, "__imported_wasi_snapshot_preview1_fd_tell") +extern(C) private int imported_fdTell(int, int); + +/** + * Write to a file descriptor. + * + * Like POSIX, any calls of `fdWrite` (and other functions to read or write) + * for a regular file by other threads in the WASI process should not be + * interleaved while `fdWrite` is executed. + * + * Note: + * This is similar to `writev` in POSIX. + */ +Errno fdWrite(Fd fd, CIOVecArray iovs, out size_t bytesWritten) { + return cast(Errno)imported_fdWrite( + cast(int)fd, + cast(int)iovs.ptr, + cast(int)iovs.length, + cast(int)&bytesWritten + ); +} +@wasiImport!("fd_write") +pragma(mangle, "__imported_wasi_snapshot_preview1_fd_write") +extern(C) private int imported_fdWrite(int, int, int, int); + +/** + * Create a directory. + * + * Note: + * This is similar to `mkdirat` in POSIX. + */ +Errno pathCreateDirectory(Fd fd, const(char)[] path) { + return cast(Errno)imported_pathCreateDirectory( + cast(int)fd, + cast(int)path.ptr, + cast(int)path.length + ); +} +@wasiImport!("path_create_directory") +pragma(mangle, "__imported_wasi_snapshot_preview1_path_create_directory") +extern(C) private int imported_pathCreateDirectory(int, int, int); + +/** + * Return the attributes of a file or directory. + * + * Note: + * This is similar to `stat` in POSIX. + */ +Errno pathFilestatGet( + Fd fd, + LookupFlags flags, + const(char)[] path, + out FileStat stat +) { + return cast(Errno)imported_pathFilestatGet( + cast(int)fd, + cast(int)flags, + cast(int)path.ptr, + cast(int)path.length, + cast(int)&stat + ); +} +@wasiImport!("path_filestat_get") +pragma(mangle, "__imported_wasi_snapshot_preview1_path_filestat_get") +extern(C) private int imported_pathFilestatGet(int, int, int, int, int); + +/** + * Adjust the timestamps of a file or directory. + * + * Note: + * This is similar to `utimensat` in POSIX. + */ +Errno pathFilestatSetTimes( + Fd fd, + LookupFlags flags, + const(char)[] path, + Timestamp accessTime, + Timestamp modifyTime, + FstFlags fstFlags +) { + return cast(Errno)imported_pathFilestatSetTimes( + cast(int)fd, + cast(int)flags, + cast(int)path.ptr, + cast(int)path.length, + cast(long)accessTime, + cast(long)modifyTime, + cast(int)fstFlags + ); +} +@wasiImport!("path_filestat_set_times") +pragma(mangle, "__imported_wasi_snapshot_preview1_path_filestat_set_times") +extern(C) private int imported_pathFilestatSetTimes(int, int, int, int, long, long, int); + +/** + * Create a hard link. + * + * Note: + * This is similar to `linkat` in POSIX. + */ +Errno pathLink( + Fd oldFd, + LookupFlags oldFlags, + const(char)[] oldPath, + Fd newFd, + const(char)[] newPath +) { + return cast(Errno)imported_pathLink( + cast(int)oldFd, + cast(int)oldFlags, + cast(int)oldPath.ptr, + cast(int)oldPath.length, + cast(int)newFd, + cast(int)newPath.ptr, + cast(int)newPath.length + ); +} +@wasiImport!("path_link") +pragma(mangle, "__imported_wasi_snapshot_preview1_path_link") +extern(C) private int imported_pathLink(int, int, int, int, int, int, int); + +/** + * Open a file or directory. The returned file descriptor is not guaranteed to + * be the lowest-numbered file descriptor not currently open; it is randomized + * to prevent applications from depending on making assumptions about indexes, + * since this is error-prone in multi-threaded contexts. The returned file + * descriptor is guaranteed to be less than 2**31. + * + * Note: + * This is similar to `openat` in POSIX. + */ +Errno pathOpen( + Fd fd, + LookupFlags dirFlags, + const(char)[] path, + OFlags oFlags, + Rights rightsBase, + Rights rightsInheriting, + FdFlags fdFlags, + out Fd openedFd +) { + return cast(Errno)imported_pathOpen( + cast(int)fd, + cast(int)dirFlags, + cast(int)path.ptr, + cast(int)path.length, + cast(int)oFlags, + cast(long)rightsBase, + cast(long)rightsInheriting, + cast(int)fdFlags, + cast(int)&openedFd + ); +} +@wasiImport!("path_open") +pragma(mangle, "__imported_wasi_snapshot_preview1_path_open") +extern(C) private int imported_pathOpen(int, int, int, int, int, long, long, int, int); + +/** + * Read the contents of a symbolic link. + * + * Note: + * This is similar to `readlinkat` in POSIX. + */ +Errno pathReadlink(Fd fd, const(char)[] path, ubyte[] buf, out size_t bytesRead) { + return cast(Errno)imported_pathReadlink( + cast(int)fd, + cast(int)path.ptr, + cast(int)path.length, + cast(int)buf.ptr, + cast(int)buf.length, + cast(int)&bytesRead + ); +} +@wasiImport!("path_readlink") +pragma(mangle, "__imported_wasi_snapshot_preview1_path_readlink") +extern(C) private int imported_pathReadlink(int, int, int, int, int, int); + +/** + * Remove a directory. Return `Errno.notempty` if the directory is not empty. + * + * Note: + * This is similar to `unlinkat(fd, path, AT_REMOVEDIR)` in POSIX. + */ +Errno pathRemoveDirectory(Fd fd, const(char)[] path) { + return cast(Errno)imported_pathRemoveDirectory( + cast(int)fd, + cast(int)path.ptr, + cast(int)path.length + ); +} +@wasiImport!("path_remove_directory") +pragma(mangle, "__imported_wasi_snapshot_preview1_path_remove_directory") +extern(C) private int imported_pathRemoveDirectory(int, int, int); + +/** + * Rename a file or directory. + * + * Note: + * This is similar to `renameat` in POSIX. + */ +Errno pathRename(Fd fd, const(char)[] oldPath, Fd newFd, const(char)[] newPath) { + return cast(Errno)imported_pathRename( + cast(int)fd, + cast(int)oldPath.ptr, + cast(int)oldPath.length, + cast(int)newFd, + cast(int)newPath.ptr, + cast(int)newPath.length + ); +} +@wasiImport!("path_rename") +pragma(mangle, "__imported_wasi_snapshot_preview1_path_rename") +extern(C) private int imported_pathRename(int, int, int, int, int, int); + +/** + * Create a symbolic link. + * + * Note: + * This is similar to `symlinkat` in POSIX. + */ +Errno pathSymlink(const(char)[] oldPath, Fd fd, const(char)[] newPath) { + return cast(Errno)imported_pathSymlink( + cast(int)oldPath.ptr, + cast(int)oldPath.length, + cast(int)fd, + cast(int)newPath.ptr, + cast(int)newPath.length + ); +} +@wasiImport!("path_symlink") +pragma(mangle, "__imported_wasi_snapshot_preview1_path_symlink") +extern(C) private int imported_pathSymlink(int, int, int, int, int); + +/** + * Unlink a file. Return `Errno.isdir` if the path refers to a directory. + * + * Note: + * This is similar to `unlinkat(fd, path, 0)` in POSIX. + */ +Errno pathUnlinkFile(Fd fd, const(char)[] path) { + return cast(Errno)imported_pathUnlinkFile( + cast(int)fd, + cast(int)path.ptr, + cast(int)path.length + ); +} +@wasiImport!("path_unlink_file") +pragma(mangle, "__imported_wasi_snapshot_preview1_path_unlink_file") +extern(C) private int imported_pathUnlinkFile(int, int, int); + +/// Concurrently poll for the occurrence of a set of events. +Errno pollOneOff( + const(Subscription)[] subscriptions, + Event[] events, + out size_t numEvents +) { + assert(subscriptions.length == events.length); + return cast(Errno)imported_pollOneOff( + cast(int)subscriptions.ptr, + cast(int)events.ptr, + cast(int)subscriptions.length, + cast(int)&numEvents + ); +} +@wasiImport!("poll_oneoff") +pragma(mangle, "__imported_wasi_snapshot_preview1_poll_oneoff") +extern(C) private int imported_pollOneOff(int, int, int, int); + +/** + * Terminate the process normally. An exit code of 0 indicates successful + * termination of the program. The meanings of other values is dependent + * on the environment. + */ +noreturn procExit(ExitCode rval) { + imported_procExit(cast(int)rval); +} +@wasiImport!("proc_exit") +pragma(mangle, "__imported_wasi_snapshot_preview1_proc_exit") +extern(C) private noreturn imported_procExit(int); + +/** + * Temporarily yield execution of the calling thread. + * + * Note: + * This is similar to `sched_yield` in POSIX. + */ +Errno schedYield() { + return cast(Errno)imported_schedYield(); +} +@wasiImport!("sched_yield") +pragma(mangle, "__imported_wasi_snapshot_preview1_sched_yield") +extern(C) private int imported_schedYield(); + +/** + * Write high-quality random data into a buffer. This function blocks when the + * implementation is unable to immediately provide sufficient high-quality + * random data. + */ +Errno randomGet(ubyte[] buf) { + return cast(Errno)imported_randomGet(cast(int)buf.ptr, cast(int)buf.length); +} +@wasiImport!("random_get") +pragma(mangle, "__imported_wasi_snapshot_preview1_random_get") +extern(C) private int imported_randomGet(int, int); + +/** + * Accept a new incoming connection. + * + * Note: + * This is similar to `accept` in POSIX. + */ +Errno sockAccept(Fd fd, FdFlags flags, out Fd acceptedFd) { + return cast(Errno)imported_sockAccept(cast(int)fd, cast(int)flags, cast(int)&acceptedFd); +} +@wasiImport!("sock_accept") +pragma(mangle, "__imported_wasi_snapshot_preview1_sock_accept") +extern(C) private int imported_sockAccept(int, int, int); + +/** + * Receive a message from a socket. + * + * Note: + * This is similar to `recv` in POSIX, though it also supports reading the + * data into multiple buffers in the manner of `readv`. + */ +Errno sockRecv( + Fd fd, + IOVecArray riData, + RiFlags riFlags, + out size_t bytesReceived, + out RoFlags roFlags +) { + return cast(Errno)imported_sockRecv( + cast(int)fd, + cast(int)riData.ptr, + cast(int)riData.length, + cast(int)riFlags, + cast(int)&bytesReceived, + cast(int)&roFlags + ); +} +@wasiImport!("sock_recv") +pragma(mangle, "__imported_wasi_snapshot_preview1_sock_recv") +extern(C) private int imported_sockRecv(int, int, int, int, int, int); + +/** + * Send a message on a socket. + * + * Note: + * This is similar to `send` in POSIX, though it also supports writing the + * data from multiple buffers in the manner of `writev`. + */ +Errno sockSend( + Fd fd, + CIOVecArray siData, + SiFlags siFlags, + out size_t bytesSent +) { + return cast(Errno)imported_sockSend( + cast(int)fd, + cast(int)siData.ptr, + cast(int)siData.length, + cast(int)siFlags, + cast(int)&bytesSent + ); +} +@wasiImport!("sock_send") +pragma(mangle, "__imported_wasi_snapshot_preview1_sock_send") +extern(C) private int imported_sockSend(int, int, int, int, int); + +/** + * Shut down socket send and receive channels. + * + * Note: + * This is similar to `shutdown` in POSIX. + */ +Errno sockShutdown(Fd fd, SdFlags how) { + return cast(Errno)imported_sockShutdown(cast(int)fd, cast(int)how); +} +@wasiImport!("sock_shutdown") +pragma(mangle, "__imported_wasi_snapshot_preview1_sock_shutdown") +extern(C) private int imported_sockShutdown(int, int); diff --git a/runtime/druntime/src/core/thread/osthread.d b/runtime/druntime/src/core/thread/osthread.d index c84e15fa3df..51384512c46 100644 --- a/runtime/druntime/src/core/thread/osthread.d +++ b/runtime/druntime/src/core/thread/osthread.d @@ -163,8 +163,15 @@ else version (WASI) // No real threading support // Just manipulations of the main "thread" import core.stdc.stdlib : free, malloc, realloc; - import core.stdc.errno : EINTR, errno; - import core.sys.wasi.posix.time : nanosleep, timespec; + + version (WASIp1) { + import core.sys.wasi.p1 : ClockID, schedYield, Subscription, SubscriptionClock, pollOneOff, Event; + } + else + { + import core.stdc.errno : EINTR, errno; + import core.sys.wasi.posix.time : nanosleep, timespec; + } } version (GNU) @@ -1141,8 +1148,22 @@ class Thread : ThreadBase tin = tout; } } + version (WASIp1) { + Subscription sub; + sub.u.tag = Subscription.u.Tag.clock; + sub.u.clock.id = ClockID.monotonic; + sub.u.clock.timeout = val.total!"nsecs"; + + size_t numEvents; + Event event; + auto err = pollOneOff((&sub)[0..1], (&event)[0..1], numEvents); + if (err || event.error) assert(0, "Unable to sleep for the specified duration"); + + return; + } else version (WASI) { + // fall back to emulated POSIX timespec tin = void; timespec tout = void; @@ -1170,6 +1191,8 @@ class Thread : ThreadBase SwitchToThread(); else version (Posix) sched_yield(); + else version (WASIp1) + schedYield(); } } diff --git a/runtime/druntime/src/core/time.d b/runtime/druntime/src/core/time.d index 2355f18036a..9af114bb533 100644 --- a/runtime/druntime/src/core/time.d +++ b/runtime/druntime/src/core/time.d @@ -92,7 +92,11 @@ else version (Posix) import core.sys.posix.sys.time : gettimeofday, timeval; import core.sys.posix.time : clock_getres, clock_gettime, CLOCK_MONOTONIC, timespec; } -else version (WASI) +else version (WASIp1) +{ + import core.sys.wasi.p1 : Errno, Timestamp, clockResGet, clockTimeGet, ClockID; +} +else version (WASIp2) { import core.sys.wasi.posix.time : clock_getres, clock_gettime, CLOCK_MONOTONIC, timespec; } @@ -341,7 +345,16 @@ else version (Solaris) enum ClockType second = 6, threadCPUTime = 7, } -else version (WASI) enum ClockType +else version (WASIp1) enum ClockType +{ + normal = 0, + coarse = 2, + precise = 3, + processCPUTime = 4, + second = 6, + threadCPUTime = 7, +} +else version (WASIp2) enum ClockType { normal = 0, coarse = 2, @@ -457,7 +470,7 @@ version (Posix) } } else -version (WASI) +version (WASIp2) { private auto _posixClock(ClockType clockType) { @@ -472,6 +485,22 @@ version (WASI) } } +version (WASIp1) +{ + private auto _wasiClock(ClockType clockType) + { + with(ClockType) final switch (clockType) + { + case coarse: return ClockID.monotonic; + case normal: return ClockID.monotonic; + case precise: return ClockID.monotonic; + case processCPUTime: return ClockID.processCPUTimeID; + case second: assert(0); + case threadCPUTime: return ClockID.threadCPUTimeID; + } + } +} + unittest { // Make sure that the values are the same across platforms. @@ -2158,7 +2187,11 @@ struct MonoTimeImpl(ClockType clockType) { enum clockArg = _posixClock(clockType); } - else version (WASI) + else version (WASIp1) + { + enum clockArg = _wasiClock(clockType); + } + else version (WASIp2) { enum clockArg = _posixClock(clockType); } @@ -2227,7 +2260,18 @@ struct MonoTimeImpl(ClockType clockType) 1_000_000_000L, ticksPerSecond)); } - else version (WASI) + else version (WASIp1) + { + Timestamp ts = void; + immutable error = clockTimeGet(clockArg, 1, ts); + if (error) + { + import core.internal.abort : abort; + abort("Call to clockTimeGet failed."); + } + return MonoTimeImpl(convClockFreq(ts, 1_000_000_000L, ticksPerSecond)); + } + else version (WASIp2) { timespec ts = void; immutable error = clock_gettime(clockArg, &ts); @@ -2640,7 +2684,27 @@ extern(C) void _d_initMonoTime() @nogc nothrow } } } - else version (WASI) + else version (WASIp1) + { + Timestamp ts; + foreach (i, typeStr; __traits(allMembers, ClockType)) + { + static if (typeStr != "second") + { + enum clockArg = _wasiClock(__traits(getMember, ClockType, typeStr)); + if (clockResGet(clockArg, ts) == Errno.success) + { + // ensure we are only writing immutable data once + if (tps[i] != 0) + // should only be called once + assert(0); + + tps[i] = 1_000_000_000L / ts; + } + } + } + } + else version (WASIp2) { timespec ts; foreach (i, typeStr; __traits(allMembers, ClockType)) @@ -2969,7 +3033,18 @@ deprecated: else ticksPerSec = 1_000_000; } - else version (WASI) + else version (WASIp1) + { + Timestamp ts; + + if (clockResGet(ClockID.monotonic, ts) != Errno.success) + ticksPerSec = 0; + else + { + ticksPerSec = 1_000_000_000 / ts; + } + } + else version (WASIp2) { timespec ts; @@ -3555,7 +3630,19 @@ deprecated: tv.tv_usec * TickDuration.ticksPerSec / 1000 / 1000); } } - else version (WASI) + else version (WASIp1) + { + Timestamp ts = void; + immutable error = clockTimeGet(ClockID.monotonic, 1, ts); + + if (error) + { + import core.internal.abort : abort; + abort("Call to clockTimeGet failed."); + } + return TickDuration(ts * TickDuration.ticksPerSec / 1_000_000_000); + } + else version (WASIp2) { timespec ts = void; immutable error = clock_gettime(CLOCK_MONOTONIC, &ts); From dc719d5269e4a039b82d60ebd26f7b86ddf22d29 Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Thu, 16 Jul 2026 12:05:26 -0700 Subject: [PATCH 11/11] druntime: Move `callWithStackShell` implementation to `LDC` block --- runtime/druntime/src/core/thread/osthread.d | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/runtime/druntime/src/core/thread/osthread.d b/runtime/druntime/src/core/thread/osthread.d index 51384512c46..b723c6c9bb8 100644 --- a/runtime/druntime/src/core/thread/osthread.d +++ b/runtime/druntime/src/core/thread/osthread.d @@ -1882,19 +1882,6 @@ in (fn) } assert(0, "implement AArch64 inline assembler for callWithStackShell()"); // TODO AArch64 } - else version (WebAssembly) - { - // Wasm is special in that this isn't really possible - // to dump "registers" (Wasm locals & value stack) onto - // the linear-memory "C" stack easily. - // - // Spilling has to be done somehow else. - - import ldc.intrinsics; - - static if (LLVM_major >= 22) sp = llvm_stackaddress(); - else sp = llvm_stacksave(); - } else { static assert(false, "Architecture not supported.");