From 6c19fcc7f4a7daa4a2fea1c8980cace483d75283 Mon Sep 17 00:00:00 2001 From: Airat Abdrakov <52558686+CrackAndDie@users.noreply.github.com> Date: Wed, 15 Apr 2026 08:29:02 +0300 Subject: [PATCH 01/26] Thread class added --- std/System/Threading/Thread.hpt | 80 ++++++++++++++++++++++++++++ std/System/Threading/ThreadStart.hpt | 8 +++ 2 files changed, 88 insertions(+) create mode 100644 std/System/Threading/Thread.hpt create mode 100644 std/System/Threading/ThreadStart.hpt diff --git a/std/System/Threading/Thread.hpt b/std/System/Threading/Thread.hpt new file mode 100644 index 00000000..c9b444ca --- /dev/null +++ b/std/System/Threading/Thread.hpt @@ -0,0 +1,80 @@ +using System; + +public partial class Thread +{ + private StartHelper _startHelper; + + // State associated with starting new thread + private sealed class StartHelper + { + internal int _maxStackSize; + internal object _start; + internal object _startArg; + + internal StartHelper(object start) + { + _start = start; + } + + // avoid long-lived stack frame in many threads + internal inline void Run() + { + RunWorker(); + } + + // avoid long-lived stack frame in many threads + private inline void RunWorker() + { + object start = _start; + _start = default; // anime? + + try + { + if (start is ThreadStart threadStart) + { + threadStart(); + } + else + { + // TODO: parametrized shite + } + } + catch (Exception ex) + { + // the handler returned "true" means the exception is now "handled" and we should gracefully exit. + } + } + } + + public Thread(ThreadStart start) + { + ArgumentNullException.ThrowIfNull(start); + + _startHelper = new StartHelper(start); + } + + private void Start() + { + // RuntimeFeature.ThrowIfMultithreadingIsNotSupported(); + StartHelper startHelper = _startHelper; + + // In the case of a null startHelper (second call to start on same thread) + // StartCore method will take care of the error reporting. + if (startHelper != null) + { + startHelper._startArg = null; + } + + StartCore(); + } + + public bool Join(int millisecondsTimeout) + { + ArgumentOutOfRangeException.ThrowIfLessThan(millisecondsTimeout, Timeout.Infinite); + if ((ThreadState & ThreadState.Unstarted) != 0) + { + throw new ThreadStateException(SR.ThreadState_NotStarted); + } + return JoinInternal(millisecondsTimeout); + } +} \ No newline at end of file diff --git a/std/System/Threading/ThreadStart.hpt b/std/System/Threading/ThreadStart.hpt new file mode 100644 index 00000000..5895d5bc --- /dev/null +++ b/std/System/Threading/ThreadStart.hpt @@ -0,0 +1,8 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// Some modifications were made for hapet-lang compatibility. + +/// +/// Represents the method that executes on a . +/// +public delegate void ThreadStart(); \ No newline at end of file From 2cbb16910f745a80e2f51e430cf4477e9595c36f Mon Sep 17 00:00:00 2001 From: Airat Abdrakov <52558686+CrackAndDie@users.noreply.github.com> Date: Wed, 15 Apr 2026 09:17:26 +0300 Subject: [PATCH 02/26] Timeout and TimeSpan added --- std/System/Threading/Thread.hpt | 4 + std/System/Threading/Timeout.hpt | 14 + std/System/TimeSpan.hpt | 953 +++++++++++++++++++++++++++++++ 3 files changed, 971 insertions(+) create mode 100644 std/System/Threading/Timeout.hpt create mode 100644 std/System/TimeSpan.hpt diff --git a/std/System/Threading/Thread.hpt b/std/System/Threading/Thread.hpt index c9b444ca..70f66192 100644 --- a/std/System/Threading/Thread.hpt +++ b/std/System/Threading/Thread.hpt @@ -1,3 +1,7 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// Some modifications were made for hapet-lang compatibility. + using System; public partial class Thread diff --git a/std/System/Threading/Timeout.hpt b/std/System/Threading/Timeout.hpt new file mode 100644 index 00000000..efa40374 --- /dev/null +++ b/std/System/Threading/Timeout.hpt @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// Some modifications were made for hapet-lang compatibility. + +// A constant used by methods that take a timeout (Object.Wait, Thread.Sleep +// etc) to indicate that no timeout should occur. +// +public static class Timeout +{ + public static readonly TimeSpan InfiniteTimeSpan = new TimeSpan(0, 0, 0, 0, Infinite); + + public const int Infinite = -1; + internal const uint UnsignedInfinite = unchecked((uint)-1); +} \ No newline at end of file diff --git a/std/System/TimeSpan.hpt b/std/System/TimeSpan.hpt new file mode 100644 index 00000000..78064ee2 --- /dev/null +++ b/std/System/TimeSpan.hpt @@ -0,0 +1,953 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// Some modifications were made for hapet-lang compatibility. + +// TimeSpan represents a duration of time. A TimeSpan can be negative +// or positive. +// +// TimeSpan is internally represented as a number of ticks. A tick is equal +// to 100 nanoseconds. While this maps well into units of time such as hours +// and days, any periods longer than that aren't representable in a nice fashion. +// For instance, a month can be between 28 and 31 days, while a year +// can contain 365 or 366 days. A decade can have between 1 and 3 leap years, +// depending on when you map the TimeSpan into the calendar. This is why +// we do not provide Years() or Months(). +// +public readonly struct TimeSpan + : IComparable, + IComparable, + IEquatable, + ISpanFormattable, + ISpanParsable, + IUtf8SpanFormattable +{ + /// + /// Represents the number of nanoseconds per tick. This field is constant. + /// + /// + /// The value of this constant is 100. + /// + public const long NanosecondsPerTick = 100; // 100 + + /// + /// Represents the number of ticks in 1 microsecond. This field is constant. + /// + /// + /// The value of this constant is 10. + /// + public const long TicksPerMicrosecond = 10; // 10 + + /// + /// Represents the number of ticks in 1 millisecond. This field is constant. + /// + /// + /// The value of this constant is 10 thousand; that is, 10,000. + /// + public const long TicksPerMillisecond = TicksPerMicrosecond * 1000; // 10,000 + + /// + /// Represents the number of ticks in 1 second. This field is constant. + /// + /// + /// The value of this constant is 10 million; that is, 10,000,000. + /// + public const long TicksPerSecond = TicksPerMillisecond * 1000; // 10,000,000 + + /// + /// Represents the number of ticks in 1 minute. This field is constant. + /// + /// + /// The value of this constant is 600 million; that is, 600,000,000. + /// + public const long TicksPerMinute = TicksPerSecond * 60; // 600,000,000 + + /// + /// Represents the number of ticks in 1 hour. This field is constant. + /// + /// + /// The value of this constant is 36 billion; that is, 36,000,000,000. + /// + public const long TicksPerHour = TicksPerMinute * 60; // 36,000,000,000 + + /// + /// Represents the number of ticks in 1 day. This field is constant. + /// + /// + /// The value of this constant is 864 billion; that is, 864,000,000,000. + /// + public const long TicksPerDay = TicksPerHour * 24; // 864,000,000,000 + + /// + /// Represents the number of microseconds in 1 millisecond. This field is constant. + /// + /// + /// The value of this constant is 1 thousand; that is, 1,000. + /// + public const long MicrosecondsPerMillisecond = TicksPerMillisecond / TicksPerMicrosecond; // 1,000 + + /// + /// Represents the number of microseconds in 1 second. This field is constant. + /// + /// + /// The value of this constant is 1 million; that is, 1,000,000. + /// + public const long MicrosecondsPerSecond = TicksPerSecond / TicksPerMicrosecond; // 1,000,000 + + /// + /// Represents the number of microseconds in 1 minute. This field is constant. + /// + /// + /// The value of this constant is 60 million; that is, 60,000,000. + /// + public const long MicrosecondsPerMinute = TicksPerMinute / TicksPerMicrosecond; // 60,000,000 + + /// + /// Represents the number of microseconds in 1 hour. This field is constant. + /// + /// + /// The value of this constant is 3.6 billion; that is, 3,600,000,000. + /// + public const long MicrosecondsPerHour = TicksPerHour / TicksPerMicrosecond; // 3,600,000,000 + + /// + /// Represents the number of microseconds in 1 day. This field is constant. + /// + /// + /// The value of this constant is 86.4 billion; that is, 86,400,000,000. + /// + public const long MicrosecondsPerDay = TicksPerDay / TicksPerMicrosecond; // 86,400,000,000 + + /// + /// Represents the number of milliseconds in 1 second. This field is constant. + /// + /// + /// The value of this constant is 1 thousand; that is, 1,000. + /// + public const long MillisecondsPerSecond = TicksPerSecond / TicksPerMillisecond; // 1,000 + + /// + /// Represents the number of milliseconds in 1 minute. This field is constant. + /// + /// + /// The value of this constant is 60 thousand; that is, 60,000. + /// + public const long MillisecondsPerMinute = TicksPerMinute / TicksPerMillisecond; // 60,000 + + /// + /// Represents the number of milliseconds in 1 hour. This field is constant. + /// + /// + /// The value of this constant is 3.6 million; that is, 3,600,000. + /// + public const long MillisecondsPerHour = TicksPerHour / TicksPerMillisecond; // 3,600,000 + + /// + /// Represents the number of milliseconds in 1 day. This field is constant. + /// + /// + /// The value of this constant is 86.4 million; that is, 86,400,000. + /// + public const long MillisecondsPerDay = TicksPerDay / TicksPerMillisecond; // 86,400,000 + + /// + /// Represents the number of seconds in 1 minute. This field is constant. + /// + /// + /// The value of this constant is 60. + /// + public const long SecondsPerMinute = TicksPerMinute / TicksPerSecond; // 60 + + /// + /// Represents the number of seconds in 1 hour. This field is constant. + /// + /// + /// The value of this constant is 3.6 thousand; that is, 3,600. + /// + public const long SecondsPerHour = TicksPerHour / TicksPerSecond; // 3,600 + + /// + /// Represents the number of seconds in 1 day. This field is constant. + /// + /// + /// The value of this constant is 86.4 thousand; that is, 86,400. + /// + public const long SecondsPerDay = TicksPerDay / TicksPerSecond; // 86,400 + + /// + /// Represents the number of minutes in 1 hour. This field is constant. + /// + /// + /// The value of this constant is 60. + /// + public const long MinutesPerHour = TicksPerHour / TicksPerMinute; // 60 + + /// + /// Represents the number of minutes in 1 day. This field is constant. + /// + /// + /// The value of this constant is 1440. + /// + public const long MinutesPerDay = TicksPerDay / TicksPerMinute; // 1,440 + + /// + /// Represents the number of hours in 1 day. This field is constant. + /// + /// + /// The value of this constant is 24. + /// + public const int HoursPerDay = (int)(TicksPerDay / TicksPerHour); // 24 + + internal const long MinTicks = long.MinValue; // -9,223,372,036,854,775,808 + internal const long MaxTicks = long.MaxValue; // +9,223,372,036,854,775,807 + + internal const long MinMicroseconds = MinTicks / TicksPerMicrosecond; // - 922,337,203,685,477,580 + internal const long MaxMicroseconds = MaxTicks / TicksPerMicrosecond; // + 922,337,203,685,477,580 + + internal const long MinMilliseconds = MinTicks / TicksPerMillisecond; // - 922,337,203,685,477 + internal const long MaxMilliseconds = MaxTicks / TicksPerMillisecond; // + 922,337,203,685,477 + + internal const long MinSeconds = MinTicks / TicksPerSecond; // - 922,337,203,685 + internal const long MaxSeconds = MaxTicks / TicksPerSecond; // + 922,337,203,685 + + internal const long MinMinutes = MinTicks / TicksPerMinute; // - 15,372,286,728 + internal const long MaxMinutes = MaxTicks / TicksPerMinute; // + 15,372,286,728 + + internal const long MinHours = MinTicks / TicksPerHour; // - 256,204,778 + internal const long MaxHours = MaxTicks / TicksPerHour; // + 256,204,778 + + internal const long MinDays = MinTicks / TicksPerDay; // - 10,675,199 + internal const long MaxDays = MaxTicks / TicksPerDay; // + 10,675,199 + + internal const long TicksPerTenthSecond = TicksPerMillisecond * 100; + + public static readonly TimeSpan Zero = new TimeSpan(0); + + public static readonly TimeSpan MaxValue = new TimeSpan(MaxTicks); + public static readonly TimeSpan MinValue = new TimeSpan(MinTicks); + + // internal so that DateTime doesn't have to call an extra get + // method for some arithmetic operations. + internal readonly long _ticks; // Do not rename (binary serialization) + + public TimeSpan(long ticks) + { + _ticks = ticks; + } + + public TimeSpan(int hours, int minutes, int seconds) + { + _ticks = TimeToTicks(hours, minutes, seconds); + } + + public TimeSpan(int days, int hours, int minutes, int seconds) + : this(days, hours, minutes, seconds, 0) + { + } + + /// + /// Initializes a new instance of the structure to a specified number of + /// days, hours, minutes, seconds, and milliseconds. + /// + /// Number of days. + /// Number of hours. + /// Number of minutes. + /// Number of seconds. + /// Number of milliseconds. + /// + /// The specified , , , + /// and are converted to ticks, and that value initializes this instance. + /// + /// + /// The parameters specify a value less than or greater than + /// + public TimeSpan(int days, int hours, int minutes, int seconds, int milliseconds) : + this(days, hours, minutes, seconds, milliseconds, 0) + { + } + + /// + /// Initializes a new instance of the structure to a specified number of + /// days, hours, minutes, seconds, and milliseconds. + /// + /// Number of days. + /// Number of hours. + /// Number of minutes. + /// Number of seconds. + /// Number of milliseconds. + /// Number of microseconds. + /// + /// The specified , , , + /// and are converted to ticks, and that value initializes this instance. + /// + /// + /// The parameters specify a value less than or greater than + /// + public TimeSpan(int days, int hours, int minutes, int seconds, int milliseconds, int microseconds) + { + long totalMicroseconds = (days * MicrosecondsPerDay) + + (hours * MicrosecondsPerHour) + + (minutes * MicrosecondsPerMinute) + + (seconds * MicrosecondsPerSecond) + + (milliseconds * MicrosecondsPerMillisecond) + + microseconds; + + if ((totalMicroseconds > MaxMicroseconds) || (totalMicroseconds < MinMicroseconds)) + { + ThrowHelper.ThrowArgumentOutOfRange_TimeSpanTooLong(); + } + _ticks = totalMicroseconds * TicksPerMicrosecond; + } + + public long Ticks => _ticks; + + public int Days => (int)(_ticks / TicksPerDay); + + public int Hours => (int)(_ticks / TicksPerHour % HoursPerDay); + + public int Milliseconds => (int)(_ticks / TicksPerMillisecond % MillisecondsPerSecond); + + /// + /// Gets the microseconds component of the time interval represented by the current structure. + /// + /// + /// The property represents whole microseconds, whereas the + /// property represents whole and fractional microseconds. + /// + public int Microseconds => (int)(_ticks / TicksPerMicrosecond % MicrosecondsPerMillisecond); + + /// + /// Gets the nanoseconds component of the time interval represented by the current structure. + /// + /// + /// The property represents whole nanoseconds, whereas the + /// property represents whole and fractional nanoseconds. + /// + public int Nanoseconds => (int)(_ticks % TicksPerMicrosecond * NanosecondsPerTick); + + public int Minutes => (int)(_ticks / TicksPerMinute % MinutesPerHour); + + public int Seconds => (int)(_ticks / TicksPerSecond % SecondsPerMinute); + + public double TotalDays => (double)_ticks / TicksPerDay; + + public double TotalHours => (double)_ticks / TicksPerHour; + + public double TotalMilliseconds + { + get + { + double temp = (double)_ticks / TicksPerMillisecond; + + if (temp > MaxMilliseconds) + { + return MaxMilliseconds; + } + + if (temp < MinMilliseconds) + { + return MinMilliseconds; + } + return temp; + } + } + + /// + /// Gets the value of the current structure expressed in whole and fractional microseconds. + /// + /// + /// This property converts the value of this instance from ticks to microseconds. + /// This number might include whole and fractional microseconds. + /// + /// The property represents whole and fractional microseconds, + /// whereas the property represents whole microseconds. + /// + public double TotalMicroseconds => (double)_ticks / TicksPerMicrosecond; + + /// + /// Gets the value of the current structure expressed in whole and fractional nanoseconds. + /// + /// + /// This property converts the value of this instance from ticks to nanoseconds. + /// This number might include whole and fractional nanoseconds. + /// + /// The property represents whole and fractional nanoseconds, + /// whereas the property represents whole nanoseconds. + /// + public double TotalNanoseconds => (double)_ticks * NanosecondsPerTick; + + public double TotalMinutes => (double)_ticks / TicksPerMinute; + + public double TotalSeconds => (double)_ticks / TicksPerSecond; + + public TimeSpan Add(TimeSpan ts) => this + ts; + + // Compares two TimeSpan values, returning an integer that indicates their + // relationship. + // + public static int Compare(TimeSpan t1, TimeSpan t2) => t1._ticks.CompareTo(t2._ticks); + + // Returns a value less than zero if this object + public int CompareTo(object? value) + { + if (value is null) + { + return 1; + } + + if (value is TimeSpan other) + { + return CompareTo(other); + } + + throw new ArgumentException(SR.Arg_MustBeTimeSpan); + } + + public int CompareTo(TimeSpan value) => Compare(this, value); + + public static TimeSpan FromDays(double value) => Interval(value, TicksPerDay); + + public TimeSpan Duration() + { + if (_ticks == MinTicks) + { + ThrowHelper.ThrowOverflowException_TimeSpanDuration(); + } + return new TimeSpan(_ticks >= 0 ? _ticks : -_ticks); + } + + public override bool Equals([NotNullWhen(true)] object? value) => (value is TimeSpan other) && Equals(other); + + public bool Equals(TimeSpan obj) => Equals(this, obj); + + public static bool Equals(TimeSpan t1, TimeSpan t2) => t1 == t2; + + public override int GetHashCode() => _ticks.GetHashCode(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static TimeSpan FromUnits(long units, long ticksPerUnit, long minUnits, long maxUnits) + { + System.Diagnostics.Debug.Assert(minUnits < 0); + System.Diagnostics.Debug.Assert(maxUnits > 0); + + if (units > maxUnits || units < minUnits) + { + ThrowHelper.ThrowArgumentOutOfRange_TimeSpanTooLong(); + } + return TimeSpan.FromTicks(units * ticksPerUnit); + } + + /// + /// Initializes a new instance of the structure to a specified number of + /// days. + /// + /// Number of days. + /// Returns a that represents a specified number of days. + /// + /// The parameters specify a value less than or greater than + /// + public static TimeSpan FromDays(int days) => FromUnits(days, TicksPerDay, MinDays, MaxDays); + + /// + /// Initializes a new instance of the structure to a specified number of + /// days, hours, minutes, seconds, milliseconds, and microseconds. + /// + /// Number of days. + /// Number of hours. + /// Number of minutes. + /// Number of seconds. + /// Number of milliseconds. + /// Number of microseconds. + /// Returns a that represents a specified number of days, hours, minutes, seconds, milliseconds, and microseconds. + /// + /// The parameters specify a value less than or greater than + /// + public static TimeSpan FromDays(int days, int hours = 0, long minutes = 0, long seconds = 0, long milliseconds = 0, long microseconds = 0) + { + Int128 totalMicroseconds = Math.BigMul(days, MicrosecondsPerDay) + + Math.BigMul(hours, MicrosecondsPerHour) + + Math.BigMul(minutes, MicrosecondsPerMinute) + + Math.BigMul(seconds, MicrosecondsPerSecond) + + Math.BigMul(milliseconds, MicrosecondsPerMillisecond) + + microseconds; + + return FromMicroseconds(totalMicroseconds); + } + + /// + /// Initializes a new instance of the structure to a specified number of + /// hours. + /// + /// Number of hours. + /// Returns a that represents a specified number of hours. + /// + /// The parameters specify a value less than or greater than + /// + public static TimeSpan FromHours(int hours) => FromUnits(hours, TicksPerHour, MinHours, MaxHours); + + /// + /// Initializes a new instance of the structure to a specified number of + /// hours, minutes, seconds, milliseconds, and microseconds. + /// + /// Number of hours. + /// Number of minutes. + /// Number of seconds. + /// Number of milliseconds. + /// Number of microseconds. + /// Returns a that represents a specified number of hours, minutes, seconds, milliseconds, and microseconds. + /// + /// The parameters specify a value less than or greater than + /// + public static TimeSpan FromHours(int hours, long minutes = 0, long seconds = 0, long milliseconds = 0, long microseconds = 0) + { + Int128 totalMicroseconds = Math.BigMul(hours, MicrosecondsPerHour) + + Math.BigMul(minutes, MicrosecondsPerMinute) + + Math.BigMul(seconds, MicrosecondsPerSecond) + + Math.BigMul(milliseconds, MicrosecondsPerMillisecond) + + microseconds; + + return FromMicroseconds(totalMicroseconds); + } + + /// + /// Initializes a new instance of the structure to a specified number of + /// minutes. + /// + /// Number of minutes. + /// Returns a that represents a specified number of minutes. + /// + /// The parameters specify a value less than or greater than + /// + public static TimeSpan FromMinutes(long minutes) => FromUnits(minutes, TicksPerMinute, MinMinutes, MaxMinutes); + + /// + /// Initializes a new instance of the structure to a specified number of + /// minutes, seconds, milliseconds, and microseconds. + /// + /// Number of minutes. + /// Number of seconds. + /// Number of milliseconds. + /// Number of microseconds. + /// Returns a that represents a specified number of minutes, seconds, milliseconds, and microseconds. + /// + /// The parameters specify a value less than or greater than + /// + public static TimeSpan FromMinutes(long minutes, long seconds = 0, long milliseconds = 0, long microseconds = 0) + { + Int128 totalMicroseconds = Math.BigMul(minutes, MicrosecondsPerMinute) + + Math.BigMul(seconds, MicrosecondsPerSecond) + + Math.BigMul(milliseconds, MicrosecondsPerMillisecond) + + microseconds; + + return FromMicroseconds(totalMicroseconds); + } + + /// + /// Initializes a new instance of the structure to a specified number of + /// seconds. + /// + /// Number of seconds. + /// Returns a that represents a specified number of seconds. + /// + /// The parameters specify a value less than or greater than + /// + public static TimeSpan FromSeconds(long seconds) => FromUnits(seconds, TicksPerSecond, MinSeconds, MaxSeconds); + + /// + /// Initializes a new instance of the structure to a specified number of + /// seconds, milliseconds, and microseconds. + /// + /// Number of seconds. + /// Number of milliseconds. + /// Number of microseconds. + /// Returns a that represents a specified number of seconds, milliseconds, and microseconds. + /// + /// The parameters specify a value less than or greater than + /// + public static TimeSpan FromSeconds(long seconds, long milliseconds = 0, long microseconds = 0) + { + Int128 totalMicroseconds = Math.BigMul(seconds, MicrosecondsPerSecond) + + Math.BigMul(milliseconds, MicrosecondsPerMillisecond) + + microseconds; + + return FromMicroseconds(totalMicroseconds); + } + + /// + /// Initializes a new instance of the structure to a specified number of + /// milliseconds. + /// + /// Number of milliseconds. + /// Returns a that represents a specified number of milliseconds. + /// + /// The parameter specify a value less than or greater than + /// + public static TimeSpan FromMilliseconds(long milliseconds) + => FromUnits(milliseconds, TicksPerMillisecond, MinMilliseconds, MaxMilliseconds); + + /// + /// Initializes a new instance of the structure to a specified number of + /// milliseconds, and microseconds. + /// + /// Number of milliseconds. + /// Number of microseconds. + /// Returns a that represents a specified number of milliseconds, and microseconds. + /// + /// The parameters specify a value less than or greater than + /// + public static TimeSpan FromMilliseconds(long milliseconds, long microseconds) + { + Int128 totalMicroseconds = Math.BigMul(milliseconds, MicrosecondsPerMillisecond) + + microseconds; + + return FromMicroseconds(totalMicroseconds); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static TimeSpan FromMicroseconds(Int128 microseconds) + { + if ((microseconds > MaxMicroseconds) || (microseconds < MinMicroseconds)) + { + ThrowHelper.ThrowArgumentOutOfRange_TimeSpanTooLong(); + } + long ticks = (long)microseconds * TicksPerMicrosecond; + return TimeSpan.FromTicks(ticks); + } + + /// + /// Initializes a new instance of the structure to a specified number of + /// microseconds. + /// + /// Number of microseconds. + /// Returns a that represents a specified number of microseconds. + /// + /// The parameters specify a value less than or greater than + /// + public static TimeSpan FromMicroseconds(long microseconds) => FromUnits(microseconds, TicksPerMicrosecond, MinMicroseconds, MaxMicroseconds); + + public static TimeSpan FromHours(double value) => Interval(value, TicksPerHour); + + private static TimeSpan Interval(double value, double scale) + { + if (double.IsNaN(value)) + { + ThrowHelper.ThrowArgumentException_Arg_CannotBeNaN(); + } + return IntervalFromDoubleTicks(value * scale); + } + + private static TimeSpan IntervalFromDoubleTicks(double ticks) + { + if ((ticks > MaxTicks) || (ticks < MinTicks) || double.IsNaN(ticks)) + { + ThrowHelper.ThrowOverflowException_TimeSpanTooLong(); + } + if (ticks == MaxTicks) + { + return MaxValue; + } + return new TimeSpan((long)ticks); + } + + public static TimeSpan FromMilliseconds(double value) => Interval(value, TicksPerMillisecond); + + /// + /// Returns a that represents a specified number of microseconds. + /// + /// A number of microseconds. + /// An object that represents . + /// + /// is less than or greater than . + /// + /// -or- + /// + /// is + /// + /// -or- + /// + /// is + /// + /// + /// is equal to . + /// + public static TimeSpan FromMicroseconds(double value) => Interval(value, TicksPerMicrosecond); + + public static TimeSpan FromMinutes(double value) => Interval(value, TicksPerMinute); + + public TimeSpan Negate() => -this; + + public static TimeSpan FromSeconds(double value) => Interval(value, TicksPerSecond); + + public TimeSpan Subtract(TimeSpan ts) => this - ts; + + public TimeSpan Multiply(double factor) => this * factor; + + public TimeSpan Divide(double divisor) => this / divisor; + + public double Divide(TimeSpan ts) => this / ts; + + public static TimeSpan FromTicks(long value) => new TimeSpan(value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static long TimeToTicks(int hour, int minute, int second) + { + // totalSeconds is bounded by 2^31 * 2^12 + 2^31 * 2^8 + 2^31, + // which is less than 2^44, meaning we won't overflow totalSeconds. + long totalSeconds = (hour * SecondsPerHour) + + (minute * SecondsPerMinute) + + second; + + if ((totalSeconds > MaxSeconds) || (totalSeconds < MinSeconds)) + { + ThrowHelper.ThrowArgumentOutOfRange_TimeSpanTooLong(); + } + return totalSeconds * TicksPerSecond; + } + + // See System.Globalization.TimeSpanParse and System.Globalization.TimeSpanFormat + #region ParseAndFormat + private static void ValidateStyles(TimeSpanStyles style) + { + if (style is not TimeSpanStyles.None and not TimeSpanStyles.AssumeNegative) + { + ThrowHelper.ThrowArgumentException_InvalidTimeSpanStyles(); + } + } + public static TimeSpan Parse(string s) + { + /* Constructs a TimeSpan from a string. Leading and trailing white space characters are allowed. */ + ArgumentNullException.ThrowIfNull(s, ExceptionArgument.input); + return TimeSpanParse.Parse(s, null); + } + public static TimeSpan Parse(string input, IFormatProvider? formatProvider) + { + if (input is null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.input); + } + return TimeSpanParse.Parse(input, formatProvider); + } + public static TimeSpan Parse(ReadOnlySpan input, IFormatProvider? formatProvider = null) => TimeSpanParse.Parse(input, formatProvider); + public static TimeSpan ParseExact(string input, [StringSyntax(StringSyntaxAttribute.TimeSpanFormat)] string format, IFormatProvider? formatProvider) + { + ArgumentNullException.ThrowIfNull(input, ExceptionArgument.input); + ArgumentNullException.ThrowIfNull(format, ExceptionArgument.format); + return TimeSpanParse.ParseExact(input, format, formatProvider, TimeSpanStyles.None); + } + public static TimeSpan ParseExact(string input, [StringSyntax(StringSyntaxAttribute.TimeSpanFormat)] string[] formats, IFormatProvider? formatProvider) + { + if (input is null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.input); + } + return TimeSpanParse.ParseExactMultiple(input, formats, formatProvider, TimeSpanStyles.None); + } + public static TimeSpan ParseExact(string input, [StringSyntax(StringSyntaxAttribute.TimeSpanFormat)] string format, IFormatProvider? formatProvider, TimeSpanStyles styles) + { + ValidateStyles(styles); + ArgumentNullException.ThrowIfNull(input, ExceptionArgument.input); + ArgumentNullException.ThrowIfNull(format, ExceptionArgument.format); + return TimeSpanParse.ParseExact(input, format, formatProvider, styles); + } + + public static TimeSpan ParseExact(ReadOnlySpan input, [StringSyntax(StringSyntaxAttribute.TimeSpanFormat)] ReadOnlySpan format, IFormatProvider? formatProvider, TimeSpanStyles styles = TimeSpanStyles.None) + { + ValidateStyles(styles); + return TimeSpanParse.ParseExact(input, format, formatProvider, styles); + } + public static TimeSpan ParseExact(string input, [StringSyntax(StringSyntaxAttribute.TimeSpanFormat)] string[] formats, IFormatProvider? formatProvider, TimeSpanStyles styles) + { + ValidateStyles(styles); + ArgumentNullException.ThrowIfNull(input, ExceptionArgument.input); + return TimeSpanParse.ParseExactMultiple(input, formats, formatProvider, styles); + } + public static TimeSpan ParseExact(ReadOnlySpan input, [StringSyntax(StringSyntaxAttribute.TimeSpanFormat)] string[] formats, IFormatProvider? formatProvider, TimeSpanStyles styles = TimeSpanStyles.None) + { + ValidateStyles(styles); + return TimeSpanParse.ParseExactMultiple(input, formats, formatProvider, styles); + } + public static bool TryParse([NotNullWhen(true)] string? s, out TimeSpan result) + { + if (s is null) + { + result = default; + return false; + } + return TimeSpanParse.TryParse(s, null, out result); + } + public static bool TryParse(ReadOnlySpan s, out TimeSpan result) => TimeSpanParse.TryParse(s, null, out result); + + public static bool TryParse([NotNullWhen(true)] string? input, IFormatProvider? formatProvider, out TimeSpan result) + { + if (input is null) + { + result = default; + return false; + } + return TimeSpanParse.TryParse(input, formatProvider, out result); + } + public static bool TryParse(ReadOnlySpan input, IFormatProvider? formatProvider, out TimeSpan result) => TimeSpanParse.TryParse(input, formatProvider, out result); + public static bool TryParseExact([NotNullWhen(true)] string? input, [NotNullWhen(true), StringSyntax(StringSyntaxAttribute.TimeSpanFormat)] string? format, IFormatProvider? formatProvider, out TimeSpan result) + { + if (input is null || format is null) + { + result = default; + return false; + } + return TimeSpanParse.TryParseExact(input, format, formatProvider, TimeSpanStyles.None, out result); + } + + public static bool TryParseExact(ReadOnlySpan input, [StringSyntax(StringSyntaxAttribute.TimeSpanFormat)] ReadOnlySpan format, IFormatProvider? formatProvider, out TimeSpan result) + => TimeSpanParse.TryParseExact(input, format, formatProvider, TimeSpanStyles.None, out result); + + public static bool TryParseExact([NotNullWhen(true)] string? input, [NotNullWhen(true), StringSyntax(StringSyntaxAttribute.TimeSpanFormat)] string?[]? formats, IFormatProvider? formatProvider, out TimeSpan result) + { + if (input is null) + { + result = default; + return false; + } + return TimeSpanParse.TryParseExactMultiple(input, formats, formatProvider, TimeSpanStyles.None, out result); + } + public static bool TryParseExact(ReadOnlySpan input, [NotNullWhen(true), StringSyntax(StringSyntaxAttribute.TimeSpanFormat)] string?[]? formats, IFormatProvider? formatProvider, out TimeSpan result) + => TimeSpanParse.TryParseExactMultiple(input, formats, formatProvider, TimeSpanStyles.None, out result); + + public static bool TryParseExact([NotNullWhen(true)] string? input, [NotNullWhen(true), StringSyntax(StringSyntaxAttribute.TimeSpanFormat)] string? format, IFormatProvider? formatProvider, TimeSpanStyles styles, out TimeSpan result) + { + ValidateStyles(styles); + + if (input is null || format is null) + { + result = default; + return false; + } + return TimeSpanParse.TryParseExact(input, format, formatProvider, styles, out result); + } + + public static bool TryParseExact(ReadOnlySpan input, [StringSyntax(StringSyntaxAttribute.TimeSpanFormat)] ReadOnlySpan format, IFormatProvider? formatProvider, TimeSpanStyles styles, out TimeSpan result) + { + ValidateStyles(styles); + return TimeSpanParse.TryParseExact(input, format, formatProvider, styles, out result); + } + public static bool TryParseExact([NotNullWhen(true)] string? input, [NotNullWhen(true), StringSyntax(StringSyntaxAttribute.TimeSpanFormat)] string?[]? formats, IFormatProvider? formatProvider, TimeSpanStyles styles, out TimeSpan result) + { + ValidateStyles(styles); + + if (input is null) + { + result = default; + return false; + } + return TimeSpanParse.TryParseExactMultiple(input, formats, formatProvider, styles, out result); + } + + public static bool TryParseExact(ReadOnlySpan input, [NotNullWhen(true), StringSyntax(StringSyntaxAttribute.TimeSpanFormat)] string?[]? formats, IFormatProvider? formatProvider, TimeSpanStyles styles, out TimeSpan result) + { + ValidateStyles(styles); + return TimeSpanParse.TryParseExactMultiple(input, formats, formatProvider, styles, out result); + } + public override string ToString() => TimeSpanFormat.FormatC(this); + public string ToString([StringSyntax(StringSyntaxAttribute.TimeSpanFormat)] string? format) => TimeSpanFormat.Format(this, format, null); + public string ToString([StringSyntax(StringSyntaxAttribute.TimeSpanFormat)] string? format, IFormatProvider? formatProvider) => TimeSpanFormat.Format(this, format, formatProvider); + + public bool TryFormat(Span destination, out int charsWritten, [StringSyntax(StringSyntaxAttribute.TimeSpanFormat)] ReadOnlySpan format = default, IFormatProvider? formatProvider = null) + => TimeSpanFormat.TryFormat(this, destination, out charsWritten, format, formatProvider); + + /// + public bool TryFormat(Span utf8Destination, out int bytesWritten, [StringSyntax(StringSyntaxAttribute.TimeSpanFormat)] ReadOnlySpan format = default, IFormatProvider? formatProvider = null) + => TimeSpanFormat.TryFormat(this, utf8Destination, out bytesWritten, format, formatProvider); + + #endregion + + public static TimeSpan operator -(TimeSpan t) + { + if (t._ticks == MinTicks) + { + ThrowHelper.ThrowOverflowException_NegateTwosCompNum(); + } + return new TimeSpan(-t._ticks); + } + + public static TimeSpan operator -(TimeSpan t1, TimeSpan t2) + { + long result = t1._ticks - t2._ticks; + long t1Sign = t1._ticks >> 63; + + if ((t1Sign != (t2._ticks >> 63)) && (t1Sign != (result >> 63))) + { + // Overflow if signs of operands was different and result's sign was opposite. + // >> 63 gives the sign bit (either 64 1's or 64 0's). + ThrowHelper.ThrowOverflowException_TimeSpanTooLong(); + } + return new TimeSpan(result); + } + + public static TimeSpan operator +(TimeSpan t) => t; + + public static TimeSpan operator +(TimeSpan t1, TimeSpan t2) + { + long result = t1._ticks + t2._ticks; + long t1Sign = t1._ticks >> 63; + + if ((t1Sign == (t2._ticks >> 63)) && (t1Sign != (result >> 63))) + { + // Overflow if signs of operands was identical and result's sign was opposite. + // >> 63 gives the sign bit (either 64 1's or 64 0's). + ThrowHelper.ThrowOverflowException_TimeSpanTooLong(); + } + return new TimeSpan(result); + } + + /// + public static TimeSpan operator *(TimeSpan timeSpan, double factor) + { + if (double.IsNaN(factor)) + { + ThrowHelper.ThrowArgumentException_Arg_CannotBeNaN(ExceptionArgument.factor); + } + + // Rounding to the nearest tick is as close to the result we would have with unlimited + // precision as possible, and so likely to have the least potential to surprise. + double ticks = Math.Round(timeSpan.Ticks * factor); + return IntervalFromDoubleTicks(ticks); + } + + /// + public static TimeSpan operator *(double factor, TimeSpan timeSpan) => timeSpan * factor; + + /// + public static TimeSpan operator /(TimeSpan timeSpan, double divisor) + { + if (double.IsNaN(divisor)) + { + ThrowHelper.ThrowArgumentException_Arg_CannotBeNaN(ExceptionArgument.divisor); + } + + double ticks = Math.Round(timeSpan.Ticks / divisor); + return IntervalFromDoubleTicks(ticks); + } + + // Using floating-point arithmetic directly means that infinities can be returned, which is reasonable + // if we consider TimeSpan.FromHours(1) / TimeSpan.Zero asks how many zero-second intervals there are in + // an hour for which infinity is the mathematic correct answer. Having TimeSpan.Zero / TimeSpan.Zero return NaN + // is perhaps less useful, but no less useful than an exception. + /// + public static double operator /(TimeSpan t1, TimeSpan t2) => t1.Ticks / (double)t2.Ticks; + + /// + public static bool operator ==(TimeSpan t1, TimeSpan t2) => t1._ticks == t2._ticks; + + /// + public static bool operator !=(TimeSpan t1, TimeSpan t2) => t1._ticks != t2._ticks; + + /// + public static bool operator <(TimeSpan t1, TimeSpan t2) => t1._ticks < t2._ticks; + + /// + public static bool operator <=(TimeSpan t1, TimeSpan t2) => t1._ticks <= t2._ticks; + + /// + public static bool operator >(TimeSpan t1, TimeSpan t2) => t1._ticks > t2._ticks; + + /// + public static bool operator >=(TimeSpan t1, TimeSpan t2) => t1._ticks >= t2._ticks; +} \ No newline at end of file From 2ebdb421bb2a77c11137bbc7c97643498dcc1485 Mon Sep 17 00:00:00 2001 From: Airat Abdrakov <52558686+CrackAndDie@users.noreply.github.com> Date: Wed, 15 Apr 2026 11:44:54 +0300 Subject: [PATCH 03/26] TimeSpan files small fixes --- std/System/TimeSpan.hpt | 76 ++++++++++++++++++++--------------------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/std/System/TimeSpan.hpt b/std/System/TimeSpan.hpt index 78064ee2..78470071 100644 --- a/std/System/TimeSpan.hpt +++ b/std/System/TimeSpan.hpt @@ -423,8 +423,7 @@ public readonly struct TimeSpan public override int GetHashCode() => _ticks.GetHashCode(); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static TimeSpan FromUnits(long units, long ticksPerUnit, long minUnits, long maxUnits) + private static inline TimeSpan FromUnits(long units, long ticksPerUnit, long minUnits, long maxUnits) { System.Diagnostics.Debug.Assert(minUnits < 0); System.Diagnostics.Debug.Assert(maxUnits > 0); @@ -602,8 +601,7 @@ public readonly struct TimeSpan return FromMicroseconds(totalMicroseconds); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static TimeSpan FromMicroseconds(Int128 microseconds) + private static inline TimeSpan FromMicroseconds(Int128 microseconds) { if ((microseconds > MaxMicroseconds) || (microseconds < MinMicroseconds)) { @@ -687,8 +685,7 @@ public readonly struct TimeSpan public static TimeSpan FromTicks(long value) => new TimeSpan(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static long TimeToTicks(int hour, int minute, int second) + internal static inline long TimeToTicks(int hour, int minute, int second) { // totalSeconds is bounded by 2^31 * 2^12 + 2^31 * 2^8 + 2^31, // which is less than 2^44, meaning we won't overflow totalSeconds. @@ -704,10 +701,10 @@ public readonly struct TimeSpan } // See System.Globalization.TimeSpanParse and System.Globalization.TimeSpanFormat - #region ParseAndFormat + //#region ParseAndFormat private static void ValidateStyles(TimeSpanStyles style) { - if (style is not TimeSpanStyles.None and not TimeSpanStyles.AssumeNegative) + if (style is not TimeSpanStyles.None && style is not TimeSpanStyles.AssumeNegative) { ThrowHelper.ThrowArgumentException_InvalidTimeSpanStyles(); } @@ -718,54 +715,56 @@ public readonly struct TimeSpan ArgumentNullException.ThrowIfNull(s, ExceptionArgument.input); return TimeSpanParse.Parse(s, null); } - public static TimeSpan Parse(string input, IFormatProvider? formatProvider) + public static TimeSpan Parse(string input, IFormatProvider formatProvider) { - if (input is null) + if (input == null) { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.input); } return TimeSpanParse.Parse(input, formatProvider); } public static TimeSpan Parse(ReadOnlySpan input, IFormatProvider? formatProvider = null) => TimeSpanParse.Parse(input, formatProvider); - public static TimeSpan ParseExact(string input, [StringSyntax(StringSyntaxAttribute.TimeSpanFormat)] string format, IFormatProvider? formatProvider) + public static TimeSpan ParseExact(string input, string format, IFormatProvider formatProvider) { - ArgumentNullException.ThrowIfNull(input, ExceptionArgument.input); - ArgumentNullException.ThrowIfNull(format, ExceptionArgument.format); + ArgumentNullException.ThrowIfNull(nameof(input)); + ArgumentNullException.ThrowIfNull(nameof(format)); + + return TimeSpanParse.ParseExact(input, format, formatProvider, TimeSpanStyles.None); } - public static TimeSpan ParseExact(string input, [StringSyntax(StringSyntaxAttribute.TimeSpanFormat)] string[] formats, IFormatProvider? formatProvider) + public static TimeSpan ParseExact(string input, string[] formats, IFormatProvider formatProvider) { - if (input is null) + if (input == null) { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.input); } return TimeSpanParse.ParseExactMultiple(input, formats, formatProvider, TimeSpanStyles.None); } - public static TimeSpan ParseExact(string input, [StringSyntax(StringSyntaxAttribute.TimeSpanFormat)] string format, IFormatProvider? formatProvider, TimeSpanStyles styles) + public static TimeSpan ParseExact(string input, string format, IFormatProvider formatProvider, TimeSpanStyles styles) { ValidateStyles(styles); - ArgumentNullException.ThrowIfNull(input, ExceptionArgument.input); - ArgumentNullException.ThrowIfNull(format, ExceptionArgument.format); + ArgumentNullException.ThrowIfNull(nameof(input)); + ArgumentNullException.ThrowIfNull(nameof(format)); return TimeSpanParse.ParseExact(input, format, formatProvider, styles); } - public static TimeSpan ParseExact(ReadOnlySpan input, [StringSyntax(StringSyntaxAttribute.TimeSpanFormat)] ReadOnlySpan format, IFormatProvider? formatProvider, TimeSpanStyles styles = TimeSpanStyles.None) + public static TimeSpan ParseExact(ReadOnlySpan input, ReadOnlySpan format, IFormatProvider formatProvider, TimeSpanStyles styles = TimeSpanStyles.None) { ValidateStyles(styles); return TimeSpanParse.ParseExact(input, format, formatProvider, styles); } - public static TimeSpan ParseExact(string input, [StringSyntax(StringSyntaxAttribute.TimeSpanFormat)] string[] formats, IFormatProvider? formatProvider, TimeSpanStyles styles) + public static TimeSpan ParseExact(string input, string[] formats, IFormatProvider formatProvider, TimeSpanStyles styles) { ValidateStyles(styles); ArgumentNullException.ThrowIfNull(input, ExceptionArgument.input); return TimeSpanParse.ParseExactMultiple(input, formats, formatProvider, styles); } - public static TimeSpan ParseExact(ReadOnlySpan input, [StringSyntax(StringSyntaxAttribute.TimeSpanFormat)] string[] formats, IFormatProvider? formatProvider, TimeSpanStyles styles = TimeSpanStyles.None) + public static TimeSpan ParseExact(ReadOnlySpan input, string[] formats, IFormatProvider formatProvider, TimeSpanStyles styles = TimeSpanStyles.None) { ValidateStyles(styles); return TimeSpanParse.ParseExactMultiple(input, formats, formatProvider, styles); } - public static bool TryParse([NotNullWhen(true)] string? s, out TimeSpan result) + public static bool TryParse(string s, out TimeSpan result) { if (s is null) { @@ -776,7 +775,7 @@ public readonly struct TimeSpan } public static bool TryParse(ReadOnlySpan s, out TimeSpan result) => TimeSpanParse.TryParse(s, null, out result); - public static bool TryParse([NotNullWhen(true)] string? input, IFormatProvider? formatProvider, out TimeSpan result) + public static bool TryParse(string input, IFormatProvider formatProvider, out TimeSpan result) { if (input is null) { @@ -785,8 +784,8 @@ public readonly struct TimeSpan } return TimeSpanParse.TryParse(input, formatProvider, out result); } - public static bool TryParse(ReadOnlySpan input, IFormatProvider? formatProvider, out TimeSpan result) => TimeSpanParse.TryParse(input, formatProvider, out result); - public static bool TryParseExact([NotNullWhen(true)] string? input, [NotNullWhen(true), StringSyntax(StringSyntaxAttribute.TimeSpanFormat)] string? format, IFormatProvider? formatProvider, out TimeSpan result) + public static bool TryParse(ReadOnlySpan input, IFormatProvider formatProvider, out TimeSpan result) => TimeSpanParse.TryParse(input, formatProvider, out result); + public static bool TryParseExact(string input, string format, IFormatProvider formatProvider, out TimeSpan result) { if (input is null || format is null) { @@ -796,10 +795,10 @@ public readonly struct TimeSpan return TimeSpanParse.TryParseExact(input, format, formatProvider, TimeSpanStyles.None, out result); } - public static bool TryParseExact(ReadOnlySpan input, [StringSyntax(StringSyntaxAttribute.TimeSpanFormat)] ReadOnlySpan format, IFormatProvider? formatProvider, out TimeSpan result) + public static bool TryParseExact(ReadOnlySpan input, ReadOnlySpan format, IFormatProvider formatProvider, out TimeSpan result) => TimeSpanParse.TryParseExact(input, format, formatProvider, TimeSpanStyles.None, out result); - public static bool TryParseExact([NotNullWhen(true)] string? input, [NotNullWhen(true), StringSyntax(StringSyntaxAttribute.TimeSpanFormat)] string?[]? formats, IFormatProvider? formatProvider, out TimeSpan result) + public static bool TryParseExact(string input, string[] formats, IFormatProvider formatProvider, out TimeSpan result) { if (input is null) { @@ -808,10 +807,10 @@ public readonly struct TimeSpan } return TimeSpanParse.TryParseExactMultiple(input, formats, formatProvider, TimeSpanStyles.None, out result); } - public static bool TryParseExact(ReadOnlySpan input, [NotNullWhen(true), StringSyntax(StringSyntaxAttribute.TimeSpanFormat)] string?[]? formats, IFormatProvider? formatProvider, out TimeSpan result) + public static bool TryParseExact(ReadOnlySpan input, string[] formats, IFormatProvider formatProvider, out TimeSpan result) => TimeSpanParse.TryParseExactMultiple(input, formats, formatProvider, TimeSpanStyles.None, out result); - public static bool TryParseExact([NotNullWhen(true)] string? input, [NotNullWhen(true), StringSyntax(StringSyntaxAttribute.TimeSpanFormat)] string? format, IFormatProvider? formatProvider, TimeSpanStyles styles, out TimeSpan result) + public static bool TryParseExact(string input, string format, IFormatProvider formatProvider, TimeSpanStyles styles, out TimeSpan result) { ValidateStyles(styles); @@ -823,12 +822,12 @@ public readonly struct TimeSpan return TimeSpanParse.TryParseExact(input, format, formatProvider, styles, out result); } - public static bool TryParseExact(ReadOnlySpan input, [StringSyntax(StringSyntaxAttribute.TimeSpanFormat)] ReadOnlySpan format, IFormatProvider? formatProvider, TimeSpanStyles styles, out TimeSpan result) + public static bool TryParseExact(ReadOnlySpan input, ReadOnlySpan format, IFormatProvider formatProvider, TimeSpanStyles styles, out TimeSpan result) { ValidateStyles(styles); return TimeSpanParse.TryParseExact(input, format, formatProvider, styles, out result); } - public static bool TryParseExact([NotNullWhen(true)] string? input, [NotNullWhen(true), StringSyntax(StringSyntaxAttribute.TimeSpanFormat)] string?[]? formats, IFormatProvider? formatProvider, TimeSpanStyles styles, out TimeSpan result) + public static bool TryParseExact(string input, string[] formats, IFormatProvider formatProvider, TimeSpanStyles styles, out TimeSpan result) { ValidateStyles(styles); @@ -840,23 +839,24 @@ public readonly struct TimeSpan return TimeSpanParse.TryParseExactMultiple(input, formats, formatProvider, styles, out result); } - public static bool TryParseExact(ReadOnlySpan input, [NotNullWhen(true), StringSyntax(StringSyntaxAttribute.TimeSpanFormat)] string?[]? formats, IFormatProvider? formatProvider, TimeSpanStyles styles, out TimeSpan result) + public static bool TryParseExact(ReadOnlySpan input, string[] formats, IFormatProvider formatProvider, TimeSpanStyles styles, out TimeSpan result) { ValidateStyles(styles); return TimeSpanParse.TryParseExactMultiple(input, formats, formatProvider, styles, out result); } + public override string ToString() => TimeSpanFormat.FormatC(this); - public string ToString([StringSyntax(StringSyntaxAttribute.TimeSpanFormat)] string? format) => TimeSpanFormat.Format(this, format, null); - public string ToString([StringSyntax(StringSyntaxAttribute.TimeSpanFormat)] string? format, IFormatProvider? formatProvider) => TimeSpanFormat.Format(this, format, formatProvider); + public string ToString(string format) => TimeSpanFormat.Format(this, format, null); + public string ToString(string format, IFormatProvider formatProvider) => TimeSpanFormat.Format(this, format, formatProvider); - public bool TryFormat(Span destination, out int charsWritten, [StringSyntax(StringSyntaxAttribute.TimeSpanFormat)] ReadOnlySpan format = default, IFormatProvider? formatProvider = null) + public bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format = default, IFormatProvider formatProvider = null) => TimeSpanFormat.TryFormat(this, destination, out charsWritten, format, formatProvider); /// - public bool TryFormat(Span utf8Destination, out int bytesWritten, [StringSyntax(StringSyntaxAttribute.TimeSpanFormat)] ReadOnlySpan format = default, IFormatProvider? formatProvider = null) + public bool TryFormat(Span utf8Destination, out int bytesWritten, ReadOnlySpan format = default, IFormatProvider formatProvider = null) => TimeSpanFormat.TryFormat(this, utf8Destination, out bytesWritten, format, formatProvider); - #endregion + //#endregion public static TimeSpan operator -(TimeSpan t) { @@ -931,7 +931,7 @@ public readonly struct TimeSpan // an hour for which infinity is the mathematic correct answer. Having TimeSpan.Zero / TimeSpan.Zero return NaN // is perhaps less useful, but no less useful than an exception. /// - public static double operator /(TimeSpan t1, TimeSpan t2) => t1.Ticks / (double)t2.Ticks; + public static double operator /(TimeSpan t1, TimeSpan t2) => (double)t1.Ticks / (double)t2.Ticks; /// public static bool operator ==(TimeSpan t1, TimeSpan t2) => t1._ticks == t2._ticks; From 1ba14b1ed5ea1b73d831aa17459f0b1706d4320d Mon Sep 17 00:00:00 2001 From: Airat Abdrakov <52558686+CrackAndDie@users.noreply.github.com> Date: Wed, 15 Apr 2026 14:58:21 +0300 Subject: [PATCH 04/26] Int128 added and TimeSpan changes --- std/System/Int128.hpt | 2158 +++++++++++++++++++++++++++++++++++++++ std/System/TimeSpan.hpt | 33 +- 2 files changed, 2176 insertions(+), 15 deletions(-) create mode 100644 std/System/Int128.hpt diff --git a/std/System/Int128.hpt b/std/System/Int128.hpt new file mode 100644 index 00000000..c6cc8dda --- /dev/null +++ b/std/System/Int128.hpt @@ -0,0 +1,2158 @@ + + +/// Represents a 128-bit signed integer.Initializes a new instance of the struct. + /// The upper 64-bits of the 128-bit value. + /// The lower 64-bits of the 128-bit value. + public Int128(ulong upper, ulong lower) + { + _lower = lower; + _upper = upper; + } + + internal ulong Lower => _lower; + + internal ulong Upper => _upper; + + /// + public int CompareTo(object value) + { + if (value is Int128 other) + { + return CompareTo(other); + } + else if (value is null) + { + return 1; + } + else + { + throw new ArgumentException(SR.Arg_MustBeInt128); + } + } + + /// + public int CompareTo(Int128 value) + { + if (this < value) + { + return -1; + } + else if (this > value) + { + return 1; + } + else + { + return 0; + } + } + + /// + public override bool Equals(object obj) + { + return (obj is Int128 other) && Equals(other); + } + + /// + public bool Equals(Int128 other) + { + return this == other; + } + + /// + public override int GetHashCode() => HashCode.Combine(_lower, _upper); + + /// + public override string ToString() + { + return Number.Int128ToDecStr(this); + } + + public string ToString(IFormatProvider? provider) + { + return Number.FormatInt128(this, null, provider); + } + + public string ToString(string format) + { + return Number.FormatInt128(this, format, null); + } + + public string ToString(string format, IFormatProvider? provider) + { + return Number.FormatInt128(this, format, provider); + } + + public bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format = default, IFormatProvider? provider = null) + { + return Number.TryFormatInt128(this, format, provider, destination, out charsWritten); + } + + /// + public bool TryFormat(Span utf8Destination, out int bytesWritten, ReadOnlySpan format = default, IFormatProvider? provider = null) + { + return Number.TryFormatInt128(this, format, provider, utf8Destination, out bytesWritten); + } + + public static Int128 Parse(string s) => Parse(s, NumberStyles.Integer, provider: null); + + public static Int128 Parse(string s, NumberStyles style) => Parse(s, style, provider: null); + + public static Int128 Parse(string s, IFormatProvider provider) => Parse(s, NumberStyles.Integer, provider); + + public static Int128 Parse(string s, NumberStyles style, IFormatProvider provider) + { + if (s is null) { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s); } + return Parse(s.AsSpan(), style, provider); + } + + public static Int128 Parse(ReadOnlySpan s, NumberStyles style = NumberStyles.Integer, IFormatProvider? provider = null) + { + NumberFormatInfo.ValidateParseStyleInteger(style); + return Number.ParseBinaryInteger(s, style, NumberFormatInfo.GetInstance(provider)); + } + + public static bool TryParse(string s, out Int128 result) => TryParse(s, NumberStyles.Integer, provider: null, out result); + + public static bool TryParse(ReadOnlySpan s, out Int128 result) => TryParse(s, NumberStyles.Integer, provider: null, out result); + + /// Tries to convert a UTF-8 character span containing the string representation of a number to its 128-bit signed integer equivalent. + /// A span containing the UTF-8 characters representing the number to convert. + /// When this method returns, contains the 128-bit signed integer value equivalent to the number contained in if the conversion succeeded, or zero if the conversion failed. This parameter is passed uninitialized; any value originally supplied in result will be overwritten. + /// true if was converted successfully; otherwise, false. + public static bool TryParse(ReadOnlySpan utf8Text, out Int128 result) => TryParse(utf8Text, NumberStyles.Integer, provider: null, out result); + + public static bool TryParse(string s, NumberStyles style, IFormatProvider provider, out Int128 result) + { + NumberFormatInfo.ValidateParseStyleInteger(style); + + if (s is null) + { + result = 0; + return false; + } + return Number.TryParseBinaryInteger(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; + } + + public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider provider, out Int128 result) + { + NumberFormatInfo.ValidateParseStyleInteger(style); + return Number.TryParseBinaryInteger(s, style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; + } + + // + // Explicit Conversions From Int128 + // + + /// Explicitly converts a 128-bit signed integer to a value. + /// The value to convert. + /// converted to a . + public static explicit operator byte(Int128 value) => (byte)value._lower; + + /// Explicitly converts a 128-bit signed integer to a value, throwing an overflow exception for any values that fall outside the representable range. + /// The value to convert. + /// converted to a . + /// is not representable by . + /* TODO: + public static explicit operator checked byte(Int128 value) + { + if (value._upper != 0) + { + ThrowHelper.ThrowOverflowException(); + } + return checked((byte)value._lower); + } + */ + + /// Explicitly converts a 128-bit signed integer to a value. + /// The value to convert. + /// converted to a . + public static explicit operator char(Int128 value) => (char)value._lower; + + /// Explicitly converts a 128-bit signed integer to a value, throwing an overflow exception for any values that fall outside the representable range. + /// The value to convert. + /// converted to a . + /// is not representable by . + /* TODO: + public static explicit operator checked char(Int128 value) + { + if (value._upper != 0) + { + ThrowHelper.ThrowOverflowException(); + } + return checked((char)value._lower); + } + */ + + /// Explicitly converts a 128-bit signed integer to a value. + /// The value to convert. + /// converted to a . + public static explicit operator decimal(Int128 value) + { + if (IsNegative(value)) + { + value = -value; + return -(decimal)(UInt128)(value); + } + return (decimal)(UInt128)(value); + } + + /// Explicitly converts a 128-bit signed integer to a value. + /// The value to convert. + /// converted to a . + public static explicit operator double(Int128 value) + { + if (IsNegative(value)) + { + value = -value; + return -(double)(UInt128)(value); + } + return (double)(UInt128)(value); + } + + /// Explicitly converts a 128-bit signed integer to a value. + /// The value to convert. + /// converted to a . + public static explicit operator Half(Int128 value) + { + if (IsNegative(value)) + { + value = -value; + return -(Half)(UInt128)(value); + } + return (Half)(UInt128)(value); + } + + /// Explicitly converts a 128-bit signed integer to a value. + /// The value to convert. + /// converted to a . + public static explicit operator short(Int128 value) => (short)value._lower; + + /// Explicitly converts a 128-bit signed integer to a value, throwing an overflow exception for any values that fall outside the representable range. + /// The value to convert. + /// converted to a . + /// is not representable by . + /* TODO: + public static explicit operator checked short(Int128 value) + { + long lower = (long)value._lower; + if ((long)value._upper != lower >> 63) + { + ThrowHelper.ThrowOverflowException(); + } + return checked((short)lower); + } + */ + + /// Explicitly converts a 128-bit signed integer to a value. + /// The value to convert. + /// converted to a . + public static explicit operator int(Int128 value) => (int)value._lower; + + /// Explicitly converts a 128-bit signed integer to a value, throwing an overflow exception for any values that fall outside the representable range. + /// The value to convert. + /// converted to a . + /// is not representable by . + /* TODO: + public static explicit operator checked int(Int128 value) + { + long lower = (long)value._lower; + if ((long)value._upper != lower >> 63) + { + ThrowHelper.ThrowOverflowException(); + } + return checked((int)lower); + } + */ + + /// Explicitly converts a 128-bit signed integer to a value. + /// The value to convert. + /// converted to a . + public static explicit operator long(Int128 value) => (long)value._lower; + + /// Explicitly converts a 128-bit signed integer to a value, throwing an overflow exception for any values that fall outside the representable range. + /// The value to convert. + /// converted to a . + /// is not representable by . + /* TODO: + public static explicit operator checked long(Int128 value) + { + long lower = (long)value._lower; + if ((long)value._upper != lower >> 63) + { + ThrowHelper.ThrowOverflowException(); + } + return lower; + } + */ + + /// Explicitly converts a 128-bit signed integer to a value. + /// The value to convert. + /// converted to a . + public static explicit operator nint(Int128 value) => (nint)value._lower; + + /// Explicitly converts a 128-bit signed integer to a value, throwing an overflow exception for any values that fall outside the representable range. + /// The value to convert. + /// converted to a . + /// is not representable by . + /* TODO: + public static explicit operator checked nint(Int128 value) + { + long lower = (long)value._lower; + if ((long)value._upper != lower >> 63) + { + ThrowHelper.ThrowOverflowException(); + } + return checked((nint)lower); + } + */ + + /// Explicitly converts a 128-bit signed integer to a value. + /// The value to convert. + /// converted to a . + public static explicit operator sbyte(Int128 value) => (sbyte)value._lower; + + /// Explicitly converts a 128-bit signed integer to a value, throwing an overflow exception for any values that fall outside the representable range. + /// The value to convert. + /// converted to a . + /// is not representable by . + /* TODO: + public static explicit operator checked sbyte(Int128 value) + { + long lower = (long)value._lower; + if ((long)value._upper != lower >> 63) + { + ThrowHelper.ThrowOverflowException(); + } + return checked((sbyte)lower); + } + */ + + /// Explicitly converts a 128-bit signed integer to a value. + /// The value to convert. + /// converted to a . + public static explicit operator float(Int128 value) + { + if (IsNegative(value)) + { + value = -value; + return -(float)(UInt128)(value); + } + return (float)(UInt128)(value); + } + + /// Explicitly converts a 128-bit signed integer to a value. + /// The value to convert. + /// converted to a . + public static explicit operator ushort(Int128 value) => (ushort)value._lower; + + /// Explicitly converts a 128-bit signed integer to a value, throwing an overflow exception for any values that fall outside the representable range. + /// The value to convert. + /// converted to a . + /// is not representable by . + /* TODO: + public static explicit operator checked ushort(Int128 value) + { + if (value._upper != 0) + { + ThrowHelper.ThrowOverflowException(); + } + return checked((ushort)value._lower); + } + */ + + /// Explicitly converts a 128-bit signed integer to a value. + /// The value to convert. + /// converted to a . + public static explicit operator uint(Int128 value) => (uint)value._lower; + + /// Explicitly converts a 128-bit signed integer to a value, throwing an overflow exception for any values that fall outside the representable range. + /// The value to convert. + /// converted to a . + /// is not representable by . + /* TODO: + public static explicit operator checked uint(Int128 value) + { + if (value._upper != 0) + { + ThrowHelper.ThrowOverflowException(); + } + return checked((uint)value._lower); + } + */ + + /// Explicitly converts a 128-bit signed integer to a value. + /// The value to convert. + /// converted to a . + public static explicit operator ulong(Int128 value) => value._lower; + + /// Explicitly converts a 128-bit signed integer to a value, throwing an overflow exception for any values that fall outside the representable range. + /// The value to convert. + /// converted to a . + /// is not representable by . + /* TODO: + public static explicit operator checked ulong(Int128 value) + { + if (value._upper != 0) + { + ThrowHelper.ThrowOverflowException(); + } + return value._lower; + } + */ + + /// Explicitly converts a 128-bit signed integer to a value. + /// The value to convert. + /// converted to a . + public static explicit operator UInt128(Int128 value) => new UInt128(value._upper, value._lower); + + /// Explicitly converts a 128-bit signed integer to a value, throwing an overflow exception for any values that fall outside the representable range. + /// The value to convert. + /// converted to a . + /// is not representable by . + /* TODO: + public static explicit operator checked UInt128(Int128 value) + { + if ((long)value._upper < 0) + { + ThrowHelper.ThrowOverflowException(); + } + return new UInt128(value._upper, value._lower); + } + */ + + /// Explicitly converts a 128-bit signed integer to a value. + /// The value to convert. + /// converted to a . + public static explicit operator nuint(Int128 value) => (nuint)value._lower; + + /// Explicitly converts a 128-bit signed integer to a value, throwing an overflow exception for any values that fall outside the representable range. + /// The value to convert. + /// converted to a . + /// is not representable by . + /* TODO: + public static explicit operator checked nuint(Int128 value) + { + if (value._upper != 0) + { + ThrowHelper.ThrowOverflowException(); + } + return checked((nuint)value._lower); + } + */ + + // + // Explicit Conversions To Int128 + // + + /// Explicitly converts a value to a 128-bit signed integer. + /// The value to convert. + /// converted to a 128-bit signed integer. + public static explicit operator Int128(decimal value) + { + value = decimal.Truncate(value); + Int128 result = new Int128(value.High, value.Low64); + + if (decimal.IsNegative(value)) + { + result = -result; + } + return result; + } + + /// Explicitly converts a value to a 128-bit signed integer. + /// The value to convert. + /// converted to a 128-bit signed integer. + public static explicit operator Int128(double value) + { + const double TwoPow127 = 170141183460469231731687303715884105728.0; + + if (value <= -TwoPow127) + { + return MinValue; + } + else if (double.IsNaN(value)) + { + return 0; + } + else if (value >= +TwoPow127) + { + return MaxValue; + } + + return ToInt128(value); + } + + /// Explicitly converts a value to a 128-bit signed integer, throwing an overflow exception for any values that fall outside the representable range. + /// The value to convert. + /// converted to a 128-bit signed integer. + /// is not representable by . + /* TODO: + public static explicit operator checked Int128(double value) + { + const double TwoPow127 = 170141183460469231731687303715884105728.0; + + if ((value < -TwoPow127) || double.IsNaN(value) || (value >= +TwoPow127)) + { + ThrowHelper.ThrowOverflowException(); + } + + return ToInt128(value); + } + */ + + internal static Int128 ToInt128(double value) + { + const double TwoPow127 = 170141183460469231731687303715884105728.0; + + Debug.Assert(value >= -TwoPow127); + Debug.Assert(double.IsFinite(value)); + Debug.Assert(value < TwoPow127); + + // This code is based on `f64_to_i128` from m-ou-se/floatconv + // Copyright (c) 2020 Mara Bos . All rights reserved. + // + // Licensed under the BSD 2 - Clause "Simplified" License + // See THIRD-PARTY-NOTICES.TXT for the full license text + + bool isNegative = double.IsNegative(value); + + if (isNegative) + { + value = -value; + } + + if (value >= 1.0) + { + // In order to convert from double to int128 we first need to extract the signficand, + // including the implicit leading bit, as a full 128-bit significand. We can then adjust + // this down to the represented integer by right shifting by the unbiased exponent, taking + // into account the significand is now represented as 128-bits. + + ulong bits = BitConverter.DoubleToUInt64Bits(value); + Int128 result = new Int128((bits << 12) >> 1 | 0x8000_0000_0000_0000, 0x0000_0000_0000_0000); + + result >>>= (1023 + 128 - 1 - (int)(bits >> 52)); + + if (isNegative) + { + result = -result; + } + return result; + } + else + { + return 0; + } + } + + /// Explicitly converts a value to a 128-bit signed integer. + /// The value to convert. + /// converted to a 128-bit signed integer. + public static explicit operator Int128(float value) => (Int128)(double)(value); + + /// Explicitly converts a value to a 128-bit signed integer, throwing an overflow exception for any values that fall outside the representable range. + /// The value to convert. + /// converted to a 128-bit signed integer. + /// is not representable by . + /* TODO: + public static explicit operator checked Int128(float value) => checked((Int128)(double)(value)); + */ + // + // Implicit Conversions To Int128 + // + + /// Implicitly converts a value to a 128-bit signed integer. + /// The value to convert. + /// converted to a 128-bit signed integer. + public static implicit operator Int128(byte value) => new Int128(0, value); + + /// Implicitly converts a value to a 128-bit signed integer. + /// The value to convert. + /// converted to a 128-bit signed integer. + public static implicit operator Int128(char value) => new Int128(0, value); + + /// Implicitly converts a value to a 128-bit signed integer. + /// The value to convert. + /// converted to a 128-bit signed integer. + public static implicit operator Int128(short value) + { + long lower = value; + return new Int128((ulong)(lower >> 63), (ulong)lower); + } + + /// Implicitly converts a value to a 128-bit signed integer. + /// The value to convert. + /// converted to a 128-bit signed integer. + public static implicit operator Int128(int value) + { + long lower = value; + return new Int128((ulong)(lower >> 63), (ulong)lower); + } + + /// Implicitly converts a value to a 128-bit signed integer. + /// The value to convert. + /// converted to a 128-bit signed integer. + public static implicit operator Int128(long value) + { + long lower = value; + return new Int128((ulong)(lower >> 63), (ulong)lower); + } + + /// Implicitly converts a value to a 128-bit signed integer. + /// The value to convert. + /// converted to a 128-bit signed integer. + public static implicit operator Int128(nint value) + { + long lower = value; + return new Int128((ulong)(lower >> 63), (ulong)lower); + } + + /// Implicitly converts a value to a 128-bit signed integer. + /// The value to convert. + /// converted to a 128-bit signed integer. + [CLSCompliant(false)] + public static implicit operator Int128(sbyte value) + { + long lower = value; + return new Int128((ulong)(lower >> 63), (ulong)lower); + } + + /// Implicitly converts a value to a 128-bit signed integer. + /// The value to convert. + /// converted to a 128-bit signed integer. + [CLSCompliant(false)] + public static implicit operator Int128(ushort value) => new Int128(0, value); + + /// Implicitly converts a value to a 128-bit signed integer. + /// The value to convert. + /// converted to a 128-bit signed integer. + [CLSCompliant(false)] + public static implicit operator Int128(uint value) => new Int128(0, value); + + /// Implicitly converts a value to a 128-bit signed integer. + /// The value to convert. + /// converted to a 128-bit signed integer. + [CLSCompliant(false)] + public static implicit operator Int128(ulong value) => new Int128(0, value); + + /// Implicitly converts a value to a 128-bit signed integer. + /// The value to convert. + /// converted to a 128-bit signed integer. + [CLSCompliant(false)] + public static implicit operator Int128(nuint value) => new Int128(0, value); + + // + // IAdditionOperators + // + + /// + public static Int128 operator +(Int128 left, Int128 right) + { + // For unsigned addition, we can detect overflow by checking `(x + y) < x` + // This gives us the carry to add to upper to compute the correct result + + ulong lower = left._lower + right._lower; + ulong carry = (lower < left._lower) ? 1UL : 0UL; + + ulong upper = left._upper + right._upper + carry; + return new Int128(upper, lower); + } + + /// + /* TODO: + public static Int128 operator checked +(Int128 left, Int128 right) + { + // For signed addition, we can detect overflow by checking if the sign of + // both inputs are the same and then if that differs from the sign of the + // output. The logic for how this works is explained in the + // System.Runtime.Intrinsics.Scalar.AddSaturate method + + Int128 result = left + right; + + if ((long)((result._upper ^ left._upper) & ~(left._upper ^ right._upper)) < 0) + { + ThrowHelper.ThrowOverflowException(); + } + return result; + } + */ + + // + // IAdditiveIdentity + // + + /// + static Int128 IAdditiveIdentity.AdditiveIdentity => default; + + // + // IBinaryInteger + // + + /// + public static (Int128 Quotient, Int128 Remainder) DivRem(Int128 left, Int128 right) + { + Int128 quotient = left / right; + return (quotient, left - (quotient * right)); + } + + /// + public static Int128 LeadingZeroCount(Int128 value) + => LeadingZeroCountAsInt32(value); + + /// Computes the number of leading zero bits in this value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int LeadingZeroCountAsInt32(Int128 value) + { + if (value._upper == 0) + { + return 64 + BitOperations.LeadingZeroCount(value._lower); + } + return BitOperations.LeadingZeroCount(value._upper); + } + + /// + public static Int128 Log10(Int128 value) + { + if (IsNegative(value)) + { + ThrowHelper.ThrowValueArgumentOutOfRange_NeedNonNegNumException(); + } + return (Int128)UInt128.Log10((UInt128)value); + } + + /// + public static Int128 PopCount(Int128 value) + => ulong.PopCount(value._lower) + ulong.PopCount(value._upper); + + /// + public static Int128 RotateLeft(Int128 value, int rotateAmount) + => (value << rotateAmount) | (value >>> (128 - rotateAmount)); + + /// + public static Int128 RotateRight(Int128 value, int rotateAmount) + => (value >>> rotateAmount) | (value << (128 - rotateAmount)); + + /// + public static Int128 TrailingZeroCount(Int128 value) + { + if (value._lower == 0) + { + return 64 + ulong.TrailingZeroCount(value._upper); + } + return ulong.TrailingZeroCount(value._lower); + } + + /// + static bool IBinaryInteger.TryReadBigEndian(ReadOnlySpan source, bool isUnsigned, out Int128 value) + { + Int128 result = default; + + if (source.Length != 0) + { + // Propagate the most significant bit so we have `0` or `-1` + sbyte sign = (sbyte)(source[0]); + sign >>= 31; + Debug.Assert((sign == 0) || (sign == -1)); + + // We need to also track if the input data is unsigned + isUnsigned |= (sign == 0); + + if (isUnsigned && sbyte.IsNegative(sign) && (source.Length >= Size)) + { + // When we are unsigned and the most significant bit is set, we are a large positive + // and therefore definitely out of range + + value = result; + return false; + } + + if (source.Length > Size) + { + if (source[..^Size].ContainsAnyExcept((byte)sign)) + { + // When we are unsigned and have any non-zero leading data or signed with any non-set leading + // data, we are a large positive/negative, respectively, and therefore definitely out of range + + value = result; + return false; + } + + if (isUnsigned == sbyte.IsNegative((sbyte)source[^Size])) + { + // When the most significant bit of the value being set/clear matches whether we are unsigned + // or signed then we are a large positive/negative and therefore definitely out of range + + value = result; + return false; + } + } + + ref byte sourceRef = ref MemoryMarshal.GetReference(source); + + if (source.Length >= Size) + { + sourceRef = ref Unsafe.Add(ref sourceRef, source.Length - Size); + + // We have at least 16 bytes, so just read the ones we need directly + result = Unsafe.ReadUnaligned(ref sourceRef); + + if (BitConverter.IsLittleEndian) + { + result = BinaryPrimitives.ReverseEndianness(result); + } + } + else + { + // We have between 1 and 15 bytes, so construct the relevant value directly + // since the data is in Big Endian format, we can just read the bytes and + // shift left by 8-bits for each subsequent part + + for (int i = 0; i < source.Length; i++) + { + result <<= 8; + result |= Unsafe.Add(ref sourceRef, i); + } + + if (!isUnsigned) + { + result |= ((One << ((Size * 8) - 1)) >> (((Size - source.Length) * 8) - 1)); + } + } + } + + value = result; + return true; + } + + /// + static bool IBinaryInteger.TryReadLittleEndian(ReadOnlySpan source, bool isUnsigned, out Int128 value) + { + Int128 result = default; + + if (source.Length != 0) + { + // Propagate the most significant bit so we have `0` or `-1` + sbyte sign = (sbyte)(source[^1]); + sign >>= 31; + Debug.Assert((sign == 0) || (sign == -1)); + + // We need to also track if the input data is unsigned + isUnsigned |= (sign == 0); + + if (isUnsigned && sbyte.IsNegative(sign) && (source.Length >= Size)) + { + // When we are unsigned and the most significant bit is set, we are a large positive + // and therefore definitely out of range + + value = result; + return false; + } + + if (source.Length > Size) + { + if (source[Size..].ContainsAnyExcept((byte)sign)) + { + // When we are unsigned and have any non-zero leading data or signed with any non-set leading + // data, we are a large positive/negative, respectively, and therefore definitely out of range + + value = result; + return false; + } + + if (isUnsigned == sbyte.IsNegative((sbyte)source[Size - 1])) + { + // When the most significant bit of the value being set/clear matches whether we are unsigned + // or signed then we are a large positive/negative and therefore definitely out of range + + value = result; + return false; + } + } + + ref byte sourceRef = ref MemoryMarshal.GetReference(source); + + if (source.Length >= Size) + { + // We have at least 16 bytes, so just read the ones we need directly + result = Unsafe.ReadUnaligned(ref sourceRef); + + if (!BitConverter.IsLittleEndian) + { + result = BinaryPrimitives.ReverseEndianness(result); + } + } + else + { + // We have between 1 and 15 bytes, so construct the relevant value directly + // since the data is in Little Endian format, we can just read the bytes and + // shift left by 8-bits for each subsequent part, then reverse endianness to + // ensure the order is correct. This is more efficient than iterating in reverse + // due to current JIT limitations + + for (int i = 0; i < source.Length; i++) + { + result <<= 8; + result |= Unsafe.Add(ref sourceRef, i); + } + + result <<= ((Size - source.Length) * 8); + result = BinaryPrimitives.ReverseEndianness(result); + + if (!isUnsigned) + { + result |= ((One << ((Size * 8) - 1)) >> (((Size - source.Length) * 8) - 1)); + } + } + } + + value = result; + return true; + } + + /// + int IBinaryInteger.GetShortestBitLength() + { + Int128 value = this; + + if (IsPositive(value)) + { + return (Size * 8) - LeadingZeroCountAsInt32(value); + } + else + { + return (Size * 8) + 1 - LeadingZeroCountAsInt32(~value); + } + } + + /// + int IBinaryInteger.GetByteCount() => Size; + + /// + bool IBinaryInteger.TryWriteBigEndian(Span destination, out int bytesWritten) + { + if (BinaryPrimitives.TryWriteInt128BigEndian(destination, this)) + { + bytesWritten = Size; + return true; + } + + bytesWritten = 0; + return false; + } + + /// + bool IBinaryInteger.TryWriteLittleEndian(Span destination, out int bytesWritten) + { + if (BinaryPrimitives.TryWriteInt128LittleEndian(destination, this)) + { + bytesWritten = Size; + return true; + } + + bytesWritten = 0; + return false; + } + + // + // IBinaryNumber + // + + /// + static Int128 IBinaryNumber.AllBitsSet => new Int128(0xFFFF_FFFF_FFFF_FFFF, 0xFFFF_FFFF_FFFF_FFFF); + + /// + public static bool IsPow2(Int128 value) => (PopCount(value) == 1U) && IsPositive(value); + + /// + public static Int128 Log2(Int128 value) + { + if (IsNegative(value)) + { + ThrowHelper.ThrowValueArgumentOutOfRange_NeedNonNegNumException(); + } + + if (value._upper == 0) + { + return ulong.Log2(value._lower); + } + return 64 + ulong.Log2(value._upper); + } + + // + // IBitwiseOperators + // + + /// + public static Int128 operator &(Int128 left, Int128 right) => new Int128(left._upper & right._upper, left._lower & right._lower); + + /// + public static Int128 operator |(Int128 left, Int128 right) => new Int128(left._upper | right._upper, left._lower | right._lower); + + /// + public static Int128 operator ^(Int128 left, Int128 right) => new Int128(left._upper ^ right._upper, left._lower ^ right._lower); + + /// + public static Int128 operator ~(Int128 value) => new Int128(~value._upper, ~value._lower); + + // + // IComparisonOperators + // + + /// + public static bool operator <(Int128 left, Int128 right) + { + // If left and right have different signs: Signed comparison of _upper gives result since it is stored as two's complement + // If signs are equal and left._upper < right._upper: left < right for negative and positive values, + // since _upper is upper 64 bits in two's complement. + // If signs are equal and left._upper > right._upper: left > right for negative and positive values, + // since _upper is upper 64 bits in two's complement. + // If left._upper == right._upper: unsigned comparison of _lower gives the result for both negative and positive values since + // lower values are lower 64 bits in two's complement. + return ((long)left._upper < (long)right._upper) + || ((left._upper == right._upper) && (left._lower < right._lower)); + } + + /// + public static bool operator <=(Int128 left, Int128 right) + { + // See comment in < operator for how this works. + return ((long)left._upper < (long)right._upper) + || ((left._upper == right._upper) && (left._lower <= right._lower)); + } + + /// + public static bool operator >(Int128 left, Int128 right) + { + // See comment in < operator for how this works. + return ((long)left._upper > (long)right._upper) + || ((left._upper == right._upper) && (left._lower > right._lower)); + } + + /// + public static bool operator >=(Int128 left, Int128 right) + { + // See comment in < operator for how this works. + return ((long)left._upper > (long)right._upper) + || ((left._upper == right._upper) && (left._lower >= right._lower)); + } + + // + // IDecrementOperators + // + + /// + public static Int128 operator --(Int128 value) => value - One; + + /// + /* TODO: + public static Int128 operator checked --(Int128 value) => checked(value - One); + */ + // + // IDivisionOperators + // + + /// + public static Int128 operator /(Int128 left, Int128 right) + { + if ((right == -1) && (left._upper == 0x8000_0000_0000_0000) && (left._lower == 0)) + { + ThrowHelper.ThrowOverflowException(); + } + + // We simplify the logic here by just doing unsigned division on the + // two's complement representation and then taking the correct sign. + + ulong sign = (left._upper ^ right._upper) & (1UL << 63); + + if (IsNegative(left)) + { + left = ~left + 1U; + } + + if (IsNegative(right)) + { + right = ~right + 1U; + } + + UInt128 result = (UInt128)(left) / (UInt128)(right); + + if (sign != 0) + { + result = ~result + 1U; + } + + return new Int128( + result.Upper, + result.Lower + ); + } + + /// + /* TODO: + public static Int128 operator checked /(Int128 left, Int128 right) => left / right; + */ + // + // IEqualityOperators + // + + /// + public static bool operator ==(Int128 left, Int128 right) => (left._lower == right._lower) && (left._upper == right._upper); + + /// + public static bool operator !=(Int128 left, Int128 right) => (left._lower != right._lower) || (left._upper != right._upper); + + // + // IIncrementOperators + // + + /// + public static Int128 operator ++(Int128 value) => value + One; + + /// + /* TODO: + public static Int128 operator checked ++(Int128 value) => checked(value + One); + */ + // + // IMinMaxValue + // + + /// + public static Int128 MinValue => new Int128(0x8000_0000_0000_0000, 0); + + /// + public static Int128 MaxValue => new Int128(0x7FFF_FFFF_FFFF_FFFF, 0xFFFF_FFFF_FFFF_FFFF); + + // + // IModulusOperators + // + + /// + public static Int128 operator %(Int128 left, Int128 right) + { + Int128 quotient = left / right; + return left - (quotient * right); + } + + // + // IMultiplicativeIdentity + // + + /// + static Int128 IMultiplicativeIdentity.MultiplicativeIdentity => One; + + // + // IMultiplyOperators + // + + /// + public static Int128 operator *(Int128 left, Int128 right) + { + // Multiplication is the same for signed and unsigned provided the "upper" bits aren't needed + return (Int128)((UInt128)(left) * (UInt128)(right)); + } + + /// + /* TODO: + public static Int128 operator checked *(Int128 left, Int128 right) + { + Int128 upper = BigMul(left, right, out Int128 lower); + + if (((upper != 0) || (lower < 0)) && ((~upper != 0) || (lower >= 0))) + { + // The upper bits can safely be either Zero or AllBitsSet + // where the former represents a positive value and the + // latter a negative value. + // + // However, when the upper bits are Zero, we also need to + // confirm the lower bits are positive, otherwise we have + // a positive value greater than MaxValue and should throw + // + // Likewise, when the upper bits are AllBitsSet, we also + // need to confirm the lower bits are negative, otherwise + // we have a large negative value less than MinValue and + // should throw. + + ThrowHelper.ThrowOverflowException(); + } + + return lower; + } + */ + + /// Produces the full product of two unsigned native integers. + /// The integer to multiply with . + /// The integer to multiply with . + /// The lower half of the full product. + /// The upper half of the full product. + public static Int128 BigMul(Int128 left, Int128 right, out Int128 lower) + { + // This follows the same logic as is used in `long Math.BigMul(long, long, out long)` + + UInt128 upper = UInt128.BigMul((UInt128)(left), (UInt128)(right), out UInt128 ulower); + lower = (Int128)(ulower); + return (Int128)(upper) - ((left >> 127) & right) - ((right >> 127) & left); + } + + // + // INumber + // + + /// + public static Int128 Clamp(Int128 value, Int128 min, Int128 max) + { + if (min > max) + { + Math.ThrowMinMaxException(min, max); + } + + if (value < min) + { + return min; + } + else if (value > max) + { + return max; + } + + return value; + } + + /// + public static Int128 CopySign(Int128 value, Int128 sign) + { + Int128 absValue = value; + + if (IsNegative(absValue)) + { + absValue = -absValue; + } + + if (IsPositive(sign)) + { + if (IsNegative(absValue)) + { + Math.ThrowNegateTwosCompOverflow(); + } + return absValue; + } + return -absValue; + } + + /// + public static Int128 Max(Int128 x, Int128 y) => (x >= y) ? x : y; + + /// + static Int128 INumber.MaxNumber(Int128 x, Int128 y) => Max(x, y); + + /// + public static Int128 Min(Int128 x, Int128 y) => (x <= y) ? x : y; + + /// + static Int128 INumber.MinNumber(Int128 x, Int128 y) => Min(x, y); + + /// + public static int Sign(Int128 value) + { + if (IsNegative(value)) + { + return -1; + } + else if (value != default) + { + return 1; + } + else + { + return 0; + } + } + + // + // INumberBase + // + + /// + public static Int128 One => new Int128(0, 1); + + /// + static int INumberBase.Radix => 2; + + /// + public static Int128 Zero => default; + + /// + public static Int128 Abs(Int128 value) + { + if (IsNegative(value)) + { + value = -value; + + if (IsNegative(value)) + { + Math.ThrowNegateTwosCompOverflow(); + } + } + return value; + } + + /// + public static inline Int128 CreateChecked(TOther value) + where TOther : INumberBase + { + Int128 result; + + if (typeof(TOther) == typeof(Int128)) + { + result = (Int128)(object)value; + } + else if (!TryConvertFromChecked(value, out result) && !TOther.TryConvertToChecked(value, out result)) + { + ThrowHelper.ThrowNotSupportedException(); + } + + return result; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Int128 CreateSaturating(TOther value) + where TOther : INumberBase + { + Int128 result; + + if (typeof(TOther) == typeof(Int128)) + { + result = (Int128)(object)value; + } + else if (!TryConvertFromSaturating(value, out result) && !TOther.TryConvertToSaturating(value, out result)) + { + ThrowHelper.ThrowNotSupportedException(); + } + + return result; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Int128 CreateTruncating(TOther value) + where TOther : INumberBase + { + Int128 result; + + if (typeof(TOther) == typeof(Int128)) + { + result = (Int128)(object)value; + } + else if (!TryConvertFromTruncating(value, out result) && !TOther.TryConvertToTruncating(value, out result)) + { + ThrowHelper.ThrowNotSupportedException(); + } + + return result; + } + + /// + static bool INumberBase.IsCanonical(Int128 value) => true; + + /// + static bool INumberBase.IsComplexNumber(Int128 value) => false; + + /// + public static bool IsEvenInteger(Int128 value) => (value._lower & 1) == 0; + + /// + static bool INumberBase.IsFinite(Int128 value) => true; + + /// + static bool INumberBase.IsImaginaryNumber(Int128 value) => false; + + /// + static bool INumberBase.IsInfinity(Int128 value) => false; + + /// + static bool INumberBase.IsInteger(Int128 value) => true; + + /// + static bool INumberBase.IsNaN(Int128 value) => false; + + /// + public static bool IsNegative(Int128 value) => (long)value._upper < 0; + + /// + static bool INumberBase.IsNegativeInfinity(Int128 value) => false; + + /// + static bool INumberBase.IsNormal(Int128 value) => value != 0; + + /// + public static bool IsOddInteger(Int128 value) => (value._lower & 1) != 0; + + /// + public static bool IsPositive(Int128 value) => (long)value._upper >= 0; + + /// + static bool INumberBase.IsPositiveInfinity(Int128 value) => false; + + /// + static bool INumberBase.IsRealNumber(Int128 value) => true; + + /// + static bool INumberBase.IsSubnormal(Int128 value) => false; + + /// + static bool INumberBase.IsZero(Int128 value) => (value == 0); + + /// + public static Int128 MaxMagnitude(Int128 x, Int128 y) + { + Int128 absX = x; + + if (IsNegative(absX)) + { + absX = -absX; + + if (IsNegative(absX)) + { + return x; + } + } + + Int128 absY = y; + + if (IsNegative(absY)) + { + absY = -absY; + + if (IsNegative(absY)) + { + return y; + } + } + + if (absX > absY) + { + return x; + } + + if (absX == absY) + { + return IsNegative(x) ? y : x; + } + + return y; + } + + /// + static Int128 INumberBase.MaxMagnitudeNumber(Int128 x, Int128 y) => MaxMagnitude(x, y); + + /// + public static Int128 MinMagnitude(Int128 x, Int128 y) + { + Int128 absX = x; + + if (IsNegative(absX)) + { + absX = -absX; + + if (IsNegative(absX)) + { + return y; + } + } + + Int128 absY = y; + + if (IsNegative(absY)) + { + absY = -absY; + + if (IsNegative(absY)) + { + return x; + } + } + + if (absX < absY) + { + return x; + } + + if (absX == absY) + { + return IsNegative(x) ? x : y; + } + + return y; + } + + /// + static Int128 INumberBase.MinMagnitudeNumber(Int128 x, Int128 y) => MinMagnitude(x, y); + + /// + static Int128 INumberBase.MultiplyAddEstimate(Int128 left, Int128 right, Int128 addend) => (left * right) + addend; + + /// + static inline bool INumberBase.TryConvertFromChecked(TOther value, out Int128 result) => TryConvertFromChecked(value, out result); + + private static inline bool TryConvertFromChecked(TOther value, out Int128 result) + where TOther : INumberBase + { + // In order to reduce overall code duplication and improve the inlinabilty of these + // methods for the corelib types we have `ConvertFrom` handle the same sign and + // `ConvertTo` handle the opposite sign. However, since there is an uneven split + // between signed and unsigned types, the one that handles unsigned will also + // handle `Decimal`. + // + // That is, `ConvertFrom` for `Int128` will handle the other signed types and + // `ConvertTo` will handle the unsigned types + + if (typeof(TOther) == typeof(double)) + { + double actualValue = (double)(object)value; + result = checked((Int128)actualValue); + return true; + } + else if (typeof(TOther) == typeof(Half)) + { + Half actualValue = (Half)(object)value; + result = checked((Int128)actualValue); + return true; + } + else if (typeof(TOther) == typeof(short)) + { + short actualValue = (short)(object)value; + result = actualValue; + return true; + } + else if (typeof(TOther) == typeof(int)) + { + int actualValue = (int)(object)value; + result = actualValue; + return true; + } + else if (typeof(TOther) == typeof(long)) + { + long actualValue = (long)(object)value; + result = actualValue; + return true; + } + else if (typeof(TOther) == typeof(nint)) + { + nint actualValue = (nint)(object)value; + result = actualValue; + return true; + } + else if (typeof(TOther) == typeof(sbyte)) + { + sbyte actualValue = (sbyte)(object)value; + result = actualValue; + return true; + } + else if (typeof(TOther) == typeof(float)) + { + float actualValue = (float)(object)value; + result = checked((Int128)actualValue); + return true; + } + else + { + result = default; + return false; + } + } + + /// + static inline bool INumberBase.TryConvertFromSaturating(TOther value, out Int128 result) => TryConvertFromSaturating(value, out result); + + private static inline bool TryConvertFromSaturating(TOther value, out Int128 result) + where TOther : INumberBase + { + // In order to reduce overall code duplication and improve the inlinabilty of these + // methods for the corelib types we have `ConvertFrom` handle the same sign and + // `ConvertTo` handle the opposite sign. However, since there is an uneven split + // between signed and unsigned types, the one that handles unsigned will also + // handle `Decimal`. + // + // That is, `ConvertFrom` for `Int128` will handle the other signed types and + // `ConvertTo` will handle the unsigned types + + if (typeof(TOther) == typeof(double)) + { + double actualValue = (double)(object)value; + result = (Int128)actualValue; + return true; + } + else if (typeof(TOther) == typeof(Half)) + { + Half actualValue = (Half)(object)value; + result = (Int128)actualValue; + return true; + } + else if (typeof(TOther) == typeof(short)) + { + short actualValue = (short)(object)value; + result = actualValue; + return true; + } + else if (typeof(TOther) == typeof(int)) + { + int actualValue = (int)(object)value; + result = actualValue; + return true; + } + else if (typeof(TOther) == typeof(long)) + { + long actualValue = (long)(object)value; + result = actualValue; + return true; + } + else if (typeof(TOther) == typeof(nint)) + { + nint actualValue = (nint)(object)value; + result = actualValue; + return true; + } + else if (typeof(TOther) == typeof(sbyte)) + { + sbyte actualValue = (sbyte)(object)value; + result = actualValue; + return true; + } + else if (typeof(TOther) == typeof(float)) + { + float actualValue = (float)(object)value; + result = (Int128)actualValue; + return true; + } + else + { + result = default; + return false; + } + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static bool INumberBase.TryConvertFromTruncating(TOther value, out Int128 result) => TryConvertFromTruncating(value, out result); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool TryConvertFromTruncating(TOther value, out Int128 result) + where TOther : INumberBase + { + // In order to reduce overall code duplication and improve the inlinabilty of these + // methods for the corelib types we have `ConvertFrom` handle the same sign and + // `ConvertTo` handle the opposite sign. However, since there is an uneven split + // between signed and unsigned types, the one that handles unsigned will also + // handle `Decimal`. + // + // That is, `ConvertFrom` for `Int128` will handle the other signed types and + // `ConvertTo` will handle the unsigned types + + if (typeof(TOther) == typeof(double)) + { + double actualValue = (double)(object)value; + result = (Int128)actualValue; + return true; + } + else if (typeof(TOther) == typeof(Half)) + { + Half actualValue = (Half)(object)value; + result = (Int128)actualValue; + return true; + } + else if (typeof(TOther) == typeof(short)) + { + short actualValue = (short)(object)value; + result = actualValue; + return true; + } + else if (typeof(TOther) == typeof(int)) + { + int actualValue = (int)(object)value; + result = actualValue; + return true; + } + else if (typeof(TOther) == typeof(long)) + { + long actualValue = (long)(object)value; + result = actualValue; + return true; + } + else if (typeof(TOther) == typeof(nint)) + { + nint actualValue = (nint)(object)value; + result = actualValue; + return true; + } + else if (typeof(TOther) == typeof(sbyte)) + { + sbyte actualValue = (sbyte)(object)value; + result = actualValue; + return true; + } + else if (typeof(TOther) == typeof(float)) + { + float actualValue = (float)(object)value; + result = (Int128)actualValue; + return true; + } + else + { + result = default; + return false; + } + } + + /// + static inline bool INumberBase.TryConvertToChecked(Int128 value, [MaybeNullWhen(false)] out TOther result) + { + // In order to reduce overall code duplication and improve the inlinabilty of these + // methods for the corelib types we have `ConvertFrom` handle the same sign and + // `ConvertTo` handle the opposite sign. However, since there is an uneven split + // between signed and unsigned types, the one that handles unsigned will also + // handle `Decimal`. + // + // That is, `ConvertFrom` for `Int128` will handle the other signed types and + // `ConvertTo` will handle the unsigned types + + if (typeof(TOther) == typeof(byte)) + { + byte actualResult = checked((byte)value); + result = (TOther)(object)actualResult; + return true; + } + else if (typeof(TOther) == typeof(char)) + { + char actualResult = checked((char)value); + result = (TOther)(object)actualResult; + return true; + } + else if (typeof(TOther) == typeof(decimal)) + { + decimal actualResult = checked((decimal)value); + result = (TOther)(object)actualResult; + return true; + } + else if (typeof(TOther) == typeof(ushort)) + { + ushort actualResult = checked((ushort)value); + result = (TOther)(object)actualResult; + return true; + } + else if (typeof(TOther) == typeof(uint)) + { + uint actualResult = checked((uint)value); + result = (TOther)(object)actualResult; + return true; + } + else if (typeof(TOther) == typeof(ulong)) + { + ulong actualResult = checked((ulong)value); + result = (TOther)(object)actualResult; + return true; + } + else if (typeof(TOther) == typeof(UInt128)) + { + UInt128 actualResult = checked((UInt128)value); + result = (TOther)(object)actualResult; + return true; + } + else if (typeof(TOther) == typeof(nuint)) + { + nuint actualResult = checked((nuint)value); + result = (TOther)(object)actualResult; + return true; + } + else + { + result = default; + return false; + } + } + + /// + static inline bool INumberBase.TryConvertToSaturating(Int128 value, [MaybeNullWhen(false)] out TOther result) + { + // In order to reduce overall code duplication and improve the inlinabilty of these + // methods for the corelib types we have `ConvertFrom` handle the same sign and + // `ConvertTo` handle the opposite sign. However, since there is an uneven split + // between signed and unsigned types, the one that handles unsigned will also + // handle `Decimal`. + // + // That is, `ConvertFrom` for `Int128` will handle the other signed types and + // `ConvertTo` will handle the unsigned types + + if (typeof(TOther) == typeof(byte)) + { + byte actualResult = (value >= byte.MaxValue) ? byte.MaxValue : + (value <= byte.MinValue) ? byte.MinValue : (byte)value; + result = (TOther)(object)actualResult; + return true; + } + else if (typeof(TOther) == typeof(char)) + { + char actualResult = (value >= char.MaxValue) ? char.MaxValue : + (value <= char.MinValue) ? char.MinValue : (char)value; + result = (TOther)(object)actualResult; + return true; + } + else if (typeof(TOther) == typeof(decimal)) + { + decimal actualResult = (value >= new Int128(0x0000_0000_FFFF_FFFF, 0xFFFF_FFFF_FFFF_FFFF)) ? decimal.MaxValue : + (value <= new Int128(0xFFFF_FFFF_0000_0000, 0x0000_0000_0000_0001)) ? decimal.MinValue : (decimal)value; + result = (TOther)(object)actualResult; + return true; + } + else if (typeof(TOther) == typeof(ushort)) + { + ushort actualResult = (value >= ushort.MaxValue) ? ushort.MaxValue : + (value <= ushort.MinValue) ? ushort.MinValue : (ushort)value; + result = (TOther)(object)actualResult; + return true; + } + else if (typeof(TOther) == typeof(uint)) + { + uint actualResult = (value >= uint.MaxValue) ? uint.MaxValue : + (value <= uint.MinValue) ? uint.MinValue : (uint)value; + result = (TOther)(object)actualResult; + return true; + } + else if (typeof(TOther) == typeof(ulong)) + { + ulong actualResult = (value >= ulong.MaxValue) ? ulong.MaxValue : + (value <= ulong.MinValue) ? ulong.MinValue : (ulong)value; + result = (TOther)(object)actualResult; + return true; + } + else if (typeof(TOther) == typeof(UInt128)) + { + UInt128 actualResult = (value <= 0) ? UInt128.MinValue : (UInt128)value; + result = (TOther)(object)actualResult; + return true; + } + else if (typeof(TOther) == typeof(nuint)) + { + nuint actualResult = (value >= nuint.MaxValue) ? nuint.MaxValue : + (value <= nuint.MinValue) ? nuint.MinValue : (nuint)value; + result = (TOther)(object)actualResult; + return true; + } + else + { + result = default; + return false; + } + } + + /// + static inline bool INumberBase.TryConvertToTruncating(Int128 value, [MaybeNullWhen(false)] out TOther result) + { + // In order to reduce overall code duplication and improve the inlinabilty of these + // methods for the corelib types we have `ConvertFrom` handle the same sign and + // `ConvertTo` handle the opposite sign. However, since there is an uneven split + // between signed and unsigned types, the one that handles unsigned will also + // handle `Decimal`. + // + // That is, `ConvertFrom` for `Int128` will handle the other signed types and + // `ConvertTo` will handle the unsigned types + + if (typeof(TOther) == typeof(byte)) + { + byte actualResult = (byte)value; + result = (TOther)(object)actualResult; + return true; + } + else if (typeof(TOther) == typeof(char)) + { + char actualResult = (char)value; + result = (TOther)(object)actualResult; + return true; + } + else if (typeof(TOther) == typeof(decimal)) + { + decimal actualResult = (value >= new Int128(0x0000_0000_FFFF_FFFF, 0xFFFF_FFFF_FFFF_FFFF)) ? decimal.MaxValue : + (value <= new Int128(0xFFFF_FFFF_0000_0000, 0x0000_0000_0000_0001)) ? decimal.MinValue : (decimal)value; + result = (TOther)(object)actualResult; + return true; + } + else if (typeof(TOther) == typeof(ushort)) + { + ushort actualResult = (ushort)value; + result = (TOther)(object)actualResult; + return true; + } + else if (typeof(TOther) == typeof(uint)) + { + uint actualResult = (uint)value; + result = (TOther)(object)actualResult; + return true; + } + else if (typeof(TOther) == typeof(ulong)) + { + ulong actualResult = (ulong)value; + result = (TOther)(object)actualResult; + return true; + } + else if (typeof(TOther) == typeof(UInt128)) + { + UInt128 actualResult = (UInt128)value; + result = (TOther)(object)actualResult; + return true; + } + else if (typeof(TOther) == typeof(nuint)) + { + nuint actualResult = (nuint)value; + result = (TOther)(object)actualResult; + return true; + } + else + { + result = default; + return false; + } + } + + // + // IParsable + // + + /// + public static bool TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, out Int128 result) => TryParse(s, NumberStyles.Integer, provider, out result); + + // + // IShiftOperators + // + + /// + public static Int128 operator <<(Int128 value, int shiftAmount) + { + // C# automatically masks the shift amount for UInt64 to be 0x3F. So we + // need to specially handle things if the 7th bit is set. + + shiftAmount &= 0x7F; + + if ((shiftAmount & 0x40) != 0) + { + // In the case it is set, we know the entire lower bits must be zero + // and so the upper bits are just the lower shifted by the remaining + // masked amount + + ulong upper = value._lower << shiftAmount; + return new Int128(upper, 0); + } + else if (shiftAmount != 0) + { + // Otherwise we need to shift both upper and lower halves by the masked + // amount and then or that with whatever bits were shifted "out" of lower + + ulong lower = value._lower << shiftAmount; + ulong upper = (value._upper << shiftAmount) | (value._lower >> (64 - shiftAmount)); + + return new Int128(upper, lower); + } + else + { + return value; + } + } + + /// + public static Int128 operator >>(Int128 value, int shiftAmount) + { + // C# automatically masks the shift amount for UInt64 to be 0x3F. So we + // need to specially handle things if the 7th bit is set. + + shiftAmount &= 0x7F; + + if ((shiftAmount & 0x40) != 0) + { + // In the case it is set, we know the entire upper bits must be the sign + // and so the lower bits are just the upper shifted by the remaining + // masked amount + + ulong lower = (ulong)((long)value._upper >> shiftAmount); + ulong upper = (ulong)((long)value._upper >> 63); + + return new Int128(upper, lower); + } + else if (shiftAmount != 0) + { + // Otherwise we need to shift both upper and lower halves by the masked + // amount and then or that with whatever bits were shifted "out" of upper + + ulong lower = (value._lower >> shiftAmount) | (value._upper << (64 - shiftAmount)); + ulong upper = (ulong)((long)value._upper >> shiftAmount); + + return new Int128(upper, lower); + } + else + { + return value; + } + } + + /* TODO: + /// + public static Int128 operator >>>(Int128 value, int shiftAmount) + { + // C# automatically masks the shift amount for UInt64 to be 0x3F. So we + // need to specially handle things if the 7th bit is set. + + shiftAmount &= 0x7F; + + if ((shiftAmount & 0x40) != 0) + { + // In the case it is set, we know the entire upper bits must be zero + // and so the lower bits are just the upper shifted by the remaining + // masked amount + + ulong lower = value._upper >> shiftAmount; + return new Int128(0, lower); + } + else if (shiftAmount != 0) + { + // Otherwise we need to shift both upper and lower halves by the masked + // amount and then or that with whatever bits were shifted "out" of upper + + ulong lower = (value._lower >> shiftAmount) | (value._upper << (64 - shiftAmount)); + ulong upper = value._upper >> shiftAmount; + + return new Int128(upper, lower); + } + else + { + return value; + } + } + */ + + // + // ISignedNumber + // + + /// + public static Int128 NegativeOne => new Int128(0xFFFF_FFFF_FFFF_FFFF, 0xFFFF_FFFF_FFFF_FFFF); + + // + // ISpanParsable + // + + /// + public static Int128 Parse(ReadOnlySpan s, IFormatProvider? provider) => Parse(s, NumberStyles.Integer, provider); + + /// + public static bool TryParse(ReadOnlySpan s, IFormatProvider? provider, out Int128 result) => TryParse(s, NumberStyles.Integer, provider, out result); + + // + // ISubtractionOperators + // + + /// + public static Int128 operator -(Int128 left, Int128 right) + { + // For unsigned subtract, we can detect overflow by checking `(x - y) > x` + // This gives us the borrow to subtract from upper to compute the correct result + + ulong lower = left._lower - right._lower; + ulong borrow = (lower > left._lower) ? 1UL : 0UL; + + ulong upper = left._upper - right._upper - borrow; + return new Int128(upper, lower); + } + + /// + /* TODO: + public static Int128 operator checked -(Int128 left, Int128 right) + { + // For signed subtraction, we can detect overflow by checking if the sign of + // both inputs are different and then if that differs from the sign of the + // output. The logic for how this works is explained in the + // System.Runtime.Intrinsics.Scalar.SubtractSaturate method + + Int128 result = left - right; + + if ((long)((result._upper ^ left._upper) & (left._upper ^ right._upper)) < 0) + { + ThrowHelper.ThrowOverflowException(); + } + return result; + } + */ + + // + // IUnaryNegationOperators + // + + /// + public static Int128 operator -(Int128 value) => Zero - value; + + /// + /* TODO: + public static Int128 operator checked -(Int128 value) => checked(Zero - value); + */ + // + // IUnaryPlusOperators + // + + /// + public static Int128 operator +(Int128 value) => value; + + // + // IUtf8SpanParsable + // + + /// + public static Int128 Parse(ReadOnlySpan utf8Text, NumberStyles style = NumberStyles.Integer, IFormatProvider? provider = null) + { + NumberFormatInfo.ValidateParseStyleInteger(style); + return Number.ParseBinaryInteger(utf8Text, style, NumberFormatInfo.GetInstance(provider)); + } + + /// + public static bool TryParse(ReadOnlySpan utf8Text, NumberStyles style, IFormatProvider? provider, out Int128 result) + { + NumberFormatInfo.ValidateParseStyleInteger(style); + return Number.TryParseBinaryInteger(utf8Text, style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; + } + + /// + public static Int128 Parse(ReadOnlySpan utf8Text, IFormatProvider? provider) => Parse(utf8Text, NumberStyles.Integer, provider); + + /// + public static bool TryParse(ReadOnlySpan utf8Text, IFormatProvider? provider, out Int128 result) => TryParse(utf8Text, NumberStyles.Integer, provider, out result); + + // + // IBinaryIntegerParseAndFormatInfo + // + + static bool IBinaryIntegerParseAndFormatInfo.IsSigned => true; + + static int IBinaryIntegerParseAndFormatInfo.MaxDigitCount => 39; // 170_141_183_460_469_231_731_687_303_715_884_105_727 + + static int IBinaryIntegerParseAndFormatInfo.MaxHexDigitCount => 32; // 0x7FFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF + + static Int128 IBinaryIntegerParseAndFormatInfo.MaxValueDiv10 => new Int128(0x0CCC_CCCC_CCCC_CCCC, 0xCCCC_CCCC_CCCC_CCCC); + + static string IBinaryIntegerParseAndFormatInfo.OverflowMessage => SR.Overflow_Int128; + + static bool IBinaryIntegerParseAndFormatInfo.IsGreaterThanAsUnsigned(Int128 left, Int128 right) => (UInt128)(left) > (UInt128)(right); + + static Int128 IBinaryIntegerParseAndFormatInfo.MultiplyBy10(Int128 value) => value * 10; + + static Int128 IBinaryIntegerParseAndFormatInfo.MultiplyBy16(Int128 value) => value * 16; +} \ No newline at end of file diff --git a/std/System/TimeSpan.hpt b/std/System/TimeSpan.hpt index 78470071..e4cd9992 100644 --- a/std/System/TimeSpan.hpt +++ b/std/System/TimeSpan.hpt @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. // Some modifications were made for hapet-lang compatibility. +using System; + // TimeSpan represents a duration of time. A TimeSpan can be negative // or positive. // @@ -14,12 +16,7 @@ // we do not provide Years() or Months(). // public readonly struct TimeSpan - : IComparable, - IComparable, - IEquatable, - ISpanFormattable, - ISpanParsable, - IUtf8SpanFormattable + : IComparable, IComparable, IEquatable { /// /// Represents the number of nanoseconds per tick. This field is constant. @@ -387,9 +384,9 @@ public readonly struct TimeSpan public static int Compare(TimeSpan t1, TimeSpan t2) => t1._ticks.CompareTo(t2._ticks); // Returns a value less than zero if this object - public int CompareTo(object? value) + public int CompareTo(object value) { - if (value is null) + if (value == null) { return 1; } @@ -415,7 +412,12 @@ public readonly struct TimeSpan return new TimeSpan(_ticks >= 0 ? _ticks : -_ticks); } - public override bool Equals([NotNullWhen(true)] object? value) => (value is TimeSpan other) && Equals(other); + public override bool Equals(object value) + { + if (value is TimeSpan other) + return Equals(other); + return false; + } public bool Equals(TimeSpan obj) => Equals(this, obj); @@ -425,8 +427,9 @@ public readonly struct TimeSpan private static inline TimeSpan FromUnits(long units, long ticksPerUnit, long minUnits, long maxUnits) { - System.Diagnostics.Debug.Assert(minUnits < 0); - System.Diagnostics.Debug.Assert(maxUnits > 0); + // TODO: + // System.Diagnostics.Debug.Assert(minUnits < 0); + // System.Diagnostics.Debug.Assert(maxUnits > 0); if (units > maxUnits || units < minUnits) { @@ -787,7 +790,7 @@ public readonly struct TimeSpan public static bool TryParse(ReadOnlySpan input, IFormatProvider formatProvider, out TimeSpan result) => TimeSpanParse.TryParse(input, formatProvider, out result); public static bool TryParseExact(string input, string format, IFormatProvider formatProvider, out TimeSpan result) { - if (input is null || format is null) + if (input == null || format == null) { result = default; return false; @@ -800,7 +803,7 @@ public readonly struct TimeSpan public static bool TryParseExact(string input, string[] formats, IFormatProvider formatProvider, out TimeSpan result) { - if (input is null) + if (input == null) { result = default; return false; @@ -814,7 +817,7 @@ public readonly struct TimeSpan { ValidateStyles(styles); - if (input is null || format is null) + if (input == null || format == null) { result = default; return false; @@ -831,7 +834,7 @@ public readonly struct TimeSpan { ValidateStyles(styles); - if (input is null) + if (input == null) { result = default; return false; From f47b95d8883067230d11bb280cc5e63926bf4be8 Mon Sep 17 00:00:00 2001 From: Airat Abdrakov <52558686+CrackAndDie@users.noreply.github.com> Date: Wed, 15 Apr 2026 15:17:43 +0300 Subject: [PATCH 05/26] Small changes in Int128 --- std/System/Int128.hpt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/std/System/Int128.hpt b/std/System/Int128.hpt index c6cc8dda..ed0cd7ca 100644 --- a/std/System/Int128.hpt +++ b/std/System/Int128.hpt @@ -34,7 +34,7 @@ public readonly struct Int128 { return CompareTo(other); } - else if (value is null) + else if (value == null) { return 1; } @@ -92,7 +92,7 @@ public readonly struct Int128 return Number.FormatInt128(this, format, null); } - public string ToString(string format, IFormatProvider? provider) + public string ToString(string format, IFormatProvider provider) { return Number.FormatInt128(this, format, provider); } From 958991803c82f6f3478a0ed7746a8b7661a55186 Mon Sep 17 00:00:00 2001 From: Airat Abdrakov <52558686+CrackAndDie@users.noreply.github.com> Date: Wed, 15 Apr 2026 15:50:23 +0300 Subject: [PATCH 06/26] Many lines removed from Int128 --- std/System/Int128.hpt | 558 ++---------------------------------------- 1 file changed, 18 insertions(+), 540 deletions(-) diff --git a/std/System/Int128.hpt b/std/System/Int128.hpt index ed0cd7ca..fcd3ad3b 100644 --- a/std/System/Int128.hpt +++ b/std/System/Int128.hpt @@ -303,7 +303,7 @@ public readonly struct Int128 /// Explicitly converts a 128-bit signed integer to a value. /// The value to convert. /// converted to a . - public static explicit operator nint(Int128 value) => (nint)value._lower; + public static explicit operator uintptr(Int128 value) => (uintptr)value._lower; /// Explicitly converts a 128-bit signed integer to a value, throwing an overflow exception for any values that fall outside the representable range. /// The value to convert. @@ -438,7 +438,7 @@ public readonly struct Int128 /// Explicitly converts a 128-bit signed integer to a value. /// The value to convert. /// converted to a . - public static explicit operator nuint(Int128 value) => (nuint)value._lower; + public static explicit operator uintptr(Int128 value) => (uintptr)value._lower; /// Explicitly converts a 128-bit signed integer to a value, throwing an overflow exception for any values that fall outside the representable range. /// The value to convert. @@ -519,9 +519,10 @@ public readonly struct Int128 { const double TwoPow127 = 170141183460469231731687303715884105728.0; - Debug.Assert(value >= -TwoPow127); - Debug.Assert(double.IsFinite(value)); - Debug.Assert(value < TwoPow127); + // TODO: + // Debug.Assert(value >= -TwoPow127); + // Debug.Assert(double.IsFinite(value)); + // Debug.Assert(value < TwoPow127); // This code is based on `f64_to_i128` from m-ou-se/floatconv // Copyright (c) 2020 Mara Bos . All rights reserved. @@ -546,7 +547,8 @@ public readonly struct Int128 ulong bits = BitConverter.DoubleToUInt64Bits(value); Int128 result = new Int128((bits << 12) >> 1 | 0x8000_0000_0000_0000, 0x0000_0000_0000_0000); - result >>>= (1023 + 128 - 1 - (int)(bits >> 52)); + // TODO: + // result >>>= (1023 + 128 - 1 - (int)(bits >> 52)); if (isNegative) { @@ -757,224 +759,6 @@ public readonly struct Int128 return ulong.TrailingZeroCount(value._lower); } - /// - static bool IBinaryInteger.TryReadBigEndian(ReadOnlySpan source, bool isUnsigned, out Int128 value) - { - Int128 result = default; - - if (source.Length != 0) - { - // Propagate the most significant bit so we have `0` or `-1` - sbyte sign = (sbyte)(source[0]); - sign >>= 31; - Debug.Assert((sign == 0) || (sign == -1)); - - // We need to also track if the input data is unsigned - isUnsigned |= (sign == 0); - - if (isUnsigned && sbyte.IsNegative(sign) && (source.Length >= Size)) - { - // When we are unsigned and the most significant bit is set, we are a large positive - // and therefore definitely out of range - - value = result; - return false; - } - - if (source.Length > Size) - { - if (source[..^Size].ContainsAnyExcept((byte)sign)) - { - // When we are unsigned and have any non-zero leading data or signed with any non-set leading - // data, we are a large positive/negative, respectively, and therefore definitely out of range - - value = result; - return false; - } - - if (isUnsigned == sbyte.IsNegative((sbyte)source[^Size])) - { - // When the most significant bit of the value being set/clear matches whether we are unsigned - // or signed then we are a large positive/negative and therefore definitely out of range - - value = result; - return false; - } - } - - ref byte sourceRef = ref MemoryMarshal.GetReference(source); - - if (source.Length >= Size) - { - sourceRef = ref Unsafe.Add(ref sourceRef, source.Length - Size); - - // We have at least 16 bytes, so just read the ones we need directly - result = Unsafe.ReadUnaligned(ref sourceRef); - - if (BitConverter.IsLittleEndian) - { - result = BinaryPrimitives.ReverseEndianness(result); - } - } - else - { - // We have between 1 and 15 bytes, so construct the relevant value directly - // since the data is in Big Endian format, we can just read the bytes and - // shift left by 8-bits for each subsequent part - - for (int i = 0; i < source.Length; i++) - { - result <<= 8; - result |= Unsafe.Add(ref sourceRef, i); - } - - if (!isUnsigned) - { - result |= ((One << ((Size * 8) - 1)) >> (((Size - source.Length) * 8) - 1)); - } - } - } - - value = result; - return true; - } - - /// - static bool IBinaryInteger.TryReadLittleEndian(ReadOnlySpan source, bool isUnsigned, out Int128 value) - { - Int128 result = default; - - if (source.Length != 0) - { - // Propagate the most significant bit so we have `0` or `-1` - sbyte sign = (sbyte)(source[^1]); - sign >>= 31; - Debug.Assert((sign == 0) || (sign == -1)); - - // We need to also track if the input data is unsigned - isUnsigned |= (sign == 0); - - if (isUnsigned && sbyte.IsNegative(sign) && (source.Length >= Size)) - { - // When we are unsigned and the most significant bit is set, we are a large positive - // and therefore definitely out of range - - value = result; - return false; - } - - if (source.Length > Size) - { - if (source[Size..].ContainsAnyExcept((byte)sign)) - { - // When we are unsigned and have any non-zero leading data or signed with any non-set leading - // data, we are a large positive/negative, respectively, and therefore definitely out of range - - value = result; - return false; - } - - if (isUnsigned == sbyte.IsNegative((sbyte)source[Size - 1])) - { - // When the most significant bit of the value being set/clear matches whether we are unsigned - // or signed then we are a large positive/negative and therefore definitely out of range - - value = result; - return false; - } - } - - ref byte sourceRef = ref MemoryMarshal.GetReference(source); - - if (source.Length >= Size) - { - // We have at least 16 bytes, so just read the ones we need directly - result = Unsafe.ReadUnaligned(ref sourceRef); - - if (!BitConverter.IsLittleEndian) - { - result = BinaryPrimitives.ReverseEndianness(result); - } - } - else - { - // We have between 1 and 15 bytes, so construct the relevant value directly - // since the data is in Little Endian format, we can just read the bytes and - // shift left by 8-bits for each subsequent part, then reverse endianness to - // ensure the order is correct. This is more efficient than iterating in reverse - // due to current JIT limitations - - for (int i = 0; i < source.Length; i++) - { - result <<= 8; - result |= Unsafe.Add(ref sourceRef, i); - } - - result <<= ((Size - source.Length) * 8); - result = BinaryPrimitives.ReverseEndianness(result); - - if (!isUnsigned) - { - result |= ((One << ((Size * 8) - 1)) >> (((Size - source.Length) * 8) - 1)); - } - } - } - - value = result; - return true; - } - - /// - int IBinaryInteger.GetShortestBitLength() - { - Int128 value = this; - - if (IsPositive(value)) - { - return (Size * 8) - LeadingZeroCountAsInt32(value); - } - else - { - return (Size * 8) + 1 - LeadingZeroCountAsInt32(~value); - } - } - - /// - int IBinaryInteger.GetByteCount() => Size; - - /// - bool IBinaryInteger.TryWriteBigEndian(Span destination, out int bytesWritten) - { - if (BinaryPrimitives.TryWriteInt128BigEndian(destination, this)) - { - bytesWritten = Size; - return true; - } - - bytesWritten = 0; - return false; - } - - /// - bool IBinaryInteger.TryWriteLittleEndian(Span destination, out int bytesWritten) - { - if (BinaryPrimitives.TryWriteInt128LittleEndian(destination, this)) - { - bytesWritten = Size; - return true; - } - - bytesWritten = 0; - return false; - } - - // - // IBinaryNumber - // - - /// - static Int128 IBinaryNumber.AllBitsSet => new Int128(0xFFFF_FFFF_FFFF_FFFF, 0xFFFF_FFFF_FFFF_FFFF); - /// public static bool IsPow2(Int128 value) => (PopCount(value) == 1U) && IsPositive(value); @@ -1148,13 +932,6 @@ public readonly struct Int128 return left - (quotient * right); } - // - // IMultiplicativeIdentity - // - - /// - static Int128 IMultiplicativeIdentity.MultiplicativeIdentity => One; - // // IMultiplyOperators // @@ -1256,15 +1033,9 @@ public readonly struct Int128 /// public static Int128 Max(Int128 x, Int128 y) => (x >= y) ? x : y; - /// - static Int128 INumber.MaxNumber(Int128 x, Int128 y) => Max(x, y); - /// public static Int128 Min(Int128 x, Int128 y) => (x <= y) ? x : y; - /// - static Int128 INumber.MinNumber(Int128 x, Int128 y) => Min(x, y); - /// public static int Sign(Int128 value) { @@ -1289,9 +1060,6 @@ public readonly struct Int128 /// public static Int128 One => new Int128(0, 1); - /// - static int INumberBase.Radix => 2; - /// public static Int128 Zero => default; @@ -1329,8 +1097,7 @@ public readonly struct Int128 } /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Int128 CreateSaturating(TOther value) + public static inline Int128 CreateSaturating(TOther value) where TOther : INumberBase { Int128 result; @@ -1348,8 +1115,7 @@ public readonly struct Int128 } /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Int128 CreateTruncating(TOther value) + public static inline Int128 CreateTruncating(TOther value) where TOther : INumberBase { Int128 result; @@ -1366,57 +1132,18 @@ public readonly struct Int128 return result; } - /// - static bool INumberBase.IsCanonical(Int128 value) => true; - - /// - static bool INumberBase.IsComplexNumber(Int128 value) => false; - /// public static bool IsEvenInteger(Int128 value) => (value._lower & 1) == 0; - /// - static bool INumberBase.IsFinite(Int128 value) => true; - - /// - static bool INumberBase.IsImaginaryNumber(Int128 value) => false; - - /// - static bool INumberBase.IsInfinity(Int128 value) => false; - - /// - static bool INumberBase.IsInteger(Int128 value) => true; - - /// - static bool INumberBase.IsNaN(Int128 value) => false; - /// public static bool IsNegative(Int128 value) => (long)value._upper < 0; - /// - static bool INumberBase.IsNegativeInfinity(Int128 value) => false; - - /// - static bool INumberBase.IsNormal(Int128 value) => value != 0; - /// public static bool IsOddInteger(Int128 value) => (value._lower & 1) != 0; /// public static bool IsPositive(Int128 value) => (long)value._upper >= 0; - /// - static bool INumberBase.IsPositiveInfinity(Int128 value) => false; - - /// - static bool INumberBase.IsRealNumber(Int128 value) => true; - - /// - static bool INumberBase.IsSubnormal(Int128 value) => false; - - /// - static bool INumberBase.IsZero(Int128 value) => (value == 0); - /// public static Int128 MaxMagnitude(Int128 x, Int128 y) { @@ -1457,9 +1184,6 @@ public readonly struct Int128 return y; } - /// - static Int128 INumberBase.MaxMagnitudeNumber(Int128 x, Int128 y) => MaxMagnitude(x, y); - /// public static Int128 MinMagnitude(Int128 x, Int128 y) { @@ -1500,15 +1224,6 @@ public readonly struct Int128 return y; } - /// - static Int128 INumberBase.MinMagnitudeNumber(Int128 x, Int128 y) => MinMagnitude(x, y); - - /// - static Int128 INumberBase.MultiplyAddEstimate(Int128 left, Int128 right, Int128 addend) => (left * right) + addend; - - /// - static inline bool INumberBase.TryConvertFromChecked(TOther value, out Int128 result) => TryConvertFromChecked(value, out result); - private static inline bool TryConvertFromChecked(TOther value, out Int128 result) where TOther : INumberBase { @@ -1576,9 +1291,6 @@ public readonly struct Int128 } } - /// - static inline bool INumberBase.TryConvertFromSaturating(TOther value, out Int128 result) => TryConvertFromSaturating(value, out result); - private static inline bool TryConvertFromSaturating(TOther value, out Int128 result) where TOther : INumberBase { @@ -1621,9 +1333,9 @@ public readonly struct Int128 result = actualValue; return true; } - else if (typeof(TOther) == typeof(nint)) + else if (typeof(TOther) == typeof(uintptr)) { - nint actualValue = (nint)(object)value; + uintptr actualValue = (uintptr)(object)value; result = actualValue; return true; } @@ -1646,12 +1358,7 @@ public readonly struct Int128 } } - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertFromTruncating(TOther value, out Int128 result) => TryConvertFromTruncating(value, out result); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool TryConvertFromTruncating(TOther value, out Int128 result) + private static inline bool TryConvertFromTruncating(TOther value, out Int128 result) where TOther : INumberBase { // In order to reduce overall code duplication and improve the inlinabilty of these @@ -1693,9 +1400,9 @@ public readonly struct Int128 result = actualValue; return true; } - else if (typeof(TOther) == typeof(nint)) + else if (typeof(TOther) == typeof(uintptr)) { - nint actualValue = (nint)(object)value; + uintptr actualValue = (uintptr)(object)value; result = actualValue; return true; } @@ -1718,221 +1425,12 @@ public readonly struct Int128 } } - /// - static inline bool INumberBase.TryConvertToChecked(Int128 value, [MaybeNullWhen(false)] out TOther result) - { - // In order to reduce overall code duplication and improve the inlinabilty of these - // methods for the corelib types we have `ConvertFrom` handle the same sign and - // `ConvertTo` handle the opposite sign. However, since there is an uneven split - // between signed and unsigned types, the one that handles unsigned will also - // handle `Decimal`. - // - // That is, `ConvertFrom` for `Int128` will handle the other signed types and - // `ConvertTo` will handle the unsigned types - - if (typeof(TOther) == typeof(byte)) - { - byte actualResult = checked((byte)value); - result = (TOther)(object)actualResult; - return true; - } - else if (typeof(TOther) == typeof(char)) - { - char actualResult = checked((char)value); - result = (TOther)(object)actualResult; - return true; - } - else if (typeof(TOther) == typeof(decimal)) - { - decimal actualResult = checked((decimal)value); - result = (TOther)(object)actualResult; - return true; - } - else if (typeof(TOther) == typeof(ushort)) - { - ushort actualResult = checked((ushort)value); - result = (TOther)(object)actualResult; - return true; - } - else if (typeof(TOther) == typeof(uint)) - { - uint actualResult = checked((uint)value); - result = (TOther)(object)actualResult; - return true; - } - else if (typeof(TOther) == typeof(ulong)) - { - ulong actualResult = checked((ulong)value); - result = (TOther)(object)actualResult; - return true; - } - else if (typeof(TOther) == typeof(UInt128)) - { - UInt128 actualResult = checked((UInt128)value); - result = (TOther)(object)actualResult; - return true; - } - else if (typeof(TOther) == typeof(nuint)) - { - nuint actualResult = checked((nuint)value); - result = (TOther)(object)actualResult; - return true; - } - else - { - result = default; - return false; - } - } - - /// - static inline bool INumberBase.TryConvertToSaturating(Int128 value, [MaybeNullWhen(false)] out TOther result) - { - // In order to reduce overall code duplication and improve the inlinabilty of these - // methods for the corelib types we have `ConvertFrom` handle the same sign and - // `ConvertTo` handle the opposite sign. However, since there is an uneven split - // between signed and unsigned types, the one that handles unsigned will also - // handle `Decimal`. - // - // That is, `ConvertFrom` for `Int128` will handle the other signed types and - // `ConvertTo` will handle the unsigned types - - if (typeof(TOther) == typeof(byte)) - { - byte actualResult = (value >= byte.MaxValue) ? byte.MaxValue : - (value <= byte.MinValue) ? byte.MinValue : (byte)value; - result = (TOther)(object)actualResult; - return true; - } - else if (typeof(TOther) == typeof(char)) - { - char actualResult = (value >= char.MaxValue) ? char.MaxValue : - (value <= char.MinValue) ? char.MinValue : (char)value; - result = (TOther)(object)actualResult; - return true; - } - else if (typeof(TOther) == typeof(decimal)) - { - decimal actualResult = (value >= new Int128(0x0000_0000_FFFF_FFFF, 0xFFFF_FFFF_FFFF_FFFF)) ? decimal.MaxValue : - (value <= new Int128(0xFFFF_FFFF_0000_0000, 0x0000_0000_0000_0001)) ? decimal.MinValue : (decimal)value; - result = (TOther)(object)actualResult; - return true; - } - else if (typeof(TOther) == typeof(ushort)) - { - ushort actualResult = (value >= ushort.MaxValue) ? ushort.MaxValue : - (value <= ushort.MinValue) ? ushort.MinValue : (ushort)value; - result = (TOther)(object)actualResult; - return true; - } - else if (typeof(TOther) == typeof(uint)) - { - uint actualResult = (value >= uint.MaxValue) ? uint.MaxValue : - (value <= uint.MinValue) ? uint.MinValue : (uint)value; - result = (TOther)(object)actualResult; - return true; - } - else if (typeof(TOther) == typeof(ulong)) - { - ulong actualResult = (value >= ulong.MaxValue) ? ulong.MaxValue : - (value <= ulong.MinValue) ? ulong.MinValue : (ulong)value; - result = (TOther)(object)actualResult; - return true; - } - else if (typeof(TOther) == typeof(UInt128)) - { - UInt128 actualResult = (value <= 0) ? UInt128.MinValue : (UInt128)value; - result = (TOther)(object)actualResult; - return true; - } - else if (typeof(TOther) == typeof(nuint)) - { - nuint actualResult = (value >= nuint.MaxValue) ? nuint.MaxValue : - (value <= nuint.MinValue) ? nuint.MinValue : (nuint)value; - result = (TOther)(object)actualResult; - return true; - } - else - { - result = default; - return false; - } - } - - /// - static inline bool INumberBase.TryConvertToTruncating(Int128 value, [MaybeNullWhen(false)] out TOther result) - { - // In order to reduce overall code duplication and improve the inlinabilty of these - // methods for the corelib types we have `ConvertFrom` handle the same sign and - // `ConvertTo` handle the opposite sign. However, since there is an uneven split - // between signed and unsigned types, the one that handles unsigned will also - // handle `Decimal`. - // - // That is, `ConvertFrom` for `Int128` will handle the other signed types and - // `ConvertTo` will handle the unsigned types - - if (typeof(TOther) == typeof(byte)) - { - byte actualResult = (byte)value; - result = (TOther)(object)actualResult; - return true; - } - else if (typeof(TOther) == typeof(char)) - { - char actualResult = (char)value; - result = (TOther)(object)actualResult; - return true; - } - else if (typeof(TOther) == typeof(decimal)) - { - decimal actualResult = (value >= new Int128(0x0000_0000_FFFF_FFFF, 0xFFFF_FFFF_FFFF_FFFF)) ? decimal.MaxValue : - (value <= new Int128(0xFFFF_FFFF_0000_0000, 0x0000_0000_0000_0001)) ? decimal.MinValue : (decimal)value; - result = (TOther)(object)actualResult; - return true; - } - else if (typeof(TOther) == typeof(ushort)) - { - ushort actualResult = (ushort)value; - result = (TOther)(object)actualResult; - return true; - } - else if (typeof(TOther) == typeof(uint)) - { - uint actualResult = (uint)value; - result = (TOther)(object)actualResult; - return true; - } - else if (typeof(TOther) == typeof(ulong)) - { - ulong actualResult = (ulong)value; - result = (TOther)(object)actualResult; - return true; - } - else if (typeof(TOther) == typeof(UInt128)) - { - UInt128 actualResult = (UInt128)value; - result = (TOther)(object)actualResult; - return true; - } - else if (typeof(TOther) == typeof(nuint)) - { - nuint actualResult = (nuint)value; - result = (TOther)(object)actualResult; - return true; - } - else - { - result = default; - return false; - } - } - // // IParsable // /// - public static bool TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, out Int128 result) => TryParse(s, NumberStyles.Integer, provider, out result); + public static bool TryParse(string s, IFormatProvider provider, out Int128 result) => TryParse(s, NumberStyles.Integer, provider, out result); // // IShiftOperators @@ -2053,10 +1551,10 @@ public readonly struct Int128 // /// - public static Int128 Parse(ReadOnlySpan s, IFormatProvider? provider) => Parse(s, NumberStyles.Integer, provider); + public static Int128 Parse(ReadOnlySpan s, IFormatProvider provider) => Parse(s, NumberStyles.Integer, provider); /// - public static bool TryParse(ReadOnlySpan s, IFormatProvider? provider, out Int128 result) => TryParse(s, NumberStyles.Integer, provider, out result); + public static bool TryParse(ReadOnlySpan s, IFormatProvider provider, out Int128 result) => TryParse(s, NumberStyles.Integer, provider, out result); // // ISubtractionOperators @@ -2135,24 +1633,4 @@ public readonly struct Int128 /// public static bool TryParse(ReadOnlySpan utf8Text, IFormatProvider? provider, out Int128 result) => TryParse(utf8Text, NumberStyles.Integer, provider, out result); - - // - // IBinaryIntegerParseAndFormatInfo - // - - static bool IBinaryIntegerParseAndFormatInfo.IsSigned => true; - - static int IBinaryIntegerParseAndFormatInfo.MaxDigitCount => 39; // 170_141_183_460_469_231_731_687_303_715_884_105_727 - - static int IBinaryIntegerParseAndFormatInfo.MaxHexDigitCount => 32; // 0x7FFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF - - static Int128 IBinaryIntegerParseAndFormatInfo.MaxValueDiv10 => new Int128(0x0CCC_CCCC_CCCC_CCCC, 0xCCCC_CCCC_CCCC_CCCC); - - static string IBinaryIntegerParseAndFormatInfo.OverflowMessage => SR.Overflow_Int128; - - static bool IBinaryIntegerParseAndFormatInfo.IsGreaterThanAsUnsigned(Int128 left, Int128 right) => (UInt128)(left) > (UInt128)(right); - - static Int128 IBinaryIntegerParseAndFormatInfo.MultiplyBy10(Int128 value) => value * 10; - - static Int128 IBinaryIntegerParseAndFormatInfo.MultiplyBy16(Int128 value) => value * 16; } \ No newline at end of file From 1f6d22f31f7ff3950e064e8ecb64bdec2aa53499 Mon Sep 17 00:00:00 2001 From: Airat Abdrakov <52558686+CrackAndDie@users.noreply.github.com> Date: Fri, 17 Apr 2026 10:56:55 +0300 Subject: [PATCH 07/26] Int128 more updates --- std/System/BitConverter.hpt | 16 + std/System/Double.hpt | 5 + std/System/Int128.hpt | 509 +----------------- std/System/Numerics/BitOperations.hpt | 61 ++- .../Runtime/CompilerServices/Unsafe.hpt | 29 + std/System/SR.hpt | 1 + 6 files changed, 118 insertions(+), 503 deletions(-) create mode 100644 std/System/Runtime/CompilerServices/Unsafe.hpt diff --git a/std/System/BitConverter.hpt b/std/System/BitConverter.hpt index 25f22d31..9cdbf354 100644 --- a/std/System/BitConverter.hpt +++ b/std/System/BitConverter.hpt @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. // Some modifications were made for hapet-lang compatibility. +using System.Runtime.CompilerServices; + /// /// Converts base data types to an array of bytes, and an array of bytes to base data types. /// @@ -15,4 +17,18 @@ public static class BitConverter #else public static readonly bool IsLittleEndian = true; #endif + + /// + /// Converts the specified double-precision floating point number to a 64-bit signed integer. + /// + /// The number to convert. + /// A 64-bit signed integer whose bits are identical to . + public static inline long DoubleToInt64Bits(double value) => Unsafe.BitCast(value); + + /// + /// Converts the specified double-precision floating point number to a 64-bit unsigned integer. + /// + /// The number to convert. + /// A 64-bit unsigned integer whose bits are identical to . + public static inline ulong DoubleToUInt64Bits(double value) => Unsafe.BitCast(value); } \ No newline at end of file diff --git a/std/System/Double.hpt b/std/System/Double.hpt index 84677763..3eb8c40c 100644 --- a/std/System/Double.hpt +++ b/std/System/Double.hpt @@ -65,4 +65,9 @@ public struct Double : IComparable, IComparable, IFormattable // TODO: is it correct way or need to do as in c#? return d == NaN; } + + public static inline bool IsNegative(double d) + { + return BitConverter.DoubleToInt64Bits(d) < 0; + } } \ No newline at end of file diff --git a/std/System/Int128.hpt b/std/System/Int128.hpt index fcd3ad3b..13809a29 100644 --- a/std/System/Int128.hpt +++ b/std/System/Int128.hpt @@ -1,7 +1,9 @@ +using System.Globalization; +using System.Text.Formatting; +using System.Runtime.InteropServices; +using System.Numerics; - -/// Represents a 128-bit signed integer.Represents a 128-bit signed integer. public readonly struct Int128 { internal const int Size = 16; @@ -74,7 +76,7 @@ public readonly struct Int128 } /// - public override int GetHashCode() => HashCode.Combine(_lower, _upper); + public override int GetHashCode() => HashCode.Combine(_lower, _upper); /// public override string ToString() @@ -82,78 +84,11 @@ public readonly struct Int128 return Number.Int128ToDecStr(this); } - public string ToString(IFormatProvider? provider) - { - return Number.FormatInt128(this, null, provider); - } - public string ToString(string format) { return Number.FormatInt128(this, format, null); } - public string ToString(string format, IFormatProvider provider) - { - return Number.FormatInt128(this, format, provider); - } - - public bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format = default, IFormatProvider? provider = null) - { - return Number.TryFormatInt128(this, format, provider, destination, out charsWritten); - } - - /// - public bool TryFormat(Span utf8Destination, out int bytesWritten, ReadOnlySpan format = default, IFormatProvider? provider = null) - { - return Number.TryFormatInt128(this, format, provider, utf8Destination, out bytesWritten); - } - - public static Int128 Parse(string s) => Parse(s, NumberStyles.Integer, provider: null); - - public static Int128 Parse(string s, NumberStyles style) => Parse(s, style, provider: null); - - public static Int128 Parse(string s, IFormatProvider provider) => Parse(s, NumberStyles.Integer, provider); - - public static Int128 Parse(string s, NumberStyles style, IFormatProvider provider) - { - if (s is null) { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s); } - return Parse(s.AsSpan(), style, provider); - } - - public static Int128 Parse(ReadOnlySpan s, NumberStyles style = NumberStyles.Integer, IFormatProvider? provider = null) - { - NumberFormatInfo.ValidateParseStyleInteger(style); - return Number.ParseBinaryInteger(s, style, NumberFormatInfo.GetInstance(provider)); - } - - public static bool TryParse(string s, out Int128 result) => TryParse(s, NumberStyles.Integer, provider: null, out result); - - public static bool TryParse(ReadOnlySpan s, out Int128 result) => TryParse(s, NumberStyles.Integer, provider: null, out result); - - /// Tries to convert a UTF-8 character span containing the string representation of a number to its 128-bit signed integer equivalent. - /// A span containing the UTF-8 characters representing the number to convert. - /// When this method returns, contains the 128-bit signed integer value equivalent to the number contained in if the conversion succeeded, or zero if the conversion failed. This parameter is passed uninitialized; any value originally supplied in result will be overwritten. - /// true if was converted successfully; otherwise, false. - public static bool TryParse(ReadOnlySpan utf8Text, out Int128 result) => TryParse(utf8Text, NumberStyles.Integer, provider: null, out result); - - public static bool TryParse(string s, NumberStyles style, IFormatProvider provider, out Int128 result) - { - NumberFormatInfo.ValidateParseStyleInteger(style); - - if (s is null) - { - result = 0; - return false; - } - return Number.TryParseBinaryInteger(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; - } - - public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider provider, out Int128 result) - { - NumberFormatInfo.ValidateParseStyleInteger(style); - return Number.TryParseBinaryInteger(s, style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; - } - // // Explicit Conversions From Int128 // @@ -198,19 +133,6 @@ public readonly struct Int128 } */ - /// Explicitly converts a 128-bit signed integer to a value. - /// The value to convert. - /// converted to a . - public static explicit operator decimal(Int128 value) - { - if (IsNegative(value)) - { - value = -value; - return -(decimal)(UInt128)(value); - } - return (decimal)(UInt128)(value); - } - /// Explicitly converts a 128-bit signed integer to a value. /// The value to convert. /// converted to a . @@ -224,19 +146,6 @@ public readonly struct Int128 return (double)(UInt128)(value); } - /// Explicitly converts a 128-bit signed integer to a value. - /// The value to convert. - /// converted to a . - public static explicit operator Half(Int128 value) - { - if (IsNegative(value)) - { - value = -value; - return -(Half)(UInt128)(value); - } - return (Half)(UInt128)(value); - } - /// Explicitly converts a 128-bit signed integer to a value. /// The value to convert. /// converted to a . @@ -459,21 +368,6 @@ public readonly struct Int128 // Explicit Conversions To Int128 // - /// Explicitly converts a value to a 128-bit signed integer. - /// The value to convert. - /// converted to a 128-bit signed integer. - public static explicit operator Int128(decimal value) - { - value = decimal.Truncate(value); - Int128 result = new Int128(value.High, value.Low64); - - if (decimal.IsNegative(value)) - { - result = -result; - } - return result; - } - /// Explicitly converts a value to a 128-bit signed integer. /// The value to convert. /// converted to a 128-bit signed integer. @@ -618,7 +512,7 @@ public readonly struct Int128 /// Implicitly converts a value to a 128-bit signed integer. /// The value to convert. /// converted to a 128-bit signed integer. - public static implicit operator Int128(nint value) + public static implicit operator Int128(uintptr value) { long lower = value; return new Int128((ulong)(lower >> 63), (ulong)lower); @@ -627,7 +521,6 @@ public readonly struct Int128 /// Implicitly converts a value to a 128-bit signed integer. /// The value to convert. /// converted to a 128-bit signed integer. - [CLSCompliant(false)] public static implicit operator Int128(sbyte value) { long lower = value; @@ -637,27 +530,18 @@ public readonly struct Int128 /// Implicitly converts a value to a 128-bit signed integer. /// The value to convert. /// converted to a 128-bit signed integer. - [CLSCompliant(false)] public static implicit operator Int128(ushort value) => new Int128(0, value); /// Implicitly converts a value to a 128-bit signed integer. /// The value to convert. /// converted to a 128-bit signed integer. - [CLSCompliant(false)] public static implicit operator Int128(uint value) => new Int128(0, value); /// Implicitly converts a value to a 128-bit signed integer. /// The value to convert. /// converted to a 128-bit signed integer. - [CLSCompliant(false)] public static implicit operator Int128(ulong value) => new Int128(0, value); - /// Implicitly converts a value to a 128-bit signed integer. - /// The value to convert. - /// converted to a 128-bit signed integer. - [CLSCompliant(false)] - public static implicit operator Int128(nuint value) => new Int128(0, value); - // // IAdditionOperators // @@ -694,89 +578,6 @@ public readonly struct Int128 } */ - // - // IAdditiveIdentity - // - - /// - static Int128 IAdditiveIdentity.AdditiveIdentity => default; - - // - // IBinaryInteger - // - - /// - public static (Int128 Quotient, Int128 Remainder) DivRem(Int128 left, Int128 right) - { - Int128 quotient = left / right; - return (quotient, left - (quotient * right)); - } - - /// - public static Int128 LeadingZeroCount(Int128 value) - => LeadingZeroCountAsInt32(value); - - /// Computes the number of leading zero bits in this value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int LeadingZeroCountAsInt32(Int128 value) - { - if (value._upper == 0) - { - return 64 + BitOperations.LeadingZeroCount(value._lower); - } - return BitOperations.LeadingZeroCount(value._upper); - } - - /// - public static Int128 Log10(Int128 value) - { - if (IsNegative(value)) - { - ThrowHelper.ThrowValueArgumentOutOfRange_NeedNonNegNumException(); - } - return (Int128)UInt128.Log10((UInt128)value); - } - - /// - public static Int128 PopCount(Int128 value) - => ulong.PopCount(value._lower) + ulong.PopCount(value._upper); - - /// - public static Int128 RotateLeft(Int128 value, int rotateAmount) - => (value << rotateAmount) | (value >>> (128 - rotateAmount)); - - /// - public static Int128 RotateRight(Int128 value, int rotateAmount) - => (value >>> rotateAmount) | (value << (128 - rotateAmount)); - - /// - public static Int128 TrailingZeroCount(Int128 value) - { - if (value._lower == 0) - { - return 64 + ulong.TrailingZeroCount(value._upper); - } - return ulong.TrailingZeroCount(value._lower); - } - - /// - public static bool IsPow2(Int128 value) => (PopCount(value) == 1U) && IsPositive(value); - - /// - public static Int128 Log2(Int128 value) - { - if (IsNegative(value)) - { - ThrowHelper.ThrowValueArgumentOutOfRange_NeedNonNegNumException(); - } - - if (value._upper == 0) - { - return ulong.Log2(value._lower); - } - return 64 + ulong.Log2(value._upper); - } - // // IBitwiseOperators // @@ -1078,60 +879,6 @@ public readonly struct Int128 return value; } - /// - public static inline Int128 CreateChecked(TOther value) - where TOther : INumberBase - { - Int128 result; - - if (typeof(TOther) == typeof(Int128)) - { - result = (Int128)(object)value; - } - else if (!TryConvertFromChecked(value, out result) && !TOther.TryConvertToChecked(value, out result)) - { - ThrowHelper.ThrowNotSupportedException(); - } - - return result; - } - - /// - public static inline Int128 CreateSaturating(TOther value) - where TOther : INumberBase - { - Int128 result; - - if (typeof(TOther) == typeof(Int128)) - { - result = (Int128)(object)value; - } - else if (!TryConvertFromSaturating(value, out result) && !TOther.TryConvertToSaturating(value, out result)) - { - ThrowHelper.ThrowNotSupportedException(); - } - - return result; - } - - /// - public static inline Int128 CreateTruncating(TOther value) - where TOther : INumberBase - { - Int128 result; - - if (typeof(TOther) == typeof(Int128)) - { - result = (Int128)(object)value; - } - else if (!TryConvertFromTruncating(value, out result) && !TOther.TryConvertToTruncating(value, out result)) - { - ThrowHelper.ThrowNotSupportedException(); - } - - return result; - } - /// public static bool IsEvenInteger(Int128 value) => (value._lower & 1) == 0; @@ -1224,214 +971,6 @@ public readonly struct Int128 return y; } - private static inline bool TryConvertFromChecked(TOther value, out Int128 result) - where TOther : INumberBase - { - // In order to reduce overall code duplication and improve the inlinabilty of these - // methods for the corelib types we have `ConvertFrom` handle the same sign and - // `ConvertTo` handle the opposite sign. However, since there is an uneven split - // between signed and unsigned types, the one that handles unsigned will also - // handle `Decimal`. - // - // That is, `ConvertFrom` for `Int128` will handle the other signed types and - // `ConvertTo` will handle the unsigned types - - if (typeof(TOther) == typeof(double)) - { - double actualValue = (double)(object)value; - result = checked((Int128)actualValue); - return true; - } - else if (typeof(TOther) == typeof(Half)) - { - Half actualValue = (Half)(object)value; - result = checked((Int128)actualValue); - return true; - } - else if (typeof(TOther) == typeof(short)) - { - short actualValue = (short)(object)value; - result = actualValue; - return true; - } - else if (typeof(TOther) == typeof(int)) - { - int actualValue = (int)(object)value; - result = actualValue; - return true; - } - else if (typeof(TOther) == typeof(long)) - { - long actualValue = (long)(object)value; - result = actualValue; - return true; - } - else if (typeof(TOther) == typeof(nint)) - { - nint actualValue = (nint)(object)value; - result = actualValue; - return true; - } - else if (typeof(TOther) == typeof(sbyte)) - { - sbyte actualValue = (sbyte)(object)value; - result = actualValue; - return true; - } - else if (typeof(TOther) == typeof(float)) - { - float actualValue = (float)(object)value; - result = checked((Int128)actualValue); - return true; - } - else - { - result = default; - return false; - } - } - - private static inline bool TryConvertFromSaturating(TOther value, out Int128 result) - where TOther : INumberBase - { - // In order to reduce overall code duplication and improve the inlinabilty of these - // methods for the corelib types we have `ConvertFrom` handle the same sign and - // `ConvertTo` handle the opposite sign. However, since there is an uneven split - // between signed and unsigned types, the one that handles unsigned will also - // handle `Decimal`. - // - // That is, `ConvertFrom` for `Int128` will handle the other signed types and - // `ConvertTo` will handle the unsigned types - - if (typeof(TOther) == typeof(double)) - { - double actualValue = (double)(object)value; - result = (Int128)actualValue; - return true; - } - else if (typeof(TOther) == typeof(Half)) - { - Half actualValue = (Half)(object)value; - result = (Int128)actualValue; - return true; - } - else if (typeof(TOther) == typeof(short)) - { - short actualValue = (short)(object)value; - result = actualValue; - return true; - } - else if (typeof(TOther) == typeof(int)) - { - int actualValue = (int)(object)value; - result = actualValue; - return true; - } - else if (typeof(TOther) == typeof(long)) - { - long actualValue = (long)(object)value; - result = actualValue; - return true; - } - else if (typeof(TOther) == typeof(uintptr)) - { - uintptr actualValue = (uintptr)(object)value; - result = actualValue; - return true; - } - else if (typeof(TOther) == typeof(sbyte)) - { - sbyte actualValue = (sbyte)(object)value; - result = actualValue; - return true; - } - else if (typeof(TOther) == typeof(float)) - { - float actualValue = (float)(object)value; - result = (Int128)actualValue; - return true; - } - else - { - result = default; - return false; - } - } - - private static inline bool TryConvertFromTruncating(TOther value, out Int128 result) - where TOther : INumberBase - { - // In order to reduce overall code duplication and improve the inlinabilty of these - // methods for the corelib types we have `ConvertFrom` handle the same sign and - // `ConvertTo` handle the opposite sign. However, since there is an uneven split - // between signed and unsigned types, the one that handles unsigned will also - // handle `Decimal`. - // - // That is, `ConvertFrom` for `Int128` will handle the other signed types and - // `ConvertTo` will handle the unsigned types - - if (typeof(TOther) == typeof(double)) - { - double actualValue = (double)(object)value; - result = (Int128)actualValue; - return true; - } - else if (typeof(TOther) == typeof(Half)) - { - Half actualValue = (Half)(object)value; - result = (Int128)actualValue; - return true; - } - else if (typeof(TOther) == typeof(short)) - { - short actualValue = (short)(object)value; - result = actualValue; - return true; - } - else if (typeof(TOther) == typeof(int)) - { - int actualValue = (int)(object)value; - result = actualValue; - return true; - } - else if (typeof(TOther) == typeof(long)) - { - long actualValue = (long)(object)value; - result = actualValue; - return true; - } - else if (typeof(TOther) == typeof(uintptr)) - { - uintptr actualValue = (uintptr)(object)value; - result = actualValue; - return true; - } - else if (typeof(TOther) == typeof(sbyte)) - { - sbyte actualValue = (sbyte)(object)value; - result = actualValue; - return true; - } - else if (typeof(TOther) == typeof(float)) - { - float actualValue = (float)(object)value; - result = (Int128)actualValue; - return true; - } - else - { - result = default; - return false; - } - } - - // - // IParsable - // - - /// - public static bool TryParse(string s, IFormatProvider provider, out Int128 result) => TryParse(s, NumberStyles.Integer, provider, out result); - // // IShiftOperators // @@ -1546,16 +1085,6 @@ public readonly struct Int128 /// public static Int128 NegativeOne => new Int128(0xFFFF_FFFF_FFFF_FFFF, 0xFFFF_FFFF_FFFF_FFFF); - // - // ISpanParsable - // - - /// - public static Int128 Parse(ReadOnlySpan s, IFormatProvider provider) => Parse(s, NumberStyles.Integer, provider); - - /// - public static bool TryParse(ReadOnlySpan s, IFormatProvider provider, out Int128 result) => TryParse(s, NumberStyles.Integer, provider, out result); - // // ISubtractionOperators // @@ -1609,28 +1138,4 @@ public readonly struct Int128 /// public static Int128 operator +(Int128 value) => value; - - // - // IUtf8SpanParsable - // - - /// - public static Int128 Parse(ReadOnlySpan utf8Text, NumberStyles style = NumberStyles.Integer, IFormatProvider? provider = null) - { - NumberFormatInfo.ValidateParseStyleInteger(style); - return Number.ParseBinaryInteger(utf8Text, style, NumberFormatInfo.GetInstance(provider)); - } - - /// - public static bool TryParse(ReadOnlySpan utf8Text, NumberStyles style, IFormatProvider? provider, out Int128 result) - { - NumberFormatInfo.ValidateParseStyleInteger(style); - return Number.TryParseBinaryInteger(utf8Text, style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; - } - - /// - public static Int128 Parse(ReadOnlySpan utf8Text, IFormatProvider? provider) => Parse(utf8Text, NumberStyles.Integer, provider); - - /// - public static bool TryParse(ReadOnlySpan utf8Text, IFormatProvider? provider, out Int128 result) => TryParse(utf8Text, NumberStyles.Integer, provider, out result); } \ No newline at end of file diff --git a/std/System/Numerics/BitOperations.hpt b/std/System/Numerics/BitOperations.hpt index ce2c36ac..79c2bf4e 100644 --- a/std/System/Numerics/BitOperations.hpt +++ b/std/System/Numerics/BitOperations.hpt @@ -5,9 +5,16 @@ using System; /// Utility methods for intrinsic bit-twiddling operations. -[SuppressStaticCtorCall] public static class BitOperations { + private static byte[] Log2DeBruijn = new byte[] + { + 00, 09, 01, 10, 13, 21, 02, 29, + 11, 14, 16, 18, 22, 25, 03, 30, + 08, 12, 20, 28, 15, 17, 24, 07, + 19, 27, 23, 06, 26, 05, 04, 31 + }; + /// Rotates the specified value left by the specified number of bits. public static uint RotateLeft(uint value, int offset) { @@ -29,4 +36,56 @@ public static class BitOperations } return targetlevel; } + + /// + /// Count the number of leading zero bits in a mask. + /// Similar in behavior to the x86 instruction LZCNT. + /// + /// The value. + public static inline int LeadingZeroCount(uint value) + { + // Unguarded fallback contract is 0->31, BSR contract is 0->undefined + if (value == 0) + { + return 32; + } + return 31 ^ Log2SoftwareFallback(value); + } + + /// + /// Count the number of leading zero bits in a mask. + /// Similar in behavior to the x86 instruction LZCNT. + /// + /// The value. + public static inline int LeadingZeroCount(ulong value) + { + uint hi = (uint)(value >> 32); + if (hi == 0) + { + return 32 + LeadingZeroCount((uint)value); + } + return LeadingZeroCount(hi); + } + + /// + /// Returns the integer (floor) log of the specified value, base 2. + /// Note that by convention, input value 0 returns 0 since Log(0) is undefined. + /// Does not directly use any hardware intrinsics, nor does it incur branching. + /// + /// The value. + private static int Log2SoftwareFallback(uint value) + { + // No AggressiveInlining due to large method size + // Has conventional contract 0->0 (Log(0) is undefined) + + // Fill trailing zeros with ones, eg 00010010 becomes 00011111 + value |= value >> 01; + value |= value >> 02; + value |= value >> 04; + value |= value >> 08; + value |= value >> 16; + + // Using deBruijn sequence, k=2, n=5 (2^5=32) : 0b_0000_0111_1100_0100_1010_1100_1101_1101u + return Log2DeBruijn[(int)((value * 0x07C4ACDDu) >> 27)]; + } } \ No newline at end of file diff --git a/std/System/Runtime/CompilerServices/Unsafe.hpt b/std/System/Runtime/CompilerServices/Unsafe.hpt new file mode 100644 index 00000000..47cb057e --- /dev/null +++ b/std/System/Runtime/CompilerServices/Unsafe.hpt @@ -0,0 +1,29 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// Some modifications were made for hapet-lang compatibility. + +using System; + +// The implementations of most the methods in this file are provided as intrinsics. +// In CoreCLR, the body of the functions are replaced by the EE with unsafe code. See see getILIntrinsicImplementationForUnsafe for details. +// In AOT compilers, see Internal.IL.Stubs.UnsafeIntrinsics for details. + +/// +/// Contains generic, low-level functionality for manipulating pointers. +/// +public static partial unsafe class Unsafe +{ + /// + /// Reinterprets the given value of type as a value of type . + /// + /// The sizes of and are not the same + /// or the type parameters are not s. + public static inline TTo BitCast(TFrom source) + { + if (sizeof(TFrom) != sizeof(TTo)) + { + throw new NotSupportedException(); + } + return *((TTo*)source); + } +} \ No newline at end of file diff --git a/std/System/SR.hpt b/std/System/SR.hpt index 3b5ead53..226d3549 100644 --- a/std/System/SR.hpt +++ b/std/System/SR.hpt @@ -31,6 +31,7 @@ internal static class SR internal const string Arg_MustBeInt16 = "Object must be of type Int16 (short)."; internal const string Arg_MustBeInt32 = "Object must be of type Int32 (int)."; internal const string Arg_MustBeInt64 = "Object must be of type Int64 (long)."; + internal const string Arg_MustBeInt128 = "Object must be of type Int128."; internal const string Arg_MustBeByte = "Object must be of type Byte."; internal const string Arg_MustBeUInt16 = "Object must be of type UInt16 (ushort)."; internal const string Arg_MustBeUInt32 = "Object must be of type UInt32 (uint)."; From 10256554b65392cdf121c45741fc3cb67a54542b Mon Sep 17 00:00:00 2001 From: Airat Abdrakov <52558686+CrackAndDie@users.noreply.github.com> Date: Fri, 17 Apr 2026 15:15:21 +0300 Subject: [PATCH 08/26] UInt128 added and small other changes --- std/System/BitConverter.hpt | 7 + std/System/Int128.hpt | 7 +- std/System/SR.hpt | 1 + std/System/UInt128.hpt | 1181 +++++++++++++++++++++++++++++++++++ 4 files changed, 1195 insertions(+), 1 deletion(-) create mode 100644 std/System/UInt128.hpt diff --git a/std/System/BitConverter.hpt b/std/System/BitConverter.hpt index 9cdbf354..420a58ce 100644 --- a/std/System/BitConverter.hpt +++ b/std/System/BitConverter.hpt @@ -31,4 +31,11 @@ public static class BitConverter /// The number to convert. /// A 64-bit unsigned integer whose bits are identical to . public static inline ulong DoubleToUInt64Bits(double value) => Unsafe.BitCast(value); + + /// + /// Converts the specified 64-bit unsigned integer to a double-precision floating point number. + /// + /// The number to convert. + /// A double-precision floating point number whose bits are identical to . + public static inline double UInt64BitsToDouble(ulong value) => Unsafe.BitCast(value); } \ No newline at end of file diff --git a/std/System/Int128.hpt b/std/System/Int128.hpt index 13809a29..e98a2b75 100644 --- a/std/System/Int128.hpt +++ b/std/System/Int128.hpt @@ -1,3 +1,7 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// Some modifications were made for hapet-lang compatibility. + using System.Globalization; using System.Text.Formatting; using System.Runtime.InteropServices; @@ -781,7 +785,8 @@ public readonly struct Int128 { // This follows the same logic as is used in `long Math.BigMul(long, long, out long)` - UInt128 upper = UInt128.BigMul((UInt128)(left), (UInt128)(right), out UInt128 ulower); + UInt128 ulower; + UInt128 upper = UInt128.BigMul((UInt128)(left), (UInt128)(right), out ulower); lower = (Int128)(ulower); return (Int128)(upper) - ((left >> 127) & right) - ((right >> 127) & left); } diff --git a/std/System/SR.hpt b/std/System/SR.hpt index 226d3549..bd2bae40 100644 --- a/std/System/SR.hpt +++ b/std/System/SR.hpt @@ -36,6 +36,7 @@ internal static class SR internal const string Arg_MustBeUInt16 = "Object must be of type UInt16 (ushort)."; internal const string Arg_MustBeUInt32 = "Object must be of type UInt32 (uint)."; internal const string Arg_MustBeUInt64 = "Object must be of type UInt64 (ulong)."; + internal const string Arg_MustBeUInt128 = "Object must be of type UInt128."; internal const string Arg_MustBePtrDiff = "Object must be of type PtrDiff."; internal const string Arg_MustBeUIntPtr = "Object must be of type UIntPtr."; diff --git a/std/System/UInt128.hpt b/std/System/UInt128.hpt new file mode 100644 index 00000000..85d7a58f --- /dev/null +++ b/std/System/UInt128.hpt @@ -0,0 +1,1181 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// Some modifications were made for hapet-lang compatibility. + +using System.Globalization; +using System.Text.Formatting; +using System.Runtime.InteropServices; +using System.Numerics; + +/// Represents a 128-bit unsigned integer. +public readonly struct UInt128 +{ + internal const int Size = 16; + +#if BIGENDIAN; + private readonly ulong _upper; + private readonly ulong _lower; +#else + private readonly ulong _lower; + private readonly ulong _upper; +#endif + + /// Initializes a new instance of the struct. + /// The upper 64-bits of the 128-bit value. + /// The lower 64-bits of the 128-bit value. + public UInt128(ulong upper, ulong lower) + { + _lower = lower; + _upper = upper; + } + + internal ulong Lower => _lower; + + internal ulong Upper => _upper; + + /// + public int CompareTo(object value) + { + if (value is UInt128 other) + { + return CompareTo(other); + } + else if (value == null) + { + return 1; + } + else + { + throw new ArgumentException(SR.Arg_MustBeUInt128); + } + } + + /// + public int CompareTo(UInt128 value) + { + if (this < value) + { + return -1; + } + else if (this > value) + { + return 1; + } + else + { + return 0; + } + } + + /// + public override bool Equals(object obj) + { + return (obj is UInt128 other) && Equals(other); + } + + /// + public bool Equals(UInt128 other) + { + return this == other; + } + + /// + public override int GetHashCode() => HashCode.Combine(_lower, _upper); + + /// + public override string ToString() + { + return Number.UInt128ToDecStr(this); + } + + public string ToString(string format) + { + return Number.FormatUInt128(this, format, null); + } + + // + // Explicit Conversions From UInt128 + // + + /// Explicitly converts a 128-bit unsigned integer to a value. + /// The value to convert. + /// converted to a . + public static explicit operator byte(UInt128 value) => (byte)value._lower; + + /// Explicitly converts a 128-bit unsigned integer to a value, throwing an overflow exception for any values that fall outside the representable range. + /// The value to convert. + /// converted to a . + /// is not representable by . + /* TODO: + public static explicit operator checked byte(UInt128 value) + { + if (value._upper != 0) + { + ThrowHelper.ThrowOverflowException(); + } + return checked((byte)value._lower); + } + */ + + /// Explicitly converts a 128-bit unsigned integer to a value. + /// The value to convert. + /// converted to a . + public static explicit operator char(UInt128 value) => (char)value._lower; + + /// Explicitly converts a 128-bit unsigned integer to a value, throwing an overflow exception for any values that fall outside the representable range. + /// The value to convert. + /// converted to a . + /// is not representable by . + /* TODO: + public static explicit operator checked char(UInt128 value) + { + if (value._upper != 0) + { + ThrowHelper.ThrowOverflowException(); + } + return checked((char)value._lower); + } + */ + + /// Explicitly converts a 128-bit unsigned integer to a value. + /// The value to convert. + /// converted to a . + public static explicit operator double(UInt128 value) + { + // This code is based on `u128_to_f64_round` from m-ou-se/floatconv + // Copyright (c) 2020 Mara Bos . All rights reserved. + // + // Licensed under the BSD 2 - Clause "Simplified" License + // See THIRD-PARTY-NOTICES.TXT for the full license text + + const double TwoPow52 = 4503599627370496.0; + const double TwoPow76 = 75557863725914323419136.0; + const double TwoPow104 = 20282409603651670423947251286016.0; + const double TwoPow128 = 340282366920938463463374607431768211456.0; + + const ulong TwoPow52Bits = 0x4330000000000000; + const ulong TwoPow76Bits = 0x44B0000000000000; + const ulong TwoPow104Bits = 0x4670000000000000; + const ulong TwoPow128Bits = 0x47F0000000000000; + + if (value._upper == 0) + { + // For values between 0 and ulong.MaxValue, we just use the existing conversion + return (double)(value._lower); + } + else if ((value._upper >> 40) == 0) // value < (2^104) + { + // For values greater than ulong.MaxValue but less than 2^104 this takes advantage + // that we can represent both "halves" of the uint128 within the 52-bit mantissa of + // a pair of doubles. + + double lower = BitConverter.UInt64BitsToDouble(TwoPow52Bits | ((value._lower << 12) >> 12)) - TwoPow52; + double upper = BitConverter.UInt64BitsToDouble(TwoPow104Bits | (ulong)(value >> 52)) - TwoPow104; + + return lower + upper; + } + else + { + // For values greater than 2^104 we basically do the same as before but we need to account + // for the precision loss that double will have. As such, the lower value effectively drops the + // lowest 24 bits and then or's them back to ensure rounding stays correct. + + double lower = BitConverter.UInt64BitsToDouble(TwoPow76Bits | ((ulong)(value >> 12) >> 12) | (value._lower & 0xFFFFFF)) - TwoPow76; + double upper = BitConverter.UInt64BitsToDouble(TwoPow128Bits | (value._upper >> 12)) - TwoPow128; + + return lower + upper; + } + } + + /// Explicitly converts a 128-bit unsigned integer to a value. + /// The value to convert. + /// converted to a . + public static explicit operator short(UInt128 value) => (short)value._lower; + + /// Explicitly converts a 128-bit unsigned integer to a value, throwing an overflow exception for any values that fall outside the representable range. + /// The value to convert. + /// converted to a . + /// is not representable by . + /* TODO: + public static explicit operator checked short(UInt128 value) + { + if (value._upper != 0) + { + ThrowHelper.ThrowOverflowException(); + } + return checked((short)value._lower); + } + */ + + /// Explicitly converts a 128-bit unsigned integer to a value. + /// The value to convert. + /// converted to a . + public static explicit operator int(UInt128 value) => (int)value._lower; + + /// Explicitly converts a 128-bit unsigned integer to a value, throwing an overflow exception for any values that fall outside the representable range. + /// The value to convert. + /// converted to a . + /// is not representable by . + /* TODO: + public static explicit operator checked int(UInt128 value) + { + if (value._upper != 0) + { + ThrowHelper.ThrowOverflowException(); + } + return checked((int)value._lower); + } + */ + + /// Explicitly converts a 128-bit unsigned integer to a value. + /// The value to convert. + /// converted to a . + public static explicit operator long(UInt128 value) => (long)value._lower; + + /// Explicitly converts a 128-bit unsigned integer to a value, throwing an overflow exception for any values that fall outside the representable range. + /// The value to convert. + /// converted to a . + /// is not representable by . + /* TODO: + public static explicit operator checked long(UInt128 value) + { + if (value._upper != 0) + { + ThrowHelper.ThrowOverflowException(); + } + return checked((long)value._lower); + } + */ + + /// Explicitly converts a 128-bit unsigned integer to a value. + /// The value to convert. + /// converted to a . + public static explicit operator Int128(UInt128 value) => new Int128(value._upper, value._lower); + + /// Explicitly converts a 128-bit unsigned integer to a value, throwing an overflow exception for any values that fall outside the representable range. + /// The value to convert. + /// converted to a . + /// is not representable by . + /* TODO: + public static explicit operator checked Int128(UInt128 value) + { + if ((long)value._upper < 0) + { + ThrowHelper.ThrowOverflowException(); + } + return new Int128(value._upper, value._lower); + } + */ + + /// Explicitly converts a 128-bit unsigned integer to a value, throwing an overflow exception for any values that fall outside the representable range. + /// The value to convert. + /// converted to a . + /// is not representable by . + /* TODO: + public static explicit operator checked nint(UInt128 value) + { + if (value._upper != 0) + { + ThrowHelper.ThrowOverflowException(); + } + return checked((nint)value._lower); + } + */ + + /// Explicitly converts a 128-bit unsigned integer to a value. + /// The value to convert. + /// converted to a . + public static explicit operator sbyte(UInt128 value) => (sbyte)value._lower; + + /// Explicitly converts a 128-bit unsigned integer to a value, throwing an overflow exception for any values that fall outside the representable range. + /// The value to convert. + /// converted to a . + /// is not representable by . + /* TODO: + public static explicit operator checked sbyte(UInt128 value) + { + if (value._upper != 0) + { + ThrowHelper.ThrowOverflowException(); + } + return checked((sbyte)value._lower); + } + */ + + /// Explicitly converts a 128-bit unsigned integer to a value. + /// The value to convert. + /// converted to a . + public static explicit operator float(UInt128 value) => (float)(double)(value); + + /// Explicitly converts a 128-bit unsigned integer to a value. + /// The value to convert. + /// converted to a . + public static explicit operator ushort(UInt128 value) => (ushort)value._lower; + + /// Explicitly converts a 128-bit unsigned integer to a value, throwing an overflow exception for any values that fall outside the representable range. + /// The value to convert. + /// converted to a . + /// is not representable by . + /* TODO: + public static explicit operator checked ushort(UInt128 value) + { + if (value._upper != 0) + { + ThrowHelper.ThrowOverflowException(); + } + return checked((ushort)value._lower); + } + */ + + /// Explicitly converts a 128-bit unsigned integer to a value. + /// The value to convert. + /// converted to a . + public static explicit operator uint(UInt128 value) => (uint)value._lower; + + /// Explicitly converts a 128-bit unsigned integer to a value, throwing an overflow exception for any values that fall outside the representable range. + /// The value to convert. + /// converted to a . + /// is not representable by . + /* TODO: + public static explicit operator checked uint(UInt128 value) + { + if (value._upper != 0) + { + ThrowHelper.ThrowOverflowException(); + } + return checked((uint)value._lower); + } + */ + + /// Explicitly converts a 128-bit unsigned integer to a value. + /// The value to convert. + /// converted to a . + public static explicit operator ulong(UInt128 value) => value._lower; + + /// Explicitly converts a 128-bit unsigned integer to a value, throwing an overflow exception for any values that fall outside the representable range. + /// The value to convert. + /// converted to a . + /// is not representable by . + /* TODO: + public static explicit operator checked ulong(UInt128 value) + { + if (value._upper != 0) + { + ThrowHelper.ThrowOverflowException(); + } + return value._lower; + } + */ + + /// Explicitly converts a 128-bit unsigned integer to a value. + /// The value to convert. + /// converted to a . + public static explicit operator uintptr(UInt128 value) => (uintptr)value._lower; + + /// Explicitly converts a 128-bit unsigned integer to a value, throwing an overflow exception for any values that fall outside the representable range. + /// The value to convert. + /// converted to a . + /// is not representable by . + /* TODO: + public static explicit operator checked nuint(UInt128 value) + { + if (value._upper != 0) + { + ThrowHelper.ThrowOverflowException(); + } + return checked((nuint)value._lower); + } + */ + + // + // Explicit Conversions To UInt128 + // + + /// Explicitly converts a value to a 128-bit unsigned integer. + /// The value to convert. + /// converted to a 128-bit unsigned integer. + public static explicit operator UInt128(double value) + { + const double TwoPow128 = 340282366920938463463374607431768211456.0; + + if (double.IsNegative(value) || double.IsNaN(value)) + { + return MinValue; + } + else if (value >= TwoPow128) + { + return MaxValue; + } + + return ToUInt128(value); + } + + /// Explicitly converts a value to a 128-bit unsigned integer, throwing an overflow exception for any values that fall outside the representable range. + /// The value to convert. + /// converted to a 128-bit unsigned integer. + /// is not representable by . + /* TODO: + public static explicit operator checked UInt128(double value) + { + const double TwoPow128 = 340282366920938463463374607431768211456.0; + + // We need to convert -0.0 to 0 and not throw, so we compare + // value against 0 rather than checking IsNegative + + if ((value < 0.0) || double.IsNaN(value) || (value >= TwoPow128)) + { + ThrowHelper.ThrowOverflowException(); + } + + return ToUInt128(value); + } + */ + + internal static UInt128 ToUInt128(double value) + { + const double TwoPow128 = 340282366920938463463374607431768211456.0; + + // TODO: + //Debug.Assert(value >= 0); + //Debug.Assert(double.IsFinite(value)); + //Debug.Assert(value < TwoPow128); + + // This code is based on `f64_to_u128` from m-ou-se/floatconv + // Copyright (c) 2020 Mara Bos . All rights reserved. + // + // Licensed under the BSD 2 - Clause "Simplified" License + // See THIRD-PARTY-NOTICES.TXT for the full license text + + if (value >= 1.0) + { + // In order to convert from double to uint128 we first need to extract the signficand, + // including the implicit leading bit, as a full 128-bit significand. We can then adjust + // this down to the represented integer by right shifting by the unbiased exponent, taking + // into account the significand is now represented as 128-bits. + + ulong bits = BitConverter.DoubleToUInt64Bits(value); + UInt128 result = new UInt128((bits << 12) >> 1 | 0x8000_0000_0000_0000, 0x0000_0000_0000_0000); + + result = result >> (1023 + 128 - 1 - (int)(bits >> 52)); + return result; + } + else + { + return MinValue; + } + } + + /// Explicitly converts a value to a 128-bit unsigned integer. + /// The value to convert. + /// converted to a 128-bit unsigned integer. + public static explicit operator UInt128(short value) + { + long lower = value; + return new UInt128((ulong)(lower >> 63), (ulong)lower); + } + + /// Explicitly converts a value to a 128-bit unsigned integer, throwing an overflow exception for any values that fall outside the representable range. + /// The value to convert. + /// converted to a 128-bit unsigned integer. + /// is not representable by . + /* TODO: + public static explicit operator checked UInt128(short value) + { + if (value < 0) + { + ThrowHelper.ThrowOverflowException(); + } + return new UInt128(0, (ushort)value); + } + */ + + /// Explicitly converts a value to a 128-bit unsigned integer. + /// The value to convert. + /// converted to a 128-bit unsigned integer. + public static explicit operator UInt128(int value) + { + long lower = value; + return new UInt128((ulong)(lower >> 63), (ulong)lower); + } + + /// Explicitly converts a value to a 128-bit unsigned integer, throwing an overflow exception for any values that fall outside the representable range. + /// The value to convert. + /// converted to a 128-bit unsigned integer. + /// is not representable by . + /* TODO: + public static explicit operator checked UInt128(int value) + { + if (value < 0) + { + ThrowHelper.ThrowOverflowException(); + } + return new UInt128(0, (uint)value); + } + */ + + /// Explicitly converts a value to a 128-bit unsigned integer. + /// The value to convert. + /// converted to a 128-bit unsigned integer. + public static explicit operator UInt128(long value) + { + long lower = value; + return new UInt128((ulong)(lower >> 63), (ulong)lower); + } + + /// Explicitly converts a value to a 128-bit unsigned integer, throwing an overflow exception for any values that fall outside the representable range. + /// The value to convert. + /// converted to a 128-bit unsigned integer. + /// is not representable by . + /* TODO: + public static explicit operator checked UInt128(long value) + { + if (value < 0) + { + ThrowHelper.ThrowOverflowException(); + } + return new UInt128(0, (ulong)value); + } + */ + + /// Explicitly converts a value to a 128-bit unsigned integer, throwing an overflow exception for any values that fall outside the representable range. + /// The value to convert. + /// converted to a 128-bit unsigned integer. + /// is not representable by . + /* TODO: + public static explicit operator checked UInt128(nint value) + { + if (value < 0) + { + ThrowHelper.ThrowOverflowException(); + } + return new UInt128(0, (nuint)value); + } + */ + + /// Explicitly converts a value to a 128-bit unsigned integer. + /// The value to convert. + /// converted to a 128-bit unsigned integer. + public static explicit operator UInt128(sbyte value) + { + long lower = value; + return new UInt128((ulong)(lower >> 63), (ulong)lower); + } + + /// Explicitly converts a value to a 128-bit unsigned integer, throwing an overflow exception for any values that fall outside the representable range. + /// The value to convert. + /// converted to a 128-bit unsigned integer. + /// is not representable by . + /* TODO: + public static explicit operator checked UInt128(sbyte value) + { + if (value < 0) + { + ThrowHelper.ThrowOverflowException(); + } + return new UInt128(0, (byte)value); + } + */ + + /// Explicitly converts a value to a 128-bit unsigned integer. + /// The value to convert. + /// converted to a 128-bit unsigned integer. + public static explicit operator UInt128(float value) => (UInt128)(double)(value); + + /// Explicitly converts a value to a 128-bit unsigned integer, throwing an overflow exception for any values that fall outside the representable range. + /// The value to convert. + /// converted to a 128-bit unsigned integer. + /// is not representable by . + /* TODO: + public static explicit operator checked UInt128(float value) => checked((UInt128)(double)(value)); + */ + + // + // Implicit Conversions To UInt128 + // + + /// Implicitly converts a value to a 128-bit unsigned integer. + /// The value to convert. + /// converted to a 128-bit unsigned integer. + public static implicit operator UInt128(byte value) => new UInt128(0, value); + + /// Implicitly converts a value to a 128-bit unsigned integer. + /// The value to convert. + /// converted to a 128-bit unsigned integer. + public static implicit operator UInt128(char value) => new UInt128(0, value); + + /// Implicitly converts a value to a 128-bit unsigned integer. + /// The value to convert. + /// converted to a 128-bit unsigned integer. + public static implicit operator UInt128(ushort value) => new UInt128(0, value); + + /// Implicitly converts a value to a 128-bit unsigned integer. + /// The value to convert. + /// converted to a 128-bit unsigned integer. + public static implicit operator UInt128(uint value) => new UInt128(0, value); + + /// Implicitly converts a value to a 128-bit unsigned integer. + /// The value to convert. + /// converted to a 128-bit unsigned integer. + public static implicit operator UInt128(ulong value) => new UInt128(0, value); + + /// Implicitly converts a value to a 128-bit unsigned integer. + /// The value to convert. + /// converted to a 128-bit unsigned integer. + public static implicit operator UInt128(uintptr value) => new UInt128(0, value); + + // + // IAdditionOperators + // + + /// + public static UInt128 operator +(UInt128 left, UInt128 right) + { + // For unsigned addition, we can detect overflow by checking `(x + y) < x` + // This gives us the carry to add to upper to compute the correct result + + ulong lower = left._lower + right._lower; + ulong carry = (lower < left._lower) ? (ulong)1UL : 0UL; + + ulong upper = left._upper + right._upper + carry; + return new UInt128(upper, lower); + } + + /// + /* TODO: + public static UInt128 operator checked +(UInt128 left, UInt128 right) + { + // For unsigned addition, we can detect overflow by checking `(x + y) < x` + // This gives us the carry to add to upper to compute the correct result + + ulong lower = left._lower + right._lower; + ulong carry = (lower < left._lower) ? 1UL : 0UL; + + ulong upper = checked(left._upper + right._upper + carry); + return new UInt128(upper, lower); + } + */ + + /// + public static UInt128 PopCount(UInt128 value) + => ulong.PopCount(value._lower) + ulong.PopCount(value._upper); + + // + // IBitwiseOperators + // + + /// + public static UInt128 operator &(UInt128 left, UInt128 right) => new UInt128(left._upper & right._upper, left._lower & right._lower); + + /// + public static UInt128 operator |(UInt128 left, UInt128 right) => new UInt128(left._upper | right._upper, left._lower | right._lower); + + /// + public static UInt128 operator ^(UInt128 left, UInt128 right) => new UInt128(left._upper ^ right._upper, left._lower ^ right._lower); + + /// + public static UInt128 operator ~(UInt128 value) => new UInt128(~value._upper, ~value._lower); + + // + // IComparisonOperators + // + + /// + public static bool operator <(UInt128 left, UInt128 right) + { + return (left._upper < right._upper) + || (left._upper == right._upper) && (left._lower < right._lower); + } + + /// + public static bool operator <=(UInt128 left, UInt128 right) + { + return (left._upper < right._upper) + || (left._upper == right._upper) && (left._lower <= right._lower); + } + + /// + public static bool operator >(UInt128 left, UInt128 right) + { + return (left._upper > right._upper) + || (left._upper == right._upper) && (left._lower > right._lower); + } + + /// + public static bool operator >=(UInt128 left, UInt128 right) + { + return (left._upper > right._upper) + || (left._upper == right._upper) && (left._lower >= right._lower); + } + + // + // IDecrementOperators + // + + /// + public static UInt128 operator --(UInt128 value) => value - One; + + /// + /* TODO: + public static UInt128 operator checked --(UInt128 value) => checked(value - One); + */ + + // + // IDivisionOperators + // + + /// + public static UInt128 operator /(UInt128 left, UInt128 right) + { + if (right._upper == 0) + { + if (right._lower == 0) + { + ThrowHelper.ThrowDivideByZeroException(); + } + + if (left._upper == 0) + { + // left and right are both uint64 + return left._lower / right._lower; + } + } + + if (right >= left) + { + return (right == left) ? One : Zero; + } + + return DivideSlow(left, right); + + static uint AddDivisor(Span left, ReadOnlySpan right) + { + Debug.Assert(left.Length >= right.Length); + + // Repairs the dividend, if the last subtract was too much + + ulong carry = 0UL; + + for (int i = 0; i < right.Length; i++) + { + ref uint leftElement = ref left[i]; + ulong digit = (leftElement + carry) + right[i]; + + leftElement = unchecked((uint)digit); + carry = digit >> 32; + } + + return (uint)carry; + } + + static bool DivideGuessTooBig(ulong q, ulong valHi, uint valLo, uint divHi, uint divLo) + { + // TODO: + // Debug.Assert(q <= 0xFFFFFFFF); + + // We multiply the two most significant limbs of the divisor + // with the current guess for the quotient. If those are bigger + // than the three most significant limbs of the current dividend + // we return true, which means the current guess is still too big. + + ulong chkHi = divHi * q; + ulong chkLo = divLo * q; + + chkHi += (chkLo >> 32); + chkLo = (uint)(chkLo); + + return (chkHi > valHi) || ((chkHi == valHi) && (chkLo > valLo)); + } + + unsafe static UInt128 DivideSlow(UInt128 quotient, UInt128 divisor) + { + // This is the same algorithm currently used by BigInteger so + // we need to get a Span containing the value represented + // in the least number of elements possible. + + // We need to ensure that we end up with 4x uints representing the bits from + // least significant to most significant so the math will be correct on both + // little and big endian systems. So we'll just allocate the relevant buffer + // space and then write out the four parts using the native endianness of the + // system. + + uint* pLeft = (stackalloc uint[Size / sizeof(uint)]).Buffer; + + Unsafe.WriteUnaligned(ref *(byte*)(pLeft + 0), (uint)(quotient._lower >> 00)); + Unsafe.WriteUnaligned(ref *(byte*)(pLeft + 1), (uint)(quotient._lower >> 32)); + + Unsafe.WriteUnaligned(ref *(byte*)(pLeft + 2), (uint)(quotient._upper >> 00)); + Unsafe.WriteUnaligned(ref *(byte*)(pLeft + 3), (uint)(quotient._upper >> 32)); + + Span left = new Span(pLeft, (Size / sizeof(uint)) - (LeadingZeroCountAsInt32(quotient) / 32)); + + // Repeat the same operation with the divisor + + uint* pRight = stackalloc uint[Size / sizeof(uint)]; + + Unsafe.WriteUnaligned(ref *(byte*)(pRight + 0), (uint)(divisor._lower >> 00)); + Unsafe.WriteUnaligned(ref *(byte*)(pRight + 1), (uint)(divisor._lower >> 32)); + + Unsafe.WriteUnaligned(ref *(byte*)(pRight + 2), (uint)(divisor._upper >> 00)); + Unsafe.WriteUnaligned(ref *(byte*)(pRight + 3), (uint)(divisor._upper >> 32)); + + Span right = new Span(pRight, (Size / sizeof(uint)) - (LeadingZeroCountAsInt32(divisor) / 32)); + + Span rawBits = stackalloc uint[Size / sizeof(uint)]; + rawBits.Clear(); + Span bits = rawBits.Slice(0, left.Length - right.Length + 1); + + Debug.Assert(left.Length >= 1); + Debug.Assert(right.Length >= 1); + Debug.Assert(left.Length >= right.Length); + + // Executes the "grammar-school" algorithm for computing q = a / b. + // Before calculating q_i, we get more bits into the highest bit + // block of the divisor. Thus, guessing digits of the quotient + // will be more precise. Additionally we'll get r = a % b. + + uint divHi = right[^1]; + uint divLo = right.Length > 1 ? right[^2] : 0; + + // We measure the leading zeros of the divisor + int shift = BitOperations.LeadingZeroCount(divHi); + int backShift = 32 - shift; + + // And, we make sure the most significant bit is set + if (shift > 0) + { + uint divNx = right.Length > 2 ? right[^3] : 0; + + divHi = (divHi << shift) | (divLo >> backShift); + divLo = (divLo << shift) | (divNx >> backShift); + } + + // Then, we divide all of the bits as we would do it using + // pen and paper: guessing the next digit, subtracting, ... + for (int i = left.Length; i >= right.Length; i--) + { + int n = i - right.Length; + uint t = ((uint)(i) < (uint)(left.Length)) ? left[i] : 0; + + ulong valHi = ((ulong)(t) << 32) | left[i - 1]; + uint valLo = (i > 1) ? left[i - 2] : 0; + + // We shifted the divisor, we shift the dividend too + if (shift > 0) + { + uint valNx = i > 2 ? left[i - 3] : 0; + + valHi = (valHi << shift) | (valLo >> backShift); + valLo = (valLo << shift) | (valNx >> backShift); + } + + // First guess for the current digit of the quotient, + // which naturally must have only 32 bits... + ulong digit = valHi / divHi; + + if (digit > 0xFFFFFFFF) + { + digit = 0xFFFFFFFF; + } + + // Our first guess may be a little bit to big + while (DivideGuessTooBig(digit, valHi, valLo, divHi, divLo)) + { + --digit; + } + + if (digit > 0) + { + // Now it's time to subtract our current quotient + uint carry = SubtractDivisor(left.Slice(n), right, digit); + + if (carry != t) + { + Debug.Assert(carry == (t + 1)); + + // Our guess was still exactly one too high + carry = AddDivisor(left.Slice(n), right); + + --digit; + Debug.Assert(carry == 1); + } + } + + // We have the digit! + if ((uint)(n) < (uint)(bits.Length)) + { + bits[n] = (uint)(digit); + } + + if ((uint)(i) < (uint)(left.Length)) + { + left[i] = 0; + } + } + + return new UInt128( + ((ulong)(rawBits[3]) << 32) | rawBits[2], + ((ulong)(rawBits[1]) << 32) | rawBits[0] + ); + } + + static uint SubtractDivisor(Span left, ReadOnlySpan right, ulong q) + { + Debug.Assert(left.Length >= right.Length); + Debug.Assert(q <= 0xFFFFFFFF); + + // Combines a subtract and a multiply operation, which is naturally + // more efficient than multiplying and then subtracting... + + ulong carry = 0UL; + + for (int i = 0; i < right.Length; i++) + { + carry += right[i] * q; + + uint digit = (uint)(carry); + carry >>= 32; + + ref uint leftElement = ref left[i]; + + if (leftElement < digit) + { + ++carry; + } + leftElement -= digit; + } + + return (uint)(carry); + } + } + + /// + /* TODO: + public static UInt128 operator checked /(UInt128 left, UInt128 right) => left / right; + */ + + // + // IEqualityOperators + // + + /// + public static bool operator ==(UInt128 left, UInt128 right) => (left._lower == right._lower) && (left._upper == right._upper); + + /// + public static bool operator !=(UInt128 left, UInt128 right) => (left._lower != right._lower) || (left._upper != right._upper); + + // + // IIncrementOperators + // + + /// + public static UInt128 operator ++(UInt128 value) => value + One; + + /// + /* TODO: + public static UInt128 operator checked ++(UInt128 value) => checked(value + One); + */ + + // + // IMinMaxValue + // + + /// + public static UInt128 MinValue => new UInt128(0, 0); + + /// + public static UInt128 MaxValue => new UInt128(0xFFFF_FFFF_FFFF_FFFF, 0xFFFF_FFFF_FFFF_FFFF); + + // + // IModulusOperators + // + + /// + public static UInt128 operator %(UInt128 left, UInt128 right) + { + UInt128 quotient = left / right; + return left - (quotient * right); + } + + // + // IMultiplyOperators + // + + /// + public static UInt128 operator *(UInt128 left, UInt128 right) + { + ulong upper = Math.BigMul(left._lower, right._lower, out ulong lower); + upper += (left._upper * right._lower) + (left._lower * right._upper); + return new UInt128(upper, lower); + } + + /// + /* TODO: + public static UInt128 operator checked *(UInt128 left, UInt128 right) + { + UInt128 upper = BigMul(left, right, out UInt128 lower); + + if (upper != 0U) + { + ThrowHelper.ThrowOverflowException(); + } + + return lower; + } + */ + + /// Produces the full product of two unsigned native integers. + /// The integer to multiply with . + /// The integer to multiply with . + /// The lower half of the full product. + /// The upper half of the full product. + public static UInt128 BigMul(UInt128 left, UInt128 right, out UInt128 lower) + { + // Adaptation of algorithm for multiplication + // of 32-bit unsigned integers described + // in Hacker's Delight by Henry S. Warren, Jr. (ISBN 0-201-91465-4), Chapter 8 + // Basically, it's an optimized version of FOIL method applied to + // low and high qwords of each operand + + ulong al = left._lower; + ulong ah = left._upper; + + ulong bl = right._lower; + ulong bh = right._upper; + + UInt128 mull = Math.BigMul(al, bl); + UInt128 t = Math.BigMul(ah, bl) + mull._upper; + UInt128 tl = Math.BigMul(al, bh) + t._lower; + + lower = new UInt128(tl._lower, mull._lower); + return Math.BigMul(ah, bh) + t._upper + tl._upper; + } + + // + // INumberBase + // + + /// + public static UInt128 One => new UInt128(0, 1); + + /// + public static UInt128 Zero => default; + + // + // IShiftOperators + // + + /// + public static UInt128 operator <<(UInt128 value, int shiftAmount) + { + // C# automatically masks the shift amount for UInt64 to be 0x3F. So we + // need to specially handle things if the 7th bit is set. + + shiftAmount &= 0x7F; + + if ((shiftAmount & 0x40) != 0) + { + // In the case it is set, we know the entire lower bits must be zero + // and so the upper bits are just the lower shifted by the remaining + // masked amount + + ulong upper = value._lower << shiftAmount; + return new UInt128(upper, 0); + } + else if (shiftAmount != 0) + { + // Otherwise we need to shift both upper and lower halves by the masked + // amount and then or that with whatever bits were shifted "out" of lower + + ulong lower = value._lower << shiftAmount; + ulong upper = (value._upper << shiftAmount) | (value._lower >> (64 - shiftAmount)); + + return new UInt128(upper, lower); + } + else + { + return value; + } + } + + /// + public static UInt128 operator >>(UInt128 value, int shiftAmount) => value >>> shiftAmount; + + /// + /* TODO: + public static UInt128 operator >>>(UInt128 value, int shiftAmount) + { + // C# automatically masks the shift amount for UInt64 to be 0x3F. So we + // need to specially handle things if the 7th bit is set. + + shiftAmount &= 0x7F; + + if ((shiftAmount & 0x40) != 0) + { + // In the case it is set, we know the entire upper bits must be zero + // and so the lower bits are just the upper shifted by the remaining + // masked amount + + ulong lower = value._upper >> shiftAmount; + return new UInt128(0, lower); + } + else if (shiftAmount != 0) + { + // Otherwise we need to shift both upper and lower halves by the masked + // amount and then or that with whatever bits were shifted "out" of upper + + ulong lower = (value._lower >> shiftAmount) | (value._upper << (64 - shiftAmount)); + ulong upper = value._upper >> shiftAmount; + + return new UInt128(upper, lower); + } + else + { + return value; + } + } + */ + + // + // ISubtractionOperators + // + + /// + public static UInt128 operator -(UInt128 left, UInt128 right) + { + // For unsigned subtract, we can detect overflow by checking `(x - y) > x` + // This gives us the borrow to subtract from upper to compute the correct result + + ulong lower = left._lower - right._lower; + ulong borrow = (lower > left._lower) ? 1UL : 0UL; + + ulong upper = left._upper - right._upper - borrow; + return new UInt128(upper, lower); + } + + /// + /* TODO: + public static UInt128 operator checked -(UInt128 left, UInt128 right) + { + // For unsigned subtract, we can detect overflow by checking `(x - y) > x` + // This gives us the borrow to subtract from upper to compute the correct result + + ulong lower = left._lower - right._lower; + ulong borrow = (lower > left._lower) ? 1UL : 0UL; + + ulong upper = checked(left._upper - right._upper - borrow); + return new UInt128(upper, lower); + } + */ + + // + // IUnaryNegationOperators + // + + /// + public static UInt128 operator -(UInt128 value) => Zero - value; + + /// + /* TODO: + public static UInt128 operator checked -(UInt128 value) => checked(Zero - value); + */ +} From 4c1e50588a64d7ed529b336ef8d4606154617284 Mon Sep 17 00:00:00 2001 From: Airat Abdrakov <52558686+CrackAndDie@users.noreply.github.com> Date: Sat, 18 Apr 2026 12:59:18 +0300 Subject: [PATCH 09/26] Removed ulong cast --- std/System/UInt128.hpt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/std/System/UInt128.hpt b/std/System/UInt128.hpt index 85d7a58f..1faa2aaf 100644 --- a/std/System/UInt128.hpt +++ b/std/System/UInt128.hpt @@ -634,7 +634,7 @@ public readonly struct UInt128 // This gives us the carry to add to upper to compute the correct result ulong lower = left._lower + right._lower; - ulong carry = (lower < left._lower) ? (ulong)1UL : 0UL; + ulong carry = (lower < left._lower) ? 1UL : 0UL; ulong upper = left._upper + right._upper + carry; return new UInt128(upper, lower); From b679ff58881dd52120430be63944e148c3f7645e Mon Sep 17 00:00:00 2001 From: Airat Abdrakov <52558686+CrackAndDie@users.noreply.github.com> Date: Sat, 18 Apr 2026 13:26:19 +0300 Subject: [PATCH 10/26] Const field fix in Timeout.hpt --- std/System/Threading/Timeout.hpt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/std/System/Threading/Timeout.hpt b/std/System/Threading/Timeout.hpt index efa40374..ed7820db 100644 --- a/std/System/Threading/Timeout.hpt +++ b/std/System/Threading/Timeout.hpt @@ -10,5 +10,5 @@ public static class Timeout public static readonly TimeSpan InfiniteTimeSpan = new TimeSpan(0, 0, 0, 0, Infinite); public const int Infinite = -1; - internal const uint UnsignedInfinite = unchecked((uint)-1); + internal const uint UnsignedInfinite = unchecked((uint)(-1)); } \ No newline at end of file From 725f662ed942e14640270f6130c5b0247f172a5f Mon Sep 17 00:00:00 2001 From: Airat Abdrakov <52558686+CrackAndDie@users.noreply.github.com> Date: Tue, 21 Apr 2026 10:45:20 +0300 Subject: [PATCH 11/26] Some cringe shite added for UInt128 --- std/System/DivideByZeroException.hpt | 23 +++++++++++++++ std/System/Int128.hpt | 2 +- std/System/Math.hpt | 44 ++++++++++++++++++++++++++++ std/System/SR.hpt | 1 + std/System/UInt128.hpt | 44 ++++++++++++++++++---------- 5 files changed, 97 insertions(+), 17 deletions(-) create mode 100644 std/System/DivideByZeroException.hpt diff --git a/std/System/DivideByZeroException.hpt b/std/System/DivideByZeroException.hpt new file mode 100644 index 00000000..10352fa9 --- /dev/null +++ b/std/System/DivideByZeroException.hpt @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// Some modifications were made for hapet-lang compatibility. + +using System.ComponentModel; +using System.Runtime.CompilerServices; +using System.Runtime.Serialization; + +/// +/// The exception that is thrown when there is an attempt to divide an integral or value by zero. +/// +public class DivideByZeroException : ArithmeticException +{ + public DivideByZeroException() + : base(SR.Arg_DivideByZero) + { + } + + public DivideByZeroException(string message) + : base(message) + { + } +} \ No newline at end of file diff --git a/std/System/Int128.hpt b/std/System/Int128.hpt index e98a2b75..2a47fe14 100644 --- a/std/System/Int128.hpt +++ b/std/System/Int128.hpt @@ -660,7 +660,7 @@ public readonly struct Int128 { if ((right == -1) && (left._upper == 0x8000_0000_0000_0000) && (left._lower == 0)) { - ThrowHelper.ThrowOverflowException(); + throw new OverflowException(); } // We simplify the logic here by just doing unsigned division on the diff --git a/std/System/Math.hpt b/std/System/Math.hpt index 9bbb9c23..10c0fe98 100644 --- a/std/System/Math.hpt +++ b/std/System/Math.hpt @@ -16,4 +16,48 @@ public static class Math Marshal.ModF(d, &d); return d; } + + /// Produces the full product of two unsigned 64-bit numbers. + /// The first number to multiply. + /// The second number to multiply. + /// The low 64-bit of the product of the specified numbers. + /// The high 64-bit of the product of the specified numbers. + public static inline unsafe ulong BigMul(ulong a, ulong b, out ulong low) + { + return SoftwareFallback(a, b, out low); + + static ulong SoftwareFallback(ulong a, ulong b, out ulong low) + { + // Adaptation of algorithm for multiplication + // of 32-bit unsigned integers described + // in Hacker's Delight by Henry S. Warren, Jr. (ISBN 0-201-91465-4), Chapter 8 + // Basically, it's an optimized version of FOIL method applied to + // low and high dwords of each operand + + // Use 32-bit uints to optimize the fallback for 32-bit platforms. + uint al = (uint)a; + uint ah = (uint)(a >> 32); + uint bl = (uint)b; + uint bh = (uint)(b >> 32); + + ulong mull = ((ulong)al) * bl; + ulong t = ((ulong)ah) * bl + (mull >> 32); + ulong tl = ((ulong)al) * bh + (uint)t; + + low = tl << 32 | (uint)mull; + + return ((ulong)ah) * bh + (t >> 32) + (tl >> 32); + } + } + + /// Produces the full product of two unsigned 64-bit numbers. + /// The first number to multiply. + /// The second number to multiply. + /// The full product of the specified numbers. + public static inline UInt128 BigMul(ulong a, ulong b) + { + ulong low; + ulong high = BigMul(a, b, out low); + return new UInt128(high, low); + } } \ No newline at end of file diff --git a/std/System/SR.hpt b/std/System/SR.hpt index bd2bae40..8390d157 100644 --- a/std/System/SR.hpt +++ b/std/System/SR.hpt @@ -43,6 +43,7 @@ internal static class SR internal const string Arg_BogusIComparer = "Unable to sort because the IComparer.Compare() method returns inconsistent results. Either a value does not compare equal to itself, or one value repeatedly compared to another value yields different results."; internal const string Arg_InvalidConsoleColor = "The ConsoleColor enum value was not defined on that enum. Please use a defined color from the enum."; internal const string Arg_OverflowException = "Arithmetic operation resulted in an overflow."; + internal const string Arg_DivideByZero = "Attempted to divide by zero."; internal const string Arg_DllNotFoundException = "Dll was not found."; diff --git a/std/System/UInt128.hpt b/std/System/UInt128.hpt index 1faa2aaf..b66af466 100644 --- a/std/System/UInt128.hpt +++ b/std/System/UInt128.hpt @@ -655,6 +655,20 @@ public readonly struct UInt128 } */ + /// + public static UInt128 LeadingZeroCount(UInt128 value) + => (uint)LeadingZeroCountAsInt32(value); + + /// Computes the number of leading zero bits in this value. + private static inline int LeadingZeroCountAsInt32(UInt128 value) + { + if (value._upper == 0) + { + return 64 + BitOperations.LeadingZeroCount(value._lower); + } + return BitOperations.LeadingZeroCount(value._upper); + } + /// public static UInt128 PopCount(UInt128 value) => ulong.PopCount(value._lower) + ulong.PopCount(value._upper); @@ -730,7 +744,7 @@ public readonly struct UInt128 { if (right._lower == 0) { - ThrowHelper.ThrowDivideByZeroException(); + throw new DivideByZeroException(); } if (left._upper == 0) @@ -800,17 +814,16 @@ public readonly struct UInt128 uint* pLeft = (stackalloc uint[Size / sizeof(uint)]).Buffer; - Unsafe.WriteUnaligned(ref *(byte*)(pLeft + 0), (uint)(quotient._lower >> 00)); - Unsafe.WriteUnaligned(ref *(byte*)(pLeft + 1), (uint)(quotient._lower >> 32)); - - Unsafe.WriteUnaligned(ref *(byte*)(pLeft + 2), (uint)(quotient._upper >> 00)); - Unsafe.WriteUnaligned(ref *(byte*)(pLeft + 3), (uint)(quotient._upper >> 32)); + *(pLeft + 0) = (uint)(quotient._lower >> 00); + *(pLeft + 1) = (uint)(quotient._lower >> 32); + *(pLeft + 2) = (uint)(quotient._upper >> 00); + *(pLeft + 3) = (uint)(quotient._upper >> 32); Span left = new Span(pLeft, (Size / sizeof(uint)) - (LeadingZeroCountAsInt32(quotient) / 32)); // Repeat the same operation with the divisor - uint* pRight = stackalloc uint[Size / sizeof(uint)]; + uint* pRight = (stackalloc uint[Size / sizeof(uint)]).Buffer; Unsafe.WriteUnaligned(ref *(byte*)(pRight + 0), (uint)(divisor._lower >> 00)); Unsafe.WriteUnaligned(ref *(byte*)(pRight + 1), (uint)(divisor._lower >> 32)); @@ -1003,7 +1016,8 @@ public readonly struct UInt128 /// public static UInt128 operator *(UInt128 left, UInt128 right) { - ulong upper = Math.BigMul(left._lower, right._lower, out ulong lower); + ulong lower; + ulong upper = Math.BigMul(left._lower, right._lower, out lower); upper += (left._upper * right._lower) + (left._lower * right._upper); return new UInt128(upper, lower); } @@ -1078,7 +1092,7 @@ public readonly struct UInt128 // and so the upper bits are just the lower shifted by the remaining // masked amount - ulong upper = value._lower << shiftAmount; + ulong upper = value._lower << (ulong)shiftAmount; return new UInt128(upper, 0); } else if (shiftAmount != 0) @@ -1086,8 +1100,8 @@ public readonly struct UInt128 // Otherwise we need to shift both upper and lower halves by the masked // amount and then or that with whatever bits were shifted "out" of lower - ulong lower = value._lower << shiftAmount; - ulong upper = (value._upper << shiftAmount) | (value._lower >> (64 - shiftAmount)); + ulong lower = value._lower << (ulong)shiftAmount; + ulong upper = (value._upper << (ulong)shiftAmount) | (value._lower >> (ulong)(64 - shiftAmount)); return new UInt128(upper, lower); } @@ -1101,7 +1115,6 @@ public readonly struct UInt128 public static UInt128 operator >>(UInt128 value, int shiftAmount) => value >>> shiftAmount; /// - /* TODO: public static UInt128 operator >>>(UInt128 value, int shiftAmount) { // C# automatically masks the shift amount for UInt64 to be 0x3F. So we @@ -1115,7 +1128,7 @@ public readonly struct UInt128 // and so the lower bits are just the upper shifted by the remaining // masked amount - ulong lower = value._upper >> shiftAmount; + ulong lower = value._upper >> (ulong)shiftAmount; return new UInt128(0, lower); } else if (shiftAmount != 0) @@ -1123,8 +1136,8 @@ public readonly struct UInt128 // Otherwise we need to shift both upper and lower halves by the masked // amount and then or that with whatever bits were shifted "out" of upper - ulong lower = (value._lower >> shiftAmount) | (value._upper << (64 - shiftAmount)); - ulong upper = value._upper >> shiftAmount; + ulong lower = (value._lower >> (ulong)shiftAmount) | (value._upper << (ulong)(64 - shiftAmount)); + ulong upper = value._upper >> (ulong)shiftAmount; return new UInt128(upper, lower); } @@ -1133,7 +1146,6 @@ public readonly struct UInt128 return value; } } - */ // // ISubtractionOperators From 09a85dcebd0fd0db09e1880344f801c5e933f092 Mon Sep 17 00:00:00 2001 From: Airat Abdrakov <52558686+CrackAndDie@users.noreply.github.com> Date: Tue, 21 Apr 2026 15:53:00 +0300 Subject: [PATCH 12/26] Int128 and UInt128 probably done --- std/System/Int128.hpt | 4 +- std/System/Math.hpt | 61 ++++- .../Text/Formatting/Number.Formatting.hpt | 237 +++++++++++++++++- std/System/TimeSpan.hpt | 10 +- std/System/UInt128.hpt | 108 ++++---- 5 files changed, 354 insertions(+), 66 deletions(-) diff --git a/std/System/Int128.hpt b/std/System/Int128.hpt index 2a47fe14..b8456315 100644 --- a/std/System/Int128.hpt +++ b/std/System/Int128.hpt @@ -85,12 +85,12 @@ public readonly struct Int128 /// public override string ToString() { - return Number.Int128ToDecStr(this); + return System.Text.Formatting.Number.Format(this, ""); } public string ToString(string format) { - return Number.FormatInt128(this, format, null); + return System.Text.Formatting.Number.Format(this, format); } // diff --git a/std/System/Math.hpt b/std/System/Math.hpt index 10c0fe98..494611a6 100644 --- a/std/System/Math.hpt +++ b/std/System/Math.hpt @@ -6,17 +6,50 @@ using System.Runtime.InteropServices; public static class Math { - public static int Min(int val1, int val2) + public static inline int Min(int val1, int val2) { return (val1 <= val2) ? val1 : val2; } - public static double Truncate(double d) + public static inline double Truncate(double d) { Marshal.ModF(d, &d); return d; } + /// Produces the full product of two unsigned 32-bit numbers. + /// The first number to multiply. + /// The second number to multiply. + /// The number containing the product of the specified numbers. + public static inline ulong BigMul(uint a, uint b) + { + return ((ulong)a) * b; + } + + /// Produces the full product of two 32-bit numbers. + /// The first number to multiply. + /// The second number to multiply. + /// The number containing the product of the specified numbers. + public static inline long BigMul(int a, int b) + { + return ((long)a) * b; + } + + + /// + /// Perform multiplication between 64 and 32 bit numbers, returning lower 64 bits in + /// + /// hi bits of the result + /// REMOVE once BigMul(ulong, ulong) is treated as intrinsics and optimizes 32 by 64 multiplications + internal static inline ulong BigMul(ulong a, uint b, out ulong low) + { + return Math.BigMul((ulong)a, (ulong)b, out low); + } + + /// + internal static inline ulong BigMul(uint a, ulong b, out ulong low) + => BigMul(b, a, out low); + /// Produces the full product of two unsigned 64-bit numbers. /// The first number to multiply. /// The second number to multiply. @@ -50,6 +83,19 @@ public static class Math } } + /// Produces the full product of two 64-bit numbers. + /// The first number to multiply. + /// The second number to multiply. + /// The low 64-bit of the product of the specified numbers. + /// The high 64-bit of the product of the specified numbers. + public static inline long BigMul(long a, long b, out long low) + { + ulong ulow; + ulong high = BigMul((ulong)a, (ulong)b, out ulow); + low = (long)ulow; + return (long)high - ((a >> 63) & b) - ((b >> 63) & a); + } + /// Produces the full product of two unsigned 64-bit numbers. /// The first number to multiply. /// The second number to multiply. @@ -60,4 +106,15 @@ public static class Math ulong high = BigMul(a, b, out low); return new UInt128(high, low); } + + /// Produces the full product of two 64-bit numbers. + /// The first number to multiply. + /// The second number to multiply. + /// The full product of the specified numbers. + public static inline Int128 BigMul(long a, long b) + { + long low; + long high = BigMul(a, b, out low); + return new Int128((ulong)high, (ulong)low); + } } \ No newline at end of file diff --git a/std/System/Text/Formatting/Number.Formatting.hpt b/std/System/Text/Formatting/Number.Formatting.hpt index 26bee5c7..a21745f0 100644 --- a/std/System/Text/Formatting/Number.Formatting.hpt +++ b/std/System/Text/Formatting/Number.Formatting.hpt @@ -23,6 +23,8 @@ public static class Number const int UInt32Precision = 10; const int Int64Precision = 19; const int UInt64Precision = 20; + const int Int128Precision = 39; + const int UInt128Precision = 39; const int FloatPrecision = 7; const int DoublePrecision = 15; const int DecimalPrecision = 29; @@ -83,6 +85,18 @@ public static class Number return FormatUInt64(value, specifier, cc); } + public static inline string Format(Int128 value, string specifier, CultureInfo culture = null) + { + CachedCulture cc = culture == null ? CachedInvariantCulture : new CachedCulture(culture); + return FormatInt128(value, specifier, cc); + } + + public static inline string Format(UInt128 value, string specifier, CultureInfo culture = null) + { + CachedCulture cc = culture == null ? CachedInvariantCulture : new CachedCulture(culture); + return FormatUInt128(value, specifier, cc); + } + public static inline string Format(float value, string specifier, CultureInfo culture = null) { CachedCulture cc = culture == null ? CachedInvariantCulture : new CachedCulture(culture); @@ -427,6 +441,84 @@ public static class Number } } + static string FormatInt128(Int128 value, string specifier, CachedCulture culture) + { + int digits; + var fmt = ParseFormatSpecifier(specifier, out digits); + + // ANDing with 0xFFDF has the effect of uppercasing the character + switch (fmt & 0xFFDF) + { + case ('G') + { + if (digits > 0) + goto caseDefault; + else + goto caseD; + } + case ('D') $caseD + { + return Int128ToDecStr(value, digits, culture.NegativeSign); + } + case ('X') + { + // fmt-('X'-'A'+1) gives us the base hex character in either + // uppercase or lowercase, depending on the casing of fmt + return Int128ToHexStr((UInt128)value, fmt - ('X' - 'A' + 10), digits); + } + default $caseDefault + { + var number = new NumberStruct(); + var buffer = (stackalloc char[MaxNumberDigits + 1]).Buffer; + number.Digits = buffer; + Int128ToNumber(value, ref number); + if (fmt != 0) + return NumberToString(ref number, fmt, digits, culture); + else + return NumberToCustomFormatString(ref number, specifier, culture); + } + } + } + + static string FormatUInt128(UInt128 value, string specifier, CachedCulture culture) + { + int digits; + var fmt = ParseFormatSpecifier(specifier, out digits); + + // ANDing with 0xFFDF has the effect of uppercasing the character + switch (fmt & 0xFFDF) + { + case ('G') + { + if (digits > 0) + goto caseDefault; + else + goto caseD; + } + case ('D') $caseD + { + return UInt128ToDecStr(value, digits); + } + case ('X') + { + // fmt-('X'-'A'+1) gives us the base hex character in either + // uppercase or lowercase, depending on the casing of fmt + return Int128ToHexStr(value, fmt - ('X' - 'A' + 10), digits); + } + default $caseDefault + { + var number = new NumberStruct(); + var buffer = (stackalloc char[MaxNumberDigits + 1]).Buffer; + number.Digits = buffer; + UInt128ToNumber(value, ref number); + if (fmt != 0) + return NumberToString(ref number, fmt, digits, culture); + else + return NumberToCustomFormatString(ref number, specifier, culture); + } + } + } + static string FormatSingle(float value, string specifier, CachedCulture culture) { int digits; @@ -598,6 +690,47 @@ public static class Number return GetStringFromBufferCopy(p, (int)(buffer + bufferLength - p)); } + static string Int128ToDecStr(Int128 value, int digits, string negativeSign) + { + if (digits < 1) + digits = 1; + + int sign = (int)High32((ulong)(value >> 64)); + int maxDigits = 39; + int bufferLength = maxDigits > 100 ? maxDigits : 100; + + if (sign < 0) + { + value = -value; + var negativeLength = negativeSign.Length; + if (negativeLength > bufferLength - maxDigits) + bufferLength = negativeLength + maxDigits; + } + + var buffer = (stackalloc char[bufferLength]).Buffer; + var p = UInt128ToDecChars(buffer + bufferLength, (UInt128)value, digits); + if (sign < 0) + { + // add the negative sign + for (int i = negativeSign.Length - 1; i >= 0; i--) + *(--p) = negativeSign[i]; + } + return GetStringFromBufferCopy(p, (int)(buffer + bufferLength - p)); + } + + static string UInt128ToDecStr(UInt128 value, int digits) + { + if (digits < 1) + digits = 1; + + int maxDigits = 39; + int bufferLength = maxDigits > 100 ? maxDigits : 100; + + var buffer = (stackalloc char[bufferLength]).Buffer; + var p = UInt128ToDecChars(buffer + bufferLength, value, digits); + return GetStringFromBufferCopy(p, (int)(buffer + bufferLength - p)); + } + [SuppressStackTrace] internal static char* UInt32ToDecChars(char* p, uint value, int digits) { @@ -652,6 +785,24 @@ public static class Number return p; } + [SuppressStackTrace] + internal static char* UInt128ToDecChars(char* p, UInt128 value, int digits) + { + while (value != 0) + { + char c = (char)(value % 10 + '0'); + *--p = c; + value /= 10; + digits--; + } + + while (--digits >= 0) + { + *--p = '0'; + } + return p; + } + static string Int32ToHexStr(uint value, int hexBase, int digits) { var buffer = (stackalloc char[100]).Buffer; @@ -664,7 +815,7 @@ public static class Number static string Int64ToHexStr(ulong value, int hexBase, int digits) { - var buffer = (stackalloc char[100]).Buffer; + char* buffer = (stackalloc char[100]).Buffer; char* ptr; if (High32(value) != 0) { @@ -681,6 +832,40 @@ public static class Number return GetStringFromBufferCopy(ptr, (int)(buffer + 100 - ptr)); } + static string Int128ToHexStr(UInt128 value, int hexBase, int digits) + { + char* buffer = (stackalloc char[200]).Buffer; + char* ptr; + + ulong low = Low64(value); + ulong high = High64(value); + + if (high != 0) + { + Int64ToHexChars(buffer + 200, low, hexBase, 16); + ptr = Int64ToHexChars(buffer + 200 - 16, high, hexBase, digits - 16); + } + else + { + if (digits < 1) + digits = 1; + ptr = Int64ToHexChars(buffer + 200, low, hexBase, digits); + } + + return GetStringFromBufferCopy(ptr, (int)(buffer + 200 - ptr)); + } + + static char* Int64ToHexChars(char* p, ulong value, int hexBase, int digits) + { + while (--digits >= 0 || value != 0) + { + var digit = value & 0xF; + *--p = (char)((int)digit + (digit < 10 ? '0' : hexBase)); + value = value >> 4; + } + return p; + } + static char* Int32ToHexChars(char* p, uint value, int hexBase, int digits) { while (--digits >= 0 || value != 0) @@ -768,6 +953,44 @@ public static class Number *dest = '\0'; } + static void Int128ToNumber(Int128 value, ref NumberStruct number) + { + number.Precision = Int128Precision; + if (value >= 0) + number.Sign = 0; + else + { + number.Sign = 1; + value = -value; + } + + var buffer = (stackalloc char[Int128Precision + 1]).Buffer; + var ptr = UInt128ToDecChars(buffer + Int128Precision, (UInt128)value, 0); + var len = (int)(buffer + Int128Precision - ptr); + number.Scale = len; + + var dest = number.Digits; + while (--len >= 0) + *dest++ = *ptr++; + *dest = '\0'; + } + + static void UInt128ToNumber(UInt128 value, ref NumberStruct number) + { + number.Precision = UInt128Precision; + number.Sign = 0; + + var buffer = (stackalloc char[UInt128Precision + 1]).Buffer; + var ptr = UInt128ToDecChars(buffer + UInt128Precision, value, 0); + var len = (int)(buffer + UInt128Precision - ptr); + number.Scale = len; + + var dest = number.Digits; + while (--len >= 0) + *dest++ = *ptr++; + *dest = '\0'; + } + static void DoubleToNumber(double value, int precision, ref NumberStruct number) { number.Precision = precision; @@ -1274,6 +1497,18 @@ public static class Number return (uint)((value & 0xFFFFFFFF00000000) >> 32); } + [SuppressStackTrace] + internal static ulong Low64(UInt128 value) + { + return (ulong)value; + } + + [SuppressStackTrace] + internal static ulong High64(UInt128 value) + { + return (ulong)(value >> 64); + } + static readonly CachedCulture CachedInvariantCulture = new CachedCulture(CultureInfo.InvariantCulture); static readonly CachedCulture CachedCurrentCulture = new CachedCulture(CultureInfo.CurrentCulture); } diff --git a/std/System/TimeSpan.hpt b/std/System/TimeSpan.hpt index e4cd9992..3095247d 100644 --- a/std/System/TimeSpan.hpt +++ b/std/System/TimeSpan.hpt @@ -507,7 +507,7 @@ public readonly struct TimeSpan + Math.BigMul(milliseconds, MicrosecondsPerMillisecond) + microseconds; - return FromMicroseconds(totalMicroseconds); + return FromMicroseconds((Int128)totalMicroseconds); } /// @@ -715,7 +715,7 @@ public readonly struct TimeSpan public static TimeSpan Parse(string s) { /* Constructs a TimeSpan from a string. Leading and trailing white space characters are allowed. */ - ArgumentNullException.ThrowIfNull(s, ExceptionArgument.input); + ArgumentNullException.ThrowIfNull(s); return TimeSpanParse.Parse(s, null); } public static TimeSpan Parse(string input, IFormatProvider formatProvider) @@ -726,7 +726,7 @@ public readonly struct TimeSpan } return TimeSpanParse.Parse(input, formatProvider); } - public static TimeSpan Parse(ReadOnlySpan input, IFormatProvider? formatProvider = null) => TimeSpanParse.Parse(input, formatProvider); + public static TimeSpan Parse(ReadOnlySpan input, IFormatProvider formatProvider = null) => TimeSpanParse.Parse(input, formatProvider); public static TimeSpan ParseExact(string input, string format, IFormatProvider formatProvider) { ArgumentNullException.ThrowIfNull(nameof(input)); @@ -769,7 +769,7 @@ public readonly struct TimeSpan } public static bool TryParse(string s, out TimeSpan result) { - if (s is null) + if (s == null) { result = default; return false; @@ -780,7 +780,7 @@ public readonly struct TimeSpan public static bool TryParse(string input, IFormatProvider formatProvider, out TimeSpan result) { - if (input is null) + if (input == null) { result = default; return false; diff --git a/std/System/UInt128.hpt b/std/System/UInt128.hpt index b66af466..972a9cc0 100644 --- a/std/System/UInt128.hpt +++ b/std/System/UInt128.hpt @@ -85,12 +85,12 @@ public readonly struct UInt128 /// public override string ToString() { - return Number.UInt128ToDecStr(this); + return System.Text.Formatting.Number.Format(this, ""); } public string ToString(string format) { - return Number.FormatUInt128(this, format, null); + return System.Text.Formatting.Number.Format(this, format); } // @@ -669,10 +669,6 @@ public readonly struct UInt128 return BitOperations.LeadingZeroCount(value._upper); } - /// - public static UInt128 PopCount(UInt128 value) - => ulong.PopCount(value._lower) + ulong.PopCount(value._upper); - // // IBitwiseOperators // @@ -761,20 +757,20 @@ public readonly struct UInt128 return DivideSlow(left, right); - static uint AddDivisor(Span left, ReadOnlySpan right) + static uint AddDivisor(uint* left, int leftLength, uint* right, int rightLength) { - Debug.Assert(left.Length >= right.Length); + // TODO: + // Debug.Assert(left.Length >= right.Length); // Repairs the dividend, if the last subtract was too much ulong carry = 0UL; - for (int i = 0; i < right.Length; i++) + for (int i = 0; i < rightLength; i++) { - ref uint leftElement = ref left[i]; - ulong digit = (leftElement + carry) + right[i]; + ulong digit = (left[i] + carry) + right[i]; - leftElement = unchecked((uint)digit); + left[i] = unchecked((uint)digit); carry = digit >> 32; } @@ -819,35 +815,34 @@ public readonly struct UInt128 *(pLeft + 2) = (uint)(quotient._upper >> 00); *(pLeft + 3) = (uint)(quotient._upper >> 32); - Span left = new Span(pLeft, (Size / sizeof(uint)) - (LeadingZeroCountAsInt32(quotient) / 32)); + var leftLength = (Size / sizeof(uint)) - (LeadingZeroCountAsInt32(quotient) / 32); // Repeat the same operation with the divisor uint* pRight = (stackalloc uint[Size / sizeof(uint)]).Buffer; - Unsafe.WriteUnaligned(ref *(byte*)(pRight + 0), (uint)(divisor._lower >> 00)); - Unsafe.WriteUnaligned(ref *(byte*)(pRight + 1), (uint)(divisor._lower >> 32)); + *(pRight + 0) = (uint)(divisor._lower >> 00); + *(pRight + 1) = (uint)(divisor._lower >> 32); + *(pRight + 2) = (uint)(divisor._upper >> 00); + *(pRight + 3) = (uint)(divisor._upper >> 32); - Unsafe.WriteUnaligned(ref *(byte*)(pRight + 2), (uint)(divisor._upper >> 00)); - Unsafe.WriteUnaligned(ref *(byte*)(pRight + 3), (uint)(divisor._upper >> 32)); + var rightLength = (Size / sizeof(uint)) - (LeadingZeroCountAsInt32(divisor) / 32); - Span right = new Span(pRight, (Size / sizeof(uint)) - (LeadingZeroCountAsInt32(divisor) / 32)); + uint* rawBits = (stackalloc uint[Size / sizeof(uint)]).Buffer; + var bitsLength = leftLength - rightLength + 1; - Span rawBits = stackalloc uint[Size / sizeof(uint)]; - rawBits.Clear(); - Span bits = rawBits.Slice(0, left.Length - right.Length + 1); - - Debug.Assert(left.Length >= 1); - Debug.Assert(right.Length >= 1); - Debug.Assert(left.Length >= right.Length); + // TODO: + // Debug.Assert(left.Length >= 1); + // Debug.Assert(right.Length >= 1); + // Debug.Assert(left.Length >= right.Length); // Executes the "grammar-school" algorithm for computing q = a / b. // Before calculating q_i, we get more bits into the highest bit // block of the divisor. Thus, guessing digits of the quotient // will be more precise. Additionally we'll get r = a % b. - uint divHi = right[^1]; - uint divLo = right.Length > 1 ? right[^2] : 0; + uint divHi = pRight[rightLength - 1]; + uint divLo = rightLength > 1 ? pRight[rightLength - 2] : 0; // We measure the leading zeros of the divisor int shift = BitOperations.LeadingZeroCount(divHi); @@ -856,29 +851,29 @@ public readonly struct UInt128 // And, we make sure the most significant bit is set if (shift > 0) { - uint divNx = right.Length > 2 ? right[^3] : 0; - - divHi = (divHi << shift) | (divLo >> backShift); - divLo = (divLo << shift) | (divNx >> backShift); + uint divNx = rightLength > 2 ? pRight[rightLength - 3] : 0; + + divHi = (divHi << (uint)shift) | (divLo >> (uint)backShift); + divLo = (divLo << (uint)shift) | (divNx >> (uint)backShift); } // Then, we divide all of the bits as we would do it using // pen and paper: guessing the next digit, subtracting, ... - for (int i = left.Length; i >= right.Length; i--) + for (int i = leftLength; i >= rightLength; i--) { - int n = i - right.Length; - uint t = ((uint)(i) < (uint)(left.Length)) ? left[i] : 0; + int n = i - rightLength; + uint t = ((uint)(i) < (uint)(leftLength)) ? pLeft[i] : 0; - ulong valHi = ((ulong)(t) << 32) | left[i - 1]; - uint valLo = (i > 1) ? left[i - 2] : 0; + ulong valHi = ((ulong)(t) << 32) | pLeft[i - 1]; + uint valLo = (i > 1) ? pLeft[i - 2] : 0; // We shifted the divisor, we shift the dividend too if (shift > 0) { - uint valNx = i > 2 ? left[i - 3] : 0; + uint valNx = i > 2 ? pLeft[i - 3] : 0; - valHi = (valHi << shift) | (valLo >> backShift); - valLo = (valLo << shift) | (valNx >> backShift); + valHi = (valHi << (uint)shift) | (valLo >> (uint)backShift); + valLo = (valLo << (uint)shift) | (valNx >> (uint)backShift); } // First guess for the current digit of the quotient, @@ -899,29 +894,31 @@ public readonly struct UInt128 if (digit > 0) { // Now it's time to subtract our current quotient - uint carry = SubtractDivisor(left.Slice(n), right, digit); + uint carry = SubtractDivisor(pLeft + n, leftLength - n, pRight, rightLength, digit); if (carry != t) { - Debug.Assert(carry == (t + 1)); + // TODO: + // Debug.Assert(carry == (t + 1)); // Our guess was still exactly one too high - carry = AddDivisor(left.Slice(n), right); + carry = AddDivisor(pLeft + n, leftLength - n, pRight, rightLength); --digit; - Debug.Assert(carry == 1); + // TODO: + // Debug.Assert(carry == 1); } } // We have the digit! - if ((uint)(n) < (uint)(bits.Length)) + if ((uint)(n) < (uint)(bitsLength)) { - bits[n] = (uint)(digit); + rawBits[n] = (uint)(digit); } - if ((uint)(i) < (uint)(left.Length)) + if ((uint)(i) < (uint)(leftLength)) { - left[i] = 0; + pLeft[i] = 0; } } @@ -931,30 +928,29 @@ public readonly struct UInt128 ); } - static uint SubtractDivisor(Span left, ReadOnlySpan right, ulong q) + static uint SubtractDivisor(uint* left, int leftLength, uint* right, int rightLength, ulong q) { - Debug.Assert(left.Length >= right.Length); - Debug.Assert(q <= 0xFFFFFFFF); + // TODO: + //Debug.Assert(left.Length >= rightLength); + //Debug.Assert(q <= 0xFFFFFFFF); // Combines a subtract and a multiply operation, which is naturally // more efficient than multiplying and then subtracting... ulong carry = 0UL; - for (int i = 0; i < right.Length; i++) + for (int i = 0; i < rightLength; i++) { carry += right[i] * q; uint digit = (uint)(carry); - carry >>= 32; - - ref uint leftElement = ref left[i]; + carry = carry >> 32; - if (leftElement < digit) + if (left[i] < digit) { ++carry; } - leftElement -= digit; + left[i] = left[i] - digit; } return (uint)(carry); From fee918c2774c8349d2dba80ab933af3208fa8a24 Mon Sep 17 00:00:00 2001 From: Airat Abdrakov <52558686+CrackAndDie@users.noreply.github.com> Date: Wed, 6 May 2026 08:42:42 +0300 Subject: [PATCH 13/26] Some additional implementations for TimeSpan --- HapetLsp/Colorizers/HapetColorizer.cs | 3 +- std/System/Double.hpt | 2 + std/System/Math.hpt | 68 ++++++++++ std/System/SR.hpt | 8 ++ std/System/Single.hpt | 2 + std/System/Threading/Timeout.hpt | 2 + std/System/TimeSpan.hpt | 182 ++------------------------ 7 files changed, 96 insertions(+), 171 deletions(-) diff --git a/HapetLsp/Colorizers/HapetColorizer.cs b/HapetLsp/Colorizers/HapetColorizer.cs index 67ce6648..e0934358 100644 --- a/HapetLsp/Colorizers/HapetColorizer.cs +++ b/HapetLsp/Colorizers/HapetColorizer.cs @@ -754,7 +754,8 @@ private void ColorizeSATExpr(AstSATOfExpr expr) // colorize 'sat' word AddSemanticToken(expr, expr.Location.Beginning, _tokenTypes[2], _tokenModifiers[0]); - ColorizeExpr(expr.TargetType); + if (expr.TargetType != null) + ColorizeExpr(expr.TargetType); } private void ColorizeLambdaExpr(AstLambdaExpr expr) diff --git a/std/System/Double.hpt b/std/System/Double.hpt index 3eb8c40c..747b7039 100644 --- a/std/System/Double.hpt +++ b/std/System/Double.hpt @@ -20,6 +20,8 @@ public struct Double : IComparable, IComparable, IFormattable public const double PositiveInfinity = (double)(1.0) / (double)(0.0); public const double NaN = (double)0.0 / (double)0.0; + internal const ulong SignMask = 0x8000_0000_0000_0000; + public int CompareTo(object value) { if (value == null) diff --git a/std/System/Math.hpt b/std/System/Math.hpt index 494611a6..3f3856f5 100644 --- a/std/System/Math.hpt +++ b/std/System/Math.hpt @@ -6,6 +6,14 @@ using System.Runtime.InteropServices; public static class Math { + public static inline double Abs(double value) + { + const ulong mask = 0x7FFFFFFFFFFFFFFF; + ulong raw = BitConverter.DoubleToUInt64Bits(value); + + return BitConverter.UInt64BitsToDouble(raw & mask); + } + public static inline int Min(int val1, int val2) { return (val1 <= val2) ? val1 : val2; @@ -17,6 +25,23 @@ public static class Math return d; } + public static inline double CopySign(double x, double y) + { + return SoftwareFallback(x, y); + + static double SoftwareFallback(double x, double y) + { + // This method is required to work for all inputs, + // including NaN, so we operate on the raw bits. + ulong xbits = BitConverter.DoubleToUInt64Bits(x); + ulong ybits = BitConverter.DoubleToUInt64Bits(y); + + // Remove the sign from x, and remove everything but the sign from y + // Then, simply OR them to get the correct sign + return BitConverter.UInt64BitsToDouble((xbits & ~double.SignMask) | (ybits & double.SignMask)); + } + } + /// Produces the full product of two unsigned 32-bit numbers. /// The first number to multiply. /// The second number to multiply. @@ -117,4 +142,47 @@ public static class Math long high = BigMul(a, b, out low); return new Int128((ulong)high, (ulong)low); } + + public static double Round(double a) + { + // ************************************************************************************ + // IMPORTANT: Do not change this implementation without also updating MathF.Round(float), + // FloatingPointUtils::round(double), and FloatingPointUtils::round(float) + // ************************************************************************************ + + // This code is based on `nearbyint` from amd/aocl-libm-ose + // Copyright (C) 2008-2022 Advanced Micro Devices, Inc. All rights reserved. + // + // Licensed under the BSD 3-Clause "New" or "Revised" License + // See THIRD-PARTY-NOTICES.TXT for the full license text + + // This represents the boundary at which point we can only represent whole integers + const double IntegerBoundary = 4503599627370496.0; // 2^52 + + if (Abs(a) >= IntegerBoundary) + { + // Values above this boundary don't have a fractional + // portion and so we can simply return them as-is. + return a; + } + + // Otherwise, since floating-point takes the inputs, performs + // the computation as if to infinite precision and unbounded + // range, and then rounds to the nearest representable result + // using the current rounding mode, we can rely on this to + // cheaply round. + // + // In particular, .NET doesn't support changing the rounding + // mode and defaults to "round to nearest, ties to even", thus + // by adding the original value to the IntegerBoundary we get + // an exactly represented whole integer that is precisely the + // IntegerBoundary greater in magnitude than the answer we want. + // + // We can then simply remove that offset to get the correct answer, + // noting that we also need to copy back the original sign to + // correctly handle -0.0 + + double temp = CopySign(IntegerBoundary, a); + return CopySign((a + temp) - temp, a); + } } \ No newline at end of file diff --git a/std/System/SR.hpt b/std/System/SR.hpt index 8390d157..316a2ca1 100644 --- a/std/System/SR.hpt +++ b/std/System/SR.hpt @@ -39,13 +39,21 @@ internal static class SR internal const string Arg_MustBeUInt128 = "Object must be of type UInt128."; internal const string Arg_MustBePtrDiff = "Object must be of type PtrDiff."; internal const string Arg_MustBeUIntPtr = "Object must be of type UIntPtr."; + internal const string Arg_MustBeTimeSpan = "Object must be of type TimeSpan."; internal const string Arg_BogusIComparer = "Unable to sort because the IComparer.Compare() method returns inconsistent results. Either a value does not compare equal to itself, or one value repeatedly compared to another value yields different results."; internal const string Arg_InvalidConsoleColor = "The ConsoleColor enum value was not defined on that enum. Please use a defined color from the enum."; internal const string Arg_OverflowException = "Arithmetic operation resulted in an overflow."; internal const string Arg_DivideByZero = "Attempted to divide by zero."; + internal const string Argument_InvalidTimeSpanStyles = "An undefined TimeSpanStyles value is being used."; internal const string Arg_DllNotFoundException = "Dll was not found."; + internal const string Arg_CannotBeNaN = "TimeSpan does not accept floating point Not-a-Number values."; + + // overflow + internal const string Overflow_TimeSpanTooLong = "TimeSpan overflowed because the duration is too long."; + internal const string Overflow_Duration = "The duration cannot be returned for TimeSpan.MinValue because the absolute value of TimeSpan.MinValue exceeds the value of TimeSpan.MaxValue."; + internal const string Overflow_NegateTwosCompNum = "Negating the minimum value of a twos complement number is invalid."; // not internal const string NotSupported_ReadOnlyCollection = "Collection is read-only."; diff --git a/std/System/Single.hpt b/std/System/Single.hpt index da9a2b9f..c5bff4f8 100644 --- a/std/System/Single.hpt +++ b/std/System/Single.hpt @@ -18,6 +18,8 @@ public struct Single : IComparable, IComparable, IFormattable public const float PositiveInfinity = (float)(1.0) / (float)(0.0); public const float NaN = (float)0.0 / (float)0.0; + internal const uint SignMask = 0x8000_0000; + public int CompareTo(object value) { if (value == null) diff --git a/std/System/Threading/Timeout.hpt b/std/System/Threading/Timeout.hpt index ed7820db..7d26ef2e 100644 --- a/std/System/Threading/Timeout.hpt +++ b/std/System/Threading/Timeout.hpt @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. // Some modifications were made for hapet-lang compatibility. +using System; + // A constant used by methods that take a timeout (Object.Wait, Thread.Sleep // etc) to indicate that no timeout should occur. // diff --git a/std/System/TimeSpan.hpt b/std/System/TimeSpan.hpt index 3095247d..76d2c29a 100644 --- a/std/System/TimeSpan.hpt +++ b/std/System/TimeSpan.hpt @@ -290,7 +290,7 @@ public readonly struct TimeSpan if ((totalMicroseconds > MaxMicroseconds) || (totalMicroseconds < MinMicroseconds)) { - ThrowHelper.ThrowArgumentOutOfRange_TimeSpanTooLong(); + throw new ArgumentOutOfRangeException(null, SR.Overflow_TimeSpanTooLong); } _ticks = totalMicroseconds * TicksPerMicrosecond; } @@ -407,7 +407,7 @@ public readonly struct TimeSpan { if (_ticks == MinTicks) { - ThrowHelper.ThrowOverflowException_TimeSpanDuration(); + throw new OverflowException(SR.Overflow_Duration); } return new TimeSpan(_ticks >= 0 ? _ticks : -_ticks); } @@ -433,7 +433,7 @@ public readonly struct TimeSpan if (units > maxUnits || units < minUnits) { - ThrowHelper.ThrowArgumentOutOfRange_TimeSpanTooLong(); + throw new ArgumentOutOfRangeException(null, SR.Overflow_TimeSpanTooLong); } return TimeSpan.FromTicks(units * ticksPerUnit); } @@ -608,7 +608,7 @@ public readonly struct TimeSpan { if ((microseconds > MaxMicroseconds) || (microseconds < MinMicroseconds)) { - ThrowHelper.ThrowArgumentOutOfRange_TimeSpanTooLong(); + throw new ArgumentOutOfRangeException(null, SR.Overflow_TimeSpanTooLong); } long ticks = (long)microseconds * TicksPerMicrosecond; return TimeSpan.FromTicks(ticks); @@ -631,7 +631,7 @@ public readonly struct TimeSpan { if (double.IsNaN(value)) { - ThrowHelper.ThrowArgumentException_Arg_CannotBeNaN(); + throw new ArgumentException(SR.Arg_CannotBeNaN); } return IntervalFromDoubleTicks(value * scale); } @@ -640,7 +640,7 @@ public readonly struct TimeSpan { if ((ticks > MaxTicks) || (ticks < MinTicks) || double.IsNaN(ticks)) { - ThrowHelper.ThrowOverflowException_TimeSpanTooLong(); + throw new ArgumentOutOfRangeException(null, SR.Overflow_TimeSpanTooLong); } if (ticks == MaxTicks) { @@ -698,174 +698,16 @@ public readonly struct TimeSpan if ((totalSeconds > MaxSeconds) || (totalSeconds < MinSeconds)) { - ThrowHelper.ThrowArgumentOutOfRange_TimeSpanTooLong(); + throw new ArgumentOutOfRangeException(null, SR.Overflow_TimeSpanTooLong); } return totalSeconds * TicksPerSecond; } - // See System.Globalization.TimeSpanParse and System.Globalization.TimeSpanFormat - //#region ParseAndFormat - private static void ValidateStyles(TimeSpanStyles style) - { - if (style is not TimeSpanStyles.None && style is not TimeSpanStyles.AssumeNegative) - { - ThrowHelper.ThrowArgumentException_InvalidTimeSpanStyles(); - } - } - public static TimeSpan Parse(string s) - { - /* Constructs a TimeSpan from a string. Leading and trailing white space characters are allowed. */ - ArgumentNullException.ThrowIfNull(s); - return TimeSpanParse.Parse(s, null); - } - public static TimeSpan Parse(string input, IFormatProvider formatProvider) - { - if (input == null) - { - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.input); - } - return TimeSpanParse.Parse(input, formatProvider); - } - public static TimeSpan Parse(ReadOnlySpan input, IFormatProvider formatProvider = null) => TimeSpanParse.Parse(input, formatProvider); - public static TimeSpan ParseExact(string input, string format, IFormatProvider formatProvider) - { - ArgumentNullException.ThrowIfNull(nameof(input)); - ArgumentNullException.ThrowIfNull(nameof(format)); - - - return TimeSpanParse.ParseExact(input, format, formatProvider, TimeSpanStyles.None); - } - public static TimeSpan ParseExact(string input, string[] formats, IFormatProvider formatProvider) - { - if (input == null) - { - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.input); - } - return TimeSpanParse.ParseExactMultiple(input, formats, formatProvider, TimeSpanStyles.None); - } - public static TimeSpan ParseExact(string input, string format, IFormatProvider formatProvider, TimeSpanStyles styles) - { - ValidateStyles(styles); - ArgumentNullException.ThrowIfNull(nameof(input)); - ArgumentNullException.ThrowIfNull(nameof(format)); - return TimeSpanParse.ParseExact(input, format, formatProvider, styles); - } - - public static TimeSpan ParseExact(ReadOnlySpan input, ReadOnlySpan format, IFormatProvider formatProvider, TimeSpanStyles styles = TimeSpanStyles.None) - { - ValidateStyles(styles); - return TimeSpanParse.ParseExact(input, format, formatProvider, styles); - } - public static TimeSpan ParseExact(string input, string[] formats, IFormatProvider formatProvider, TimeSpanStyles styles) - { - ValidateStyles(styles); - ArgumentNullException.ThrowIfNull(input, ExceptionArgument.input); - return TimeSpanParse.ParseExactMultiple(input, formats, formatProvider, styles); - } - public static TimeSpan ParseExact(ReadOnlySpan input, string[] formats, IFormatProvider formatProvider, TimeSpanStyles styles = TimeSpanStyles.None) - { - ValidateStyles(styles); - return TimeSpanParse.ParseExactMultiple(input, formats, formatProvider, styles); - } - public static bool TryParse(string s, out TimeSpan result) - { - if (s == null) - { - result = default; - return false; - } - return TimeSpanParse.TryParse(s, null, out result); - } - public static bool TryParse(ReadOnlySpan s, out TimeSpan result) => TimeSpanParse.TryParse(s, null, out result); - - public static bool TryParse(string input, IFormatProvider formatProvider, out TimeSpan result) - { - if (input == null) - { - result = default; - return false; - } - return TimeSpanParse.TryParse(input, formatProvider, out result); - } - public static bool TryParse(ReadOnlySpan input, IFormatProvider formatProvider, out TimeSpan result) => TimeSpanParse.TryParse(input, formatProvider, out result); - public static bool TryParseExact(string input, string format, IFormatProvider formatProvider, out TimeSpan result) - { - if (input == null || format == null) - { - result = default; - return false; - } - return TimeSpanParse.TryParseExact(input, format, formatProvider, TimeSpanStyles.None, out result); - } - - public static bool TryParseExact(ReadOnlySpan input, ReadOnlySpan format, IFormatProvider formatProvider, out TimeSpan result) - => TimeSpanParse.TryParseExact(input, format, formatProvider, TimeSpanStyles.None, out result); - - public static bool TryParseExact(string input, string[] formats, IFormatProvider formatProvider, out TimeSpan result) - { - if (input == null) - { - result = default; - return false; - } - return TimeSpanParse.TryParseExactMultiple(input, formats, formatProvider, TimeSpanStyles.None, out result); - } - public static bool TryParseExact(ReadOnlySpan input, string[] formats, IFormatProvider formatProvider, out TimeSpan result) - => TimeSpanParse.TryParseExactMultiple(input, formats, formatProvider, TimeSpanStyles.None, out result); - - public static bool TryParseExact(string input, string format, IFormatProvider formatProvider, TimeSpanStyles styles, out TimeSpan result) - { - ValidateStyles(styles); - - if (input == null || format == null) - { - result = default; - return false; - } - return TimeSpanParse.TryParseExact(input, format, formatProvider, styles, out result); - } - - public static bool TryParseExact(ReadOnlySpan input, ReadOnlySpan format, IFormatProvider formatProvider, TimeSpanStyles styles, out TimeSpan result) - { - ValidateStyles(styles); - return TimeSpanParse.TryParseExact(input, format, formatProvider, styles, out result); - } - public static bool TryParseExact(string input, string[] formats, IFormatProvider formatProvider, TimeSpanStyles styles, out TimeSpan result) - { - ValidateStyles(styles); - - if (input == null) - { - result = default; - return false; - } - return TimeSpanParse.TryParseExactMultiple(input, formats, formatProvider, styles, out result); - } - - public static bool TryParseExact(ReadOnlySpan input, string[] formats, IFormatProvider formatProvider, TimeSpanStyles styles, out TimeSpan result) - { - ValidateStyles(styles); - return TimeSpanParse.TryParseExactMultiple(input, formats, formatProvider, styles, out result); - } - - public override string ToString() => TimeSpanFormat.FormatC(this); - public string ToString(string format) => TimeSpanFormat.Format(this, format, null); - public string ToString(string format, IFormatProvider formatProvider) => TimeSpanFormat.Format(this, format, formatProvider); - - public bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format = default, IFormatProvider formatProvider = null) - => TimeSpanFormat.TryFormat(this, destination, out charsWritten, format, formatProvider); - - /// - public bool TryFormat(Span utf8Destination, out int bytesWritten, ReadOnlySpan format = default, IFormatProvider formatProvider = null) - => TimeSpanFormat.TryFormat(this, utf8Destination, out bytesWritten, format, formatProvider); - - //#endregion - public static TimeSpan operator -(TimeSpan t) { if (t._ticks == MinTicks) { - ThrowHelper.ThrowOverflowException_NegateTwosCompNum(); + throw new OverflowException(SR.Overflow_NegateTwosCompNum); } return new TimeSpan(-t._ticks); } @@ -879,7 +721,7 @@ public readonly struct TimeSpan { // Overflow if signs of operands was different and result's sign was opposite. // >> 63 gives the sign bit (either 64 1's or 64 0's). - ThrowHelper.ThrowOverflowException_TimeSpanTooLong(); + throw new OverflowException(SR.Overflow_TimeSpanTooLong); } return new TimeSpan(result); } @@ -895,7 +737,7 @@ public readonly struct TimeSpan { // Overflow if signs of operands was identical and result's sign was opposite. // >> 63 gives the sign bit (either 64 1's or 64 0's). - ThrowHelper.ThrowOverflowException_TimeSpanTooLong(); + throw new OverflowException(SR.Overflow_TimeSpanTooLong); } return new TimeSpan(result); } @@ -905,7 +747,7 @@ public readonly struct TimeSpan { if (double.IsNaN(factor)) { - ThrowHelper.ThrowArgumentException_Arg_CannotBeNaN(ExceptionArgument.factor); + throw new ArgumentException(SR.Arg_CannotBeNaN, nameof(factor)); } // Rounding to the nearest tick is as close to the result we would have with unlimited @@ -922,7 +764,7 @@ public readonly struct TimeSpan { if (double.IsNaN(divisor)) { - ThrowHelper.ThrowArgumentException_Arg_CannotBeNaN(ExceptionArgument.divisor); + throw new ArgumentException(SR.Arg_CannotBeNaN, nameof(divisor)); } double ticks = Math.Round(timeSpan.Ticks / divisor); From cf5233166101d0d9f9fdaa72d7fcb163cf75e4d6 Mon Sep 17 00:00:00 2001 From: Airat Abdrakov <52558686+CrackAndDie@users.noreply.github.com> Date: Wed, 6 May 2026 14:31:07 +0300 Subject: [PATCH 14/26] ThreadState and ThreadStateException added --- std/System/SR.hpt | 4 +++ std/System/Threading/Thread.hpt | 29 ++++++++++++++++++- std/System/Threading/ThreadState.hpt | 20 +++++++++++++ std/System/Threading/ThreadStateException.hpt | 14 +++++++++ 4 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 std/System/Threading/ThreadState.hpt create mode 100644 std/System/Threading/ThreadStateException.hpt diff --git a/std/System/SR.hpt b/std/System/SR.hpt index 316a2ca1..21c6ae04 100644 --- a/std/System/SR.hpt +++ b/std/System/SR.hpt @@ -67,4 +67,8 @@ internal static class SR // io internal const string Arg_IOException = "I/O error occurred."; internal const string IO_NoConsole = "There is no console."; + + // threading + internal const string Arg_ThreadStateException = "Thread was in an invalid state for the operation being executed."; + internal const string ThreadState_NotStarted = "Thread has not been started."; } \ No newline at end of file diff --git a/std/System/Threading/Thread.hpt b/std/System/Threading/Thread.hpt index 70f66192..f47d1779 100644 --- a/std/System/Threading/Thread.hpt +++ b/std/System/Threading/Thread.hpt @@ -6,6 +6,33 @@ using System; public partial class Thread { + private uintptr _threadHandle; + private bool _isDead; + + /// + /// Return the thread state as a consistent set of bits. This is more + /// general then IsAlive or IsBackground. + /// + public ThreadState ThreadState + { + get + { + if (_isDead) + { + return System.Threading.ThreadState.Stopped; + } + + var state = (System.Threading.ThreadState.ThreadState)GetThreadState(); + // GC.KeepAlive(this); TODO: do i need it? + return state; + } + } + + private int GetThreadState() + { + return 0; // TODO: + } + private StartHelper _startHelper; // State associated with starting new thread @@ -75,7 +102,7 @@ public partial class Thread public bool Join(int millisecondsTimeout) { ArgumentOutOfRangeException.ThrowIfLessThan(millisecondsTimeout, Timeout.Infinite); - if ((ThreadState & ThreadState.Unstarted) != 0) + if ((int)(ThreadState & ThreadState.Unstarted) != 0) { throw new ThreadStateException(SR.ThreadState_NotStarted); } diff --git a/std/System/Threading/ThreadState.hpt b/std/System/Threading/ThreadState.hpt new file mode 100644 index 00000000..38883b88 --- /dev/null +++ b/std/System/Threading/ThreadState.hpt @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// Some modifications were made for hapet-lang compatibility. + +public enum ThreadState +{ + /*========================================================================= + ** Constants for thread states. + =========================================================================*/ + Running = 0, + StopRequested = 1, + SuspendRequested = 2, + Background = 4, + Unstarted = 8, + Stopped = 16, + WaitSleepJoin = 32, + Suspended = 64, + AbortRequested = 128, + Aborted = 256 +} diff --git a/std/System/Threading/ThreadStateException.hpt b/std/System/Threading/ThreadStateException.hpt new file mode 100644 index 00000000..ac07e32b --- /dev/null +++ b/std/System/Threading/ThreadStateException.hpt @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// Some modifications were made for hapet-lang compatibility. + +using System; + +/// +/// The exception that is thrown when a is in an invalid for the method call. +/// +public class ThreadStateException : SystemException +{ + public ThreadStateException() : base(SR.Arg_ThreadStateException) { } + public ThreadStateException(string message) : base(message ?? SR.Arg_ThreadStateException) { } +} \ No newline at end of file From 07cce2e9f43d60b04c73ec05f10756db6f46bd99 Mon Sep 17 00:00:00 2001 From: Airat Abdrakov <52558686+CrackAndDie@users.noreply.github.com> Date: Thu, 7 May 2026 09:43:11 +0300 Subject: [PATCH 15/26] Remove cringe thread state access --- std/System/Threading/Thread.hpt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/std/System/Threading/Thread.hpt b/std/System/Threading/Thread.hpt index f47d1779..94e36ec3 100644 --- a/std/System/Threading/Thread.hpt +++ b/std/System/Threading/Thread.hpt @@ -22,7 +22,7 @@ public partial class Thread return System.Threading.ThreadState.Stopped; } - var state = (System.Threading.ThreadState.ThreadState)GetThreadState(); + var state = (System.Threading.ThreadState)GetThreadState(); // GC.KeepAlive(this); TODO: do i need it? return state; } From 1a1aa28a322e5a378fecb008b862f03e43380b43 Mon Sep 17 00:00:00 2001 From: Airat Abdrakov <52558686+CrackAndDie@users.noreply.github.com> Date: Thu, 7 May 2026 09:55:25 +0300 Subject: [PATCH 16/26] Unroll colorizer change --- HapetLsp/Colorizers/HapetColorizer.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/HapetLsp/Colorizers/HapetColorizer.cs b/HapetLsp/Colorizers/HapetColorizer.cs index d613a0fb..c3b03d97 100644 --- a/HapetLsp/Colorizers/HapetColorizer.cs +++ b/HapetLsp/Colorizers/HapetColorizer.cs @@ -754,8 +754,7 @@ private void ColorizeSATExpr(AstSATOfExpr expr) // colorize 'sat' word AddSemanticToken(expr, expr.Location.Beginning, _tokenTypes[2], _tokenModifiers[0]); - if (expr.TargetType != null) - ColorizeExpr(expr.TargetType); + ColorizeExpr(expr.TargetType); } private void ColorizeLambdaExpr(AstLambdaExpr expr) From 87a7e0a929b71fce53725f310e32e0453203392f Mon Sep 17 00:00:00 2001 From: Airat Abdrakov <52558686+CrackAndDie@users.noreply.github.com> Date: Fri, 8 May 2026 09:29:55 +0300 Subject: [PATCH 17/26] ThreadPal for windows added and small fix in ConsolePal --- std/System/ConsolePal.Windows.hpt | 1 - std/System/Threading/ThreadPal.Windows.hpt | 14 ++++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 std/System/Threading/ThreadPal.Windows.hpt diff --git a/std/System/ConsolePal.Windows.hpt b/std/System/ConsolePal.Windows.hpt index 9a79e79d..b1bba26c 100644 --- a/std/System/ConsolePal.Windows.hpt +++ b/std/System/ConsolePal.Windows.hpt @@ -7,7 +7,6 @@ using System.Text; using System.Runtime.InteropServices; #if TARGET_PLATFORM == "win-x64" || TARGET_PLATFORM == "win-x86"; -[SuppressStaticCtorCall] internal static class ConsolePal { [LibImport("", "fputs")] diff --git a/std/System/Threading/ThreadPal.Windows.hpt b/std/System/Threading/ThreadPal.Windows.hpt new file mode 100644 index 00000000..325f249e --- /dev/null +++ b/std/System/Threading/ThreadPal.Windows.hpt @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// Some modifications were made for hapet-lang compatibility. + +using System.IO; +using System.Text; +using System.Runtime.InteropServices; + +#if TARGET_PLATFORM == "win-x64" || TARGET_PLATFORM == "win-x86"; +internal static class ThreadPal +{ + +} +#endif \ No newline at end of file From c33943ecff88598b13e278e8aba0f84e7c87d482 Mon Sep 17 00:00:00 2001 From: Airat Abdrakov <52558686+CrackAndDie@users.noreply.github.com> Date: Fri, 8 May 2026 10:12:51 +0300 Subject: [PATCH 18/26] ThreadPriority added and props for name and pool --- std/System/Threading/Thread.hpt | 10 +++++++++- std/System/Threading/ThreadPal.Windows.hpt | 3 +++ std/System/Threading/ThreadPriority.hpt | 15 +++++++++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 std/System/Threading/ThreadPriority.hpt diff --git a/std/System/Threading/Thread.hpt b/std/System/Threading/Thread.hpt index 94e36ec3..56a365d7 100644 --- a/std/System/Threading/Thread.hpt +++ b/std/System/Threading/Thread.hpt @@ -6,8 +6,16 @@ using System; public partial class Thread { + /// Gets or sets a value indicating the scheduling priority of a thread. + public ThreadPriority Priority { get; set; } = ThreadPriority.Normal; + /// Returns true if the thread is a threadpool thread. + public bool IsThreadPoolThread { get; set; } = false; + /// Get or sets the name of the thread. + public string Name { get; set; } + private uintptr _threadHandle; private bool _isDead; + private bool _isThreadPool; /// /// Return the thread state as a consistent set of bits. This is more @@ -33,7 +41,7 @@ public partial class Thread return 0; // TODO: } - private StartHelper _startHelper; + internal StartHelper _startHelper; // State associated with starting new thread private sealed class StartHelper diff --git a/std/System/Threading/ThreadPal.Windows.hpt b/std/System/Threading/ThreadPal.Windows.hpt index 325f249e..5b204827 100644 --- a/std/System/Threading/ThreadPal.Windows.hpt +++ b/std/System/Threading/ThreadPal.Windows.hpt @@ -9,6 +9,9 @@ using System.Runtime.InteropServices; #if TARGET_PLATFORM == "win-x64" || TARGET_PLATFORM == "win-x86"; internal static class ThreadPal { + public static void StartThread(Thread thread) + { + } } #endif \ No newline at end of file diff --git a/std/System/Threading/ThreadPriority.hpt b/std/System/Threading/ThreadPriority.hpt new file mode 100644 index 00000000..c40c93e3 --- /dev/null +++ b/std/System/Threading/ThreadPriority.hpt @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// Some modifications were made for hapet-lang compatibility. + +public enum ThreadPriority +{ + /*========================================================================= + ** Constants for thread priorities. + =========================================================================*/ + Lowest = 0, + BelowNormal = 1, + Normal = 2, + AboveNormal = 3, + Highest = 4 +} \ No newline at end of file From 9c478a8967b11be6c2f885230f6dd11900ec154b Mon Sep 17 00:00:00 2001 From: Airat Abdrakov <52558686+CrackAndDie@users.noreply.github.com> Date: Fri, 8 May 2026 10:32:06 +0300 Subject: [PATCH 19/26] Check that thread is already running in start --- std/System/SR.hpt | 1 + std/System/Threading/Thread.hpt | 6 ++++++ std/System/Threading/ThreadPal.Windows.hpt | 2 +- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/std/System/SR.hpt b/std/System/SR.hpt index 21c6ae04..55baea6a 100644 --- a/std/System/SR.hpt +++ b/std/System/SR.hpt @@ -71,4 +71,5 @@ internal static class SR // threading internal const string Arg_ThreadStateException = "Thread was in an invalid state for the operation being executed."; internal const string ThreadState_NotStarted = "Thread has not been started."; + internal const string ThreadState_AlreadyStarted = "Thread is running or terminated; it cannot restart."; } \ No newline at end of file diff --git a/std/System/Threading/Thread.hpt b/std/System/Threading/Thread.hpt index 56a365d7..21926b50 100644 --- a/std/System/Threading/Thread.hpt +++ b/std/System/Threading/Thread.hpt @@ -104,6 +104,12 @@ public partial class Thread startHelper._startArg = null; } + // if already running - throw exception + if (ThreadState == System.Threading.ThreadState.Running) + { + throw new ThreadStateException(SR.ThreadState_AlreadyStarted); + } + StartCore(); } diff --git a/std/System/Threading/ThreadPal.Windows.hpt b/std/System/Threading/ThreadPal.Windows.hpt index 5b204827..59c12c5d 100644 --- a/std/System/Threading/ThreadPal.Windows.hpt +++ b/std/System/Threading/ThreadPal.Windows.hpt @@ -9,7 +9,7 @@ using System.Runtime.InteropServices; #if TARGET_PLATFORM == "win-x64" || TARGET_PLATFORM == "win-x86"; internal static class ThreadPal { - public static void StartThread(Thread thread) + internal static void StartThread(Thread thread) { } From 3ef5bb0d9a60922b787ae66d1d159bb283234aad Mon Sep 17 00:00:00 2001 From: Airat Abdrakov <52558686+CrackAndDie@users.noreply.github.com> Date: Fri, 8 May 2026 15:32:13 +0300 Subject: [PATCH 20/26] Many things in windows threading added --- .../Runtime/InteropServices/Marshal.hpt | 6 +++ .../InteropServices/MarshalPal.Windows.hpt | 46 +++++++++++++++++++ std/System/SR.hpt | 1 + std/System/Threading/Thread.hpt | 40 +++++++--------- std/System/Threading/ThreadPal.Windows.hpt | 45 +++++++++++++++++- std/System/Threading/ThreadStartException.hpt | 14 ++++++ 6 files changed, 126 insertions(+), 26 deletions(-) create mode 100644 std/System/Runtime/InteropServices/MarshalPal.Windows.hpt create mode 100644 std/System/Threading/ThreadStartException.hpt diff --git a/std/System/Runtime/InteropServices/Marshal.hpt b/std/System/Runtime/InteropServices/Marshal.hpt index 7b580eeb..14ee348e 100644 --- a/std/System/Runtime/InteropServices/Marshal.hpt +++ b/std/System/Runtime/InteropServices/Marshal.hpt @@ -43,4 +43,10 @@ public static class Marshal // TODO: is it crossplatform? Fflush(AcrtIobFunc(1)); } + + [SuppressStackTrace] + public static inline string GetLastStringError() + { + return MarshalPal.GetLastStringError(); + } } \ No newline at end of file diff --git a/std/System/Runtime/InteropServices/MarshalPal.Windows.hpt b/std/System/Runtime/InteropServices/MarshalPal.Windows.hpt new file mode 100644 index 00000000..412366e8 --- /dev/null +++ b/std/System/Runtime/InteropServices/MarshalPal.Windows.hpt @@ -0,0 +1,46 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// Some modifications were made for hapet-lang compatibility. + +using System; +using System.Text; +using System.Runtime.InteropServices; + +#if TARGET_PLATFORM == "win-x64" || TARGET_PLATFORM == "win-x86"; +[SuppressStaticCtorCall] +internal static class MarshalPal +{ + [DeclarationUsed] + [LibImport("", "GetLastError")] + internal static extern inline uint GetLastError(); + [DeclarationUsed] + [LibImport("", "FormatMessageA")] + internal static extern inline uint FormatMessageA(uint flags, void* source, uint messageId, uint langId, void* buffer, uint size, void* args); + [DeclarationUsed] + [LibImport("", "LocalFree")] + internal static extern inline void* LocalFree(void*); + + internal static string GetLastStringError() + { + // https://stackoverflow.com/questions/1387064/how-to-get-the-error-message-from-the-error-code-returned-by-getlasterror + // get the error message ID, if any. + var error = GetLastError(); + if (error == 0) + return ""; + + byte* messageBuffer = null; + // FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS + uint flags = 0x00000100 | 0x00001000 | 0x00000200; + uint langId = (uint)(((short)(0x00) << 10) | ((short)(0x01))); + // Ask Win32 to give us the string version of that message ID. + // The parameters we pass in, tell Win32 to create the buffer that holds the message for us (because we don't yet know how long the message string will be). + uint size = FormatMessageA(flags, null, error, langId, &messageBuffer, 0, null); + if (messageBuffer == null) + return ""; + string toReturn = UTF8Encoding.GetString(messageBuffer); + // Free the Win32's string's buffer. + LocalFree(messageBuffer); + return toReturn; + } +} +#endif \ No newline at end of file diff --git a/std/System/SR.hpt b/std/System/SR.hpt index 55baea6a..8a59d26a 100644 --- a/std/System/SR.hpt +++ b/std/System/SR.hpt @@ -72,4 +72,5 @@ internal static class SR internal const string Arg_ThreadStateException = "Thread was in an invalid state for the operation being executed."; internal const string ThreadState_NotStarted = "Thread has not been started."; internal const string ThreadState_AlreadyStarted = "Thread is running or terminated; it cannot restart."; + internal const string Arg_ThreadStartException = "Thread failed to start."; } \ No newline at end of file diff --git a/std/System/Threading/Thread.hpt b/std/System/Threading/Thread.hpt index 21926b50..6e259276 100644 --- a/std/System/Threading/Thread.hpt +++ b/std/System/Threading/Thread.hpt @@ -21,38 +21,22 @@ public partial class Thread /// Return the thread state as a consistent set of bits. This is more /// general then IsAlive or IsBackground. /// - public ThreadState ThreadState - { - get - { - if (_isDead) - { - return System.Threading.ThreadState.Stopped; - } - - var state = (System.Threading.ThreadState)GetThreadState(); - // GC.KeepAlive(this); TODO: do i need it? - return state; - } - } - - private int GetThreadState() - { - return 0; // TODO: - } + public ThreadState ThreadState { get; set; } internal StartHelper _startHelper; // State associated with starting new thread private sealed class StartHelper { + internal Thread _thread; internal int _maxStackSize; internal object _start; internal object _startArg; - internal StartHelper(object start) + internal StartHelper(object start, Thread thread) { _start = start; + _thread = thread; } // avoid long-lived stack frame in many threads @@ -64,6 +48,9 @@ public partial class Thread // avoid long-lived stack frame in many threads private inline void RunWorker() { + // set that the thread is currently running + _thread.ThreadState = System.Threading.ThreadState.Running; + object start = _start; _start = default; // anime? @@ -80,7 +67,12 @@ public partial class Thread } catch (Exception ex) { - // the handler returned "true" means the exception is now "handled" and we should gracefully exit. + + } + finally + { + // set that the thread is stopped + _thread.ThreadState = System.Threading.ThreadState.Stopped; } } } @@ -89,7 +81,7 @@ public partial class Thread { ArgumentNullException.ThrowIfNull(start); - _startHelper = new StartHelper(start); + _startHelper = new StartHelper(start, this); } private void Start() @@ -110,13 +102,13 @@ public partial class Thread throw new ThreadStateException(SR.ThreadState_AlreadyStarted); } - StartCore(); + ThreadPal.StartThread(this); } public bool Join(int millisecondsTimeout) { ArgumentOutOfRangeException.ThrowIfLessThan(millisecondsTimeout, Timeout.Infinite); - if ((int)(ThreadState & ThreadState.Unstarted) != 0) + if ((int)(ThreadState & System.Threading.ThreadState.Unstarted) != 0) { throw new ThreadStateException(SR.ThreadState_NotStarted); } diff --git a/std/System/Threading/ThreadPal.Windows.hpt b/std/System/Threading/ThreadPal.Windows.hpt index 59c12c5d..f23ec8f3 100644 --- a/std/System/Threading/ThreadPal.Windows.hpt +++ b/std/System/Threading/ThreadPal.Windows.hpt @@ -2,16 +2,57 @@ // The .NET Foundation licenses this file to you under the MIT license. // Some modifications were made for hapet-lang compatibility. -using System.IO; -using System.Text; +using System; using System.Runtime.InteropServices; #if TARGET_PLATFORM == "win-x64" || TARGET_PLATFORM == "win-x86"; internal static class ThreadPal { + [LibImport("", "CreateThread")] + private static extern inline void* CreateThread(void* attrs, uintptr stackSize, void* func, void* funcParam, uint creationFlags, uint* threadId); + [LibImport("", "ResumeThread")] + private static extern inline int ResumeThread(void* handle); + [LibImport("", "SetThreadPriority")] + private static extern inline int SetThreadPriority(void* handle, int priority); + // fuck win < 10 + [LibImport("", "SetThreadDescription")] + private static extern inline int SetThreadDescription(void* handle, ushort* desc); + internal static void StartThread(Thread thread) { + uint threadId = 0; + // get delegate to extract func and object ptrs + Delegate del = thread._startHelper.Run; + // CREATE_SUSPENDED and STACK_SIZE_PARAM_IS_A_RESERVATION + uint creationFlags = 0x00000004 | 0x00010000; + // create a thread in suspended state and start it manually + uintptr handle = CreateThread(null, thread._startHelper._maxStackSize, del._funcPtr, del._objectPtr, creationFlags, &threadId); + if (handle == null) throw new ThreadStartException(Marshal.GetLastStringError()); + + // set handle + thread._threadHandle = handle; + // set name + SetThreadDescription(handle, (ushort*)thread.Name.Buffer); // do we need to check result? + // set priority + SetThreadPriority(handle, ConvertPriority(thread.Priority)); // do we need to check result? + // resume thread + int resumeResult = ResumeThread(handle); + if (resumeResult == -1) throw new ThreadStartException(Marshal.GetLastStringError()); + } + + private static int ConvertPriority(ThreadPriority priority) + { + // https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-setthreadpriority#parameters + switch (priority) + { + case (ThreadPriority.Lowest) return -2; + case (ThreadPriority.BelowNormal) return -1; + case (ThreadPriority.Normal) return 0; + case (ThreadPriority.AboveNormal) return 1; + case (ThreadPriority.Highest) return 2; + } + return 0; } } #endif \ No newline at end of file diff --git a/std/System/Threading/ThreadStartException.hpt b/std/System/Threading/ThreadStartException.hpt new file mode 100644 index 00000000..30dd0934 --- /dev/null +++ b/std/System/Threading/ThreadStartException.hpt @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// Some modifications were made for hapet-lang compatibility. + +using System; + +/// +/// The exception that is thrown when a is could not be started. +/// +public class ThreadStartException : SystemException +{ + public ThreadStartException() : base(SR.Arg_ThreadStartException) { } + public ThreadStartException(string message) : base(message ?? SR.Arg_ThreadStartException) { } +} \ No newline at end of file From 989df7385c44871ab5dc6967a726a86fe1a0c803 Mon Sep 17 00:00:00 2001 From: Airat Abdrakov <52558686+CrackAndDie@users.noreply.github.com> Date: Tue, 12 May 2026 10:58:44 +0300 Subject: [PATCH 21/26] Current thread prop impl --- std/System/Threading/Thread.hpt | 17 +++++ std/System/Threading/ThreadPal.Windows.hpt | 82 ++++++++++++++++++++-- std/System/Threading/ThreadStatic.hpt | 20 ++++++ 3 files changed, 115 insertions(+), 4 deletions(-) create mode 100644 std/System/Threading/ThreadStatic.hpt diff --git a/std/System/Threading/Thread.hpt b/std/System/Threading/Thread.hpt index 6e259276..69c8da7b 100644 --- a/std/System/Threading/Thread.hpt +++ b/std/System/Threading/Thread.hpt @@ -6,6 +6,15 @@ using System; public partial class Thread { + private static ThreadStatic t_currentThread = new ThreadStatic(); + public static Thread CurrentThread + { + get + { + return t_currentThread.Value ?? InitializeCurrentThread(); + } + } + /// Gets or sets a value indicating the scheduling priority of a thread. public ThreadPriority Priority { get; set; } = ThreadPriority.Normal; /// Returns true if the thread is a threadpool thread. @@ -114,4 +123,12 @@ public partial class Thread } return JoinInternal(millisecondsTimeout); } + + private static Thread InitializeCurrentThread() + { + Thread thread = null; + GetCurrentThread(ref thread); + t_currentThread.Value = thread; + return thread; + } } \ No newline at end of file diff --git a/std/System/Threading/ThreadPal.Windows.hpt b/std/System/Threading/ThreadPal.Windows.hpt index f23ec8f3..136f2a31 100644 --- a/std/System/Threading/ThreadPal.Windows.hpt +++ b/std/System/Threading/ThreadPal.Windows.hpt @@ -9,14 +9,23 @@ using System.Runtime.InteropServices; internal static class ThreadPal { [LibImport("", "CreateThread")] - private static extern inline void* CreateThread(void* attrs, uintptr stackSize, void* func, void* funcParam, uint creationFlags, uint* threadId); + internal static extern inline void* CreateThread(void* attrs, uintptr stackSize, void* func, void* funcParam, uint creationFlags, uint* threadId); [LibImport("", "ResumeThread")] - private static extern inline int ResumeThread(void* handle); + internal static extern inline int ResumeThread(void* handle); [LibImport("", "SetThreadPriority")] - private static extern inline int SetThreadPriority(void* handle, int priority); + internal static extern inline int SetThreadPriority(void* handle, int priority); // fuck win < 10 [LibImport("", "SetThreadDescription")] - private static extern inline int SetThreadDescription(void* handle, ushort* desc); + internal static extern inline int SetThreadDescription(void* handle, ushort* desc); + [LibImport("", "GetCurrentThreadId")] + internal static extern inline int GetCurrentThreadId(); + [LibImport("", "WaitForSingleObject")] + internal static extern inline uint WaitForSingleObject(void* handle, uint millis); + + // cringe winapi consts + internal const uint WAIT_FAILED = (0xFFFFFFFF); + internal const uint STATUS_WAIT_0 = ((uint)0x00000000L); + internal const uint WAIT_OBJECT_0 = ((STATUS_WAIT_0) + 0); internal static void StartThread(Thread thread) { @@ -54,5 +63,70 @@ internal static class ThreadPal } return 0; } + + internal static bool JoinThread(Thread thread, int millisecondsTimeout) + { + // This method assumes the thread has been started + // Debug.Assert((thread.ThreadState & ThreadState.Unstarted) == 0 || (millisecondsTimeout == 0)); // TODO: + + if (thread._threadHandle == null) + { + // If the thread has died, the handle can be marked as invalid. + // Waiting on an invalid handle will never complete, so complete now. + return true; + } + + if (millisecondsTimeout == 0) + { + int result = (int)WaitForSingleObject(thread._threadHandle, 0); + return result == (int)WAIT_OBJECT_0; + } + else + { + Thread currentThread = CurrentThread; + currentThread.SetWaitSleepJoinState(); + uint result; + if (millisecondsTimeout == Timeout.Infinite) + { + // Infinite wait + while (true) + { + result = Interop.Kernel32.WaitForSingleObjectEx(waitHandle.DangerousGetHandle(), Timeout.UnsignedInfinite, Interop.BOOL.TRUE); + if (result != Interop.Kernel32.WAIT_IO_COMPLETION) + { + break; + } + // Check if this was our interrupt APC + CheckForPendingInterrupt(); + } + } + else + { + long startTime = Environment.TickCount64; + while (true) + { + result = Interop.Kernel32.WaitForSingleObjectEx(waitHandle.DangerousGetHandle(), (uint)millisecondsTimeout, Interop.BOOL.TRUE); + if (result != Interop.Kernel32.WAIT_IO_COMPLETION) + { + break; + } + // Check if this was our interrupt APC + CheckForPendingInterrupt(); + // Handle APC completion by adjusting timeout and retrying + long currentTime = Environment.TickCount64; + long elapsed = currentTime - startTime; + if (elapsed >= millisecondsTimeout) + { + result = Interop.Kernel32.WAIT_TIMEOUT; + break; + } + millisecondsTimeout -= (int)elapsed; + startTime = currentTime; + } + } + currentThread.ClearWaitSleepJoinState(); + return result == (int)Interop.Kernel32.WAIT_OBJECT_0; + } + } } #endif \ No newline at end of file diff --git a/std/System/Threading/ThreadStatic.hpt b/std/System/Threading/ThreadStatic.hpt new file mode 100644 index 00000000..165c8f64 --- /dev/null +++ b/std/System/Threading/ThreadStatic.hpt @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// Some modifications were made for hapet-lang compatibility. + +public class ThreadStatic +{ + private T _defaultValue; + public ThreadStatic(T defaultValue = default) + { + _defaultValue = defaultValue; + } + + public T Value + { + get + {} + set + {} + } +} \ No newline at end of file From 3442cd15668f22e44a460583c9522e7acae24004 Mon Sep 17 00:00:00 2001 From: Airat Abdrakov <52558686+CrackAndDie@users.noreply.github.com> Date: Tue, 12 May 2026 14:40:03 +0300 Subject: [PATCH 22/26] Dictionary impl begin --- HapetFrontend/Helpers/GenericsHelper.cs | 4 +- std/System/Collections/Generic/Dictionary.hpt | 1301 +++++++++++++++++ .../Collections/Generic/IDictionary.hpt | 45 + .../Generic/IDictionaryEnumerator.hpt | 55 + .../Collections/Generic/KeyValuePair.hpt | 49 + std/System/Comparer.hpt | 5 + 6 files changed, 1458 insertions(+), 1 deletion(-) create mode 100644 std/System/Collections/Generic/Dictionary.hpt create mode 100644 std/System/Collections/Generic/IDictionary.hpt create mode 100644 std/System/Collections/Generic/IDictionaryEnumerator.hpt create mode 100644 std/System/Collections/Generic/KeyValuePair.hpt diff --git a/HapetFrontend/Helpers/GenericsHelper.cs b/HapetFrontend/Helpers/GenericsHelper.cs index 13eb0756..02e3d913 100644 --- a/HapetFrontend/Helpers/GenericsHelper.cs +++ b/HapetFrontend/Helpers/GenericsHelper.cs @@ -302,7 +302,9 @@ public static bool HasAnyGenericTypes(AstExpression expr) return HasAnyGenericTypes(nullE); else if (expr is AstIdExpr || expr is AstEmptyExpr || expr is AstNullableExpr) return false; // just return false because OutType is checked above - Debug.Assert(false, "Unexcepted expr type to check"); + + // do not error, just skip this shite. probably errored somewhere above + // Debug.Assert(false, "Unexcepted expr type to check"); return false; } diff --git a/std/System/Collections/Generic/Dictionary.hpt b/std/System/Collections/Generic/Dictionary.hpt new file mode 100644 index 00000000..6ad2107e --- /dev/null +++ b/std/System/Collections/Generic/Dictionary.hpt @@ -0,0 +1,1301 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections; + +public class Dictionary : IDictionary +{ + // constants for serialization + private const string VersionName = "Version"; // Do not rename (binary serialization) + private const string HashSizeName = "HashSize"; // Do not rename (binary serialization). Must save buckets.Length + private const string KeyValuePairsName = "KeyValuePairs"; // Do not rename (binary serialization) + private const string ComparerName = "Comparer"; // Do not rename (binary serialization) + + private int[] _buckets; + private Entry[] _entries; +#if TARGET_64BIT; + private ulong _fastModMultiplier; +#endif + private int _count; + private int _freeList; + private int _freeCount; + private int _version; + private IComparer _comparer; + private List _keys; + private List _values; + private const int StartOfFreeList = -3; + + public Dictionary() : this(0, null) { } + + public Dictionary(int capacity) : this(capacity, null) { } + + public Dictionary(IComparer comparer) : this(0, comparer) { } + + public Dictionary(int capacity, IComparer comparer) + { + if (capacity < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.capacity); + } + + if (capacity > 0) + { + Initialize(capacity); + } + + // For reference types, we always want to store a comparer instance, either + // the one provided, or if one wasn't provided, the default (accessing + // EqualityComparer.Default with shared generics on every dictionary + // access can add measurable overhead). For value types, if no comparer is + // provided, or if the default is provided, we'd prefer to use + // EqualityComparer.Default.Equals on every use, enabling the JIT to + // devirtualize and possibly inline the operation. + if (!typeof(TKey).IsValueType) + { + _comparer = comparer ?? System.Comparer.Default(); + + // Special-case EqualityComparer.Default, StringComparer.Ordinal, and StringComparer.OrdinalIgnoreCase. + // We use a non-randomized comparer for improved perf, falling back to a randomized comparer if the + // hash buckets become unbalanced. + if (typeof(TKey) == typeof(string) && + NonRandomizedStringEqualityComparer.GetStringComparer(_comparer) is IComparer stringComparer) + { + _comparer = (IComparer)stringComparer; + } + } + else if (comparer != null && // first check for null to avoid forcing default comparer instantiation unnecessarily + comparer != System.Comparer.Default()) + { + _comparer = comparer; + } + } + + public Dictionary(IDictionary dictionary) : this(dictionary, null) { } + + public Dictionary(IDictionary dictionary, IComparer comparer) : + this(dictionary?.Count ?? 0, comparer) + { + if (dictionary == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.dictionary); + } + + AddRange(dictionary); + } + + public Dictionary(IEnumerable> collection) : this(collection, null) { } + + public Dictionary(IEnumerable> collection, IComparer comparer) : + this((collection as ICollection>)?.Count ?? 0, comparer) + { + if (collection == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.collection); + } + + AddRange(collection); + } + + private void AddRange(IEnumerable> enumerable) + { + // Fallback path for all other enumerables + for (KeyValuePair pair in enumerable) + { + Add(pair.Key, pair.Value); + } + } + + public IComparer Comparer + { + get + { + if (typeof(TKey) == typeof(string)) + { + // TODO: + // Debug.Assert(_comparer is not null, "The comparer should never be null for a reference type."); + return (IComparer)IInternalStringEqualityComparer.GetUnderlyingEqualityComparer((IEqualityComparer)_comparer); + } + else + { + return _comparer ?? System.Comparer.Default(); + } + } + } + + public int Count => _count - _freeCount; + + /// + /// Gets the total numbers of elements the internal data structure can hold without resizing. + /// + public int Capacity => (int)(_entries?.Length ?? 0); + + public List Keys + { + get + { + _keys ??= new List(); + return _keys; + } + } + + ICollection IDictionary.Keys => Keys; + + public List Values + { + get + { + _values ??= new List(); + return _values; + } + } + + ICollection IDictionary.Values => Values; + + public TValue this[TKey key] + { + get + { + TValue value = FindValue(key); + if (!Unsafe.IsNullRef(ref value)) + { + return value; + } + + ThrowHelper.ThrowKeyNotFoundException(key); + return default; + } + set + { + bool modified = TryInsert(key, value, InsertionBehavior.OverwriteExisting); + // Debug.Assert(modified); // TODO: + } + } + + public void Add(TKey key, TValue value) + { + bool modified = TryInsert(key, value, InsertionBehavior.ThrowOnExisting); + // TODO: + // Debug.Assert(modified); // If there was an existing key and the Add failed, an exception will already have been thrown. + } + + void ICollection>.Add(KeyValuePair keyValuePair) => + Add(keyValuePair.Key, keyValuePair.Value); + + bool ICollection>.Contains(KeyValuePair keyValuePair) + { + TValue value = FindValue(keyValuePair.Key); + if (!Unsafe.IsNullRef(ref value) && System.Comparer.Default().Compare(value, keyValuePair.Value) == 0) + { + return true; + } + + return false; + } + + bool ICollection>.Remove(KeyValuePair keyValuePair) + { + TValue value = FindValue(keyValuePair.Key); + if (!Unsafe.IsNullRef(ref value) && System.Comparer.Default().Compare(value, keyValuePair.Value) == 0) + { + Remove(keyValuePair.Key); + return true; + } + + return false; + } + + public void Clear() + { + int count = _count; + if (count > 0) + { + // TODO: + // Debug.Assert(_buckets != null, "_buckets should be non-null"); + // Debug.Assert(_entries != null, "_entries should be non-null"); + + Array.Clear(_buckets); + + _count = 0; + _freeList = -1; + _freeCount = 0; + Array.Clear(_entries, 0, count); + } + } + + public bool ContainsKey(TKey key) => + !Unsafe.IsNullRef(ref FindValue(key)); + + public bool ContainsValue(TValue value) + { + Entry[] entries = _entries; + if (value == null) + { + for (int i = 0; i < _count; i++) + { + if (entries[i].next >= -1 && entries[i].value == null) + { + return true; + } + } + } + else if (typeof(TValue).IsValueType) + { + // ValueType: Devirtualize with EqualityComparer.Default intrinsic + for (int i = 0; i < _count; i++) + { + if (entries[i].next >= -1 && System.Comparer.Default().Compare(entries[i].value, value) == 0) + { + return true; + } + } + } + else + { + // Object type: Shared Generic, EqualityComparer.Default won't devirtualize + // https://github.com/dotnet/runtime/issues/10050 + // So cache in a local rather than get EqualityComparer per loop iteration + Comparer defaultComparer = System.Comparer.Default(); + for (int i = 0; i < _count; i++) + { + if (entries[i].next >= -1 && defaultComparer.Compare(entries[i].value, value) == 0) + { + return true; + } + } + } + + return false; + } + + private void CopyTo(KeyValuePair[] array, int index) + { + if (array == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); + } + + if ((uint)index > (uint)array.Length) + { + ThrowHelper.ThrowIndexArgumentOutOfRange_NeedNonNegNumException(); + } + + if (array.Length - index < Count) + { + ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_ArrayPlusOffTooSmall); + } + + int count = _count; + Entry[] entries = _entries; + for (int i = 0; i < count; i++) + { + if (entries[i].next >= -1) + { + array[index++] = new KeyValuePair(entries[i].key, entries[i].value); + } + } + } + + public DictionaryEnumerator GetEnumerator() => new DictionaryEnumerator(this, DictionaryEnumerator.KeyValuePair); + + IEnumerator> IEnumerable>.GetEnumerator() => + Count == 0 ? EmptyEnumerator.Get>() : + GetEnumerator(); + + internal TValue FindValue(TKey key) + { + if (key == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key); + } + + Entry entry = Unsafe.NullRef(); + if (_buckets != null) + { + // Debug.Assert(_entries != null, "expected entries to be != null"); // TODO: + IComparer comparer = _comparer; + if (typeof(TKey).IsValueType && // comparer can only be null for value types; enable JIT to eliminate entire if block for ref types + comparer == null) + { + // TODO: Replace with just key.GetHashCode once https://github.com/dotnet/runtime/issues/117521 is resolved. + uint hashCode = (uint)key.GetHashCode(); + int i = GetBucket(hashCode); + Entry[] entries = _entries; + uint collisionCount = 0; + + // ValueType: Devirtualize with EqualityComparer.Default intrinsic + i--; // Value in _buckets is 1-based; subtract 1 from i. We do it here so it fuses with the following conditional. + do + { + // Test in if to drop range check for following array access + if ((uint)i >= (uint)entries.Length) + { + TValue value = Unsafe.NullRef(); + return value; + } + + entry = entries[i]; + if (entry.hashCode == hashCode && System.Comparer.Default().Compare(entry.key, key) == 0) + { + TValue value = entry.value; + return value; + } + + i = entry.next; + + collisionCount++; + } while (collisionCount <= (uint)entries.Length); + + // The chain of entries forms a loop; which means a concurrent update has happened. + // Break out of the loop and throw, rather than looping forever. + ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported(); + } + else + { + // Debug.Assert(comparer is not null); // TODO: + uint hashCode = (uint)key.GetHashCode(); + int i = GetBucket(hashCode); + Entry[] entries = _entries; + uint collisionCount = 0; + i--; // Value in _buckets is 1-based; subtract 1 from i. We do it here so it fuses with the following conditional. + do + { + // Test in if to drop range check for following array access + if ((uint)i >= (uint)entries.Length) + { + TValue value = Unsafe.NullRef(); + return value; + } + + entry = entries[i]; + if (entry.hashCode == hashCode && comparer.Compare(entry.key, key) == 0) + { + TValue value = entry.value; + return value; + } + + i = entry.next; + + collisionCount++; + } while (collisionCount <= (uint)entries.Length); + + // The chain of entries forms a loop; which means a concurrent update has happened. + // Break out of the loop and throw, rather than looping forever. + ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported(); + } + } + + var value = Unsafe.NullRef(); + return value; + } + + private int Initialize(int capacity) + { + int size = HashHelpers.GetPrime(capacity); + int[] buckets = new int[size]; + Entry[] entries = new Entry[size]; + + // Assign member variables after both arrays allocated to guard against corruption from OOM if second fails + _freeList = -1; +#if TARGET_64BIT; + _fastModMultiplier = HashHelpers.GetFastModMultiplier((uint)size); +#endif + _buckets = buckets; + _entries = entries; + + return size; + } + + private bool TryInsert(TKey key, TValue value, InsertionBehavior behavior) + { + // NOTE: this method is mirrored in CollectionsMarshal.GetValueRefOrAddDefault below. + // If you make any changes here, make sure to keep that version in sync as well. + + if (key == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key); + } + + if (_buckets == null) + { + Initialize(0); + } + Debug.Assert(_buckets != null); + + Entry[] entries = _entries; + Debug.Assert(entries != null, "expected entries to be non-null"); + + IEqualityComparer comparer = _comparer; + Debug.Assert(comparer is not null || typeof(TKey).IsValueType); + uint hashCode = (uint)((typeof(TKey).IsValueType && comparer == null) ? key.GetHashCode() : comparer.GetHashCode(key)); + + uint collisionCount = 0; + int bucket = GetBucket(hashCode); + int i = bucket - 1; // Value in _buckets is 1-based + + if (typeof(TKey).IsValueType && // comparer can only be null for value types; enable JIT to eliminate entire if block for ref types + comparer == null) + { + // ValueType: Devirtualize with EqualityComparer.Default intrinsic + while ((uint)i < (uint)entries.Length) + { + if (entries[i].hashCode == hashCode && EqualityComparer.Default.Equals(entries[i].key, key)) + { + if (behavior == InsertionBehavior.OverwriteExisting) + { + entries[i].value = value; + return true; + } + + if (behavior == InsertionBehavior.ThrowOnExisting) + { + ThrowHelper.ThrowAddingDuplicateWithKeyArgumentException(key); + } + + return false; + } + + i = entries[i].next; + + collisionCount++; + if (collisionCount > (uint)entries.Length) + { + // The chain of entries forms a loop; which means a concurrent update has happened. + // Break out of the loop and throw, rather than looping forever. + ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported(); + } + } + } + else + { + Debug.Assert(comparer is not null); + while ((uint)i < (uint)entries.Length) + { + if (entries[i].hashCode == hashCode && comparer.Equals(entries[i].key, key)) + { + if (behavior == InsertionBehavior.OverwriteExisting) + { + entries[i].value = value; + return true; + } + + if (behavior == InsertionBehavior.ThrowOnExisting) + { + ThrowHelper.ThrowAddingDuplicateWithKeyArgumentException(key); + } + + return false; + } + + i = entries[i].next; + + collisionCount++; + if (collisionCount > (uint)entries.Length) + { + // The chain of entries forms a loop; which means a concurrent update has happened. + // Break out of the loop and throw, rather than looping forever. + ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported(); + } + } + } + + int index; + if (_freeCount > 0) + { + index = _freeList; + Debug.Assert((StartOfFreeList - entries[_freeList].next) >= -1, "shouldn't overflow because `next` cannot underflow"); + _freeList = StartOfFreeList - entries[_freeList].next; + _freeCount--; + } + else + { + int count = _count; + if (count == entries.Length) + { + Resize(); + bucket = GetBucket(hashCode); + } + index = count; + _count = count + 1; + entries = _entries; + } + + Entry entry = entries[index]; + entry.hashCode = hashCode; + entry.next = bucket - 1; // Value in _buckets is 1-based + entry.key = key; + entry.value = value; + bucket = index + 1; // Value in _buckets is 1-based + _version++; + + // Value types never rehash + if (!typeof(TKey).IsValueType && collisionCount > HashHelpers.HashCollisionThreshold && comparer is NonRandomizedStringEqualityComparer) + { + // If we hit the collision threshold we'll need to switch to the comparer which is using randomized string hashing + // i.e. EqualityComparer.Default. + Resize(entries.Length, true); + } + + return true; + } + + /// + /// A helper class containing APIs exposed through or . + /// These methods are relatively niche and only used in specific scenarios, so adding them in a separate type avoids + /// the additional overhead on each instantiation, especially in AOT scenarios. + /// + internal static class CollectionsMarshalHelper + { + /// + public static TValue GetValueRefOrAddDefault(Dictionary dictionary, TKey key, out bool exists) + { + // NOTE: this method is mirrored by Dictionary.TryInsert above. + // If you make any changes here, make sure to keep that version in sync as well. + + if (key == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key); + } + + if (dictionary._buckets == null) + { + dictionary.Initialize(0); + } + // Debug.Assert(dictionary._buckets != null); // TODO: + + Entry[] entries = dictionary._entries; + // Debug.Assert(entries != null, "expected entries to be non-null"); // TODO: + + IComparer comparer = dictionary._comparer; + // Debug.Assert(comparer is not null || typeof(TKey).IsValueType); // TODO: + uint hashCode = (uint)((typeof(TKey).IsValueType && comparer == null) ? key.GetHashCode() : comparer.GetHashCode(key)); + + uint collisionCount = 0; + int bucket = dictionary.GetBucket(hashCode); + int i = bucket - 1; // Value in _buckets is 1-based + + if (typeof(TKey).IsValueType && // comparer can only be null for value types; enable JIT to eliminate entire if block for ref types + comparer == null) + { + // ValueType: Devirtualize with EqualityComparer.Default intrinsic + while ((uint)i < (uint)entries.Length) + { + if (entries[i].hashCode == hashCode && EqualityComparer.Default.Equals(entries[i].key, key)) + { + exists = true; + + return entries[i].value; + } + + i = entries[i].next; + + collisionCount++; + if (collisionCount > (uint)entries.Length) + { + // The chain of entries forms a loop; which means a concurrent update has happened. + // Break out of the loop and throw, rather than looping forever. + ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported(); + } + } + } + else + { + // Debug.Assert(comparer is not null); // TODO: + while ((uint)i < (uint)entries.Length) + { + if (entries[i].hashCode == hashCode && comparer.Equals(entries[i].key, key)) + { + exists = true; + + return entries[i].value; + } + + i = entries[i].next; + + collisionCount++; + if (collisionCount > (uint)entries.Length) + { + // The chain of entries forms a loop; which means a concurrent update has happened. + // Break out of the loop and throw, rather than looping forever. + ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported(); + } + } + } + + int index; + if (dictionary._freeCount > 0) + { + index = dictionary._freeList; + Debug.Assert((StartOfFreeList - entries[dictionary._freeList].next) >= -1, "shouldn't overflow because `next` cannot underflow"); + dictionary._freeList = StartOfFreeList - entries[dictionary._freeList].next; + dictionary._freeCount--; + } + else + { + int count = dictionary._count; + if (count == entries.Length) + { + dictionary.Resize(); + bucket = dictionary.GetBucket(hashCode); + } + index = count; + dictionary._count = count + 1; + entries = dictionary._entries; + } + + Entry entry = entries[index]; + entry.hashCode = hashCode; + entry.next = bucket - 1; // Value in _buckets is 1-based + entry.key = key; + entry.value = default; + bucket = index + 1; // Value in _buckets is 1-based + dictionary._version++; + + // Value types never rehash + if (!typeof(TKey).IsValueType && collisionCount > HashHelpers.HashCollisionThreshold && comparer is NonRandomizedStringEqualityComparer) + { + // If we hit the collision threshold we'll need to switch to the comparer which is using randomized string hashing + // i.e. EqualityComparer.Default. + dictionary.Resize(entries.Length, true); + + exists = false; + + // At this point the entries array has been resized, so the current reference we have is no longer valid. + // We're forced to do a new lookup and return an updated reference to the new entry instance. This new + // lookup is guaranteed to always find a value though and it will never return a null reference here. + TValue value = dictionary.FindValue(key); + + Debug.Assert(!Unsafe.IsNullRef(ref value), "the lookup result cannot be a null ref here"); + + return value; + } + + exists = false; + + return entry.value; + } + } + + private void Resize() => Resize(HashHelpers.ExpandPrime(_count), false); + + private void Resize(int newSize, bool forceNewHashCodes) + { + // Value types never rehash + // TODO: + // Debug.Assert(!forceNewHashCodes || !typeof(TKey).IsValueType); + // Debug.Assert(_entries != null, "_entries should be non-null"); + // Debug.Assert(newSize >= _entries.Length); + + Entry[] entries = new Entry[newSize]; + + int count = _count; + Array.Copy(_entries, entries, count); + + if (!typeof(TKey).IsValueType && forceNewHashCodes) + { + // Debug.Assert(_comparer is NonRandomizedStringEqualityComparer); // TODO: + _comparer = (IComparer)((NonRandomizedStringEqualityComparer)_comparer).GetRandomizedEqualityComparer(); + + for (int i = 0; i < count; i++) + { + if (entries[i].next >= -1) + { + entries[i].hashCode = (uint)_comparer.GetHashCode(entries[i].key); + } + } + } + + // Assign member variables after both arrays allocated to guard against corruption from OOM if second fails + _buckets = new int[newSize]; +#if TARGET_64BIT; + _fastModMultiplier = HashHelpers.GetFastModMultiplier((uint)newSize); +#endif + for (int i = 0; i < count; i++) + { + if (entries[i].next >= -1) + { + int bucket = GetBucket(entries[i].hashCode); + entries[i].next = bucket - 1; // Value in _buckets is 1-based + bucket = i + 1; + } + } + + _entries = entries; + } + + public bool Remove(TKey key) + { + // The overload Remove(TKey key, out TValue value) is a copy of this method with one additional + // statement to copy the value for entry being removed into the output parameter. + // Code has been intentionally duplicated for performance reasons. + // If you make any changes here, make sure to keep that version in sync as well. + + if (key == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key); + } + + if (_buckets != null) + { + // Debug.Assert(_entries != null, "entries should be non-null"); // TODO: + uint collisionCount = 0; + + IComparer comparer = _comparer; + // Debug.Assert(typeof(TKey).IsValueType || comparer is not null); // TODO: + uint hashCode = (uint)(typeof(TKey).IsValueType && comparer == null ? key.GetHashCode() : comparer.GetHashCode(key)); + + int bucket = GetBucket(hashCode); + Entry[] entries = _entries; + int last = -1; + int i = bucket - 1; // Value in buckets is 1-based + + if (typeof(TKey).IsValueType && // comparer can only be null for value types; enable JIT to eliminate entire if block for ref types + comparer == null) + { + while ((uint)i < (uint)entries.Length) + { + Entry entry = entries[i]; + + if (entry.hashCode == hashCode && EqualityComparer.Default.Equals(entry.key, key)) + { + if (last < 0) + { + bucket = entry.next + 1; // Value in buckets is 1-based + } + else + { + entries[last].next = entry.next; + } + + // TODO: + // Debug.Assert((StartOfFreeList - _freeList) < 0, "shouldn't underflow because max hashtable length is MaxPrimeArrayLength = 0x7FEFFFFD(2146435069) _freelist underflow threshold 2147483646"); + entry.next = StartOfFreeList - _freeList; + + if (RuntimeHelpers.IsReferenceOrContainsReferences()) + { + entry.key = default; + } + + if (RuntimeHelpers.IsReferenceOrContainsReferences()) + { + entry.value = default; + } + + _freeList = i; + _freeCount++; + return true; + } + + last = i; + i = entry.next; + + collisionCount++; + if (collisionCount > (uint)entries.Length) + { + // The chain of entries forms a loop; which means a concurrent update has happened. + // Break out of the loop and throw, rather than looping forever. + ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported(); + } + } + } + else + { + // Debug.Assert(comparer is not null); // TODO: + while ((uint)i < (uint)entries.Length) + { + Entry entry = entries[i]; + + if (entry.hashCode == hashCode && comparer.Equals(entry.key, key)) + { + if (last < 0) + { + bucket = entry.next + 1; // Value in buckets is 1-based + } + else + { + entries[last].next = entry.next; + } + + // TODO: + // Debug.Assert((StartOfFreeList - _freeList) < 0, "shouldn't underflow because max hashtable length is MaxPrimeArrayLength = 0x7FEFFFFD(2146435069) _freelist underflow threshold 2147483646"); + entry.next = StartOfFreeList - _freeList; + + if (RuntimeHelpers.IsReferenceOrContainsReferences()) + { + entry.key = default; + } + + if (RuntimeHelpers.IsReferenceOrContainsReferences()) + { + entry.value = default; + } + + _freeList = i; + _freeCount++; + return true; + } + + last = i; + i = entry.next; + + collisionCount++; + if (collisionCount > (uint)entries.Length) + { + // The chain of entries forms a loop; which means a concurrent update has happened. + // Break out of the loop and throw, rather than looping forever. + ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported(); + } + } + } + } + return false; + } + + public bool Remove(TKey key, out TValue value) + { + // This overload is a copy of the overload Remove(TKey key) with one additional + // statement to copy the value for entry being removed into the output parameter. + // Code has been intentionally duplicated for performance reasons. + // If you make any changes here, make sure to keep the other overload in sync as well. + + if (key == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key); + } + + if (_buckets != null) + { + // TODO: + // Debug.Assert(_entries != null, "entries should be non-null"); + uint collisionCount = 0; + + IComparer comparer = _comparer; + // Debug.Assert(typeof(TKey).IsValueType || comparer is not null); // TODO: + uint hashCode = (uint)(typeof(TKey).IsValueType && comparer == null ? key.GetHashCode() : comparer.GetHashCode(key)); + + int bucket = GetBucket(hashCode); + Entry[] entries = _entries; + int last = -1; + int i = bucket - 1; // Value in buckets is 1-based + + if (typeof(TKey).IsValueType && // comparer can only be null for value types; enable JIT to eliminate entire if block for ref types + comparer == null) + { + while ((uint)i < (uint)entries.Length) + { + Entry entry = entries[i]; + + if (entry.hashCode == hashCode && EqualityComparer.Default.Equals(entry.key, key)) + { + if (last < 0) + { + bucket = entry.next + 1; // Value in buckets is 1-based + } + else + { + entries[last].next = entry.next; + } + + value = entry.value; + + // TODO: + // Debug.Assert((StartOfFreeList - _freeList) < 0, "shouldn't underflow because max hashtable length is MaxPrimeArrayLength = 0x7FEFFFFD(2146435069) _freelist underflow threshold 2147483646"); + entry.next = StartOfFreeList - _freeList; + + if (RuntimeHelpers.IsReferenceOrContainsReferences()) + { + entry.key = default; + } + + if (RuntimeHelpers.IsReferenceOrContainsReferences()) + { + entry.value = default; + } + + _freeList = i; + _freeCount++; + return true; + } + + last = i; + i = entry.next; + + collisionCount++; + if (collisionCount > (uint)entries.Length) + { + // The chain of entries forms a loop; which means a concurrent update has happened. + // Break out of the loop and throw, rather than looping forever. + ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported(); + } + } + } + else + { + // Debug.Assert(comparer is not null); // TODO: + while ((uint)i < (uint)entries.Length) + { + Entry entry = entries[i]; + + if (entry.hashCode == hashCode && comparer.Equals(entry.key, key)) + { + if (last < 0) + { + bucket = entry.next + 1; // Value in buckets is 1-based + } + else + { + entries[last].next = entry.next; + } + + value = entry.value; + + // TODO: + // Debug.Assert((StartOfFreeList - _freeList) < 0, "shouldn't underflow because max hashtable length is MaxPrimeArrayLength = 0x7FEFFFFD(2146435069) _freelist underflow threshold 2147483646"); + entry.next = StartOfFreeList - _freeList; + + if (RuntimeHelpers.IsReferenceOrContainsReferences()) + { + entry.key = default; + } + + if (RuntimeHelpers.IsReferenceOrContainsReferences()) + { + entry.value = default; + } + + _freeList = i; + _freeCount++; + return true; + } + + last = i; + i = entry.next; + + collisionCount++; + if (collisionCount > (uint)entries.Length) + { + // The chain of entries forms a loop; which means a concurrent update has happened. + // Break out of the loop and throw, rather than looping forever. + ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported(); + } + } + } + } + + value = default; + return false; + } + + public int RemoveAll(Predicate match) + { + // TODO: + return 0; + } + + public bool TryGetValue(TKey key, out TValue value) + { + TValue valRef = FindValue(key); + if (!Unsafe.IsNullRef(ref valRef)) + { + value = valRef; + return true; + } + + value = default; + return false; + } + + public bool TryAdd(TKey key, TValue value) => + TryInsert(key, value, InsertionBehavior.None); + + bool ICollection>.IsReadOnly => false; + + void ICollection>.CopyTo(KeyValuePair[] array, int index) => + CopyTo(array, index); + + void CopyTo(KeyValuePair[] array, int index) + { + if (array == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); + } + + if (array.Rank != 1) + { + ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_RankMultiDimNotSupported); + } + + if (array.GetLowerBound(0) != 0) + { + ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_NonZeroLowerBound); + } + + if ((uint)index > (uint)array.Length) + { + ThrowHelper.ThrowIndexArgumentOutOfRange_NeedNonNegNumException(); + } + + if (array.Length - index < Count) + { + ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_ArrayPlusOffTooSmall); + } + + try + { + int count = _count; + Entry[] entries = _entries; + for (int i = 0; i < count; i++) + { + if (entries[i].next >= -1) + { + array[index++] = new KeyValuePair(entries[i].key, entries[i].value); + } + } + } + catch (ArrayTypeMismatchException) + { + ThrowHelper.ThrowArgumentException_Argument_IncompatibleArrayType(); + } + } + + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable>)this).GetEnumerator(); + + /// + /// Ensures that the dictionary can hold up to 'capacity' entries without any further expansion of its backing storage + /// + public int EnsureCapacity(int capacity) + { + if (capacity < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.capacity); + } + + int currentCapacity = _entries == null ? 0 : _entries.Length; + if (currentCapacity >= capacity) + { + return currentCapacity; + } + + _version++; + + if (_buckets == null) + { + return Initialize(capacity); + } + + int newSize = HashHelpers.GetPrime(capacity); + Resize(newSize, forceNewHashCodes: false); + return newSize; + } + + /// + /// Sets the capacity of this dictionary to what it would be if it had been originally initialized with all its entries + /// + /// + /// This method can be used to minimize the memory overhead + /// once it is known that no new elements will be added. + /// + /// To allocate minimum size storage array, execute the following statements: + /// + /// dictionary.Clear(); + /// dictionary.TrimExcess(); + /// + public void TrimExcess() => TrimExcess(Count); + + /// + /// Sets the capacity of this dictionary to hold up 'capacity' entries without any further expansion of its backing storage + /// + /// + /// This method can be used to minimize the memory overhead + /// once it is known that no new elements will be added. + /// + /// Passed capacity is lower than entries count. + public void TrimExcess(int capacity) + { + if (capacity < Count) + { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.capacity); + } + + int newSize = HashHelpers.GetPrime(capacity); + Entry[] oldEntries = _entries; + int currentCapacity = oldEntries == null ? 0 : oldEntries.Length; + if (newSize >= currentCapacity) + { + return; + } + + int oldCount = _count; + _version++; + Initialize(newSize); + + // Debug.Assert(oldEntries is not null); // TODO: + + CopyEntries(oldEntries, oldCount); + } + + private void CopyEntries(Entry[] entries, int count) + { + // Debug.Assert(_entries is not null); // TODO: + + Entry[] newEntries = _entries; + int newCount = 0; + for (int i = 0; i < count; i++) + { + uint hashCode = entries[i].hashCode; + if (entries[i].next >= -1) + { + Entry entry = newEntries[newCount]; + entry = entries[i]; + int bucket = GetBucket(hashCode); + entry.next = bucket - 1; // Value in _buckets is 1-based + bucket = newCount + 1; + newCount++; + } + } + + _count = newCount; + _freeCount = 0; + } + + public bool IsSynchronized => false; + public object SyncRoot => this; + public bool IsFixedSize => false; + + private static bool IsCompatibleKey(object key) + { + if (key == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key); + } + return key is TKey; + } + + private int GetBucket(uint hashCode) + { + int[] buckets = _buckets; +#if TARGET_64BIT; + return ref buckets[HashHelpers.FastMod(hashCode, (uint)buckets.Length, _fastModMultiplier)]; +#else + return buckets[(int)(hashCode % (uint)buckets.Length)]; +#endif + } + + private struct Entry + { + public uint hashCode; + /// + /// 0-based index of next entry in chain: -1 means end of chain + /// also encodes whether this entry _itself_ is part of the free list by changing sign and subtracting 3, + /// so -2 means end of free list, -3 means index 0 but on free list, -4 means index 1 but on free list, etc. + /// + public int next; + public TKey key; // Key of entry + public TValue value; // Value of entry + } + + public struct DictionaryEnumerator : IEnumerator>, IDictionaryEnumerator + { + private readonly Dictionary _dictionary; + private readonly int _version; + private int _index; + private KeyValuePair _current; + private readonly int _getEnumeratorRetType; // What should Enumerator.Current return? + + internal const int DictEntry = 1; + internal const int KeyValuePair = 2; + + internal DictionaryEnumerator(Dictionary dictionary, int getEnumeratorRetType) + { + _dictionary = dictionary; + _version = dictionary._version; + _index = 0; + _getEnumeratorRetType = getEnumeratorRetType; + _current = default; + } + + public bool MoveNext() + { + if (_version != _dictionary._version) + { + ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion(); + } + + // Use unsigned comparison since we set index to dictionary.count+1 when the enumeration ends. + // dictionary.count+1 could be negative if dictionary.count is int.MaxValue + while ((uint)_index < (uint)_dictionary._count) + { + Entry entry = _dictionary._entries[_index++]; + + if (entry.next >= -1) + { + _current = new KeyValuePair(entry.key, entry.value); + return true; + } + } + + _index = _dictionary._count + 1; + _current = default; + return false; + } + + public KeyValuePair Current => _current; + + public void Dispose() { } + + object IEnumerator.Current + { + get + { + if (_index == 0 || (_index == _dictionary._count + 1)) + { + ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen(); + } + + if (_getEnumeratorRetType == DictEntry) + { + return new DictionaryEntry(_current.Key, _current.Value); + } + + return new KeyValuePair(_current.Key, _current.Value); + } + } + + void IEnumerator.Reset() + { + if (_version != _dictionary._version) + { + ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion(); + } + + _index = 0; + _current = default; + } + + object IDictionaryEnumerator.Key + { + get + { + if (_index == 0 || (_index == _dictionary._count + 1)) + { + ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen(); + } + + return _current.Key; + } + } + + object IDictionaryEnumerator.Value + { + get + { + if (_index == 0 || (_index == _dictionary._count + 1)) + { + ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen(); + } + + return _current.Value; + } + } + } +} \ No newline at end of file diff --git a/std/System/Collections/Generic/IDictionary.hpt b/std/System/Collections/Generic/IDictionary.hpt new file mode 100644 index 00000000..078d221d --- /dev/null +++ b/std/System/Collections/Generic/IDictionary.hpt @@ -0,0 +1,45 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// Some modifications were made for hapet-lang compatibility. + +// An IDictionary is a possibly unordered set of key-value pairs. +// Keys can be any non-null object. Values can be any object. +// You can look up a value in an IDictionary via the default indexed +// property, Items. +public interface IDictionary : ICollection> +{ + // Interfaces are not serializable + // The Item property provides methods to read and edit entries + // in the Dictionary. + TValue this[TKey key] + { + get; + set; + } + + // Returns a collections of the keys in this dictionary. + ICollection Keys + { + get; + } + + // Returns a collections of the values in this dictionary. + ICollection Values + { + get; + } + + // Returns whether this dictionary contains a particular key. + // + bool ContainsKey(TKey key); + + // Adds a key-value pair to the dictionary. + // + void Add(TKey key, TValue value); + + // Removes a particular key from the dictionary. + // + bool Remove(TKey key); + + bool TryGetValue(TKey key, out TValue value); +} \ No newline at end of file diff --git a/std/System/Collections/Generic/IDictionaryEnumerator.hpt b/std/System/Collections/Generic/IDictionaryEnumerator.hpt new file mode 100644 index 00000000..205d1c3e --- /dev/null +++ b/std/System/Collections/Generic/IDictionaryEnumerator.hpt @@ -0,0 +1,55 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// Some modifications were made for hapet-lang compatibility. + +// This interface represents an enumerator that allows sequential access to the +// elements of a dictionary. Upon creation, an enumerator is conceptually +// positioned before the first element of the enumeration. The first call to the +// MoveNext method brings the first element of the enumeration into view, +// and each successive call to MoveNext brings the next element into +// view until MoveNext returns false, indicating that there are no more +// elements to enumerate. Following each call to MoveNext, the +// Key and Value methods are used to obtain the key and +// value of the element currently in view. The values returned by calls to +// Key and Value are undefined before the first call to +// MoveNext and following a call to MoveNext that returned false. +// Enumerators are typically used in while loops of the form +// +// IDictionaryEnumerator e = ...; +// while (e.MoveNext()) { +// Object key = e.Key; +// Object value = e.Value; +// ... +// } +// +// The IDictionaryEnumerator interface extends the IEnumerator +// inerface and can thus be used as a regular enumerator. The Current +// method of an IDictionaryEnumerator returns a DictionaryEntry containing +// the current key and value pair. However, the GetEntry method will +// return the same DictionaryEntry and avoids boxing the DictionaryEntry (boxing +// is somewhat expensive). +// +public interface IDictionaryEnumerator : IEnumerator> +{ + // Returns the key of the current element of the enumeration. The returned + // value is undefined before the first call to GetNext and following + // a call to GetNext that returned false. Multiple calls to + // GetKey with no intervening calls to GetNext will return + // the same object. + // + object Key + { + get; + } + + // Returns the value of the current element of the enumeration. The + // returned value is undefined before the first call to GetNext and + // following a call to GetNext that returned false. Multiple calls + // to GetValue with no intervening calls to GetNext will + // return the same object. + // + object Value + { + get; + } +} \ No newline at end of file diff --git a/std/System/Collections/Generic/KeyValuePair.hpt b/std/System/Collections/Generic/KeyValuePair.hpt new file mode 100644 index 00000000..0ba9a98b --- /dev/null +++ b/std/System/Collections/Generic/KeyValuePair.hpt @@ -0,0 +1,49 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// Some modifications were made for hapet-lang compatibility. + +using System.ComponentModel; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Text; + +// Provides the Create factory method for KeyValuePair. +public static class KeyValuePair +{ + // Creates a new KeyValuePair from the given values. + public static KeyValuePair Create(TKey key, TValue value) => + new KeyValuePair(key, value); + + /// Used by KeyValuePair.ToString to reduce generic code + internal static string PairToString(object key, object value) => $"[{key}, {value}]"; +} + +// A KeyValuePair holds a key and a value from a dictionary. +// It is used by the IEnumerable implementation for both IDictionary +// and IReadOnlyDictionary. +public readonly struct KeyValuePair +{ + private readonly TKey key; // Do not rename (binary serialization) + private readonly TValue value; // Do not rename (binary serialization) + + public KeyValuePair(TKey key, TValue value) + { + this.key = key; + this.value = value; + } + + public TKey Key => key; + + public TValue Value => value; + + public override string ToString() + { + return KeyValuePair.PairToString(Key, Value); + } + + public void Deconstruct(out TKey key, out TValue value) + { + key = Key; + value = Value; + } +} \ No newline at end of file diff --git a/std/System/Comparer.hpt b/std/System/Comparer.hpt index 9dc7b359..5790342d 100644 --- a/std/System/Comparer.hpt +++ b/std/System/Comparer.hpt @@ -40,6 +40,9 @@ public class Comparer : IComparer if (b is IComparable ib) return -ib.CompareTo(a); + if (b.Equals(a)) // fallback + return 0; + throw new ArgumentException(SR.Argument_ImplementIComparable); } } @@ -56,6 +59,8 @@ public class Comparer : IComparer return icmp.CompareTo(y); else if (y is IComparable icmp2) return -icmp2.CompareTo(x); + else if (y.Equals(x)) // fallback + return 0; } return 1; } From d39006b79087631558a2382a9c79445df4c8532c Mon Sep 17 00:00:00 2001 From: Airat Abdrakov <52558686+CrackAndDie@users.noreply.github.com> Date: Tue, 12 May 2026 16:29:12 +0300 Subject: [PATCH 23/26] Type extended and some fixes in dictionary --- .../Llvm/TypeInfo/TypeInfoGenerator.cs | 5 +- std/System/ArrayTypeMismatchException.hpt | 20 + std/System/Collections/Generic/Dictionary.hpt | 354 ++++++++---------- .../Generic/KeyNotFoundException.hpt | 11 + std/System/Runtime/TypeInfoUnsafe.hpt | 1 + std/System/SR.hpt | 11 +- std/System/Type.hpt | 2 + 7 files changed, 199 insertions(+), 205 deletions(-) create mode 100644 std/System/ArrayTypeMismatchException.hpt create mode 100644 std/System/Collections/Generic/KeyNotFoundException.hpt diff --git a/HapetBackend/Llvm/TypeInfo/TypeInfoGenerator.cs b/HapetBackend/Llvm/TypeInfo/TypeInfoGenerator.cs index 926d7937..a88fa271 100644 --- a/HapetBackend/Llvm/TypeInfo/TypeInfoGenerator.cs +++ b/HapetBackend/Llvm/TypeInfo/TypeInfoGenerator.cs @@ -103,8 +103,11 @@ private void GenerateInitializerForTypeInfo(HapetType type) int rawSize = (type is PointerType pp && pp.CreatedByCompiler ? pp.TargetType : type).GetSize(); LLVMValueRef rawSizeValue = LLVMValueRef.CreateConstInt(_context.Int32Type, (ulong)rawSize); + // is value type + LLVMValueRef isValueType = LLVMValueRef.CreateConstInt(_context.Int1Type, (ulong)(type is StructType ? 1 : 0)); + _builder.BuildStore(LLVMValueRef.CreateConstNamedStruct(typeInfoType, - [interfaceOffsets, dtorDelegate, rawSizeValue]), globConst); + [interfaceOffsets, dtorDelegate, rawSizeValue, isValueType]), globConst); } private LLVMTypeRef _typeInfoType; diff --git a/std/System/ArrayTypeMismatchException.hpt b/std/System/ArrayTypeMismatchException.hpt new file mode 100644 index 00000000..3ba7962e --- /dev/null +++ b/std/System/ArrayTypeMismatchException.hpt @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + + +/// +/// The exception that is thrown when an attempt is made to store an element of the wrong type within an array. +/// +public class ArrayTypeMismatchException : SystemException +{ + // Creates a new ArrayMismatchException with its message string set to + // the empty string, its HRESULT set to COR_E_ARRAYTYPEMISMATCH, + // and its ExceptionInfo reference set to null. + public ArrayTypeMismatchException() : base(SR.Arg_ArrayTypeMismatchException) { } + + // Creates a new ArrayMismatchException with its message string set to + // message, its HRESULT set to COR_E_ARRAYTYPEMISMATCH, + // and its ExceptionInfo reference set to null. + // + public ArrayTypeMismatchException(string message) : base(message ?? SR.Arg_ArrayTypeMismatchException) { } +} \ No newline at end of file diff --git a/std/System/Collections/Generic/Dictionary.hpt b/std/System/Collections/Generic/Dictionary.hpt index 6ad2107e..eec6df00 100644 --- a/std/System/Collections/Generic/Dictionary.hpt +++ b/std/System/Collections/Generic/Dictionary.hpt @@ -13,7 +13,7 @@ public class Dictionary : IDictionary private const string ComparerName = "Comparer"; // Do not rename (binary serialization) private int[] _buckets; - private Entry[] _entries; + private DictionaryEntry[] _entries; #if TARGET_64BIT; private ulong _fastModMultiplier; #endif @@ -36,7 +36,7 @@ public class Dictionary : IDictionary { if (capacity < 0) { - ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.capacity); + throw new ArgumentOutOfRangeException(nameof(capacity), SR.ArgumentOutOfRange_NeedNonNegNum); } if (capacity > 0) @@ -54,15 +54,6 @@ public class Dictionary : IDictionary if (!typeof(TKey).IsValueType) { _comparer = comparer ?? System.Comparer.Default(); - - // Special-case EqualityComparer.Default, StringComparer.Ordinal, and StringComparer.OrdinalIgnoreCase. - // We use a non-randomized comparer for improved perf, falling back to a randomized comparer if the - // hash buckets become unbalanced. - if (typeof(TKey) == typeof(string) && - NonRandomizedStringEqualityComparer.GetStringComparer(_comparer) is IComparer stringComparer) - { - _comparer = (IComparer)stringComparer; - } } else if (comparer != null && // first check for null to avoid forcing default comparer instantiation unnecessarily comparer != System.Comparer.Default()) @@ -78,7 +69,7 @@ public class Dictionary : IDictionary { if (dictionary == null) { - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.dictionary); + throw new ArgumentNullException(nameof(dictionary)); } AddRange(dictionary); @@ -91,7 +82,7 @@ public class Dictionary : IDictionary { if (collection == null) { - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.collection); + throw new ArgumentNullException(nameof(collection)); } AddRange(collection); @@ -114,7 +105,7 @@ public class Dictionary : IDictionary { // TODO: // Debug.Assert(_comparer is not null, "The comparer should never be null for a reference type."); - return (IComparer)IInternalStringEqualityComparer.GetUnderlyingEqualityComparer((IEqualityComparer)_comparer); + return (IComparer)_comparer; } else { @@ -156,13 +147,13 @@ public class Dictionary : IDictionary { get { - TValue value = FindValue(key); - if (!Unsafe.IsNullRef(ref value)) + TValue* value = FindValue(key); + if (value != null) { - return value; + return *value; } - ThrowHelper.ThrowKeyNotFoundException(key); + throw new KeyNotFoundException(string.Format(SR.Arg_KeyNotFoundWithKey, key.ToString())); return default; } set @@ -184,8 +175,8 @@ public class Dictionary : IDictionary bool ICollection>.Contains(KeyValuePair keyValuePair) { - TValue value = FindValue(keyValuePair.Key); - if (!Unsafe.IsNullRef(ref value) && System.Comparer.Default().Compare(value, keyValuePair.Value) == 0) + TValue* value = FindValue(keyValuePair.Key); + if (value != null && System.Comparer.Default().Compare(value, keyValuePair.Value) == 0) { return true; } @@ -195,8 +186,8 @@ public class Dictionary : IDictionary bool ICollection>.Remove(KeyValuePair keyValuePair) { - TValue value = FindValue(keyValuePair.Key); - if (!Unsafe.IsNullRef(ref value) && System.Comparer.Default().Compare(value, keyValuePair.Value) == 0) + TValue* value = FindValue(keyValuePair.Key); + if (value != null && System.Comparer.Default().Compare(value, keyValuePair.Value) == 0) { Remove(keyValuePair.Key); return true; @@ -223,12 +214,11 @@ public class Dictionary : IDictionary } } - public bool ContainsKey(TKey key) => - !Unsafe.IsNullRef(ref FindValue(key)); + public bool ContainsKey(TKey key) => FindValue(key) != null; public bool ContainsValue(TValue value) { - Entry[] entries = _entries; + DictionaryEntry[] entries = _entries; if (value == null) { for (int i = 0; i < _count; i++) @@ -272,21 +262,21 @@ public class Dictionary : IDictionary { if (array == null) { - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); + throw new ArgumentNullException(nameof(array)); } if ((uint)index > (uint)array.Length) { - ThrowHelper.ThrowIndexArgumentOutOfRange_NeedNonNegNumException(); + throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_NeedNonNegNum); } if (array.Length - index < Count) { - ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_ArrayPlusOffTooSmall); + throw new ArgumentException(nameof(array), SR.Arg_ArrayPlusOffTooSmall); } int count = _count; - Entry[] entries = _entries; + DictionaryEntry[] entries = _entries; for (int i = 0; i < count; i++) { if (entries[i].next >= -1) @@ -302,14 +292,14 @@ public class Dictionary : IDictionary Count == 0 ? EmptyEnumerator.Get>() : GetEnumerator(); - internal TValue FindValue(TKey key) + internal TValue* FindValue(TKey key) { if (key == null) { - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key); + throw new ArgumentNullException(nameof(key)); } - Entry entry = Unsafe.NullRef(); + DictionaryEntry entry = null; if (_buckets != null) { // Debug.Assert(_entries != null, "expected entries to be != null"); // TODO: @@ -319,73 +309,73 @@ public class Dictionary : IDictionary { // TODO: Replace with just key.GetHashCode once https://github.com/dotnet/runtime/issues/117521 is resolved. uint hashCode = (uint)key.GetHashCode(); - int i = GetBucket(hashCode); - Entry[] entries = _entries; + int* i = GetBucket(hashCode); + DictionaryEntry[] entries = _entries; uint collisionCount = 0; // ValueType: Devirtualize with EqualityComparer.Default intrinsic - i--; // Value in _buckets is 1-based; subtract 1 from i. We do it here so it fuses with the following conditional. + *i -= 1; // Value in _buckets is 1-based; subtract 1 from i. We do it here so it fuses with the following conditional. do { // Test in if to drop range check for following array access - if ((uint)i >= (uint)entries.Length) + if ((uint)(*i) >= (uint)entries.Length) { - TValue value = Unsafe.NullRef(); + TValue* value = null; return value; } - entry = entries[i]; + entry = entries[*i]; if (entry.hashCode == hashCode && System.Comparer.Default().Compare(entry.key, key) == 0) { - TValue value = entry.value; + TValue* value = &entry.value; return value; } - i = entry.next; + *i = entry.next; collisionCount++; } while (collisionCount <= (uint)entries.Length); // The chain of entries forms a loop; which means a concurrent update has happened. // Break out of the loop and throw, rather than looping forever. - ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported(); + throw new InvalidOperationException(SR.InvalidOperation_ConcurrentOperationsNotSupported); } else { // Debug.Assert(comparer is not null); // TODO: uint hashCode = (uint)key.GetHashCode(); - int i = GetBucket(hashCode); - Entry[] entries = _entries; + int* i = GetBucket(hashCode); + DictionaryEntry[] entries = _entries; uint collisionCount = 0; - i--; // Value in _buckets is 1-based; subtract 1 from i. We do it here so it fuses with the following conditional. + *i -= 1; // Value in _buckets is 1-based; subtract 1 from i. We do it here so it fuses with the following conditional. do { // Test in if to drop range check for following array access - if ((uint)i >= (uint)entries.Length) + if ((uint)(*i) >= (uint)entries.Length) { - TValue value = Unsafe.NullRef(); + TValue* value = null; return value; } - entry = entries[i]; + entry = entries[*i]; if (entry.hashCode == hashCode && comparer.Compare(entry.key, key) == 0) { - TValue value = entry.value; + TValue* value = &entry.value; return value; } - i = entry.next; + *i = entry.next; collisionCount++; } while (collisionCount <= (uint)entries.Length); // The chain of entries forms a loop; which means a concurrent update has happened. // Break out of the loop and throw, rather than looping forever. - ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported(); + throw new InvalidOperationException(SR.InvalidOperation_ConcurrentOperationsNotSupported); } } - var value = Unsafe.NullRef(); + var value = null; return value; } @@ -393,7 +383,7 @@ public class Dictionary : IDictionary { int size = HashHelpers.GetPrime(capacity); int[] buckets = new int[size]; - Entry[] entries = new Entry[size]; + DictionaryEntry[] entries = new DictionaryEntry[size]; // Assign member variables after both arrays allocated to guard against corruption from OOM if second fails _freeList = -1; @@ -413,25 +403,25 @@ public class Dictionary : IDictionary if (key == null) { - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key); + throw new ArgumentNullException(nameof(key)); } if (_buckets == null) { Initialize(0); } - Debug.Assert(_buckets != null); + // Debug.Assert(_buckets != null); // TODO: - Entry[] entries = _entries; - Debug.Assert(entries != null, "expected entries to be non-null"); + DictionaryEntry[] entries = _entries; + // Debug.Assert(entries != null, "expected entries to be non-null"); // TODO: - IEqualityComparer comparer = _comparer; - Debug.Assert(comparer is not null || typeof(TKey).IsValueType); - uint hashCode = (uint)((typeof(TKey).IsValueType && comparer == null) ? key.GetHashCode() : comparer.GetHashCode(key)); + IComparer comparer = _comparer; + // Debug.Assert(comparer is not null || typeof(TKey).IsValueType); // TODO: + uint hashCode = (uint)(key.GetHashCode()); uint collisionCount = 0; - int bucket = GetBucket(hashCode); - int i = bucket - 1; // Value in _buckets is 1-based + int* bucket = GetBucket(hashCode); + int i = *bucket - 1; // Value in _buckets is 1-based if (typeof(TKey).IsValueType && // comparer can only be null for value types; enable JIT to eliminate entire if block for ref types comparer == null) @@ -439,7 +429,7 @@ public class Dictionary : IDictionary // ValueType: Devirtualize with EqualityComparer.Default intrinsic while ((uint)i < (uint)entries.Length) { - if (entries[i].hashCode == hashCode && EqualityComparer.Default.Equals(entries[i].key, key)) + if (entries[i].hashCode == hashCode && System.Comparer.Default().Compare(entries[i].key, key) == 0) { if (behavior == InsertionBehavior.OverwriteExisting) { @@ -449,7 +439,7 @@ public class Dictionary : IDictionary if (behavior == InsertionBehavior.ThrowOnExisting) { - ThrowHelper.ThrowAddingDuplicateWithKeyArgumentException(key); + throw new ArgumentException(string.Format(SR.Argument_AddingDuplicateWithKey, key.ToString())); } return false; @@ -457,21 +447,23 @@ public class Dictionary : IDictionary i = entries[i].next; + collisionCount++; if (collisionCount > (uint)entries.Length) { + // The chain of entries forms a loop; which means a concurrent update has happened. // Break out of the loop and throw, rather than looping forever. - ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported(); + throw new InvalidOperationException(SR.InvalidOperation_ConcurrentOperationsNotSupported); } } } else { - Debug.Assert(comparer is not null); + // Debug.Assert(comparer is not null); // TODO: while ((uint)i < (uint)entries.Length) { - if (entries[i].hashCode == hashCode && comparer.Equals(entries[i].key, key)) + if (entries[i].hashCode == hashCode && comparer.Compare(entries[i].key, key) == 0) { if (behavior == InsertionBehavior.OverwriteExisting) { @@ -481,7 +473,7 @@ public class Dictionary : IDictionary if (behavior == InsertionBehavior.ThrowOnExisting) { - ThrowHelper.ThrowAddingDuplicateWithKeyArgumentException(key); + throw new ArgumentException(string.Format(SR.Argument_AddingDuplicateWithKey, key)); } return false; @@ -494,7 +486,7 @@ public class Dictionary : IDictionary { // The chain of entries forms a loop; which means a concurrent update has happened. // Break out of the loop and throw, rather than looping forever. - ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported(); + throw new InvalidOperationException(SR.InvalidOperation_ConcurrentOperationsNotSupported); } } } @@ -503,7 +495,8 @@ public class Dictionary : IDictionary if (_freeCount > 0) { index = _freeList; - Debug.Assert((StartOfFreeList - entries[_freeList].next) >= -1, "shouldn't overflow because `next` cannot underflow"); + // TODO: + // Debug.Assert((StartOfFreeList - entries[_freeList].next) >= -1, "shouldn't overflow because `next` cannot underflow"); _freeList = StartOfFreeList - entries[_freeList].next; _freeCount--; } @@ -520,16 +513,16 @@ public class Dictionary : IDictionary entries = _entries; } - Entry entry = entries[index]; + DictionaryEntry entry = entries[index]; entry.hashCode = hashCode; - entry.next = bucket - 1; // Value in _buckets is 1-based + entry.next = *bucket - 1; // Value in _buckets is 1-based entry.key = key; entry.value = value; - bucket = index + 1; // Value in _buckets is 1-based + *bucket = index + 1; // Value in _buckets is 1-based _version++; // Value types never rehash - if (!typeof(TKey).IsValueType && collisionCount > HashHelpers.HashCollisionThreshold && comparer is NonRandomizedStringEqualityComparer) + if (!typeof(TKey).IsValueType && collisionCount > HashHelpers.HashCollisionThreshold) { // If we hit the collision threshold we'll need to switch to the comparer which is using randomized string hashing // i.e. EqualityComparer.Default. @@ -554,7 +547,7 @@ public class Dictionary : IDictionary if (key == null) { - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key); + throw new ArgumentNullException(nameof(key)); } if (dictionary._buckets == null) @@ -563,16 +556,16 @@ public class Dictionary : IDictionary } // Debug.Assert(dictionary._buckets != null); // TODO: - Entry[] entries = dictionary._entries; + DictionaryEntry[] entries = dictionary._entries; // Debug.Assert(entries != null, "expected entries to be non-null"); // TODO: IComparer comparer = dictionary._comparer; // Debug.Assert(comparer is not null || typeof(TKey).IsValueType); // TODO: - uint hashCode = (uint)((typeof(TKey).IsValueType && comparer == null) ? key.GetHashCode() : comparer.GetHashCode(key)); + uint hashCode = (uint)(key.GetHashCode()); uint collisionCount = 0; - int bucket = dictionary.GetBucket(hashCode); - int i = bucket - 1; // Value in _buckets is 1-based + int* bucket = dictionary.GetBucket(hashCode); + int i = *bucket - 1; // Value in _buckets is 1-based if (typeof(TKey).IsValueType && // comparer can only be null for value types; enable JIT to eliminate entire if block for ref types comparer == null) @@ -580,7 +573,7 @@ public class Dictionary : IDictionary // ValueType: Devirtualize with EqualityComparer.Default intrinsic while ((uint)i < (uint)entries.Length) { - if (entries[i].hashCode == hashCode && EqualityComparer.Default.Equals(entries[i].key, key)) + if (entries[i].hashCode == hashCode && System.Comparer.Default().Compare(entries[i].key, key) == 0) { exists = true; @@ -594,7 +587,7 @@ public class Dictionary : IDictionary { // The chain of entries forms a loop; which means a concurrent update has happened. // Break out of the loop and throw, rather than looping forever. - ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported(); + throw new InvalidOperationException(SR.InvalidOperation_ConcurrentOperationsNotSupported); } } } @@ -603,7 +596,7 @@ public class Dictionary : IDictionary // Debug.Assert(comparer is not null); // TODO: while ((uint)i < (uint)entries.Length) { - if (entries[i].hashCode == hashCode && comparer.Equals(entries[i].key, key)) + if (entries[i].hashCode == hashCode && comparer.Compare(entries[i].key, key) == 0) { exists = true; @@ -617,7 +610,7 @@ public class Dictionary : IDictionary { // The chain of entries forms a loop; which means a concurrent update has happened. // Break out of the loop and throw, rather than looping forever. - ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported(); + throw new InvalidOperationException(SR.InvalidOperation_ConcurrentOperationsNotSupported); } } } @@ -626,7 +619,8 @@ public class Dictionary : IDictionary if (dictionary._freeCount > 0) { index = dictionary._freeList; - Debug.Assert((StartOfFreeList - entries[dictionary._freeList].next) >= -1, "shouldn't overflow because `next` cannot underflow"); + // TODO: + // Debug.Assert((StartOfFreeList - entries[dictionary._freeList].next) >= -1, "shouldn't overflow because `next` cannot underflow"); dictionary._freeList = StartOfFreeList - entries[dictionary._freeList].next; dictionary._freeCount--; } @@ -643,16 +637,16 @@ public class Dictionary : IDictionary entries = dictionary._entries; } - Entry entry = entries[index]; + DictionaryEntry entry = entries[index]; entry.hashCode = hashCode; - entry.next = bucket - 1; // Value in _buckets is 1-based + entry.next = *bucket - 1; // Value in _buckets is 1-based entry.key = key; entry.value = default; - bucket = index + 1; // Value in _buckets is 1-based + *bucket = index + 1; // Value in _buckets is 1-based dictionary._version++; // Value types never rehash - if (!typeof(TKey).IsValueType && collisionCount > HashHelpers.HashCollisionThreshold && comparer is NonRandomizedStringEqualityComparer) + if (!typeof(TKey).IsValueType && collisionCount > HashHelpers.HashCollisionThreshold) { // If we hit the collision threshold we'll need to switch to the comparer which is using randomized string hashing // i.e. EqualityComparer.Default. @@ -663,11 +657,11 @@ public class Dictionary : IDictionary // At this point the entries array has been resized, so the current reference we have is no longer valid. // We're forced to do a new lookup and return an updated reference to the new entry instance. This new // lookup is guaranteed to always find a value though and it will never return a null reference here. - TValue value = dictionary.FindValue(key); + TValue* value = dictionary.FindValue(key); - Debug.Assert(!Unsafe.IsNullRef(ref value), "the lookup result cannot be a null ref here"); + // Debug.Assert(!Unsafe.IsNullRef(ref value), "the lookup result cannot be a null ref here"); // TODO: - return value; + return *value; } exists = false; @@ -686,7 +680,7 @@ public class Dictionary : IDictionary // Debug.Assert(_entries != null, "_entries should be non-null"); // Debug.Assert(newSize >= _entries.Length); - Entry[] entries = new Entry[newSize]; + DictionaryEntry[] entries = new DictionaryEntry[newSize]; int count = _count; Array.Copy(_entries, entries, count); @@ -694,13 +688,11 @@ public class Dictionary : IDictionary if (!typeof(TKey).IsValueType && forceNewHashCodes) { // Debug.Assert(_comparer is NonRandomizedStringEqualityComparer); // TODO: - _comparer = (IComparer)((NonRandomizedStringEqualityComparer)_comparer).GetRandomizedEqualityComparer(); - for (int i = 0; i < count; i++) { if (entries[i].next >= -1) { - entries[i].hashCode = (uint)_comparer.GetHashCode(entries[i].key); + entries[i].hashCode = (uint)entries[i].key.GetHashCode(); } } } @@ -714,9 +706,9 @@ public class Dictionary : IDictionary { if (entries[i].next >= -1) { - int bucket = GetBucket(entries[i].hashCode); - entries[i].next = bucket - 1; // Value in _buckets is 1-based - bucket = i + 1; + int* bucket = GetBucket(entries[i].hashCode); + entries[i].next = *bucket - 1; // Value in _buckets is 1-based + *bucket = i + 1; } } @@ -732,7 +724,7 @@ public class Dictionary : IDictionary if (key == null) { - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key); + throw new ArgumentNullException(nameof(key)); } if (_buckets != null) @@ -742,25 +734,25 @@ public class Dictionary : IDictionary IComparer comparer = _comparer; // Debug.Assert(typeof(TKey).IsValueType || comparer is not null); // TODO: - uint hashCode = (uint)(typeof(TKey).IsValueType && comparer == null ? key.GetHashCode() : comparer.GetHashCode(key)); + uint hashCode = (uint)(key.GetHashCode()); - int bucket = GetBucket(hashCode); - Entry[] entries = _entries; + int* bucket = GetBucket(hashCode); + DictionaryEntry[] entries = _entries; int last = -1; - int i = bucket - 1; // Value in buckets is 1-based + int i = *bucket - 1; // Value in buckets is 1-based if (typeof(TKey).IsValueType && // comparer can only be null for value types; enable JIT to eliminate entire if block for ref types comparer == null) { while ((uint)i < (uint)entries.Length) { - Entry entry = entries[i]; + DictionaryEntry entry = entries[i]; - if (entry.hashCode == hashCode && EqualityComparer.Default.Equals(entry.key, key)) + if (entry.hashCode == hashCode && System.Comparer.Default().Compare(entry.key, key) == 0) { if (last < 0) { - bucket = entry.next + 1; // Value in buckets is 1-based + *bucket = entry.next + 1; // Value in buckets is 1-based } else { @@ -771,15 +763,8 @@ public class Dictionary : IDictionary // Debug.Assert((StartOfFreeList - _freeList) < 0, "shouldn't underflow because max hashtable length is MaxPrimeArrayLength = 0x7FEFFFFD(2146435069) _freelist underflow threshold 2147483646"); entry.next = StartOfFreeList - _freeList; - if (RuntimeHelpers.IsReferenceOrContainsReferences()) - { - entry.key = default; - } - - if (RuntimeHelpers.IsReferenceOrContainsReferences()) - { - entry.value = default; - } + entry.key = default; + entry.value = default; _freeList = i; _freeCount++; @@ -794,7 +779,7 @@ public class Dictionary : IDictionary { // The chain of entries forms a loop; which means a concurrent update has happened. // Break out of the loop and throw, rather than looping forever. - ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported(); + throw new InvalidOperationException(SR.InvalidOperation_ConcurrentOperationsNotSupported); } } } @@ -803,13 +788,13 @@ public class Dictionary : IDictionary // Debug.Assert(comparer is not null); // TODO: while ((uint)i < (uint)entries.Length) { - Entry entry = entries[i]; + DictionaryEntry entry = entries[i]; - if (entry.hashCode == hashCode && comparer.Equals(entry.key, key)) + if (entry.hashCode == hashCode && comparer.Compare(entry.key, key) == 0) { if (last < 0) { - bucket = entry.next + 1; // Value in buckets is 1-based + *bucket = entry.next + 1; // Value in buckets is 1-based } else { @@ -820,15 +805,8 @@ public class Dictionary : IDictionary // Debug.Assert((StartOfFreeList - _freeList) < 0, "shouldn't underflow because max hashtable length is MaxPrimeArrayLength = 0x7FEFFFFD(2146435069) _freelist underflow threshold 2147483646"); entry.next = StartOfFreeList - _freeList; - if (RuntimeHelpers.IsReferenceOrContainsReferences()) - { - entry.key = default; - } - - if (RuntimeHelpers.IsReferenceOrContainsReferences()) - { - entry.value = default; - } + entry.key = default; + entry.value = default; _freeList = i; _freeCount++; @@ -843,7 +821,7 @@ public class Dictionary : IDictionary { // The chain of entries forms a loop; which means a concurrent update has happened. // Break out of the loop and throw, rather than looping forever. - ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported(); + throw new InvalidOperationException(SR.InvalidOperation_ConcurrentOperationsNotSupported); } } } @@ -860,7 +838,7 @@ public class Dictionary : IDictionary if (key == null) { - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key); + throw new ArgumentNullException(nameof(key)); } if (_buckets != null) @@ -871,25 +849,25 @@ public class Dictionary : IDictionary IComparer comparer = _comparer; // Debug.Assert(typeof(TKey).IsValueType || comparer is not null); // TODO: - uint hashCode = (uint)(typeof(TKey).IsValueType && comparer == null ? key.GetHashCode() : comparer.GetHashCode(key)); + uint hashCode = (uint)(key.GetHashCode()); - int bucket = GetBucket(hashCode); - Entry[] entries = _entries; + int* bucket = GetBucket(hashCode); + DictionaryEntry[] entries = _entries; int last = -1; - int i = bucket - 1; // Value in buckets is 1-based + int i = *bucket - 1; // Value in buckets is 1-based if (typeof(TKey).IsValueType && // comparer can only be null for value types; enable JIT to eliminate entire if block for ref types comparer == null) { while ((uint)i < (uint)entries.Length) { - Entry entry = entries[i]; + DictionaryEntry entry = entries[i]; - if (entry.hashCode == hashCode && EqualityComparer.Default.Equals(entry.key, key)) + if (entry.hashCode == hashCode && System.Comparer.Default().Compare(entry.key, key) == 0) { if (last < 0) { - bucket = entry.next + 1; // Value in buckets is 1-based + *bucket = entry.next + 1; // Value in buckets is 1-based } else { @@ -902,15 +880,8 @@ public class Dictionary : IDictionary // Debug.Assert((StartOfFreeList - _freeList) < 0, "shouldn't underflow because max hashtable length is MaxPrimeArrayLength = 0x7FEFFFFD(2146435069) _freelist underflow threshold 2147483646"); entry.next = StartOfFreeList - _freeList; - if (RuntimeHelpers.IsReferenceOrContainsReferences()) - { - entry.key = default; - } - - if (RuntimeHelpers.IsReferenceOrContainsReferences()) - { - entry.value = default; - } + entry.key = default; + entry.value = default; _freeList = i; _freeCount++; @@ -925,7 +896,7 @@ public class Dictionary : IDictionary { // The chain of entries forms a loop; which means a concurrent update has happened. // Break out of the loop and throw, rather than looping forever. - ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported(); + throw new InvalidOperationException(SR.InvalidOperation_ConcurrentOperationsNotSupported); } } } @@ -934,13 +905,13 @@ public class Dictionary : IDictionary // Debug.Assert(comparer is not null); // TODO: while ((uint)i < (uint)entries.Length) { - Entry entry = entries[i]; + DictionaryEntry entry = entries[i]; - if (entry.hashCode == hashCode && comparer.Equals(entry.key, key)) + if (entry.hashCode == hashCode && comparer.Compare(entry.key, key) == 0) { if (last < 0) { - bucket = entry.next + 1; // Value in buckets is 1-based + *bucket = entry.next + 1; // Value in buckets is 1-based } else { @@ -953,15 +924,8 @@ public class Dictionary : IDictionary // Debug.Assert((StartOfFreeList - _freeList) < 0, "shouldn't underflow because max hashtable length is MaxPrimeArrayLength = 0x7FEFFFFD(2146435069) _freelist underflow threshold 2147483646"); entry.next = StartOfFreeList - _freeList; - if (RuntimeHelpers.IsReferenceOrContainsReferences()) - { - entry.key = default; - } - - if (RuntimeHelpers.IsReferenceOrContainsReferences()) - { - entry.value = default; - } + entry.key = default; + entry.value = default; _freeList = i; _freeCount++; @@ -976,7 +940,7 @@ public class Dictionary : IDictionary { // The chain of entries forms a loop; which means a concurrent update has happened. // Break out of the loop and throw, rather than looping forever. - ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported(); + throw new InvalidOperationException(SR.InvalidOperation_ConcurrentOperationsNotSupported); } } } @@ -994,10 +958,10 @@ public class Dictionary : IDictionary public bool TryGetValue(TKey key, out TValue value) { - TValue valRef = FindValue(key); - if (!Unsafe.IsNullRef(ref valRef)) + TValue* valRef = FindValue(key); + if (valRef != null) { - value = valRef; + value = *valRef; return true; } @@ -1017,33 +981,23 @@ public class Dictionary : IDictionary { if (array == null) { - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); - } - - if (array.Rank != 1) - { - ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_RankMultiDimNotSupported); - } - - if (array.GetLowerBound(0) != 0) - { - ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_NonZeroLowerBound); + throw new ArgumentNullException(nameof(array)); } if ((uint)index > (uint)array.Length) { - ThrowHelper.ThrowIndexArgumentOutOfRange_NeedNonNegNumException(); + throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_NeedNonNegNum); } if (array.Length - index < Count) { - ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_ArrayPlusOffTooSmall); + throw new ArgumentException(nameof(array), SR.Arg_ArrayPlusOffTooSmall); } try { int count = _count; - Entry[] entries = _entries; + DictionaryEntry[] entries = _entries; for (int i = 0; i < count; i++) { if (entries[i].next >= -1) @@ -1054,7 +1008,7 @@ public class Dictionary : IDictionary } catch (ArrayTypeMismatchException) { - ThrowHelper.ThrowArgumentException_Argument_IncompatibleArrayType(); + throw new ArgumentException(SR.Argument_IncompatibleArrayType); } } @@ -1067,7 +1021,7 @@ public class Dictionary : IDictionary { if (capacity < 0) { - ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.capacity); + throw new ArgumentOutOfRangeException(nameof(capacity), SR.ArgumentOutOfRange_NeedNonNegNum); } int currentCapacity = _entries == null ? 0 : _entries.Length; @@ -1114,11 +1068,11 @@ public class Dictionary : IDictionary { if (capacity < Count) { - ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.capacity); + throw new ArgumentOutOfRangeException(nameof(capacity), SR.ArgumentOutOfRange_NeedNonNegNum); } int newSize = HashHelpers.GetPrime(capacity); - Entry[] oldEntries = _entries; + DictionaryEntry[] oldEntries = _entries; int currentCapacity = oldEntries == null ? 0 : oldEntries.Length; if (newSize >= currentCapacity) { @@ -1134,22 +1088,22 @@ public class Dictionary : IDictionary CopyEntries(oldEntries, oldCount); } - private void CopyEntries(Entry[] entries, int count) + private void CopyEntries(DictionaryEntry[] entries, int count) { // Debug.Assert(_entries is not null); // TODO: - Entry[] newEntries = _entries; + DictionaryEntry[] newEntries = _entries; int newCount = 0; for (int i = 0; i < count; i++) { uint hashCode = entries[i].hashCode; if (entries[i].next >= -1) { - Entry entry = newEntries[newCount]; + DictionaryEntry entry = newEntries[newCount]; entry = entries[i]; - int bucket = GetBucket(hashCode); - entry.next = bucket - 1; // Value in _buckets is 1-based - bucket = newCount + 1; + int* bucket = GetBucket(hashCode); + entry.next = *bucket - 1; // Value in _buckets is 1-based + *bucket = newCount + 1; newCount++; } } @@ -1166,22 +1120,21 @@ public class Dictionary : IDictionary { if (key == null) { - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key); + throw new ArgumentNullException(nameof(key)); } return key is TKey; } - private int GetBucket(uint hashCode) + private int* GetBucket(uint hashCode) { - int[] buckets = _buckets; #if TARGET_64BIT; return ref buckets[HashHelpers.FastMod(hashCode, (uint)buckets.Length, _fastModMultiplier)]; #else - return buckets[(int)(hashCode % (uint)buckets.Length)]; + return &_buckets[(int)(hashCode % (uint)_buckets.Length)]; #endif } - private struct Entry + private class DictionaryEntry { public uint hashCode; /// @@ -1194,7 +1147,7 @@ public class Dictionary : IDictionary public TValue value; // Value of entry } - public struct DictionaryEnumerator : IEnumerator>, IDictionaryEnumerator + public struct DictionaryEnumerator : IDictionaryEnumerator { private readonly Dictionary _dictionary; private readonly int _version; @@ -1218,14 +1171,14 @@ public class Dictionary : IDictionary { if (_version != _dictionary._version) { - ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion(); + throw new InvalidOperationException(SR.InvalidOperation_EnumFailedVersion); } // Use unsigned comparison since we set index to dictionary.count+1 when the enumeration ends. // dictionary.count+1 could be negative if dictionary.count is int.MaxValue while ((uint)_index < (uint)_dictionary._count) { - Entry entry = _dictionary._entries[_index++]; + DictionaryEntry entry = _dictionary._entries[_index++]; if (entry.next >= -1) { @@ -1249,12 +1202,7 @@ public class Dictionary : IDictionary { if (_index == 0 || (_index == _dictionary._count + 1)) { - ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen(); - } - - if (_getEnumeratorRetType == DictEntry) - { - return new DictionaryEntry(_current.Key, _current.Value); + throw new InvalidOperationException(SR.InvalidOperation_EnumOpCantHappen); } return new KeyValuePair(_current.Key, _current.Value); @@ -1265,7 +1213,7 @@ public class Dictionary : IDictionary { if (_version != _dictionary._version) { - ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion(); + throw new InvalidOperationException(SR.InvalidOperation_EnumFailedVersion); } _index = 0; @@ -1278,7 +1226,7 @@ public class Dictionary : IDictionary { if (_index == 0 || (_index == _dictionary._count + 1)) { - ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen(); + throw new InvalidOperationException(SR.InvalidOperation_EnumOpCantHappen); } return _current.Key; @@ -1291,7 +1239,7 @@ public class Dictionary : IDictionary { if (_index == 0 || (_index == _dictionary._count + 1)) { - ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen(); + throw new InvalidOperationException(SR.InvalidOperation_EnumOpCantHappen); } return _current.Value; diff --git a/std/System/Collections/Generic/KeyNotFoundException.hpt b/std/System/Collections/Generic/KeyNotFoundException.hpt new file mode 100644 index 00000000..0de16f4e --- /dev/null +++ b/std/System/Collections/Generic/KeyNotFoundException.hpt @@ -0,0 +1,11 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// Some modifications were made for hapet-lang compatibility. + +using System; + +public class KeyNotFoundException : SystemException +{ + public KeyNotFoundException() : base(SR.Arg_KeyNotFound) { } + public KeyNotFoundException(string message) : base(message ?? SR.Arg_KeyNotFound) { } +} \ No newline at end of file diff --git a/std/System/Runtime/TypeInfoUnsafe.hpt b/std/System/Runtime/TypeInfoUnsafe.hpt index bd980cf3..4a85f81f 100644 --- a/std/System/Runtime/TypeInfoUnsafe.hpt +++ b/std/System/Runtime/TypeInfoUnsafe.hpt @@ -10,4 +10,5 @@ internal struct TypeInfoUnsafe internal int** _interfaceOffsets; internal DtorDelegate _dtorDelegate; internal int _rawSize; + internal bool _isValueType; } \ No newline at end of file diff --git a/std/System/SR.hpt b/std/System/SR.hpt index 8a59d26a..f13a1983 100644 --- a/std/System/SR.hpt +++ b/std/System/SR.hpt @@ -12,6 +12,9 @@ internal static class SR internal const string ArgumentOutOfRange_IndexMustBeLessOrEqual = "Index was out of range. Must be non-negative and less than or equal to the size of the collection."; internal const string ArgumentOutOfRange_Count = "Count must be positive and count must refer to a location within the string/array/collection."; internal const string Argument_InvalidOffLen = "Offset and length were out of bounds for the array or count is greater than the number of elements from index to the end of the source collection."; + internal const string Argument_AddingDuplicateWithKey = "An item with the same key has already been added. Key: {0}"; + internal const string Argument_InvalidTimeSpanStyles = "An undefined TimeSpanStyles value is being used."; + internal const string Argument_IncompatibleArrayType = "Target array type is not compatible with the type of items in the collection."; // formatting internal const string Formatting_InvalidGroupSizes = "Invalid group sizes in NumberFormatInfo."; @@ -45,10 +48,13 @@ internal static class SR internal const string Arg_InvalidConsoleColor = "The ConsoleColor enum value was not defined on that enum. Please use a defined color from the enum."; internal const string Arg_OverflowException = "Arithmetic operation resulted in an overflow."; internal const string Arg_DivideByZero = "Attempted to divide by zero."; - internal const string Argument_InvalidTimeSpanStyles = "An undefined TimeSpanStyles value is being used."; internal const string Arg_DllNotFoundException = "Dll was not found."; internal const string Arg_CannotBeNaN = "TimeSpan does not accept floating point Not-a-Number values."; + internal const string Arg_ArrayPlusOffTooSmall = "Destination array is not long enough to copy all the items in the collection. Check array index and length."; + internal const string Arg_KeyNotFound = "The given key was not present in the dictionary."; + internal const string Arg_KeyNotFoundWithKey = "The given key '{0}' was not present in the dictionary."; + internal const string Arg_ArrayTypeMismatchException = "Attempted to access an element as a type incompatible with the array."; // overflow internal const string Overflow_TimeSpanTooLong = "TimeSpan overflowed because the duration is too long."; @@ -61,8 +67,11 @@ internal static class SR // invalid internal const string InvalidOperation_EnumNotStarted = "Enumeration has not started. Call MoveNext."; internal const string InvalidOperation_EnumEnded = "Enumeration already finished."; + internal const string InvalidOperation_EnumFailedVersion = "Collection was modified; enumeration operation may not execute."; + internal const string InvalidOperation_EnumOpCantHappen = "Enumeration has either not started or has already finished."; internal const string InvalidOperation_IComparerFailed = "Failed to compare two elements in the array."; internal const string InvalidOperation_NoValue = "Nullable object must have a value."; + internal const string InvalidOperation_ConcurrentOperationsNotSupported = "Operations that change non-concurrent collections must have exclusive access. A concurrent update was performed on this collection and corrupted its state. The collection's state is no longer correct."; // io internal const string Arg_IOException = "I/O error occurred."; diff --git a/std/System/Type.hpt b/std/System/Type.hpt index 00b231eb..b7a267a6 100644 --- a/std/System/Type.hpt +++ b/std/System/Type.hpt @@ -36,6 +36,8 @@ public struct Type /// Returns raw size of the type public int RawSize => (*_typeInfo)._rawSize; + /// Returns 'true' when value type + public bool IsValueType => (*_typeInfo)._isValueType; public static bool operator ==(Type t1, Type t2) { From e256ab3c99f2534f0f317631f2a3c639e68ae8e2 Mon Sep 17 00:00:00 2001 From: Airat Abdrakov <52558686+CrackAndDie@users.noreply.github.com> Date: Wed, 13 May 2026 10:05:50 +0300 Subject: [PATCH 24/26] Removed unused readonly kw from KVP --- std/System/Collections/Generic/KeyValuePair.hpt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/std/System/Collections/Generic/KeyValuePair.hpt b/std/System/Collections/Generic/KeyValuePair.hpt index 0ba9a98b..35ddbd6d 100644 --- a/std/System/Collections/Generic/KeyValuePair.hpt +++ b/std/System/Collections/Generic/KeyValuePair.hpt @@ -21,10 +21,10 @@ public static class KeyValuePair // A KeyValuePair holds a key and a value from a dictionary. // It is used by the IEnumerable implementation for both IDictionary // and IReadOnlyDictionary. -public readonly struct KeyValuePair +public struct KeyValuePair { - private readonly TKey key; // Do not rename (binary serialization) - private readonly TValue value; // Do not rename (binary serialization) + private TKey key; // Do not rename (binary serialization) + private TValue value; // Do not rename (binary serialization) public KeyValuePair(TKey key, TValue value) { From 92704683be50fed82186c630f76f6820ef4acb23 Mon Sep 17 00:00:00 2001 From: Airat Abdrakov <52558686+CrackAndDie@users.noreply.github.com> Date: Wed, 13 May 2026 10:53:38 +0300 Subject: [PATCH 25/26] HashHelpers and other shite added --- std/System/Array.hpt | 5 + std/System/Collections/Generic/Dictionary.hpt | 73 ++++-------- .../Collections/Generic/InsertionBehavior.hpt | 24 ++++ std/System/Collections/HashHelpers.hpt | 109 ++++++++++++++++++ std/System/Math.hpt | 5 + .../Runtime/InteropServices/Marshal.hpt | 3 + std/System/SR.hpt | 1 + 7 files changed, 167 insertions(+), 53 deletions(-) create mode 100644 std/System/Collections/Generic/InsertionBehavior.hpt create mode 100644 std/System/Collections/HashHelpers.hpt diff --git a/std/System/Array.hpt b/std/System/Array.hpt index 350db78b..5149cd3d 100644 --- a/std/System/Array.hpt +++ b/std/System/Array.hpt @@ -136,6 +136,11 @@ public static class Array array = newArray; } + public static void Clear(T[] array) + { + Marshal.Memset(array.Buffer, 0, array.Length * sizeof(T)); + } + public static void Clear(T[] array, int index, int length) { Marshal.Memset(array.Buffer + index, 0, length * sizeof(T)); diff --git a/std/System/Collections/Generic/Dictionary.hpt b/std/System/Collections/Generic/Dictionary.hpt index eec6df00..ee9409ec 100644 --- a/std/System/Collections/Generic/Dictionary.hpt +++ b/std/System/Collections/Generic/Dictionary.hpt @@ -184,7 +184,7 @@ public class Dictionary : IDictionary return false; } - bool ICollection>.Remove(KeyValuePair keyValuePair) + public bool Remove(KeyValuePair keyValuePair) { TValue* value = FindValue(keyValuePair.Key); if (value != null && System.Comparer.Default().Compare(value, keyValuePair.Value) == 0) @@ -205,12 +205,12 @@ public class Dictionary : IDictionary // Debug.Assert(_buckets != null, "_buckets should be non-null"); // Debug.Assert(_entries != null, "_entries should be non-null"); - Array.Clear(_buckets); + Array.Clear(_buckets); _count = 0; _freeList = -1; _freeCount = 0; - Array.Clear(_entries, 0, count); + Array.Clear(_entries, 0, count); } } @@ -258,7 +258,7 @@ public class Dictionary : IDictionary return false; } - private void CopyTo(KeyValuePair[] array, int index) + public void CopyTo(KeyValuePair[] array, int index) { if (array == null) { @@ -275,22 +275,27 @@ public class Dictionary : IDictionary throw new ArgumentException(nameof(array), SR.Arg_ArrayPlusOffTooSmall); } - int count = _count; - DictionaryEntry[] entries = _entries; - for (int i = 0; i < count; i++) + try { - if (entries[i].next >= -1) + int count = _count; + DictionaryEntry[] entries = _entries; + for (int i = 0; i < count; i++) { - array[index++] = new KeyValuePair(entries[i].key, entries[i].value); + if (entries[i].next >= -1) + { + array[index++] = new KeyValuePair(entries[i].key, entries[i].value); + } } } + catch (ArrayTypeMismatchException) + { + throw new ArgumentException(SR.Argument_IncompatibleArrayType); + } } - public DictionaryEnumerator GetEnumerator() => new DictionaryEnumerator(this, DictionaryEnumerator.KeyValuePair); - - IEnumerator> IEnumerable>.GetEnumerator() => - Count == 0 ? EmptyEnumerator.Get>() : - GetEnumerator(); + public IEnumerator> GetEnumerator() => + Count == 0 ? (IEnumerator>)EmptyEnumerator.Get>() : + new DictionaryEnumerator(this, DictionaryEnumerator.KeyValuePair); internal TValue* FindValue(TKey key) { @@ -683,7 +688,7 @@ public class Dictionary : IDictionary DictionaryEntry[] entries = new DictionaryEntry[newSize]; int count = _count; - Array.Copy(_entries, entries, count); + Array.Copy(_entries, 0, entries, 0, count); if (!typeof(TKey).IsValueType && forceNewHashCodes) { @@ -974,44 +979,6 @@ public class Dictionary : IDictionary bool ICollection>.IsReadOnly => false; - void ICollection>.CopyTo(KeyValuePair[] array, int index) => - CopyTo(array, index); - - void CopyTo(KeyValuePair[] array, int index) - { - if (array == null) - { - throw new ArgumentNullException(nameof(array)); - } - - if ((uint)index > (uint)array.Length) - { - throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_NeedNonNegNum); - } - - if (array.Length - index < Count) - { - throw new ArgumentException(nameof(array), SR.Arg_ArrayPlusOffTooSmall); - } - - try - { - int count = _count; - DictionaryEntry[] entries = _entries; - for (int i = 0; i < count; i++) - { - if (entries[i].next >= -1) - { - array[index++] = new KeyValuePair(entries[i].key, entries[i].value); - } - } - } - catch (ArrayTypeMismatchException) - { - throw new ArgumentException(SR.Argument_IncompatibleArrayType); - } - } - IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable>)this).GetEnumerator(); /// diff --git a/std/System/Collections/Generic/InsertionBehavior.hpt b/std/System/Collections/Generic/InsertionBehavior.hpt new file mode 100644 index 00000000..290625b0 --- /dev/null +++ b/std/System/Collections/Generic/InsertionBehavior.hpt @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// Some modifications were made for hapet-lang compatibility. + +/// +/// Used internally to control behavior of insertion into a or . +/// +internal enum InsertionBehavior : byte +{ + /// + /// The default insertion behavior. + /// + None = 0, + + /// + /// Specifies that an existing entry with the same key should be overwritten if encountered. + /// + OverwriteExisting = 1, + + /// + /// Specifies that if an existing entry with the same key is encountered, an exception should be thrown. + /// + ThrowOnExisting = 2 +} \ No newline at end of file diff --git a/std/System/Collections/HashHelpers.hpt b/std/System/Collections/HashHelpers.hpt new file mode 100644 index 00000000..e06bfa95 --- /dev/null +++ b/std/System/Collections/HashHelpers.hpt @@ -0,0 +1,109 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.CompilerServices; + +internal static partial class HashHelpers +{ + public const uint HashCollisionThreshold = 100; + + // This is the maximum prime smaller than Array.MaxLength. + public const int MaxPrimeArrayLength = 0x7FFFFFC3; + + public const int HashPrime = 101; + + // Table of prime numbers to use as hash table sizes. + // A typical resize algorithm would pick the smallest prime number in this array + // that is larger than twice the previous capacity. + // Suppose our Hashtable currently has capacity x and enough elements are added + // such that a resize needs to occur. Resizing first computes 2x then finds the + // first prime in the table greater than 2x, i.e. if primes are ordered + // p_1, p_2, ..., p_i, ..., it finds p_n such that p_n-1 < 2x < p_n. + // Doubling is important for preserving the asymptotic complexity of the + // hashtable operations such as add. Having a prime guarantees that double + // hashing does not lead to infinite loops. IE, your hash function will be + // h1(key) + i*h2(key), 0 <= i < size. h2 and the size must be relatively prime. + // We prefer the low computation costs of higher prime numbers over the increased + // memory allocation of a fixed prime number i.e. when right sizing a HashSet. + internal static int[] Primes = new int[] + { + 3, 7, 11, 17, 23, 29, 37, 47, 59, 71, 89, 107, 131, 163, 197, 239, 293, 353, 431, 521, 631, 761, 919, + 1103, 1327, 1597, 1931, 2333, 2801, 3371, 4049, 4861, 5839, 7013, 8419, 10103, 12143, 14591, + 17519, 21023, 25229, 30293, 36353, 43627, 52361, 62851, 75431, 90523, 108631, 130363, 156437, + 187751, 225307, 270371, 324449, 389357, 467237, 560689, 672827, 807403, 968897, 1162687, 1395263, + 1674319, 2009191, 2411033, 2893249, 3471899, 4166287, 4999559, 5999471, 7199369 + }; + + public static bool IsPrime(int candidate) + { + if ((candidate & 1) != 0) + { + int limit = (int)Math.Sqrt(candidate); + for (int divisor = 3; divisor <= limit; divisor += 2) + { + if ((candidate % divisor) == 0) + return false; + } + return true; + } + return candidate == 2; + } + + public static int GetPrime(int min) + { + if (min < 0) + throw new ArgumentException(SR.Arg_HTCapacityOverflow); + + for (int prime in Primes) + { + if (prime >= min) + return prime; + } + + // Outside of our predefined table. Compute the hard way. + for (int i = (min | 1); i < int.MaxValue; i += 2) + { + if (IsPrime(i) && ((i - 1) % HashPrime != 0)) + return i; + } + return min; + } + + // Returns size of hashtable to grow to. + public static int ExpandPrime(int oldSize) + { + int newSize = 2 * oldSize; + + // Allow the hashtables to grow to maximum possible size (~2G elements) before encountering capacity overflow. + // Note that this check works even when _items.Length overflowed thanks to the (uint) cast + if ((uint)newSize > MaxPrimeArrayLength && MaxPrimeArrayLength > oldSize) + { + // Debug.Assert(MaxPrimeArrayLength == GetPrime(MaxPrimeArrayLength), "Invalid MaxPrimeArrayLength"); // TODO: + return MaxPrimeArrayLength; + } + + return GetPrime(newSize); + } + + /// Returns approximate reciprocal of the divisor: ceil(2**64 / divisor). + /// This should only be used on 64-bit. + public static ulong GetFastModMultiplier(uint divisor) => + ulong.MaxValue / divisor + 1; + + /// Performs a mod operation using the multiplier pre-computed with . + /// This should only be used on 64-bit. + public static inline uint FastMod(uint value, uint divisor, ulong multiplier) + { + // We use modified Daniel Lemire's fastmod algorithm (https://github.com/dotnet/runtime/pull/406), + // which allows to avoid the long multiplication if the divisor is less than 2**31. + // Debug.Assert(divisor <= int.MaxValue); // TODO: + + // This is equivalent of (uint)Math.BigMul(multiplier * value, divisor, out _). This version + // is faster than BigMul currently because we only need the high bits. + uint highbits = (uint)(((((multiplier * value) >> 32) + 1) * divisor) >> 32); + + // Debug.Assert(highbits == value % divisor); // TODO: + return highbits; + } +} \ No newline at end of file diff --git a/std/System/Math.hpt b/std/System/Math.hpt index 3f3856f5..5bfb4995 100644 --- a/std/System/Math.hpt +++ b/std/System/Math.hpt @@ -25,6 +25,11 @@ public static class Math return d; } + public static inline double Sqrt(double d) + { + return Marshal.Sqrt(d); + } + public static inline double CopySign(double x, double y) { return SoftwareFallback(x, y); diff --git a/std/System/Runtime/InteropServices/Marshal.hpt b/std/System/Runtime/InteropServices/Marshal.hpt index 14ee348e..175e4aaf 100644 --- a/std/System/Runtime/InteropServices/Marshal.hpt +++ b/std/System/Runtime/InteropServices/Marshal.hpt @@ -30,6 +30,9 @@ public static class Marshal [LibImport("", "modf")] public static extern inline double ModF(double num, double* i); + [LibImport("", "sqrt")] + public static extern inline double Sqrt(double arg); + [DeclarationUsed] [LibImport("", "fflush")] public static extern inline int Fflush(void* stream); diff --git a/std/System/SR.hpt b/std/System/SR.hpt index f13a1983..308a9f81 100644 --- a/std/System/SR.hpt +++ b/std/System/SR.hpt @@ -55,6 +55,7 @@ internal static class SR internal const string Arg_KeyNotFound = "The given key was not present in the dictionary."; internal const string Arg_KeyNotFoundWithKey = "The given key '{0}' was not present in the dictionary."; internal const string Arg_ArrayTypeMismatchException = "Attempted to access an element as a type incompatible with the array."; + internal const string Arg_HTCapacityOverflow = "Hashtable's capacity overflowed and went negative. Check load factor, capacity and the current size of the table."; // overflow internal const string Overflow_TimeSpanTooLong = "TimeSpan overflowed because the duration is too long."; From 1b26c17ec54ec019650dc6454f979805bee08ccd Mon Sep 17 00:00:00 2001 From: Airat Abdrakov <52558686+CrackAndDie@users.noreply.github.com> Date: Wed, 13 May 2026 15:46:28 +0300 Subject: [PATCH 26/26] Small changes in ThreadStatic --- std/System/Collections/Generic/Dictionary.hpt | 16 +++++++++------- std/System/Threading/Thread.hpt | 2 ++ std/System/Threading/ThreadStatic.hpt | 17 +++++++++++++++-- 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/std/System/Collections/Generic/Dictionary.hpt b/std/System/Collections/Generic/Dictionary.hpt index ee9409ec..7c677781 100644 --- a/std/System/Collections/Generic/Dictionary.hpt +++ b/std/System/Collections/Generic/Dictionary.hpt @@ -176,7 +176,7 @@ public class Dictionary : IDictionary bool ICollection>.Contains(KeyValuePair keyValuePair) { TValue* value = FindValue(keyValuePair.Key); - if (value != null && System.Comparer.Default().Compare(value, keyValuePair.Value) == 0) + if (value != null && System.Comparer.Default().Compare(*value, keyValuePair.Value) == 0) { return true; } @@ -187,7 +187,7 @@ public class Dictionary : IDictionary public bool Remove(KeyValuePair keyValuePair) { TValue* value = FindValue(keyValuePair.Key); - if (value != null && System.Comparer.Default().Compare(value, keyValuePair.Value) == 0) + if (value != null && System.Comparer.Default().Compare(*value, keyValuePair.Value) == 0) { Remove(keyValuePair.Key); return true; @@ -283,6 +283,7 @@ public class Dictionary : IDictionary { if (entries[i].next >= -1) { + array[index++] = new KeyValuePair(entries[i].key, entries[i].value); } } @@ -330,7 +331,8 @@ public class Dictionary : IDictionary } entry = entries[*i]; - if (entry.hashCode == hashCode && System.Comparer.Default().Compare(entry.key, key) == 0) + + if (entry.hashCode == hashCode && System.Comparer.Default().Compare(entry.key, key) == 0) { TValue* value = &entry.value; return value; @@ -434,7 +436,7 @@ public class Dictionary : IDictionary // ValueType: Devirtualize with EqualityComparer.Default intrinsic while ((uint)i < (uint)entries.Length) { - if (entries[i].hashCode == hashCode && System.Comparer.Default().Compare(entries[i].key, key) == 0) + if (entries[i].hashCode == hashCode && System.Comparer.Default().Compare(entries[i].key, key) == 0) { if (behavior == InsertionBehavior.OverwriteExisting) { @@ -578,7 +580,7 @@ public class Dictionary : IDictionary // ValueType: Devirtualize with EqualityComparer.Default intrinsic while ((uint)i < (uint)entries.Length) { - if (entries[i].hashCode == hashCode && System.Comparer.Default().Compare(entries[i].key, key) == 0) + if (entries[i].hashCode == hashCode && System.Comparer.Default().Compare(entries[i].key, key) == 0) { exists = true; @@ -753,7 +755,7 @@ public class Dictionary : IDictionary { DictionaryEntry entry = entries[i]; - if (entry.hashCode == hashCode && System.Comparer.Default().Compare(entry.key, key) == 0) + if (entry.hashCode == hashCode && System.Comparer.Default().Compare(entry.key, key) == 0) { if (last < 0) { @@ -868,7 +870,7 @@ public class Dictionary : IDictionary { DictionaryEntry entry = entries[i]; - if (entry.hashCode == hashCode && System.Comparer.Default().Compare(entry.key, key) == 0) + if (entry.hashCode == hashCode && System.Comparer.Default().Compare(entry.key, key) == 0) { if (last < 0) { diff --git a/std/System/Threading/Thread.hpt b/std/System/Threading/Thread.hpt index 69c8da7b..9d1833cc 100644 --- a/std/System/Threading/Thread.hpt +++ b/std/System/Threading/Thread.hpt @@ -15,6 +15,8 @@ public partial class Thread } } + public static int CurrentThreadId => ThreadPal.GetCurrentThreadId(); + /// Gets or sets a value indicating the scheduling priority of a thread. public ThreadPriority Priority { get; set; } = ThreadPriority.Normal; /// Returns true if the thread is a threadpool thread. diff --git a/std/System/Threading/ThreadStatic.hpt b/std/System/Threading/ThreadStatic.hpt index 165c8f64..592d1caf 100644 --- a/std/System/Threading/ThreadStatic.hpt +++ b/std/System/Threading/ThreadStatic.hpt @@ -2,9 +2,13 @@ // The .NET Foundation licenses this file to you under the MIT license. // Some modifications were made for hapet-lang compatibility. +using System.Collections.Generic; + public class ThreadStatic { + private Dictionary _values = new Dictionary(); private T _defaultValue; + public ThreadStatic(T defaultValue = default) { _defaultValue = defaultValue; @@ -13,8 +17,17 @@ public class ThreadStatic public T Value { get - {} + { + int currThread = Thread.CurrentThreadId; + T val; + if (_values.TryGetValue(Thread.CurrentThreadId, out val)) + return val; + return _defaultValue; + } set - {} + { + int currThread = Thread.CurrentThreadId; + _values[currThread] = value; + } } } \ No newline at end of file