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/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/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/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/BitConverter.hpt b/std/System/BitConverter.hpt index 25f22d31..420a58ce 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,25 @@ 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); + + /// + /// 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/Collections/Generic/Dictionary.hpt b/std/System/Collections/Generic/Dictionary.hpt new file mode 100644 index 00000000..7c677781 --- /dev/null +++ b/std/System/Collections/Generic/Dictionary.hpt @@ -0,0 +1,1218 @@ +// 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 DictionaryEntry[] _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) + { + throw new ArgumentOutOfRangeException(nameof(capacity), SR.ArgumentOutOfRange_NeedNonNegNum); + } + + 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(); + } + 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) + { + throw new ArgumentNullException(nameof(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) + { + throw new ArgumentNullException(nameof(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)_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 (value != null) + { + return *value; + } + + throw new KeyNotFoundException(string.Format(SR.Arg_KeyNotFoundWithKey, key.ToString())); + 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 (value != null && System.Comparer.Default().Compare(*value, keyValuePair.Value) == 0) + { + return true; + } + + return false; + } + + public bool Remove(KeyValuePair keyValuePair) + { + TValue* value = FindValue(keyValuePair.Key); + if (value != null && 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) => FindValue(key) != null; + + public bool ContainsValue(TValue value) + { + DictionaryEntry[] 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; + } + + public 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); + } + } + + public IEnumerator> GetEnumerator() => + Count == 0 ? (IEnumerator>)EmptyEnumerator.Get>() : + new DictionaryEnumerator(this, DictionaryEnumerator.KeyValuePair); + + internal TValue* FindValue(TKey key) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + DictionaryEntry entry = null; + 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); + DictionaryEntry[] entries = _entries; + uint collisionCount = 0; + + // ValueType: Devirtualize with EqualityComparer.Default intrinsic + *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) + { + TValue* value = null; + 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. + throw new InvalidOperationException(SR.InvalidOperation_ConcurrentOperationsNotSupported); + } + else + { + // Debug.Assert(comparer is not null); // TODO: + uint hashCode = (uint)key.GetHashCode(); + int* i = GetBucket(hashCode); + DictionaryEntry[] entries = _entries; + uint collisionCount = 0; + *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) + { + TValue* value = null; + 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. + throw new InvalidOperationException(SR.InvalidOperation_ConcurrentOperationsNotSupported); + } + } + + var value = null; + return value; + } + + private int Initialize(int capacity) + { + int size = HashHelpers.GetPrime(capacity); + int[] buckets = new int[size]; + DictionaryEntry[] entries = new DictionaryEntry[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) + { + throw new ArgumentNullException(nameof(key)); + } + + if (_buckets == null) + { + Initialize(0); + } + // Debug.Assert(_buckets != null); // TODO: + + DictionaryEntry[] entries = _entries; + // Debug.Assert(entries != null, "expected entries to be non-null"); // TODO: + + 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 + + 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 && System.Comparer.Default().Compare(entries[i].key, key) == 0) + { + if (behavior == InsertionBehavior.OverwriteExisting) + { + entries[i].value = value; + return true; + } + + if (behavior == InsertionBehavior.ThrowOnExisting) + { + throw new ArgumentException(string.Format(SR.Argument_AddingDuplicateWithKey, key.ToString())); + } + + 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. + throw new InvalidOperationException(SR.InvalidOperation_ConcurrentOperationsNotSupported); + } + } + } + else + { + // Debug.Assert(comparer is not null); // TODO: + while ((uint)i < (uint)entries.Length) + { + if (entries[i].hashCode == hashCode && comparer.Compare(entries[i].key, key) == 0) + { + if (behavior == InsertionBehavior.OverwriteExisting) + { + entries[i].value = value; + return true; + } + + if (behavior == InsertionBehavior.ThrowOnExisting) + { + throw new ArgumentException(string.Format(SR.Argument_AddingDuplicateWithKey, 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. + throw new InvalidOperationException(SR.InvalidOperation_ConcurrentOperationsNotSupported); + } + } + } + + int index; + if (_freeCount > 0) + { + index = _freeList; + // TODO: + // 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; + } + + DictionaryEntry 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) + { + // 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) + { + throw new ArgumentNullException(nameof(key)); + } + + if (dictionary._buckets == null) + { + dictionary.Initialize(0); + } + // Debug.Assert(dictionary._buckets != null); // TODO: + + 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)(key.GetHashCode()); + + 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 && System.Comparer.Default().Compare(entries[i].key, key) == 0) + { + 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. + throw new InvalidOperationException(SR.InvalidOperation_ConcurrentOperationsNotSupported); + } + } + } + else + { + // Debug.Assert(comparer is not null); // TODO: + while ((uint)i < (uint)entries.Length) + { + if (entries[i].hashCode == hashCode && comparer.Compare(entries[i].key, key) == 0) + { + 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. + throw new InvalidOperationException(SR.InvalidOperation_ConcurrentOperationsNotSupported); + } + } + } + + int index; + if (dictionary._freeCount > 0) + { + index = dictionary._freeList; + // 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--; + } + else + { + int count = dictionary._count; + if (count == entries.Length) + { + dictionary.Resize(); + bucket = dictionary.GetBucket(hashCode); + } + index = count; + dictionary._count = count + 1; + entries = dictionary._entries; + } + + DictionaryEntry 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) + { + // 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"); // TODO: + + 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); + + DictionaryEntry[] entries = new DictionaryEntry[newSize]; + + int count = _count; + Array.Copy(_entries, 0, entries, 0, count); + + if (!typeof(TKey).IsValueType && forceNewHashCodes) + { + // Debug.Assert(_comparer is NonRandomizedStringEqualityComparer); // TODO: + for (int i = 0; i < count; i++) + { + if (entries[i].next >= -1) + { + entries[i].hashCode = (uint)entries[i].key.GetHashCode(); + } + } + } + + // 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) + { + throw new ArgumentNullException(nameof(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)(key.GetHashCode()); + + int* bucket = GetBucket(hashCode); + DictionaryEntry[] 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) + { + DictionaryEntry entry = entries[i]; + + 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 + } + 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; + + entry.key = default; + 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. + throw new InvalidOperationException(SR.InvalidOperation_ConcurrentOperationsNotSupported); + } + } + } + else + { + // Debug.Assert(comparer is not null); // TODO: + while ((uint)i < (uint)entries.Length) + { + DictionaryEntry entry = entries[i]; + + if (entry.hashCode == hashCode && comparer.Compare(entry.key, key) == 0) + { + 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; + + entry.key = default; + 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. + throw new InvalidOperationException(SR.InvalidOperation_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) + { + throw new ArgumentNullException(nameof(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)(key.GetHashCode()); + + int* bucket = GetBucket(hashCode); + DictionaryEntry[] 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) + { + DictionaryEntry entry = entries[i]; + + 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 + } + 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; + + entry.key = default; + 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. + throw new InvalidOperationException(SR.InvalidOperation_ConcurrentOperationsNotSupported); + } + } + } + else + { + // Debug.Assert(comparer is not null); // TODO: + while ((uint)i < (uint)entries.Length) + { + DictionaryEntry entry = entries[i]; + + if (entry.hashCode == hashCode && comparer.Compare(entry.key, key) == 0) + { + 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; + + entry.key = default; + 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. + throw new InvalidOperationException(SR.InvalidOperation_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 (valRef != null) + { + value = *valRef; + return true; + } + + value = default; + return false; + } + + public bool TryAdd(TKey key, TValue value) => + TryInsert(key, value, InsertionBehavior.None); + + bool ICollection>.IsReadOnly => false; + + 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) + { + throw new ArgumentOutOfRangeException(nameof(capacity), SR.ArgumentOutOfRange_NeedNonNegNum); + } + + 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) + { + throw new ArgumentOutOfRangeException(nameof(capacity), SR.ArgumentOutOfRange_NeedNonNegNum); + } + + int newSize = HashHelpers.GetPrime(capacity); + DictionaryEntry[] 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(DictionaryEntry[] entries, int count) + { + // Debug.Assert(_entries is not null); // TODO: + + DictionaryEntry[] newEntries = _entries; + int newCount = 0; + for (int i = 0; i < count; i++) + { + uint hashCode = entries[i].hashCode; + if (entries[i].next >= -1) + { + DictionaryEntry 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) + { + throw new ArgumentNullException(nameof(key)); + } + return key is TKey; + } + + private int* GetBucket(uint hashCode) + { +#if TARGET_64BIT; + return ref buckets[HashHelpers.FastMod(hashCode, (uint)buckets.Length, _fastModMultiplier)]; +#else + return &_buckets[(int)(hashCode % (uint)_buckets.Length)]; +#endif + } + + private class DictionaryEntry + { + 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 : 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) + { + 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) + { + DictionaryEntry 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)) + { + throw new InvalidOperationException(SR.InvalidOperation_EnumOpCantHappen); + } + + return new KeyValuePair(_current.Key, _current.Value); + } + } + + void IEnumerator.Reset() + { + if (_version != _dictionary._version) + { + throw new InvalidOperationException(SR.InvalidOperation_EnumFailedVersion); + } + + _index = 0; + _current = default; + } + + object IDictionaryEnumerator.Key + { + get + { + if (_index == 0 || (_index == _dictionary._count + 1)) + { + throw new InvalidOperationException(SR.InvalidOperation_EnumOpCantHappen); + } + + return _current.Key; + } + } + + object IDictionaryEnumerator.Value + { + get + { + if (_index == 0 || (_index == _dictionary._count + 1)) + { + throw new InvalidOperationException(SR.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/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/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/Collections/Generic/KeyValuePair.hpt b/std/System/Collections/Generic/KeyValuePair.hpt new file mode 100644 index 00000000..35ddbd6d --- /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 struct KeyValuePair +{ + private TKey key; // Do not rename (binary serialization) + private 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/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/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; } 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/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/Double.hpt b/std/System/Double.hpt index 84677763..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) @@ -65,4 +67,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 new file mode 100644 index 00000000..b8456315 --- /dev/null +++ b/std/System/Int128.hpt @@ -0,0 +1,1146 @@ +// 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 signed integer. +public readonly struct Int128 +{ + 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 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 == 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 System.Text.Formatting.Number.Format(this, ""); + } + + public string ToString(string format) + { + return System.Text.Formatting.Number.Format(this, format); + } + + // + // 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 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 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 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. + /// 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 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. + /// 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(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; + + // 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. + // + // 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); + + // TODO: + // 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(uintptr 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(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. + 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. + 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. + public static implicit operator Int128(ulong 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; + } + */ + + // + // 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)) + { + throw new OverflowException(); + } + + // 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); + } + + // + // 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 ulower; + UInt128 upper = UInt128.BigMul((UInt128)(left), (UInt128)(right), out 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; + + /// + public static Int128 Min(Int128 x, Int128 y) => (x <= y) ? 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); + + /// + 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 bool IsEvenInteger(Int128 value) => (value._lower & 1) == 0; + + /// + public static bool IsNegative(Int128 value) => (long)value._upper < 0; + + /// + public static bool IsOddInteger(Int128 value) => (value._lower & 1) != 0; + + /// + public static bool IsPositive(Int128 value) => (long)value._upper >= 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; + } + + /// + 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; + } + + // + // 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); + + // + // 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; +} \ No newline at end of file diff --git a/std/System/Math.hpt b/std/System/Math.hpt index 9bbb9c23..5bfb4995 100644 --- a/std/System/Math.hpt +++ b/std/System/Math.hpt @@ -6,14 +6,188 @@ using System.Runtime.InteropServices; public static class Math { - public static int Min(int val1, int val2) + 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; } - public static double Truncate(double d) + public static inline double Truncate(double d) { Marshal.ModF(d, &d); 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); + + 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. + /// 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. + /// 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 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. + /// 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); + } + + /// 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); + } + + 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/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/Runtime/InteropServices/Marshal.hpt b/std/System/Runtime/InteropServices/Marshal.hpt index 7b580eeb..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); @@ -43,4 +46,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/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 3b5ead53..308a9f81 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."; @@ -31,18 +34,33 @@ 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)."; 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."; + 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 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."; + 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."; + 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."; @@ -50,10 +68,19 @@ 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."; 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."; + 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/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/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/Threading/Thread.hpt b/std/System/Threading/Thread.hpt new file mode 100644 index 00000000..9d1833cc --- /dev/null +++ b/std/System/Threading/Thread.hpt @@ -0,0 +1,136 @@ +// 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 +{ + private static ThreadStatic t_currentThread = new ThreadStatic(); + public static Thread CurrentThread + { + get + { + return t_currentThread.Value ?? InitializeCurrentThread(); + } + } + + 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. + 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 + /// general then IsAlive or IsBackground. + /// + 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, Thread thread) + { + _start = start; + _thread = thread; + } + + // 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() + { + // set that the thread is currently running + _thread.ThreadState = System.Threading.ThreadState.Running; + + object start = _start; + _start = default; // anime? + + try + { + if (start is ThreadStart threadStart) + { + threadStart(); + } + else + { + // TODO: parametrized shite + } + } + catch (Exception ex) + { + + } + finally + { + // set that the thread is stopped + _thread.ThreadState = System.Threading.ThreadState.Stopped; + } + } + } + + public Thread(ThreadStart start) + { + ArgumentNullException.ThrowIfNull(start); + + _startHelper = new StartHelper(start, this); + } + + 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; + } + + // if already running - throw exception + if (ThreadState == System.Threading.ThreadState.Running) + { + throw new ThreadStateException(SR.ThreadState_AlreadyStarted); + } + + ThreadPal.StartThread(this); + } + + public bool Join(int millisecondsTimeout) + { + ArgumentOutOfRangeException.ThrowIfLessThan(millisecondsTimeout, Timeout.Infinite); + if ((int)(ThreadState & System.Threading.ThreadState.Unstarted) != 0) + { + throw new ThreadStateException(SR.ThreadState_NotStarted); + } + 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 new file mode 100644 index 00000000..136f2a31 --- /dev/null +++ b/std/System/Threading/ThreadPal.Windows.hpt @@ -0,0 +1,132 @@ +// 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.Runtime.InteropServices; + +#if TARGET_PLATFORM == "win-x64" || TARGET_PLATFORM == "win-x86"; +internal static class ThreadPal +{ + [LibImport("", "CreateThread")] + internal static extern inline void* CreateThread(void* attrs, uintptr stackSize, void* func, void* funcParam, uint creationFlags, uint* threadId); + [LibImport("", "ResumeThread")] + internal static extern inline int ResumeThread(void* handle); + [LibImport("", "SetThreadPriority")] + internal static extern inline int SetThreadPriority(void* handle, int priority); + // fuck win < 10 + [LibImport("", "SetThreadDescription")] + 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) + { + 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; + } + + 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/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 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 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 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 diff --git a/std/System/Threading/ThreadStatic.hpt b/std/System/Threading/ThreadStatic.hpt new file mode 100644 index 00000000..592d1caf --- /dev/null +++ b/std/System/Threading/ThreadStatic.hpt @@ -0,0 +1,33 @@ +// 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.Collections.Generic; + +public class ThreadStatic +{ + private Dictionary _values = new Dictionary(); + private T _defaultValue; + + public ThreadStatic(T defaultValue = default) + { + _defaultValue = defaultValue; + } + + 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 diff --git a/std/System/Threading/Timeout.hpt b/std/System/Threading/Timeout.hpt new file mode 100644 index 00000000..7d26ef2e --- /dev/null +++ b/std/System/Threading/Timeout.hpt @@ -0,0 +1,16 @@ +// 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; + +// 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..76d2c29a --- /dev/null +++ b/std/System/TimeSpan.hpt @@ -0,0 +1,798 @@ +// 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; + +// 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 +{ + /// + /// 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)) + { + throw new ArgumentOutOfRangeException(null, SR.Overflow_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 == 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) + { + throw new OverflowException(SR.Overflow_Duration); + } + return new TimeSpan(_ticks >= 0 ? _ticks : -_ticks); + } + + public override bool Equals(object value) + { + if (value is TimeSpan other) + return Equals(other); + return false; + } + + public bool Equals(TimeSpan obj) => Equals(this, obj); + + public static bool Equals(TimeSpan t1, TimeSpan t2) => t1 == t2; + + public override int GetHashCode() => _ticks.GetHashCode(); + + private static inline TimeSpan FromUnits(long units, long ticksPerUnit, long minUnits, long maxUnits) + { + // TODO: + // System.Diagnostics.Debug.Assert(minUnits < 0); + // System.Diagnostics.Debug.Assert(maxUnits > 0); + + if (units > maxUnits || units < minUnits) + { + throw new ArgumentOutOfRangeException(null, SR.Overflow_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((Int128)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); + } + + private static inline TimeSpan FromMicroseconds(Int128 microseconds) + { + if ((microseconds > MaxMicroseconds) || (microseconds < MinMicroseconds)) + { + throw new ArgumentOutOfRangeException(null, SR.Overflow_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)) + { + throw new ArgumentException(SR.Arg_CannotBeNaN); + } + return IntervalFromDoubleTicks(value * scale); + } + + private static TimeSpan IntervalFromDoubleTicks(double ticks) + { + if ((ticks > MaxTicks) || (ticks < MinTicks) || double.IsNaN(ticks)) + { + throw new ArgumentOutOfRangeException(null, SR.Overflow_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); + + 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. + long totalSeconds = (hour * SecondsPerHour) + + (minute * SecondsPerMinute) + + second; + + if ((totalSeconds > MaxSeconds) || (totalSeconds < MinSeconds)) + { + throw new ArgumentOutOfRangeException(null, SR.Overflow_TimeSpanTooLong); + } + return totalSeconds * TicksPerSecond; + } + + public static TimeSpan operator -(TimeSpan t) + { + if (t._ticks == MinTicks) + { + throw new OverflowException(SR.Overflow_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). + throw new OverflowException(SR.Overflow_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). + throw new OverflowException(SR.Overflow_TimeSpanTooLong); + } + return new TimeSpan(result); + } + + /// + public static TimeSpan operator *(TimeSpan timeSpan, double factor) + { + if (double.IsNaN(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 + // 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)) + { + throw new ArgumentException(SR.Arg_CannotBeNaN, nameof(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) => (double)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 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) { diff --git a/std/System/UInt128.hpt b/std/System/UInt128.hpt new file mode 100644 index 00000000..972a9cc0 --- /dev/null +++ b/std/System/UInt128.hpt @@ -0,0 +1,1189 @@ +// 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 System.Text.Formatting.Number.Format(this, ""); + } + + public string ToString(string format) + { + return System.Text.Formatting.Number.Format(this, format); + } + + // + // 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) ? 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 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); + } + + // + // 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) + { + throw new DivideByZeroException(); + } + + 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(uint* left, int leftLength, uint* right, int rightLength) + { + // 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 < rightLength; i++) + { + ulong digit = (left[i] + carry) + right[i]; + + left[i] = 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; + + *(pLeft + 0) = (uint)(quotient._lower >> 00); + *(pLeft + 1) = (uint)(quotient._lower >> 32); + *(pLeft + 2) = (uint)(quotient._upper >> 00); + *(pLeft + 3) = (uint)(quotient._upper >> 32); + + var leftLength = (Size / sizeof(uint)) - (LeadingZeroCountAsInt32(quotient) / 32); + + // Repeat the same operation with the divisor + + uint* pRight = (stackalloc uint[Size / sizeof(uint)]).Buffer; + + *(pRight + 0) = (uint)(divisor._lower >> 00); + *(pRight + 1) = (uint)(divisor._lower >> 32); + *(pRight + 2) = (uint)(divisor._upper >> 00); + *(pRight + 3) = (uint)(divisor._upper >> 32); + + var rightLength = (Size / sizeof(uint)) - (LeadingZeroCountAsInt32(divisor) / 32); + + uint* rawBits = (stackalloc uint[Size / sizeof(uint)]).Buffer; + var bitsLength = leftLength - rightLength + 1; + + // 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 = pRight[rightLength - 1]; + uint divLo = rightLength > 1 ? pRight[rightLength - 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 = 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 = leftLength; i >= rightLength; i--) + { + int n = i - rightLength; + uint t = ((uint)(i) < (uint)(leftLength)) ? pLeft[i] : 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 ? pLeft[i - 3] : 0; + + valHi = (valHi << (uint)shift) | (valLo >> (uint)backShift); + valLo = (valLo << (uint)shift) | (valNx >> (uint)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(pLeft + n, leftLength - n, pRight, rightLength, digit); + + if (carry != t) + { + // TODO: + // Debug.Assert(carry == (t + 1)); + + // Our guess was still exactly one too high + carry = AddDivisor(pLeft + n, leftLength - n, pRight, rightLength); + + --digit; + // TODO: + // Debug.Assert(carry == 1); + } + } + + // We have the digit! + if ((uint)(n) < (uint)(bitsLength)) + { + rawBits[n] = (uint)(digit); + } + + if ((uint)(i) < (uint)(leftLength)) + { + pLeft[i] = 0; + } + } + + return new UInt128( + ((ulong)(rawBits[3]) << 32) | rawBits[2], + ((ulong)(rawBits[1]) << 32) | rawBits[0] + ); + } + + static uint SubtractDivisor(uint* left, int leftLength, uint* right, int rightLength, ulong q) + { + // 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 < rightLength; i++) + { + carry += right[i] * q; + + uint digit = (uint)(carry); + carry = carry >> 32; + + if (left[i] < digit) + { + ++carry; + } + left[i] = left[i] - 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 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); + } + + /// + /* 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 << (ulong)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 << (ulong)shiftAmount; + ulong upper = (value._upper << (ulong)shiftAmount) | (value._lower >> (ulong)(64 - shiftAmount)); + + return new UInt128(upper, lower); + } + else + { + return value; + } + } + + /// + public static UInt128 operator >>(UInt128 value, int shiftAmount) => value >>> shiftAmount; + + /// + 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 >> (ulong)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 >> (ulong)shiftAmount) | (value._upper << (ulong)(64 - shiftAmount)); + ulong upper = value._upper >> (ulong)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); + */ +}