From 8074ec831a7e8d60ea97a41f8dca18e89b3a1037 Mon Sep 17 00:00:00 2001 From: Elias Batek Date: Sat, 5 Apr 2025 00:51:05 +0200 Subject: [PATCH 01/25] Fix #10731 - Windows link error with std.random.uniform() in v2.111 (#10739) Load `Bcrypt.dll` ourselves as needed. --- Makefile | 2 +- std/internal/windows/bcrypt.d | 65 +++++++++++++++++++++++++++++++++++ std/random.d | 18 +--------- 3 files changed, 67 insertions(+), 18 deletions(-) create mode 100644 std/internal/windows/bcrypt.d diff --git a/Makefile b/Makefile index 7a0f73e2e65..0a3c51ba3c5 100644 --- a/Makefile +++ b/Makefile @@ -275,7 +275,7 @@ EXTRA_MODULES_INTERNAL := $(addprefix std/, \ scopebuffer test/dummyrange test/range \ test/sumtype_example_overloads \ $(addprefix unicode_, comp decomp grapheme norm tables) \ - windows/advapi32 \ + windows/advapi32 windows/bcrypt \ ) \ typetuple \ ) diff --git a/std/internal/windows/bcrypt.d b/std/internal/windows/bcrypt.d new file mode 100644 index 00000000000..239dcd52e66 --- /dev/null +++ b/std/internal/windows/bcrypt.d @@ -0,0 +1,65 @@ +module std.internal.windows.bcrypt; + +version (Windows): + +import core.sys.windows.bcrypt : BCryptGenRandom, BCRYPT_USE_SYSTEM_PREFERRED_RNG; +import core.sys.windows.windef : HMODULE, PUCHAR, ULONG; +import core.sys.windows.ntdef : NT_SUCCESS; + +pragma(lib, "Bcrypt.lib"); + +package(std) bool bcryptGenRandom(T)(out T result) @trusted +{ + loadBcrypt(); + + const gotRandom = ptrBCryptGenRandom( + null, + cast(PUCHAR) &result, + ULONG(T.sizeof), + BCRYPT_USE_SYSTEM_PREFERRED_RNG, + ); + + return NT_SUCCESS(gotRandom); +} + +private +{ + HMODULE hBcrypt = null; + typeof(BCryptGenRandom)* ptrBCryptGenRandom; +} + +private void loadBcrypt() @nogc nothrow +{ + import core.sys.windows.winbase : GetProcAddress, LoadLibraryA; + + if (!hBcrypt) + { + hBcrypt = LoadLibraryA("Bcrypt.dll"); + if (!hBcrypt) + assert(false, `LoadLibraryA("Bcrypt.dll") failed.`); // `@nogc` + + ptrBCryptGenRandom = cast(typeof(ptrBCryptGenRandom)) GetProcAddress(hBcrypt , "BCryptGenRandom"); + if (!ptrBCryptGenRandom) + assert(false, `GetProcAddress(hBcrypt , "BCryptGenRandom") failed.`); // `@nogc` + } +} + +// Will free `Bcrypt.dll`. +private void freeBcrypt() @nogc nothrow +{ + import core.sys.windows.winbase : FreeLibrary; + + if (hBcrypt) + { + if (!FreeLibrary(hBcrypt)) + assert(false, `FreeLibrary("Bcrypt.dll") failed.`); // `@nogc` + + hBcrypt = null; + ptrBCryptGenRandom = null; + } +} + +static ~this() +{ + freeBcrypt(); +} diff --git a/std/random.d b/std/random.d index fb4e5469088..dc1763cf71c 100644 --- a/std/random.d +++ b/std/random.d @@ -1792,23 +1792,7 @@ version (linux) version (Windows) { - pragma(lib, "Bcrypt.lib"); - - private bool bcryptGenRandom(T)(out T result) @trusted - { - import core.sys.windows.windef : PUCHAR, ULONG; - import core.sys.windows.ntdef : NT_SUCCESS; - import core.sys.windows.bcrypt : BCryptGenRandom, BCRYPT_USE_SYSTEM_PREFERRED_RNG; - - const gotRandom = BCryptGenRandom( - null, - cast(PUCHAR) &result, - ULONG(T.sizeof), - BCRYPT_USE_SYSTEM_PREFERRED_RNG, - ); - - return NT_SUCCESS(gotRandom); - } + import std.internal.windows.bcrypt : bcryptGenRandom; } /** From 35977c8029e7bb4dbe1b887688dabebe04ebea02 Mon Sep 17 00:00:00 2001 From: Akshat Sharma Date: Sun, 6 Apr 2025 05:02:25 +0530 Subject: [PATCH 02/25] fix: Add Null check before calling wsclen (#10736) fixes: #10727 --- std/file.d | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/std/file.d b/std/file.d index c3466acc055..0b8da501d3f 100644 --- a/std/file.d +++ b/std/file.d @@ -983,10 +983,10 @@ private void renameImpl(scope const(char)[] f, scope const(char)[] t, import std.conv : to, text; if (!f) - f = to!(typeof(f))(fromz[0 .. wcslen(fromz)]); + f = fromz ? to!(typeof(f))(fromz[0 .. wcslen(fromz)]) : "(null)"; if (!t) - t = to!(typeof(t))(toz[0 .. wcslen(toz)]); + t = toz ? to!(typeof(t))(toz[0 .. wcslen(toz)]) : "(null)"; enforce(false, new FileException( From 20c760be42f4236275cf2a80d1e8163fe165d041 Mon Sep 17 00:00:00 2001 From: "Richard (Rikki) Andrew Cattermole" Date: Thu, 10 Apr 2025 11:14:02 +1200 Subject: [PATCH 03/25] Improve result of #10699's fix so that it returns consumed arguments count instead of sentinel (#10728) --- std/format/write.d | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/std/format/write.d b/std/format/write.d index d704c14f5fa..68a96d48625 100644 --- a/std/format/write.d +++ b/std/format/write.d @@ -534,6 +534,8 @@ uint formattedWrite(Writer, Char, Args...)(auto ref Writer w, const scope Char[] // Are we already done with formats? Then just dump each parameter in turn uint currentArg = 0; + bool lastWasConsumeAll; + while (spec.writeUpToNextSpec(w)) { if (currentArg == Args.length && !spec.indexStart) @@ -649,7 +651,10 @@ uint formattedWrite(Writer, Char, Args...)(auto ref Writer w, const scope Char[] } default: if (spec.indexEnd == spec.indexEnd.max) + { + lastWasConsumeAll = true; break; + } else if (spec.indexEnd == spec.indexStart) throw new FormatException( text("Positional specifier %", spec.indexStart, '$', spec.spec, @@ -660,7 +665,8 @@ uint formattedWrite(Writer, Char, Args...)(auto ref Writer w, const scope Char[] " index exceeds ", Args.length)); } } - return currentArg; + + return lastWasConsumeAll ? Args.length : currentArg; } /// @@ -1212,7 +1218,8 @@ if (isSomeString!(typeof(fmt))) import std.array : appender; auto w = appender!(char[])(); - formattedWrite(w, "%1:$d", 1, 2, 3); + uint count = formattedWrite(w, "%1:$d", 1, 2, 3); + assert(count == 3); assert(w.data == "123"); } From 40ffbb3641495b02815891ee004d4c6e173b1089 Mon Sep 17 00:00:00 2001 From: Elias Batek Date: Thu, 10 Apr 2025 12:33:15 +0200 Subject: [PATCH 04/25] `getrandom()` backwards compatibility shim (#10741) * Implement `version (linux_legacy_emulate_getrandom)` * Fix style --- Makefile | 4 ++++ std/random.d | 66 ++++++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 60 insertions(+), 10 deletions(-) diff --git a/Makefile b/Makefile index 0a3c51ba3c5..95534bc7926 100644 --- a/Makefile +++ b/Makefile @@ -173,6 +173,10 @@ ifdef NO_AUTODECODE override DFLAGS += -version=NoAutodecodeStrings endif +ifdef LINUX_LEGACY_EMULATE_GETRANDOM +override DFLAGS += -version=linux_legacy_emulate_getrandom +endif + UDFLAGS=-unittest -version=StdUnittest LINKDL:=$(if $(findstring $(OS),linux),-L-ldl,) diff --git a/std/random.d b/std/random.d index dc1763cf71c..edb8902c649 100644 --- a/std/random.d +++ b/std/random.d @@ -1774,19 +1774,65 @@ else version (linux) { - // `getrandom()` was introduced in Linux 3.17. + version (linux_legacy_emulate_getrandom) + { + /+ + Emulates `getrandom()` for backwards compatibility + with outdated kernels and legacy libc versions. + + `getrandom()` was added to the GNU C Library in v2.25. + +/ + pragma(msg, "`getrandom()` emulation for legacy Linux targets is enabled."); + + /+ + On modern kernels (5.6+), `/dev/random` would behave more similar + to `getrandom()`. + However, this emulator was specifically written for systems older + than that. Hence, `/dev/urandom` is the CSPRNG of choice. + + + +/ + private static immutable _pathLinuxSystemCSPRNG = "/dev/urandom"; + + import core.sys.posix.sys.types : ssize_t; + + /+ + Linux `getrandom()` emulation built upon `/dev/urandom`. + The fourth parameter (`uint flags`) is happily ignored. + +/ + private ssize_t getrandom( + void* buf, + size_t buflen, + uint, + ) @system nothrow @nogc + { + import core.stdc.stdio : fclose, fopen, fread; - // Shim for missing bindings in druntime - version (none) - import core.sys.linux.sys.random : getrandom; + auto blockDev = fopen(_pathLinuxSystemCSPRNG.ptr, "r"); + if (blockDev is null) + assert(false, "System CSPRNG unavailable: `fopen(\"" ~ _pathLinuxSystemCSPRNG ~ "\")` failed."); + scope (exit) fclose(blockDev); + + const bytesRead = fread(buf, 1, buflen, blockDev); + return bytesRead; + } + } else { - import core.sys.posix.sys.types : ssize_t; - extern extern(C) ssize_t getrandom( - void* buf, - size_t buflen, - uint flags, - ) @system nothrow @nogc; + // `getrandom()` was introduced in Linux 3.17. + + // Shim for missing bindings in druntime + version (none) + import core.sys.linux.sys.random : getrandom; + else + { + import core.sys.posix.sys.types : ssize_t; + private extern extern(C) ssize_t getrandom( + void* buf, + size_t buflen, + uint flags, + ) @system nothrow @nogc; + } } } From 4ea5c352daaae34313baaa692f8ee430241ebe79 Mon Sep 17 00:00:00 2001 From: Elias Batek Date: Tue, 22 Apr 2025 00:32:45 +0200 Subject: [PATCH 05/25] Polish `getrandom()` backwards compatibility shim (#10757) * Add changelog entry for the `getrandom()` backwards compatibility shim * Comment message pragma of the `getrandom()` backwards compatibility shim --- changelog/emulate_getrandom.dd | 19 +++++++++++++++++++ std/random.d | 1 - 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 changelog/emulate_getrandom.dd diff --git a/changelog/emulate_getrandom.dd b/changelog/emulate_getrandom.dd new file mode 100644 index 00000000000..9110fd95188 --- /dev/null +++ b/changelog/emulate_getrandom.dd @@ -0,0 +1,19 @@ +`getrandom()` backwards compatibility shim + +To restore compatibility with older Linux platforms where `getrandom()` is +unavailable either due to an outdated kernel or a legacy C library, Phobos now +ships with a shim that emulates a limited subset of `getrandom()`’s behavior +by reading random bytes from `/dev/urandom`. + +To enable the shim, build DMD and Phobos with the environment variable +`LINUX_LEGACY_EMULATE_GETRANDOM` set to `1`. + +``` +cd phobos +LINUX_LEGACY_EMULATE_GETRANDOM=1 make +``` + +This functionality is a temporary fix and expected to be removed again soon +by an upcoming release (approx. v2.112.0 or v2.113.0). +The expected change is to replace the current “binding or shim” solution with +a syscall wrapper and automatic `/dev/urandom` fallback. diff --git a/std/random.d b/std/random.d index edb8902c649..b9876cd23d2 100644 --- a/std/random.d +++ b/std/random.d @@ -1782,7 +1782,6 @@ version (linux) `getrandom()` was added to the GNU C Library in v2.25. +/ - pragma(msg, "`getrandom()` emulation for legacy Linux targets is enabled."); /+ On modern kernels (5.6+), `/dev/random` would behave more similar From 0ce6bedae03107951b1b52301c4647c453740d97 Mon Sep 17 00:00:00 2001 From: Iain Buclaw Date: Mon, 28 Apr 2025 12:36:58 +0200 Subject: [PATCH 06/25] Revert "Add low-overhead `InPlaceAppender` (#8789)" This reverts commit bea318447955ab49df765212505d4e2e10294e74. --- std/array.d | 387 +++++++++++++--------------------------------------- 1 file changed, 97 insertions(+), 290 deletions(-) diff --git a/std/array.d b/std/array.d index 53ffb06905f..fea70258ebf 100644 --- a/std/array.d +++ b/std/array.d @@ -3571,11 +3571,18 @@ See_Also: $(LREF appender) struct Appender(A) if (isDynamicArray!A) { - import std.format.spec : FormatSpec; + import core.memory : GC; private alias T = ElementEncodingType!A; - InPlaceAppender!A* impl; + private struct Data + { + size_t capacity; + Unqual!T[] arr; + bool tryExtendBlock = false; + } + + private Data* _data; /** * Constructs an `Appender` with a given array. Note that this does not copy the @@ -3583,17 +3590,27 @@ if (isDynamicArray!A) * it will be used by the appender. After initializing an appender on an array, * appending to the original array will reallocate. */ - this(A arr) @safe + this(A arr) @trusted { - impl = new InPlaceAppender!A(arr); - } + // initialize to a given array. + _data = new Data; + _data.arr = cast(Unqual!T[]) arr; //trusted - private void ensureInit() @safe - { - if (impl is null) + if (__ctfe) + return; + + // We want to use up as much of the block the array is in as possible. + // if we consume all the block that we can, then array appending is + // safe WRT built-in append, and we can use the entire block. + // We only do this for mutable types that can be extended. + static if (isMutable!T && is(typeof(arr.length = size_t.max))) { - impl = new InPlaceAppender!A; + immutable cap = arr.capacity; //trusted + // Replace with "GC.setAttr( Not Appendable )" once pure (and fixed) + if (cap > arr.length) + arr.length = cap; } + _data.capacity = arr.length; } /** @@ -3606,10 +3623,14 @@ if (isDynamicArray!A) */ void reserve(size_t newCapacity) { - if (newCapacity != 0) + if (_data) { - ensureInit(); - impl.reserve(newCapacity); + if (newCapacity > _data.capacity) + ensureAddable(newCapacity - _data.arr.length); + } + else + { + ensureAddable(newCapacity); } } @@ -3620,11 +3641,11 @@ if (isDynamicArray!A) */ @property size_t capacity() const { - return impl ? impl.capacity : 0; + return _data ? _data.capacity : 0; } /// Returns: The number of elements appended. - @property size_t length() const => (impl is null) ? 0 : impl.length; + @property size_t length() const => _data ? _data.arr.length : 0; /** * Use opSlice() from now on. @@ -3632,219 +3653,29 @@ if (isDynamicArray!A) */ @property inout(T)[] data() inout { - return opSlice(); + return this[]; } /** * Returns: The managed array. */ - @property inout(T)[] opSlice() inout @safe - { - return impl ? impl.opSlice() : null; - } - - /** - * Appends `item` to the managed array. Performs encoding for - * `char` types if `A` is a differently typed `char` array. - * - * Params: - * item = the single item to append - */ - void put(U)(U item) - if (InPlaceAppender!A.canPutItem!U) - { - ensureInit(); - impl.put(item); - } - - // Const fixing hack. - void put(Range)(Range items) - if (InPlaceAppender!A.canPutConstRange!Range) - { - if (!items.empty) - { - ensureInit(); - impl.put(items); - } - } - - /** - * Appends an entire range to the managed array. Performs encoding for - * `char` elements if `A` is a differently typed `char` array. - * - * Params: - * items = the range of items to append - */ - void put(Range)(Range items) - if (InPlaceAppender!A.canPutRange!Range) - { - if (!items.empty) - { - ensureInit(); - impl.put(items); - } - } - - /** - * Appends to the managed array. - * - * See_Also: $(LREF Appender.put) - */ - alias opOpAssign(string op : "~") = put; - - - // only allow overwriting data on non-immutable and non-const data - static if (isMutable!T) - { - /** - * Clears the managed array. This allows the elements of the array to be reused - * for appending. - * - * Note: clear is disabled for immutable or const element types, due to the - * possibility that `Appender` might overwrite immutable data. - */ - void clear() @safe pure nothrow - { - if (impl) - { - impl.clear(); - } - } - - /** - * Shrinks the managed array to the given length. - * - * Throws: `Exception` if newlength is greater than the current array length. - * Note: shrinkTo is disabled for immutable or const element types. - */ - void shrinkTo(size_t newlength) @safe pure - { - import std.exception : enforce; - if (impl) - { - impl.shrinkTo(newlength); - } - else - { - enforce(newlength == 0, "Attempting to shrink empty Appender with non-zero newlength"); - } - } - } - - /** - * Gives a string in the form of `Appender!(A)(data)`. - * - * Params: - * w = A `char` accepting - * $(REF_ALTTEXT output range, isOutputRange, std, range, primitives). - * fmt = A $(REF FormatSpec, std, format) which controls how the array - * is formatted. - * Returns: - * A `string` if `writer` is not set; `void` otherwise. - */ - string toString()() const - { - return InPlaceAppender!A.toStringImpl(Unqual!(typeof(this)).stringof, impl ? impl.data : null); - } - - /// ditto - template toString(Writer) - if (isOutputRange!(Writer, char)) - { - void toString(scope ref Writer w, scope const ref FormatSpec!char fmt) const - { - InPlaceAppender!A.toStringImpl(Unqual!(typeof(this)).stringof, impl ? impl.data : null, w, fmt); - } - } -} - -/// -@safe pure nothrow unittest -{ - auto app = appender!string(); - string b = "abcdefg"; - foreach (char c; b) - app.put(c); - assert(app[] == "abcdefg"); - - int[] a = [ 1, 2 ]; - auto app2 = appender(a); - app2.put(3); - app2.put([ 4, 5, 6 ]); - assert(app2[] == [ 1, 2, 3, 4, 5, 6 ]); -} - -package(std) struct InPlaceAppender(A) -if (isDynamicArray!A) -{ - import core.memory : GC; - import std.format.spec : FormatSpec; - - private alias T = ElementEncodingType!A; - - private - { - size_t _capacity; - Unqual!T[] arr; - bool tryExtendBlock = false; - } - - @disable this(ref InPlaceAppender); - - this(A arrIn) @trusted - { - arr = cast(Unqual!T[]) arrIn; //trusted - - if (__ctfe) - return; - - // We want to use up as much of the block the array is in as possible. - // if we consume all the block that we can, then array appending is - // safe WRT built-in append, and we can use the entire block. - // We only do this for mutable types that can be extended. - static if (isMutable!T && is(typeof(arrIn.length = size_t.max))) - { - immutable cap = arrIn.capacity; //trusted - // Replace with "GC.setAttr( Not Appendable )" once pure (and fixed) - if (cap > arrIn.length) - arrIn.length = cap; - } - _capacity = arrIn.length; - } - - void reserve(size_t newCapacity) - { - if (newCapacity > _capacity) - ensureAddable(newCapacity - arr.length); - } - - @property size_t capacity() const - { - return _capacity; - } - - @property size_t length() const => arr.length; - - @property inout(T)[] data() inout - { - return this[]; - } - - inout(T)[] opSlice() inout @trusted + @property inout(T)[] opSlice() inout @trusted { /* @trusted operation: * casting Unqual!T[] to inout(T)[] */ - return cast(typeof(return)) arr; + return cast(typeof(return))(_data ? _data.arr : null); } // ensure we can add nelems elements, resizing as necessary private void ensureAddable(size_t nelems) { - immutable len = arr.length; + if (!_data) + _data = new Data; + immutable len = _data.arr.length; immutable reqlen = len + nelems; - if (_capacity >= reqlen) + if (_data.capacity >= reqlen) return; // need to increase capacity @@ -3852,17 +3683,17 @@ if (isDynamicArray!A) { static if (__traits(compiles, new Unqual!T[1])) { - arr.length = reqlen; + _data.arr.length = reqlen; } else { // avoid restriction of @disable this() - arr = arr[0 .. _capacity]; - foreach (i; _capacity .. reqlen) - arr ~= Unqual!T.init; + _data.arr = _data.arr[0 .. _data.capacity]; + foreach (i; _data.capacity .. reqlen) + _data.arr ~= Unqual!T.init; } - arr = arr[0 .. len]; - _capacity = reqlen; + _data.arr = _data.arr[0 .. len]; + _data.capacity = reqlen; } else { @@ -3870,11 +3701,11 @@ if (isDynamicArray!A) // Time to reallocate. // We need to almost duplicate what's in druntime, except we // have better access to the capacity field. - auto newlen = appenderNewCapacity!(T.sizeof)(_capacity, reqlen); + auto newlen = appenderNewCapacity!(T.sizeof)(_data.capacity, reqlen); // first, try extending the current block - if (tryExtendBlock) + if (_data.tryExtendBlock) { - immutable u = (() @trusted => GC.extend(arr.ptr, nelems * T.sizeof, (newlen - len) * T.sizeof))(); + immutable u = (() @trusted => GC.extend(_data.arr.ptr, nelems * T.sizeof, (newlen - len) * T.sizeof))(); if (u) { // extend worked, update the capacity @@ -3883,10 +3714,11 @@ if (isDynamicArray!A) // at large unused blocks. static if (hasIndirections!T) { - immutable addedSize = u - (_capacity * T.sizeof); - () @trusted { memset(arr.ptr + _capacity, 0, addedSize); }(); + immutable addedSize = u - (_data.capacity * T.sizeof); + () @trusted { memset(_data.arr.ptr + _data.capacity, 0, addedSize); }(); } - _capacity = u / T.sizeof; + + _data.capacity = u / T.sizeof; return; } } @@ -3900,11 +3732,11 @@ if (isDynamicArray!A) ~ "available pointer range"); auto bi = (() @trusted => GC.qalloc(nbytes, blockAttribute!T))(); - _capacity = bi.size / T.sizeof; - import core.stdc.string : memcpy; + _data.capacity = bi.size / T.sizeof; if (len) - () @trusted { memcpy(bi.base, arr.ptr, len * T.sizeof); }(); - arr = (() @trusted => (cast(Unqual!T*) bi.base)[0 .. len])(); + () @trusted { memcpy(bi.base, _data.arr.ptr, len * T.sizeof); }(); + + _data.arr = (() @trusted => (cast(Unqual!T*) bi.base)[0 .. len])(); // we requested new bytes that are not in the existing // data. If T has pointers, then this new data could point at stale @@ -3915,7 +3747,7 @@ if (isDynamicArray!A) memset(bi.base + (len * T.sizeof), 0, (newlen - len) * T.sizeof); }(); - tryExtendBlock = true; + _data.tryExtendBlock = true; // leave the old data, for safety reasons } } @@ -3931,13 +3763,13 @@ if (isDynamicArray!A) enum bool canPutConstRange = isInputRange!(Unqual!Range) && !isInputRange!Range && - is(typeof(InPlaceAppender.init.put(Range.init.front))); + is(typeof(Appender.init.put(Range.init.front))); } private template canPutRange(Range) { enum bool canPutRange = isInputRange!Range && - is(typeof(InPlaceAppender.init.put(Range.init.front))); + is(typeof(Appender.init.put(Range.init.front))); } /** @@ -3966,13 +3798,13 @@ if (isDynamicArray!A) import core.lifetime : emplace; ensureAddable(1); - immutable len = arr.length; + immutable len = _data.arr.length; - auto bigData = (() @trusted => arr.ptr[0 .. len + 1])(); + auto bigData = (() @trusted => _data.arr.ptr[0 .. len + 1])(); auto itemUnqual = (() @trusted => & cast() item)(); emplace(&bigData[len], *itemUnqual); //We do this at the end, in case of exceptions - arr = bigData; + _data.arr = bigData; } } @@ -4016,16 +3848,16 @@ if (isDynamicArray!A) auto bigDataFun(size_t extra) { ensureAddable(extra); - return (() @trusted => arr.ptr[0 .. arr.length + extra])(); + return (() @trusted => _data.arr.ptr[0 .. _data.arr.length + extra])(); } auto bigData = bigDataFun(items.length); - immutable len = arr.length; + immutable len = _data.arr.length; immutable newlen = bigData.length; alias UT = Unqual!T; - static if (is(typeof(arr[] = items[])) && + static if (is(typeof(_data.arr[] = items[])) && !hasElaborateAssign!UT && isAssignable!(UT, ElementEncodingType!Range)) { bigData[len .. newlen] = items[]; @@ -4041,7 +3873,7 @@ if (isDynamicArray!A) } //We do this at the end, in case of exceptions - arr = bigData; + _data.arr = bigData; } else static if (isSomeChar!T && isSomeChar!(ElementType!Range) && !is(immutable T == immutable ElementType!Range)) @@ -4084,7 +3916,10 @@ if (isDynamicArray!A) */ void clear() @trusted pure nothrow { - arr = arr.ptr[0 .. 0]; + if (_data) + { + _data.arr = _data.arr.ptr[0 .. 0]; + } } /** @@ -4096,8 +3931,13 @@ if (isDynamicArray!A) void shrinkTo(size_t newlength) @trusted pure { import std.exception : enforce; - enforce(newlength <= arr.length, "Attempting to shrink Appender with newlength > length"); - arr = arr.ptr[0 .. newlength]; + if (_data) + { + enforce(newlength <= _data.arr.length, "Attempting to shrink Appender with newlength > length"); + _data.arr = _data.arr.ptr[0 .. newlength]; + } + else + enforce(newlength == 0, "Attempting to shrink empty Appender with non-zero newlength"); } } @@ -4112,18 +3952,13 @@ if (isDynamicArray!A) * Returns: * A `string` if `writer` is not set; `void` otherwise. */ - auto toString() const - { - return toStringImpl(Unqual!(typeof(this)).stringof, data); - } - - static auto toStringImpl(string typeName, const T[] arr) + string toString()() const { import std.format.spec : singleSpec; - InPlaceAppender!string app; + auto app = appender!string(); auto spec = singleSpec("%s"); - immutable len = arr.length; + immutable len = _data ? _data.arr.length : 0; // different reserve lengths because each element in a // non-string-like array uses two extra characters for `, `. static if (isSomeString!A) @@ -4136,25 +3971,25 @@ if (isDynamicArray!A) // length, as it assumes each element is only one char app.reserve((len * 3) + 25); } - toStringImpl(typeName, arr, app, spec); + toString(app, spec); return app.data; } - void toString(Writer)(scope ref Writer w, scope const ref FormatSpec!char fmt) const - if (isOutputRange!(Writer, char)) - { - toStringImpl(Unqual!(typeof(this)).stringof, data, w, fmt); - } + import std.format.spec : FormatSpec; - static void toStringImpl(Writer)(string typeName, const T[] data, scope ref Writer w, - scope const ref FormatSpec!char fmt) + /// ditto + template toString(Writer) + if (isOutputRange!(Writer, char)) { - import std.format.write : formatValue; - import std.range.primitives : put; - put(w, typeName); - put(w, '('); - formatValue(w, data, fmt); - put(w, ')'); + void toString(ref Writer w, scope const ref FormatSpec!char fmt) const + { + import std.format.write : formatValue; + import std.range.primitives : put; + put(w, Unqual!(typeof(this)).stringof); + put(w, '('); + formatValue(w, data, fmt); + put(w, ')'); + } } } @@ -4197,16 +4032,6 @@ if (isDynamicArray!A) assert(app3[] == "Appender!(int[])(0001, 0002, 0003)"); } -@safe pure unittest -{ - auto app = appender!(char[])(); - app ~= "hello"; - app.clear; - // not a promise, just nothing else exercises capacity - // and this is the expected sort of behaviour - assert(app.capacity >= 5); -} - // https://issues.dlang.org/show_bug.cgi?id=17251 @safe pure nothrow unittest { @@ -4469,24 +4294,6 @@ unittest assert(app2.capacity >= 5); } -/++ - Convenience function that returns a $(LREF InPlaceAppender) instance, - optionally initialized with `array`. - +/ -package(std) InPlaceAppender!A inPlaceAppender(A)() -if (isDynamicArray!A) -{ - return InPlaceAppender!A(null); -} -/// ditto -package(std) InPlaceAppender!(E[]) inPlaceAppender(A : E[], E)(auto ref A array) -{ - static assert(!isStaticArray!A || __traits(isRef, array), - "Cannot create InPlaceAppender from an rvalue static array"); - - return InPlaceAppender!(E[])(array); -} - /++ Convenience function that returns an $(LREF Appender) instance, optionally initialized with `array`. From b1d335390f3f3b6c7a6e276effa84fe205445630 Mon Sep 17 00:00:00 2001 From: Iain Buclaw Date: Mon, 28 Apr 2025 12:54:24 +0200 Subject: [PATCH 07/25] Add unittest for #10747 Closes: #10747 Closes: #10764 --- std/array.d | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/std/array.d b/std/array.d index fea70258ebf..3701adfb078 100644 --- a/std/array.d +++ b/std/array.d @@ -4162,6 +4162,25 @@ if (isDynamicArray!A) writeln("WARNING: test of Appender zeroing did not occur"); } +// https://github.com/dlang/phobos/issues/10747 +@system unittest +{ + static struct A10747 + { + this(ref A10747 rhs) { } + } + + static class R10747 + { + A10747 front() { return A10747.init; } + void popFront() { } + bool empty = true; + } + + auto a = new R10747(); + a.array(); +} + //Calculates an efficient growth scheme based on the old capacity //of data, and the minimum requested capacity. //arg curLen: The current length From a819bf678fb1f1c63178956002e50eab294f8c8e Mon Sep 17 00:00:00 2001 From: Elias Batek Date: Sun, 4 May 2025 00:28:04 +0200 Subject: [PATCH 08/25] Add unittest from #10769 (#10770) Co-authored-by: Parmar Mahipalsinh --- std/file.d | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/std/file.d b/std/file.d index 312f8bb6f82..0d4959b226f 100644 --- a/std/file.d +++ b/std/file.d @@ -969,6 +969,19 @@ if (isConvertibleToString!RF || isConvertibleToString!RT) assert(t2.readText == "2"); } + +@safe unittest +{ + import std.file; + import std.exception : assertThrown; + + string f = null; + + // Check if FileException is thrown for invalid rename + assertThrown!FileException(rename("", f)); + assertThrown!FileException(rename(f, "")); +} + private void renameImpl(scope const(char)[] f, scope const(char)[] t, scope const(FSChar)* fromz, scope const(FSChar)* toz) @trusted { From d6608c912f175ca587bc348225bb4db966cfd2b8 Mon Sep 17 00:00:00 2001 From: Iain Buclaw Date: Sun, 11 May 2025 23:06:20 +0200 Subject: [PATCH 09/25] Revert "std.allocator: Comment out broken class instance size test (#10717)" This reverts commit 530660bd9b057e6fdbf0302e7e76a2dd6f9bce95. --- std/experimental/allocator/common.d | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/std/experimental/allocator/common.d b/std/experimental/allocator/common.d index 245157258c1..b06fb627d71 100644 --- a/std/experimental/allocator/common.d +++ b/std/experimental/allocator/common.d @@ -63,8 +63,7 @@ unittest class C2 { char c; } static assert(stateSize!C2 == 4 * size_t.sizeof); static class C3 { char c; } - // Uncomment test after dmd issue closed https://github.com/dlang/dmd/issues/21065 - //static assert(stateSize!C3 == 3 * size_t.sizeof); + static assert(stateSize!C3 == 2 * size_t.sizeof + char.sizeof); } /** From 35637c1abdd60e6a863e50ef268fcccd8217028f Mon Sep 17 00:00:00 2001 From: Steven Schveighoffer Date: Thu, 22 May 2025 06:18:16 -0400 Subject: [PATCH 10/25] Fix #10785 (#10787) Check GC.inFinalizer when writing debug garbage to referenced GC array, as this is not valid to do when called from the GC. --- std/uni/package.d | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/std/uni/package.d b/std/uni/package.d index 34d15e034ba..703bd933f0d 100644 --- a/std/uni/package.d +++ b/std/uni/package.d @@ -1785,7 +1785,9 @@ alias sharSwitchLowerBound = sharMethod!switchUniformLowerBound; { debug { - arr[] = cast(typeof(T.init[0]))(0xdead_beef); + import core.memory : GC; + if (!__ctfe && !GC.inFinalizer) // only do this if we are not in the GC finalizer + arr[] = cast(typeof(T.init[0]))(0xdead_beef); } arr = null; } From 969a072e426a49cf6a9fedab3a419867145bc66a Mon Sep 17 00:00:00 2001 From: Steven Schveighoffer Date: Thu, 22 May 2025 18:25:07 -0400 Subject: [PATCH 11/25] Further fix for CowArray to prevent using the refcount when being (#10788) destroyed from a GC finalizer. --- std/uni/package.d | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/std/uni/package.d b/std/uni/package.d index 703bd933f0d..d689f08e538 100644 --- a/std/uni/package.d +++ b/std/uni/package.d @@ -1785,9 +1785,8 @@ alias sharSwitchLowerBound = sharMethod!switchUniformLowerBound; { debug { - import core.memory : GC; - if (!__ctfe && !GC.inFinalizer) // only do this if we are not in the GC finalizer - arr[] = cast(typeof(T.init[0]))(0xdead_beef); + assert(accessIsSafe); + arr[] = cast(typeof(T.init[0]))(0xdead_beef); } arr = null; } @@ -1797,6 +1796,18 @@ alias sharSwitchLowerBound = sharMethod!switchUniformLowerBound; { arr = null; } + + // This is unfortunately necessary to "fake pure". It will only ever be called + // in the destructor for a GC-allocated CowArray, which is the only place where + // this might return false. Current code expects this to be pure, so we can't + // break that. But before this change, the code would access the referenced + // array inside a GC finalizer, which is invalid. + pragma(mangle, "gc_inFinalizer") private static extern(C) bool pureInGCFinalizer() @safe pure nothrow; + + static @property bool accessIsSafe() @safe nothrow pure + { + return __ctfe || !pureInGCFinalizer; + } } // ditto @@ -1894,6 +1905,8 @@ alias sharSwitchLowerBound = sharMethod!switchUniformLowerBound; pureFree(arr.ptr); arr = null; } + + enum accessIsSafe = true; } //build hack @@ -3236,6 +3249,10 @@ struct CowArray(SP=GcPolicy) ~this() { + if (!SP.accessIsSafe) + // detach from the array, we can no longer access it. + data = null; + if (!empty) { immutable cnt = refCount; From 5125f2ee4991e82301e27a920bf3462a084f0358 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6nke=20Ludwig?= Date: Sun, 29 Jun 2025 10:09:19 +0200 Subject: [PATCH 12/25] Fix #10811: compile error in RedBlackTree caused by the presence of a move constructor. (#10810) --- std/container/rbtree.d | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/std/container/rbtree.d b/std/container/rbtree.d index 822e47b4411..2ccee18c92c 100644 --- a/std/container/rbtree.d +++ b/std/container/rbtree.d @@ -88,6 +88,14 @@ struct RBNode(V) private Node _right; private Node _parent; + private this(Node left, Node right, Node parent, V value) + { + this._left = left; + this._right = right; + this._parent = parent; + this.value = value; + } + /** * The value held by this node */ @@ -2260,3 +2268,17 @@ if ( is(typeof(binaryFun!less((ElementType!Stuff).init, (ElementType!Stuff).init t.insert([1, 3, 5, 4, 2]); assert(t[].equal([5, 4, 3, 2, 1])); } + +// struct with move constructor +@safe unittest +{ + static struct S { + int i; + this(S s) { this.i = s.i; } + this(int i) { this.i = i; } + int opCmp(ref const S other) const @safe nothrow { return i - other.i; } + } + + auto tree = new RedBlackTree!(S, "a.i < b.i", false); + tree.insert(S(0)); +} From 30fcf07d990f68853bff1ff22abaf28f3226082f Mon Sep 17 00:00:00 2001 From: Tony Edgin Date: Tue, 8 Jul 2025 08:16:47 -0700 Subject: [PATCH 13/25] Fix issue 10801 - sgnGamma(-0.5) should be -1 (#10813) Use of rndtol to determine which domain x belongs to is replaced with trunc. rndtol depends on rounding mode. Depending on the mode, rndtol(-1.5) could be -2 or -1, which map to different domains with different signs. trunc doesn't depend on rounding mode. trunc(-1.5) is always -1. --- std/mathspecial.d | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/std/mathspecial.d b/std/mathspecial.d index 64ab9bf3714..649414ad158 100644 --- a/std/mathspecial.d +++ b/std/mathspecial.d @@ -119,7 +119,6 @@ real logGamma(real x) */ real sgnGamma(real x) { - import core.math : rndtol; /* Author: Don Clugston. */ if (isNaN(x)) return x; if (x > 0) return 1.0; @@ -128,7 +127,7 @@ real sgnGamma(real x) // Large negatives lose all precision return real.nan; } - long n = rndtol(x); + long n = cast(long) trunc(x); if (x == n) { return x == 0 ? copysign(1, x) : real.nan; @@ -141,6 +140,7 @@ real sgnGamma(real x) assert(sgnGamma(5.0) == 1.0); assert(isNaN(sgnGamma(-3.0))); assert(sgnGamma(-0.1) == -1.0); + assert(sgnGamma(-0.6) == -1.0); assert(sgnGamma(-55.1) == 1.0); assert(isNaN(sgnGamma(-real.infinity))); assert(isIdentical(sgnGamma(NaN(0xABC)), NaN(0xABC))); From 4fc3facb47241747d0919ab98501bcf7ea8c595e Mon Sep 17 00:00:00 2001 From: Teodor Dutu Date: Mon, 21 Jul 2025 03:21:38 +0300 Subject: [PATCH 14/25] `std.array.d`: Use template `_d_newarrayU{,Trace}` (#10819) dlang/dmd#21525 Adds the template `_d_newarrayUTrace`. To fix dlang/dmd#21033, this commit needs to replace the use of the non-template hook with the newly added template in `std.array.d`. Signed-off-by: Teodor Dutu --- std/array.d | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/std/array.d b/std/array.d index 3701adfb078..da934ef9b8d 100644 --- a/std/array.d +++ b/std/array.d @@ -1073,14 +1073,6 @@ if (isDynamicArray!T && allSatisfy!(isIntegral, I)) } } -// from rt/lifetime.d -private extern(C) void[] _d_newarrayU(const TypeInfo ti, size_t length) pure nothrow; - -// from rt/tracegc.d -version (D_ProfileGC) -private extern (C) void[] _d_newarrayUTrace(string file, size_t line, - string funcname, const scope TypeInfo ti, size_t length) pure nothrow; - private auto arrayAllocImpl(bool minimallyInitialized, T, I...)(I sizes) nothrow { static assert(I.length <= nDimensions!T, @@ -1131,18 +1123,19 @@ private auto arrayAllocImpl(bool minimallyInitialized, T, I...)(I sizes) nothrow which will inform the GC how to destroy the items in the block when it gets collected. - _d_newarrayU returns a void[], but with the length set according - to E.sizeof. + _d_newarrayU returns a E[], with the length set according to + to the size parameter. +/ + enum isShared = is (E == shared); version (D_ProfileGC) { // FIXME: file, line, function should be propagated from the // caller, not here. - *(cast(void[]*)&ret) = _d_newarrayUTrace(__FILE__, __LINE__, - __FUNCTION__, typeid(E[]), size); + ret = _d_newarrayUTrace!E(size, isShared, + __FILE__, __LINE__, __FUNCTION__); } else - *(cast(void[]*)&ret) = _d_newarrayU(typeid(E[]), size); + ret = _d_newarrayU!E(size, isShared); static if (minimallyInitialized && hasIndirections!E) // _d_newarrayU would have asserted if the multiplication below // had overflowed, so we don't have to check it again. From c348c30ae15e4ec11efe16a7e3899b0ae5bc8749 Mon Sep 17 00:00:00 2001 From: Tony Edgin Date: Sun, 10 Aug 2025 17:08:32 -0700 Subject: [PATCH 15/25] 10823 fix, fixed sign of beta(x,y) when x+y large (#10824) Fix the large value method of computing std.mathspecial.beta so that it recovers the signs of gamma(x), gamma(y), and gamma(x+y). --- std/mathspecial.d | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/std/mathspecial.d b/std/mathspecial.d index 649414ad158..b6e106821cc 100644 --- a/std/mathspecial.d +++ b/std/mathspecial.d @@ -154,14 +154,23 @@ real sgnGamma(real x) */ real beta(real x, real y) { - if ((x+y)> MAXGAMMA) + if (x > MAXGAMMA || y > MAXGAMMA || (x+y)> MAXGAMMA) { - return exp(logGamma(x) + logGamma(y) - logGamma(x+y)); + const sgnB = sgnGamma(x) * sgnGamma(y) / sgnGamma(x+y); + return sgnB * exp(logGamma(x) + logGamma(y) - logGamma(x+y)); } else return gamma(x) * gamma(y) / gamma(x+y); } @safe unittest { + assert(beta(0.6*MAXGAMMA, 0.5*MAXGAMMA) > 0); + assert(beta(2*MAXGAMMA, -0.5) < 0); + assert(beta(-0.1, 2*MAXGAMMA) < 0); + assert(beta(-1.6, 2*MAXGAMMA) > 0); + assert(beta(+0., 2*MAXGAMMA) == real.infinity); + assert(beta(-0., 2*MAXGAMMA) == -real.infinity); + assert(beta(-MAXGAMMA-1.5, MAXGAMMA+1) < 0); + assert(isNaN(beta(-1, 2*MAXGAMMA))); assert(isIdentical(beta(NaN(0xABC), 4), NaN(0xABC))); assert(isIdentical(beta(2, NaN(0xABC)), NaN(0xABC))); } From a630eabf59260a488ffdaaad6fb95cadbdbf9051 Mon Sep 17 00:00:00 2001 From: Tony Edgin Date: Tue, 4 Nov 2025 16:00:15 -0700 Subject: [PATCH 16/25] phobos#10888 logmdigammaInverse(-0) is set to NaN (#10891) This modifies `std.mathspecial.logmdigammaInverse(y)` so that when y is negative, not just less than zero, it returns NaN. Since NaNs can be negative, the NaN payload propagation logic is moved to the top of the function. --- std/internal/math/gammafunction.d | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/std/internal/math/gammafunction.d b/std/internal/math/gammafunction.d index 64c9378bbc8..aff6782dde1 100644 --- a/std/internal/math/gammafunction.d +++ b/std/internal/math/gammafunction.d @@ -2110,12 +2110,14 @@ real logmdigammaInverse(real y) immutable maxY = logmdigamma(real.min_normal); assert(maxY > 0 && maxY <= real.max); + if (isNaN(y)) return y; + if (y >= maxY) { //lim x->0 (log(x)-digamma(x))*x == 1 return 1 / y; } - if (y < 0) + if (signbit(y) == 1) // y is negative { return real.nan; } @@ -2124,13 +2126,10 @@ real logmdigammaInverse(real y) //6.3.18 return 0.5 / y; } - if (y > 0) - { - // x/2 <= logmdigamma(1 / x) <= x, x > 0 - // calls logmdigamma ~6 times - return 1 / findRoot((real x) => logmdigamma(1 / x) - y, y, 2*y); - } - return y; //NaN + + // x/2 <= logmdigamma(1 / x) <= x, x > 0 + // calls logmdigamma ~6 times + return 1 / findRoot((real x) => logmdigamma(1 / x) - y, y, 2*y); } @safe unittest @@ -2153,4 +2152,6 @@ real logmdigammaInverse(real y) assert(isClose(logmdigammaInverse(logmdigamma(1)), 1, 1e-15L)); assert(isClose(logmdigammaInverse(logmdigamma(real.min_normal)), real.min_normal, 1e-15L)); assert(isClose(logmdigammaInverse(logmdigamma(real.max/2)), real.max/2, 1e-15L)); + assert(logmdigammaInverse(NaN(0x1)) is NaN(0x1)); + assert(isNaN(logmdigammaInverse(-0.))); } From b0bb5907ffae42e70db9bba9d0fd706585bc3a5b Mon Sep 17 00:00:00 2001 From: Tony Edgin Date: Tue, 4 Nov 2025 18:20:36 -0700 Subject: [PATCH 17/25] phobos#10826 address Q1 cases where near origin or near infinity (#10890) * phobos#10826 explicitly address at origin in Q1 in beta This modifies the beta function so that it explicitly addresses the case where the input parameters (x,y) are (+0,+0), i.e. next to the origin in the first quadrant. This is already implicitly handled, but explicitly handling it will be make the algoritm clearer when it is extended to address the cases where x is very small and y is very small or very large. Since the cases are numbered, I had to renumber the cases. (Maybe I shouldn't have numbered them originally.) * phobos#10826 support Q1, near origin or very large This adds support for the cases when (x,y) is in the first quadrant and near the origin or the larger of x and y is very large * phobos#10826: change NaN handling behavior of beta This changes the NaN handling behavior of beta to be the same as that of the + operator, i.e., when both input arguments are NaN, the one with the larger payload is returned. * phobos#10826: apply symmetry to entire beta alogrithm --- std/internal/math/gammafunction.d | 64 ++++++++++++++++++------------- 1 file changed, 38 insertions(+), 26 deletions(-) diff --git a/std/internal/math/gammafunction.d b/std/internal/math/gammafunction.d index aff6782dde1..0cc27dc3cac 100644 --- a/std/internal/math/gammafunction.d +++ b/std/internal/math/gammafunction.d @@ -654,35 +654,34 @@ private pragma(inline, true) real betaLarge(in real x, in real y) */ real beta(in real x, in real y) { + // When one of the input parameters is NaN, return the NaN with the larger + // payload. This mimics the behavior of the + operator. + if (isNaN(x) || isNaN(y)) return getNaNPayload(x) >= getNaNPayload(y) ? x : y; + real res; + // Take advantage of the symmetry B(x,y) = B(y,x) + // smaller ≤ larger + const larger = cmp(x, y) >= 0 ? x : y; + const smaller = cmp(x, y) >= 0 ? y : x; + const sum = larger + smaller; + // the main algorithm - if (x > MAXGAMMA || y > MAXGAMMA || x + y > MAXGAMMA) + if (larger > MAXGAMMA || sum > MAXGAMMA) { - res = betaLarge(x, y); + res = betaLarge(smaller, larger); } else { - res = gamma(x) * gamma(y) / gamma(x+y); + res = gamma(smaller) * gamma(larger) / gamma(sum); // There are several regions near the asymptotes and inflection lines // gamma cannot be computed but logGamma can. - if (!isFinite(res)) res = betaLarge(x,y); + if (!isFinite(res)) res = betaLarge(smaller, larger); } if (!isNaN(res)) return res; - // For valid NaN results, always return the response from the main algorithm - // in order to preserve signaling NaNs. - - if (isNaN(x) || isNaN(y)) return res; - - // Take advantage of the symmetry B(x,y) = B(y,x) - // smaller ≤ larger - const larger = cmp(x, y) >= 0 ? x : y; - const smaller = cmp(x, y) >= 0 ? y : x; - const sum = larger + smaller; - // in a quadrant of the (smaller,larger) cartesian plane const inQ1 = cmp(smaller, +0.0L) >= 0; const inQ2 = !inQ1 && cmp(larger, +0.0L) >= 0; @@ -711,28 +710,35 @@ real beta(in real x, in real y) if (inQ1) { - // 4) On the larger axis and larger is finite, B = +∞ - // 5) On the larger axis, and larger is +∞, B = nan + // 4) At origin, B = +∞ + if (nextToOrigin) return +real.infinity; + + // 5) On the larger axis and larger is finite, B = +∞ + // 6) On the larger axis, and larger is +∞, B = nan if (nextToSmallAxis) return larger < +real.infinity ? +real.infinity : res; - // 6) Not on the larger axis, and the larger is +∞, B = +0 + // 7) Not on the larger axis, and the larger is +∞, B = +0 if (!nextToSmallAxis && larger == +real.infinity) return +0.; + + // not on larger axis, but near origin, case 4, or larger is very large, + // case 7 + if (!nextToSmallAxis) return larger < 1 ? +real.infinity : +0.; } if (inQ2) { - // 7) Next to the origin, B = nan - // 8) Next to the larger axis, but not the origin, B = -∞ + // 8) Next to the origin, B = nan + // 9) Next to the larger axis, but not the origin, B = -∞ if (nextToSmallAxis) return nextToOrigin ? res : -real.infinity; - // 9) Larger is +∞, B = ∞ * sgn(Γ(smaller)) + // 10) Larger is +∞, B = ∞ * sgn(Γ(smaller)) if (larger == +real.infinity) return copysign(real.infinity, sgnGamma(smaller)); - // 10) next to smaller axis, but not on an asymptote or at the origin, + // 11) next to smaller axis, but not on an asymptote or at the origin, // B = +∞. if (nextToLargeAxis && !onSmallAsymptote && !nextToOrigin) return +real.infinity; - // larger very large, case 9 + // larger very large, case 10 // larger so large that ln|Γ(larger)| and ln|Γ(sum)| are too large to // represent as reals. Thus they each are approximated as ∞, and the // main algorithm resolves to NaN instead of ±∞. @@ -741,10 +747,10 @@ real beta(in real x, in real y) if (inQ3) { - // 11) next to the smaller axis, but not on an asymptote, B = -∞. + // 12) next to the smaller axis, but not on an asymptote, B = -∞. if (nextToLargeAxis && !onSmallAsymptote) return -real.infinity; - // near origin, case 11 + // near origin, case 12 // -larger and -sum are so small that ln|Γ(larger)| and ln|Γ(sum)| are // too large to be represented as reals. Thus they each are approximated // as ∞, and the main algorithm resolves to NaN instead of -∞. @@ -757,14 +763,20 @@ real beta(in real x, in real y) @safe unittest { + // Test NaN payload propagation + assert(isIdentical(beta(NaN(0xABC), 2), NaN(0xABC))); assert(isIdentical(beta(2, NaN(0xABC)), NaN(0xABC))); + assert(isIdentical(beta(NaN(0x1), NaN(0x2)), NaN(0x2))); // Test symmetry + assert(beta(1, 2) is beta(2, 1)); // Test first quadrant + assert(beta(+0., +0.) == +real.infinity); assert(beta(+0., 1) == +real.infinity); - assert(beta(nextUp(+0.0L), nextUp(+0.0L) > 0), "B(εₓ,ε𞁟) > 0"); + assert(isClose(beta(nextUp(+0.0L), nextUp(+0.0L)), real.infinity), "B(εₓ,ε𞁟) ≲ +∞"); assert(!isNaN(beta(nextUp(+0.0L), 1)), "B(ε,y), y > 0 should exist"); + assert(isClose(beta(nextUp(+0.0L), nextDown(real.infinity)), +0.0L), "B(ε,y) ≳ 0, y large"); assert(beta(1, +real.infinity) is +0.0L, "lim{y→+∞} B(x,y) = 0⁺, x > 0"); assert(beta(1, 1) > 0); assert(beta(0.6*MAXGAMMA, 0.5*MAXGAMMA) > 0); From bcdaa07b9f388fc5e852097f8d22f826d9c06ff9 Mon Sep 17 00:00:00 2001 From: Tony Edgin Date: Thu, 6 Nov 2025 16:31:02 -0700 Subject: [PATCH 18/25] 10826 use cmp and abs to find larger NaN payload (#10895) `getNaNPayload(x)` returns a value even if `x` is not NaN, so it is up to the caller to verify that `x` is a NaN before calling `getNaNPayload(x)`. This means it is not a good option for determining for determining which of a set of values has the largest NaN payload when it is only known that at least one value is NaN. `cmp(x, y)` can be used. since to orders -NaN as before -infinity and NaN as after infinity. When two NaN with the same sign are compared, the one with the larger payload is considered larger. This means that `cmp(abs(x), abx(y))` will find the NaN with the larger payload when it is only known that at least one of `x` or `y` is NaN. --- std/internal/math/gammafunction.d | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/std/internal/math/gammafunction.d b/std/internal/math/gammafunction.d index 0cc27dc3cac..8c04ce3ee00 100644 --- a/std/internal/math/gammafunction.d +++ b/std/internal/math/gammafunction.d @@ -656,7 +656,7 @@ real beta(in real x, in real y) { // When one of the input parameters is NaN, return the NaN with the larger // payload. This mimics the behavior of the + operator. - if (isNaN(x) || isNaN(y)) return getNaNPayload(x) >= getNaNPayload(y) ? x : y; + if (isNaN(x) || isNaN(y)) return cmp(abs(x), abs(y)) >= 0 ? x : y; real res; @@ -764,7 +764,7 @@ real beta(in real x, in real y) @safe unittest { // Test NaN payload propagation - assert(isIdentical(beta(NaN(0xABC), 2), NaN(0xABC))); + assert(isIdentical(beta(NaN(0x1), 7), NaN(0x1))); assert(isIdentical(beta(2, NaN(0xABC)), NaN(0xABC))); assert(isIdentical(beta(NaN(0x1), NaN(0x2)), NaN(0x2))); From 70d89eb4e46092808b4573d34e76ffa4bff9e7e1 Mon Sep 17 00:00:00 2001 From: Martin Kinkelin Date: Tue, 9 Dec 2025 12:21:05 +0100 Subject: [PATCH 19/25] std.variant: Fix deprecation in VariantN.opApply() (#10910) 'Deprecation: cannot return non-zero compile-time value from `opApply`' Hit e.g. when compiling the unittests. --- std/variant.d | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/std/variant.d b/std/variant.d index a094aeb3409..b76b172521e 100644 --- a/std/variant.d +++ b/std/variant.d @@ -1207,7 +1207,7 @@ public: auto arr = get!(A[]); foreach (ref e; arr) { - if (dg(e)) return 1; + if (auto r = dg(e)) return r; } } else static if (is(A == VariantN)) @@ -1219,7 +1219,7 @@ public: // Variant when in fact they are only changing tmp. auto tmp = this[i]; debug scope(exit) assert(tmp == this[i]); - if (dg(tmp)) return 1; + if (auto r = dg(tmp)) return r; } } else From 51e0e0a3acf36825cdd67414915db201d5a16ab6 Mon Sep 17 00:00:00 2001 From: Dennis Korpel Date: Wed, 7 Jan 2026 12:25:30 +0100 Subject: [PATCH 20/25] purge changelog --- changelog/add_lazycache_function.dd | 15 -------------- changelog/emulate_getrandom.dd | 19 ----------------- changelog/entropy_system.dd | 32 ----------------------------- changelog/unicode-17.dd | 16 --------------- changelog/uuidv7.dd | 18 ---------------- changelog/write-text.dd | 20 ------------------ 6 files changed, 120 deletions(-) delete mode 100644 changelog/add_lazycache_function.dd delete mode 100644 changelog/emulate_getrandom.dd delete mode 100644 changelog/entropy_system.dd delete mode 100644 changelog/unicode-17.dd delete mode 100644 changelog/uuidv7.dd delete mode 100644 changelog/write-text.dd diff --git a/changelog/add_lazycache_function.dd b/changelog/add_lazycache_function.dd deleted file mode 100644 index 7797ea7b223..00000000000 --- a/changelog/add_lazycache_function.dd +++ /dev/null @@ -1,15 +0,0 @@ -Add lazyCache to std.algorithm.iteration - -The new `lazyCache` function provides a lazily evaluated range caching mechanism. -Unlike `cache`, which eagerly evaluates range elements during construction, -`lazyCache` defers evaluation until elements are explicitly requested. - ---- -auto result = iota(-4, 5).map!(a => tuple(a, expensiveComputation(a)))().lazyCache(); -// No computations performed at this point - -auto firstElement = result.front; -// First element is now evaluated ---- - -See the $(REF lazyCache, std,algorithm,iteration) documentation for more details. \ No newline at end of file diff --git a/changelog/emulate_getrandom.dd b/changelog/emulate_getrandom.dd deleted file mode 100644 index 9110fd95188..00000000000 --- a/changelog/emulate_getrandom.dd +++ /dev/null @@ -1,19 +0,0 @@ -`getrandom()` backwards compatibility shim - -To restore compatibility with older Linux platforms where `getrandom()` is -unavailable either due to an outdated kernel or a legacy C library, Phobos now -ships with a shim that emulates a limited subset of `getrandom()`’s behavior -by reading random bytes from `/dev/urandom`. - -To enable the shim, build DMD and Phobos with the environment variable -`LINUX_LEGACY_EMULATE_GETRANDOM` set to `1`. - -``` -cd phobos -LINUX_LEGACY_EMULATE_GETRANDOM=1 make -``` - -This functionality is a temporary fix and expected to be removed again soon -by an upcoming release (approx. v2.112.0 or v2.113.0). -The expected change is to replace the current “binding or shim” solution with -a syscall wrapper and automatic `/dev/urandom` fallback. diff --git a/changelog/entropy_system.dd b/changelog/entropy_system.dd deleted file mode 100644 index 707e87e12eb..00000000000 --- a/changelog/entropy_system.dd +++ /dev/null @@ -1,32 +0,0 @@ -Add an internal multi-backend entropy system - -This Phobos release introduces an internal multi-backend system for the -retrieval of entropy (as in cryptographically-secure random numbers obtained -from a suitable random number generator provided by the operating system). - -The current implementation supports the `getrandom` syscall on Linux. - -On BSD systems `arc4random_buf` or `getentropy` are used — depending on -which is implemented by the OS and powered by a secure (non-RC4) algorithm. - -Additionally, reading entropy from the character devices `/dev/urandom` and -`/dev/random` is available on all POSIX targets. - -On Windows `BCryptGenRandom` (from the -$(I Cryptography API: Next Generation (“BCrypt”))) is provided as a backend. -`CryptGenRandom` from the legacy $(I CryptoAPI) is not supported for the time -being. - -Furthermore, this replaces the `getrandom` backwards compatibility shim -that had been added by v2.111.1 for Linux targets. -Instead backwards compatibility is now provided by a hunt strategy algorithm -that tries potentially available entropy sources one by one to find one that -is available on the running system. -Given that the character devices serve as a fallback option here, -`urandom` is favored over `random`. That is because modern kernel versions — -where `random` would exhibit the usually more preferable behavior of blocking -only until the entropy pool has been initialized — will also provide the -`getrandom` syscall in the first place. Performing the syscall, in turn, is -even better as it does not depend on the runtime environment exposing the -special devices in predefined locations, thus working also within chroot -environments. diff --git a/changelog/unicode-17.dd b/changelog/unicode-17.dd deleted file mode 100644 index 6630ee5eaf1..00000000000 --- a/changelog/unicode-17.dd +++ /dev/null @@ -1,16 +0,0 @@ -std.uni has been upgraded from Unicode 16.0.0 to 17.0.0 - -This Unicode update was released September 9, 2025, and adds new blocks with characters. -See: https://www.unicode.org/versions/Unicode17.0.0/ - -``` -import std; - -void main() -{ - const alphaCount = iota(0, dchar.max).filter!(std.uni.isAlpha).walkLength; - writeln(alphaCount); - // formerly: 142759 - // now: 147421 -} -``` diff --git a/changelog/uuidv7.dd b/changelog/uuidv7.dd deleted file mode 100644 index 3313203a293..00000000000 --- a/changelog/uuidv7.dd +++ /dev/null @@ -1,18 +0,0 @@ -Add uuid v7 support to `std.uuid` - -Add uuid v7 support to the UUID type located in `std.uuid`. -The first 48 bits of v7 stores the milliseconds since the unix epoch -(1970-01-01), additionally 74 bit are used to store random data. - -Example: ---- -SysTime st = DateTime(2025, 8, 19, 10, 38, 45); -UUID u = UUID(st); -SysTime o = u.v7Timestamp(); -assert(o == st); - -string s = u.toString(); -UUID u2 = UUID(s); -SysTime o2 = u2.v7Timestamp(); -assert(o2 == st); ---- diff --git a/changelog/write-text.dd b/changelog/write-text.dd deleted file mode 100644 index ba161c3a413..00000000000 --- a/changelog/write-text.dd +++ /dev/null @@ -1,20 +0,0 @@ -Add `writeText`, `writeWText`, and `writeDText` to `std.conv` - -These functions are variants of the existing `text`, `wtext`, and `dtext` -functions. Instead of returning a string, they write their output to an output -range. - -Like `text`, `writeText` can accept an -$(LINK2 $(ROOT_DIR)spec/istring.html, interpolated expression sequence) as an -argument. - -Example: - ---- -import std.conv : writeText; -import std.array : appender; - -auto output = appender!string(); -output.writeText(i"2 + 2 == $(2 + 2)"); -assert(output.data == "2 + 2 == 4"); ---- From f8391792cfcf1a1999da4fed6953f7320486d0d1 Mon Sep 17 00:00:00 2001 From: Rainer Schuetze Date: Tue, 13 Jan 2026 09:58:08 +0100 Subject: [PATCH 21/25] fix #22388 - error compiling mixin Signal!(string, int); (#10931) _d_toObject has been made public in object.d, so (incompatible) forward declaration no longer necessary --- std/signals.d | 5 ----- 1 file changed, 5 deletions(-) diff --git a/std/signals.d b/std/signals.d index 97004d52ddd..14b082fc20d 100644 --- a/std/signals.d +++ b/std/signals.d @@ -66,11 +66,6 @@ import core.exception : onOutOfMemoryError; import core.stdc.stdlib : calloc, realloc, free; import std.stdio; -// Special function for internal use only. -// Use of this is where the slot had better be a delegate -// to an object or an interface that is part of an object. -extern (C) Object _d_toObject(void* p); - // Used in place of Object.notifyRegister and Object.notifyUnRegister. alias DisposeEvt = void delegate(Object); extern (C) void rt_attachDisposeEvent( Object obj, DisposeEvt evt ); From 440db1ea5b164cd47c22a5a2a24c39296f8df304 Mon Sep 17 00:00:00 2001 From: "Richard (Rikki) Andrew Cattermole" Date: Sun, 25 Jan 2026 12:22:37 +1300 Subject: [PATCH 22/25] Fix potential null check error in Socket.select (#10939) --- std/socket.d | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/std/socket.d b/std/socket.d index 99ac97ac1e3..8e408ad862a 100644 --- a/std/socket.d +++ b/std/socket.d @@ -3510,7 +3510,7 @@ public: if (checkError) checkError.setMinCapacity(n); } - int result = .select(n, fr, fw, fe, &timeout.ctimeval); + int result = .select(n, fr, fw, fe, timeout !is null ? &timeout.ctimeval : null); version (Windows) { From c61f5abe81b534b4337a873e24b8a7d821e342d4 Mon Sep 17 00:00:00 2001 From: Tony Edgin Date: Sun, 25 Jan 2026 17:46:55 -0700 Subject: [PATCH 23/25] =?UTF-8?q?10920:=20Extend=20gammaIncomplete=20to=20?= =?UTF-8?q?cover=20large=20a=E2=89=88x=20(#10938)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This addresses phobos#10920. Adapted the existing function igammaTemmeLarge os that it supports gammaIncomplete as well as its complement gammaIncompleteCompl. As part of this adaptation, log(sigma+1) was replaced with log1p(sigma), since Temme's alorithm requires a to be close to x, which means that sigma is close to 0, making log(sigma+1) imprecise. gammaIncomplete was then modified to use igammaTemmeLarge when x is much larger than 1 and a is near x. --- std/internal/math/gammafunction.d | 64 ++++++++++++++++++++++++------- 1 file changed, 51 insertions(+), 13 deletions(-) diff --git a/std/internal/math/gammafunction.d b/std/internal/math/gammafunction.d index 8c04ce3ee00..be0b8f5e0bd 100644 --- a/std/internal/math/gammafunction.d +++ b/std/internal/math/gammafunction.d @@ -137,12 +137,19 @@ real gammaStirling(real x) } /* - * Helper function: Incomplete gamma function computed by Temme's expansion. + * Regularized incomplete gamma function computed by Temme's expansion. If + * `compl` is true, it computes the complement of the regularized incomplete + * gamma function instead. + * + * For a derivation of the algorithm, see A "Set of Algorithms for the + * Incomplete Gamma Functions", N. M. Temme, Probability in the Engineering and + * Informational Sciences, Volume 8, Issue 2, April 1994, pp. 291-307, DOI: + * https://doi.org/10.1017/S0269964800003417. * * This is a port of igamma_temme_large from Boost. * */ -real igammaTemmeLarge(real a, real x) +real igammaTemmeLarge(real a, real x, bool compl) { static immutable real[][13] coef = [ [ -0.333333333333333333333L, 0.0833333333333333333333L, @@ -219,13 +226,12 @@ real igammaTemmeLarge(real a, real x) // avoid nans when one of the arguments is inf: if (x == real.infinity && a != real.infinity) - return 0; - + return compl ? 0.0L : 1.0L; if (x != real.infinity && a == real.infinity) - return 1; + return compl ? 1.0L : 0.0L; real sigma = (x - a) / a; - real phi = sigma - log(sigma + 1); + real phi = sigma - log1p(sigma); real y = a * phi; real z = sqrt(2 * phi); @@ -236,14 +242,40 @@ real igammaTemmeLarge(real a, real x) foreach (i; 0 .. coef.length) workspace[i] = poly(z, coef[i]); - real result = poly(1 / a, workspace); - result *= exp(-y) / sqrt(2 * PI * a); - if (x < a) - result = -result; + // Rₐ(𝜂) = [exp(-a𝜂²/2)/√(2𝜋a)]Sₐ(𝜂) + const r = exp(-y)/sqrt(2.0L*PI*a) * poly(1.0L/a, workspace); - result += erfc(sqrt(y)) / 2; + if (compl) + { + // Q(a,x) = erfc(+𝜂√(a/2))/2 + Rₐ(𝜂) + return erfc(+sgn(sigma)*sqrt(y))/2.0L + r; + } + else + { + // P(a,x) = erfc(-𝜂√(a/2))/2 - Rₐ(𝜂) + return erfc(-sgn(sigma)*sqrt(y))/2.0L - r; + } +} - return result; +@safe unittest +{ + // Values were generated using scipy, which restricts values to double precision. + assert(feqrel(igammaTemmeLarge(25.0, 25.0, true), 0.47339_84685_56349_37L) >= double.mant_dig); + assert(feqrel(igammaTemmeLarge(25.0, 26.0, true), 0.39592_65699_99828_5L) >= double.mant_dig); + assert(feqrel(igammaTemmeLarge(26.0, 25.0, true), 0.55292_14200_24414_8L) >= double.mant_dig); + + assert(feqrel(igammaTemmeLarge(25.0, 25.0, false), 0.52660_15314_43650_6L) >= double.mant_dig); + assert(feqrel(igammaTemmeLarge(26.0, 25.0, false), 0.44707_85799_75585_2L) >= double.mant_dig); + + assert( + feqrel(igammaTemmeLarge(30.0L,31.0L,true), 1.0L-igammaTemmeLarge(30.0L,31.0L,false)) + >= real.mant_dig - 1); + + assert(feqrel(igammaTemmeLarge(1e9, 1.1e9, false), 1.0) >= double.mant_dig); + assert(feqrel(igammaTemmeLarge(1.1e9, 1e9, false), 0.0) >= double.mant_dig); + + assert( + feqrel(igammaTemmeLarge(1e6, 1e6 + 1.0, true), 0.49946_80772_57932_46L) >= double.mant_dig); } } // private @@ -1660,6 +1692,9 @@ do if ( (x > 1.0L) && (x > a ) ) return 1.0L - gammaIncompleteCompl(a,x); + if (a > 25.0L && abs(x-a) < 0.2L*a) + return igammaTemmeLarge(a, x, false); + real ax = a * log(x) - x - logGamma(a); /+ if ( ax < MINLOGL ) return 0; // underflow @@ -1701,7 +1736,7 @@ do // log(x)-x = NaN when x = real.infinity const real MAXLOGL = 1.1356523406294143949492E4L; if (x > MAXLOGL) - return igammaTemmeLarge(a, x); + return igammaTemmeLarge(a, x, true); real ax = a * log(x) - x - logGamma(a); //const real MINLOGL = -1.1355137111933024058873E4L; @@ -1910,6 +1945,9 @@ assert(gammaIncomplete(1, 0)==0); assert(gammaIncompleteCompl(1, 0)==1); assert(gammaIncomplete(4545, real.infinity)==1); +// Value was generated using scipy, which restricts values to double precision. +assert(feqrel(gammaIncomplete(1e20, 1e20), 0.50000_00000_13298) >= double.mant_dig); + // Values from Excel's (1-GammaDist(x, alpha, 1, TRUE)) assert(fabs(1.0L-gammaIncompleteCompl(0.5L, 2) - 0.954499729507309L) < 0.00000005L); From b9a61a84d19b3cf213ee6502e98ac642de014052 Mon Sep 17 00:00:00 2001 From: Martin Kinkelin Date: Wed, 11 Feb 2026 23:25:47 +0100 Subject: [PATCH 24/25] std.container.rbtree: Refactor template constraint wrt. `less` and use it for redBlackTree() functions too (#10951) PR #10792 changed the constraint to allow const-ref predicates too, using a lambda - which seems to have a very bad effect on attributes inference. Avoiding the lambda and using `std.traits.lvalueOf()` instead seems to work better. Also update the template constraints of the free-standing `redBlackTree()` functions, which were forgotten in #10792. --- std/container/rbtree.d | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/std/container/rbtree.d b/std/container/rbtree.d index 0aab7e1d5b3..5b3964f659f 100644 --- a/std/container/rbtree.d +++ b/std/container/rbtree.d @@ -755,7 +755,7 @@ private struct RBRange(N) * inserted after all existing duplicate elements. */ final class RedBlackTree(T, alias less = "a < b", bool allowDuplicates = false) -if (is(typeof((ref const T a) => binaryFun!less(a, a)))) +if (isSuitablePredicate!(less, T)) { import std.meta : allSatisfy; import std.range : Take; @@ -2003,7 +2003,9 @@ assert(equal(rbt[], [5])); } import std.range.primitives : isInputRange, ElementType; -import std.traits : isArray, isSomeString; +import std.traits : isArray, isSomeString, lvalueOf; + +private enum isSuitablePredicate(alias less, T) = is(typeof(binaryFun!less(lvalueOf!(const T), lvalueOf!(const T)))); /++ Convenience function for creating a `RedBlackTree!E` from a list of @@ -2028,14 +2030,14 @@ auto redBlackTree(bool allowDuplicates, E)(E[] elems...) /++ Ditto +/ auto redBlackTree(alias less, E)(E[] elems...) -if (is(typeof(binaryFun!less(E.init, E.init)))) +if (isSuitablePredicate!(less, E)) { return new RedBlackTree!(E, less)(elems); } /++ Ditto +/ auto redBlackTree(alias less, bool allowDuplicates, E)(E[] elems...) -if (is(typeof(binaryFun!less(E.init, E.init)))) +if (isSuitablePredicate!(less, E)) { //We shouldn't need to instantiate less here, but for some reason, //dmd can't handle it if we don't (even though the template which @@ -2059,7 +2061,7 @@ if (isInputRange!Stuff && !isArray!(Stuff)) /++ Ditto +/ auto redBlackTree(alias less, Stuff)(Stuff range) -if ( is(typeof(binaryFun!less((ElementType!Stuff).init, (ElementType!Stuff).init))) +if (isSuitablePredicate!(less, ElementType!Stuff) && isInputRange!Stuff && !isArray!(Stuff)) { return new RedBlackTree!(ElementType!Stuff, less)(range); @@ -2067,7 +2069,7 @@ if ( is(typeof(binaryFun!less((ElementType!Stuff).init, (ElementType!Stuff).init /++ Ditto +/ auto redBlackTree(alias less, bool allowDuplicates, Stuff)(Stuff range) -if ( is(typeof(binaryFun!less((ElementType!Stuff).init, (ElementType!Stuff).init))) +if (isSuitablePredicate!(less, ElementType!Stuff) && isInputRange!Stuff && !isArray!(Stuff)) { //We shouldn't need to instantiate less here, but for some reason, From 12eb40b29556e210bf88c2bf08152ab0f27687ba Mon Sep 17 00:00:00 2001 From: Tony Edgin Date: Sat, 28 Feb 2026 15:32:55 -0700 Subject: [PATCH 25/25] 10965: extended normalDistribution domain support to +/-inf (#10966) --- std/internal/math/errorfunction.d | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/std/internal/math/errorfunction.d b/std/internal/math/errorfunction.d index 8894ffb92ea..bb132ce8dea 100644 --- a/std/internal/math/errorfunction.d +++ b/std/internal/math/errorfunction.d @@ -984,6 +984,9 @@ Journal of Statistical Software 11, (July 2004). */ real normalDistributionImpl(real a) { + if (a is -real.infinity) return 0.0L; + if (a is real.infinity) return 1.0L; + real x = a * SQRT1_2; real z = fabs(x); @@ -1005,6 +1008,8 @@ real normalDistributionImpl(real a) { assert(fabs(normalDistributionImpl(1L) - (0.841344746068543L)) < 0.0000000000000005L); assert(isIdentical(normalDistributionImpl(NaN(0x325)), NaN(0x325))); +assert(normalDistributionImpl(-real.infinity) == 0.0L); +assert(normalDistributionImpl(real.infinity) == 1.0L); } /*