Skip to content

Release v0.9.11

Choose a tag to compare

@dhadner dhadner released this 21 Apr 23:36
· 15 commits to main since this release

[0.9.11] - 2026-04-21

🔵 Fixed

  • README links to sibling .md files now resolve on nuget.org -- v0.9.9 and v0.9.10 shipped a README with relative links like [LARGE_INTEGERS.md](LARGE_INTEGERS.md) to the per-feature docs embedded in the package, so each installed version would serve version-specific documentation. The plan worked on GitHub but not on nuget.org, whose README renderer does not follow relative paths into the packed .nupkg -- clicks just bounced back to the README itself. v0.9.11 keeps the source README relative (GitHub-friendly) and rewrites those links at pack time to absolute, tag-pinned URLs of the form https://github.com/dhadner/Stardust.Utilities/blob/v0.9.11/LARGE_INTEGERS.md. Each installed package still points at its own version's docs, and the links now work on both sites.

🟤 Backwards Compatibility

  • v0.9.11 is a README-only re-release (same pattern as v0.9.10). Library binaries, source, and public API are identical to v0.9.10 and v0.9.9.

[0.9.10] - 2026-04-21

🔵 Fixed

  • README screenshots now render on nuget.org -- the v0.9.9 README rewrite switched image references from absolute raw.githubusercontent.com URLs to relative paths (icon.png, Graphics/*.png), assuming nuget.org's README renderer would resolve them against the packed .nupkg contents. It does not: nuget.org only loads images from an allow-listed set of domains. v0.9.10 restores absolute raw.githubusercontent.com URLs for all four images (the icon, and the RFC/PE/FP screenshots). v0.9.9 has been unlisted and deprecated on nuget.org; install v0.9.10 instead. No code or API changes.

🟣 Changed

  • Build-Combined-NuGetPackages.ps1 lints README image URLs -- the build script now scans README.md for any Markdown image reference whose URL is not an absolute http(s):// URL on nuget.org's trusted domain list and fails the build if any are found. This catches the class of regression that produced v0.9.9 before a package is ever uploaded.

🟤 Backwards Compatibility

  • v0.9.10 is a README-only re-release of v0.9.9. Library binaries, source, and public API are identical.

[0.9.9] - 2026-04-20

🟢 Added

  • UInt256 and Int256 value types -- high-performance fixed-width 256-bit unsigned and signed integer types backed by four ulong values. Zero heap allocations on the hot path, routed through BMI2 MULX (Bmi2.X64.MultiplyNoFlags) for multiply and the X86Base.X64.DivRem hardware intrinsic for divide / modulo / decimal chunk extraction on x64 .NET 8+. Full operator set (+ - * / % & | ^ ~ << >> >>> == != < > <= >= ++ --), parsing (decimal and hex), formatting (D, G, R, X, x, with precision specifiers), BigInteger interop, and implicit / explicit conversions to the full width spectrum (byte through UInt128 / Int128). Implements IComparable, IComparable<T>, IEquatable<T>, IFormattable, ISpanFormattable, IParsable<T>, ISpanParsable<T>. Benchmarked alongside Nethermind.Numerics.Int256 1.5.0 and MissingValues 2.2.1 with BenchmarkSuite1/Int256LibraryComparisonBenchmarks.cs -- competitive or better performance with both libraries across Add, Sub, Mul, Div, Mod, ToString, and Parse on our test hardware, and all three libraries have much better performance than BigInteger since no heap allocations are made; see LARGE_INTEGERS.md for the full table, methodology, and instructions for reproducing on your own hardware. See also ENDIAN.md for the endian-aware wrappers.
  • UInt256Be / UInt256Le endian-aware 256-bit types -- 32-byte explicit-layout structs for wire-format interop. Implicit conversion to/from the host-native UInt256. Zero-allocation WriteTo(Span<byte>) / ReadFrom(ReadOnlySpan<byte>) / TryWriteTo / ToByteArray API identical to the 16/32/64/128-bit endian types.
  • UInt256 and Int256 as [BitFields] storage types -- both [BitFields(typeof(UInt256))] / [BitFields(typeof(Int256))] (and the corresponding StorageType.UInt256 / StorageType.Int256 enum values) are now first-class. The generator emits a 4-ulong backing store (__w0..__w3), implicit conversions between the generated struct and UInt256 / Int256, full arithmetic and bitwise operators via the existing multi-word code path, JSON serialization, span I/O, With... fluent methods, and field metadata. Also wired into RecordStructViewGenerator for use as an embedded property type in zero-copy views. 13 new tests added to BitFieldSpecializationTests covering round-trip construction (U256Register_FromUInt256_RoundTrips, _Zero, _MaxValue, signed positive / negative / min / max), field-level read/write (U256Register_FieldAccess_ViaConversion, U256Register_SetFields_ConvertBack), struct size (_SizeIs32Bytes), arithmetic (U256Register_Addition, U256Register_Addition_CarriesAcrossAllWordBoundaries -- verifies the full 256-bit carry chain), and bitwise operations.
  • Auto-sized [BitFields] on value-type structs
  • Cross-type widening/narrowing conversions for all Little-Endian types -- Le types now have the same cross-type endian-to-endian implicit widening and explicit narrowing conversions that Be types have. Widening (implicit): UInt16Le to UInt32Le/UInt64Le/UInt128Le, UInt32Le to UInt64Le/UInt128Le, UInt64Le to UInt128Le, and equivalent signed conversions (Int16Le to Int32Le/Int64Le/Int128Le, etc.). Narrowing (explicit): the reverse direction. Also includes Int16Le to UInt32Le (matching Int16Be to UInt32Be) and Int32Le(Int16Le) explicit sign-extending conversion (matching Int32Be(Int16Be)).
  • SaturatingAdd / SaturatingSub extension methods for all native integer types -- complete coverage from 8-bit through 128-bit: byte/sbyte, short/ushort, int/uint, long/ulong, Int128/UInt128. Previously only int/uint/long/ulong were supported. Unsigned types clamp to 0/MaxValue; signed types clamp to MinValue/MaxValue.
  • SaturatingAdd / SaturatingSub extension methods for all big-endian and little-endian types -- every endian type from UInt16Be/Int16Be through UInt128Be/Int128Be, and UInt16Le/Int16Le through UInt128Le/Int128Le, now supports SaturatingAdd and SaturatingSub. Each delegates to the corresponding native-type saturating method for zero-overhead correctness. Combined with the native-type methods, this completes saturating arithmetic support across every integer type in the library.
  • Hi/Lo/SetHi/SetLo extension methods now cover all sizes -- native types (ushort/short through UInt128/Int128), big-endian types (UInt16Be/Int16Be through UInt128Be/Int128Be), and little-endian types (UInt16Le/Int16Le through UInt128Le/Int128Le) all have Hi/Lo/SetHi/SetLo. 8-bit types are excluded (they are the atomic unit with no halves to split).
  • Performance benchmarks for saturating arithmetic and endian types -- BenchmarkDotNet suites document the cost of SaturatingAdd/SaturatingSub vs. normal arithmetic (see EXTENSIONS.md) and the cost of Be/Le types vs. native primitives (see ENDIAN.md). Results tables with key takeaways are included in the documentation to help users make informed choices.

🟣 Changed

  • Endian type conversions optimized to native speed -- All Le types (16/32/64/128-bit) now operate at native CPU speed on little-endian hardware (x86/x64/ARM64), compiling to the same machine code as bare primitive operations. All Be types now compile to a single BSWAP instruction per conversion -- the same code a developer would hand-write, with no additional overhead from the type abstraction. Previously, conversions went through multi-level field decomposition costing 4--21x; now both Le and Be types run at the speed of optimal hand-coded byte manipulation. 16-bit types use an overlapping [FieldOffset(0)] _value field aliased with the byte fields, giving the JIT a single primitive to keep in CPU registers while preserving direct byte access. 32-bit and wider types use Unsafe.As for zero-cost memory reinterpretation. Hi/Lo extensions for 16-bit Be types refactored to use the fast native conversion path, avoiding load-after-store stalls from the field aliasing. All 549 endian and extension tests pass.
  • Missing operators added to signed Le types (Int16Le, Int32Le, Int64Le) -- *, /, %, &, |, ^, ~, >>, << were present on all unsigned Le types and all signed Be types but missing from the three signed Le types. All signed Le types now have full operator parity with their Be counterparts. Includes sign-extending right shift (>>) with test coverage. 33 new tests added.
  • Missing operators added to UInt32Le, UInt32Be, UInt64Le, Int32Be -- %, ^ added to UInt32Le and UInt64Le; ++, --, %, ^, ~ added to UInt32Be and Int32Be. All 16 endian types now have the complete operator set.
  • Unsigned right shift (>>>) added to all 16 endian types -- All signed types (Int16Le/Be through Int128Le/Be) implement >>> as a logical right shift that zero-fills from the left, matching the C# 11 semantics. All unsigned types (UInt16Le/Be through UInt128Le/Be) also define >>> for API parity with native uint/ulong/ushort/UInt128, where it is identical to >>. The >>>= compound assignment is automatically synthesized by the compiler. 11 new tests added.
  • [MethodImpl(MethodImplOptions.AggressiveInlining)] added to all operators across all 16 endian types -- every operator and conversion operator on every Be and Le type is now explicitly marked for aggressive inlining. While the JIT already inlines these one-liner methods, the attribute makes the performance contract visible and ensures inlining even under tight inlining budgets in deeply nested call chains.
  • BIT_WIDTH constant added to all generated types
  • StructDescription and StructDescriptionResourceType added to record struct views -- these static properties were previously only generated on value-type structs. They are now emitted on record struct views as well, providing programmatic access to the [BitFields(Description = ...)] value for all struct kinds. BitFieldDiagram already read this data from BitFieldInfo metadata so diagram rendering was not affected; the new properties enable direct access without going through Fields.
  • All generated private members now use __ prefix -- every private field and constant emitted by the source generator has been prefixed with double underscores to eliminate naming collisions with user-declared members. Single-word value-type structs: Value renamed to __value. Multi-word structs: _w0/_w1/etc. renamed to __w0/__w1/etc., WORD_COUNT/TOTAL_BITS/LAST_WORD_MASK renamed to __WORD_COUNT/__TOTAL_BITS/__LAST_WORD_MASK. Record struct views: _data/_bitOffset renamed to __data/__bitOffset. Per-field mask/shift/bit constants (e.g., MODE_MASK, MODE_START_BIT, MODE_SHIFTED_MASK, MODE_INVERTED_MASK, MODE_SAT_MIN, MODE_SAT_MAX) are now prefixed as __MODE_MASK, __MODE_START_BIT, etc. Fixed-name constants NORMALIZATION_AND_MASK/NORMALIZATION_OR_MASK are now __NORMALIZATION_AND_MASK/__NORMALIZATION_OR_MASK. The double-underscore prefix follows the C# convention for compiler/generator-reserved identifiers and frees users to name their properties anything they want without collision.
  • UndefinedBits property on [BitFields] is now settable -- the UndefinedBits property can now be set as a named argument (e.g., [BitFields(UndefinedBits = UndefinedBitsMustBe.Zeroes)]) in addition to the existing constructor parameter. This is especially useful with auto-sized structs where there is no constructor parameter for UndefinedBitsMustBe.
  • README.md restructured for pre-release -- the package README has been condensed from ~1660 to ~480 lines and reorganized as a pitch-first document: a tagline, a "Why Stardust.Utilities?" benefits section, a single "See It In One Snippet" IPv4-parsing example, a "What's In The Box" feature matrix, and tight per-feature summaries that link out to the existing deep-dive docs (BITFIELDS.md, RESULT.md, OPTION.md, ENDIAN.md, LARGE_INTEGERS.md, EXTENSIONS.md). No content was lost; the deep-dive docs already covered every subsection that was trimmed.
  • TROUBLESHOOTING.md added -- the former 220-line troubleshooting appendix in README.md has been lifted into a new top-level TROUBLESHOOTING.md covering every SD00xx diagnostic, installation gotchas, the v0.9.9 __ prefix rename, the Edge WASM welcome page, and instructions for viewing and persisting generated code. The README keeps a short "Common Gotchas" section with a link to the full guide.

🟤 Backwards Compatibility

  • UInt256 / Int256 and StorageType.UInt256 / .Int256 are entirely additive. New enum values (Int256 = 16, UInt256 = 17) extend StorageType without reordering or renumbering the existing members (Int128 = 14, UInt128 = 15 are unchanged). Existing [BitFields] consumers are unaffected. Code that reflects over StorageType values and switches exhaustively on them will want to add arms for the two new members.
  • BIT_WIDTH is additive
  • StructDescription / StructDescriptionResourceType on views is additive -- these were already generated on value types. Adding them to views is a new public member; code that already declares these names on a partial record struct will get a compile error.
  • All generated private members renamed with __ prefix -- code that accessed generated private fields or constants via reflection, Unsafe operations, or other non-public access patterns will break. These members were always private and not part of the public API, so normal usage through generated properties, operators, and conversions is unaffected. If you have code that references generated members directly (e.g., via typeof(MyStruct).GetField("Value", BindingFlags.NonPublic | BindingFlags.Instance)), update the name to use the __ prefix (e.g., "__value"). The full rename mapping: Value to __value, _w0/_w1 to __w0/__w1, _data to __data, _bitOffset to __bitOffset, {FIELD}_MASK to __{FIELD}_MASK (and similarly for all other per-field constants), NORMALIZATION_AND_MASK to __NORMALIZATION_AND_MASK, NORMALIZATION_OR_MASK to __NORMALIZATION_OR_MASK, WORD_COUNT to __WORD_COUNT, TOTAL_BITS to __TOTAL_BITS, LAST_WORD_MASK to __LAST_WORD_MASK.
  • Auto-sized [BitFields] is entirely additive. Existing code that specifies a storage type or bit count is unaffected.
  • Making UndefinedBits settable is a source-compatible change. Existing code that passes UndefinedBitsMustBe via constructor parameters continues to work identically.