diff options
author | Michal Strehovský <MichalStrehovsky@users.noreply.github.com> | 2018-07-31 19:04:02 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-07-31 19:04:02 +0300 |
commit | ebb3632553a4c8f1303c92fc03ce19b4879bb708 (patch) | |
tree | d1cc89bc265f5b18d9b012027332d1c319e1ce1a /src | |
parent | 7310d4bcf386371660c4bd99d9807fa296ec960d (diff) | |
parent | b7ea3897c635e746573a6ff50bf2f2a54be16126 (diff) |
Merge pull request #6158 from dotnet/master
Merge master to nmirror
Diffstat (limited to 'src')
23 files changed, 467 insertions, 143 deletions
diff --git a/src/Common/src/Internal/Runtime/EEType.cs b/src/Common/src/Internal/Runtime/EEType.cs index 4ad2aeab6..66d952d2f 100644 --- a/src/Common/src/Internal/Runtime/EEType.cs +++ b/src/Common/src/Internal/Runtime/EEType.cs @@ -1035,7 +1035,7 @@ namespace Internal.Runtime fixed (EEType* pThis = &this) { - if (IsDynamicType) + if (IsDynamicType || !SupportsRelativePointers) { uint cbSealedVirtualSlotsTypeOffset = GetFieldOffset(EETypeField.ETF_SealedVirtualSlots); IntPtr* pSealedVirtualsSlotTable = *(IntPtr**)((byte*)pThis + cbSealedVirtualSlotsTypeOffset); @@ -1318,7 +1318,7 @@ namespace Internal.Runtime // in the case of sealed vtable entries on static types, we have a UInt sized relative pointer if ((rareFlags & EETypeRareFlags.HasSealedVTableEntriesFlag) != 0) - cbOffset += (IsDynamicType ? (uint)IntPtr.Size : 4); + cbOffset += (IsDynamicType || !SupportsRelativePointers ? (uint)IntPtr.Size : 4); if (eField == EETypeField.ETF_DynamicDispatchMap) { diff --git a/src/ILCompiler.Compiler/src/Compiler/Compilation.cs b/src/ILCompiler.Compiler/src/Compiler/Compilation.cs index 6a307aac4..defedd0b7 100644 --- a/src/ILCompiler.Compiler/src/Compiler/Compilation.cs +++ b/src/ILCompiler.Compiler/src/Compiler/Compilation.cs @@ -419,7 +419,11 @@ namespace ILCompiler public void RootModuleMetadata(ModuleDesc module, string reason) { - _graph.AddRoot(_factory.ModuleMetadata(module), reason); + // RootModuleMetadata is kind of a hack - this is pretty much only used to force include + // type forwarders from assemblies metadata generator would normally not look at. + // This will go away when the temporary RD.XML parser goes away. + if (_factory.MetadataManager is UsageBasedMetadataManager) + _graph.AddRoot(_factory.ModuleMetadata(module), reason); } public void RootReadOnlyDataBlob(byte[] data, int alignment, string reason, string exportName) diff --git a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/EETypeNode.cs b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/EETypeNode.cs index 1d2c7832d..6299c3fff 100644 --- a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/EETypeNode.cs +++ b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/EETypeNode.cs @@ -774,7 +774,7 @@ namespace ILCompiler.DependencyAnalysis // Final NewSlot methods cannot be overridden, and therefore can be placed in the sealed-vtable to reduce the size of the vtable // of this type and any type that inherits from it. - if (declMethod.CanMethodBeInSealedVTable() && !declType.IsArrayTypeWithoutGenericInterfaces() && !factory.IsCppCodegenTemporaryWorkaround) + if (declMethod.CanMethodBeInSealedVTable() && !declType.IsArrayTypeWithoutGenericInterfaces()) continue; if (!implMethod.IsAbstract) @@ -838,7 +838,12 @@ namespace ILCompiler.DependencyAnalysis SealedVTableNode sealedVTable = factory.SealedVTable(_type.ConvertToCanonForm(CanonicalFormKind.Specific)); if (sealedVTable.BuildSealedVTableSlots(factory, relocsOnly) && sealedVTable.NumSealedVTableEntries > 0) - objData.EmitReloc(sealedVTable, RelocType.IMAGE_REL_BASED_RELPTR32); + { + if (factory.Target.SupportsRelativePointers) + objData.EmitReloc(sealedVTable, RelocType.IMAGE_REL_BASED_RELPTR32); + else + objData.EmitPointerReloc(sealedVTable); + } } } diff --git a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/NodeFactory.cs b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/NodeFactory.cs index d6ecd195c..26b0c703d 100644 --- a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/NodeFactory.cs +++ b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/NodeFactory.cs @@ -912,6 +912,9 @@ namespace ILCompiler.DependencyAnalysis internal TypeMetadataNode TypeMetadata(MetadataType type) { + // These are only meaningful for UsageBasedMetadataManager. We should not have them + // in the dependency graph otherwise. + Debug.Assert(MetadataManager is UsageBasedMetadataManager); return _typesWithMetadata.GetOrAdd(type); } @@ -919,6 +922,9 @@ namespace ILCompiler.DependencyAnalysis internal MethodMetadataNode MethodMetadata(MethodDesc method) { + // These are only meaningful for UsageBasedMetadataManager. We should not have them + // in the dependency graph otherwise. + Debug.Assert(MetadataManager is UsageBasedMetadataManager); return _methodsWithMetadata.GetOrAdd(method); } @@ -926,6 +932,9 @@ namespace ILCompiler.DependencyAnalysis internal FieldMetadataNode FieldMetadata(FieldDesc field) { + // These are only meaningful for UsageBasedMetadataManager. We should not have them + // in the dependency graph otherwise. + Debug.Assert(MetadataManager is UsageBasedMetadataManager); return _fieldsWithMetadata.GetOrAdd(field); } @@ -933,6 +942,9 @@ namespace ILCompiler.DependencyAnalysis internal ModuleMetadataNode ModuleMetadata(ModuleDesc module) { + // These are only meaningful for UsageBasedMetadataManager. We should not have them + // in the dependency graph otherwise. + Debug.Assert(MetadataManager is UsageBasedMetadataManager); return _modulesWithMetadata.GetOrAdd(module); } diff --git a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/SealedVTableNode.cs b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/SealedVTableNode.cs index 942e14dc4..dac6d8dbc 100644 --- a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/SealedVTableNode.cs +++ b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/SealedVTableNode.cs @@ -90,10 +90,6 @@ namespace ILCompiler.DependencyAnalysis _sealedVTableEntries = new List<MethodDesc>(); - // Cpp codegen does not support sealed vtables - if (factory.IsCppCodegenTemporaryWorkaround) - return true; - IReadOnlyList<MethodDesc> virtualSlots = factory.VTable(declType).Slots; for (int i = 0; i < virtualSlots.Count; i++) @@ -153,7 +149,12 @@ namespace ILCompiler.DependencyAnalysis for (int i = 0; i < _sealedVTableEntries.Count; i++) { MethodDesc canonImplMethod = _sealedVTableEntries[i].GetCanonMethodTarget(CanonicalFormKind.Specific); - objData.EmitReloc(factory.MethodEntrypoint(canonImplMethod, _sealedVTableEntries[i].OwningType.IsValueType), RelocType.IMAGE_REL_BASED_RELPTR32); + IMethodNode relocTarget = factory.MethodEntrypoint(canonImplMethod, _sealedVTableEntries[i].OwningType.IsValueType); + + if (factory.Target.SupportsRelativePointers) + objData.EmitReloc(relocTarget, RelocType.IMAGE_REL_BASED_RELPTR32); + else + objData.EmitPointerReloc(relocTarget); } } diff --git a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/TypeMetadataNode.cs b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/TypeMetadataNode.cs index a8416afad..39ad01e9f 100644 --- a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/TypeMetadataNode.cs +++ b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/TypeMetadataNode.cs @@ -51,6 +51,23 @@ namespace ILCompiler.DependencyAnalysis dependencies.Add(factory.MethodMetadata(_type.GetMethod("Invoke", null)), "Delegate invoke method metadata"); } + // If the user asked for complete metadata to be generated for all types that are getting metadata, ensure that. + var mdManager = (UsageBasedMetadataManager)factory.MetadataManager; + if ((mdManager._generationOptions & UsageBasedMetadataGenerationOptions.CompleteTypesOnly) != 0) + { + foreach (MethodDesc method in _type.GetMethods()) + { + if (!mdManager.IsReflectionBlocked(method)) + dependencies.Add(factory.MethodMetadata(method), "Complete metadata for type"); + } + + foreach (FieldDesc field in _type.GetFields()) + { + if (!mdManager.IsReflectionBlocked(field)) + dependencies.Add(factory.FieldMetadata(field), "Complete metadata for type"); + } + } + return dependencies; } diff --git a/src/ILCompiler.Compiler/src/Compiler/UsageBasedMetadataManager.cs b/src/ILCompiler.Compiler/src/Compiler/UsageBasedMetadataManager.cs index 3d13aa547..c6a71b2b4 100644 --- a/src/ILCompiler.Compiler/src/Compiler/UsageBasedMetadataManager.cs +++ b/src/ILCompiler.Compiler/src/Compiler/UsageBasedMetadataManager.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; using Internal.TypeSystem; @@ -23,6 +24,7 @@ namespace ILCompiler { private readonly CompilationModuleGroup _compilationModuleGroup; + internal readonly UsageBasedMetadataGenerationOptions _generationOptions; private readonly bool _hasPreciseFieldUsageInformation; private readonly List<ModuleDesc> _modulesWithMetadata = new List<ModuleDesc>(); @@ -36,12 +38,14 @@ namespace ILCompiler MetadataBlockingPolicy blockingPolicy, ManifestResourceBlockingPolicy resourceBlockingPolicy, string logFile, - StackTraceEmissionPolicy stackTracePolicy) + StackTraceEmissionPolicy stackTracePolicy, + UsageBasedMetadataGenerationOptions generationOptions) : base(typeSystemContext, blockingPolicy, resourceBlockingPolicy, logFile, stackTracePolicy) { // We use this to mark places that would behave differently if we tracked exact fields used. _hasPreciseFieldUsageInformation = false; _compilationModuleGroup = group; + _generationOptions = generationOptions; } protected override void Graph_NewMarkedNode(DependencyNodeCore<NodeFactory> obj) @@ -439,4 +443,21 @@ namespace ILCompiler } } } + + [Flags] + public enum UsageBasedMetadataGenerationOptions + { + None = 0, + + /// <summary> + /// Specifies that complete metadata should be generated for types. + /// </summary> + /// <remarks> + /// If this option is set, generated metadata will no longer be pay for play, + /// and a certain class of bugs will disappear (APIs returning "member doesn't + /// exist" at runtime, even though the member exists and we just didn't generate the metadata). + /// Reflection blocking still applies. + /// </remarks> + CompleteTypesOnly = 1, + } } diff --git a/src/ILCompiler.Compiler/src/Compiler/VirtualMethodCallHelper.cs b/src/ILCompiler.Compiler/src/Compiler/VirtualMethodCallHelper.cs index 8266ed8df..19da1f6ae 100644 --- a/src/ILCompiler.Compiler/src/Compiler/VirtualMethodCallHelper.cs +++ b/src/ILCompiler.Compiler/src/Compiler/VirtualMethodCallHelper.cs @@ -18,9 +18,7 @@ namespace ILCompiler /// </summary> public static int GetVirtualMethodSlot(NodeFactory factory, MethodDesc method, TypeDesc implType, bool countDictionarySlots = true) { - // CppCodegen does not yet support sealed vtables. - - if (method.CanMethodBeInSealedVTable() && !factory.IsCppCodegenTemporaryWorkaround) + if (method.CanMethodBeInSealedVTable()) { // If the method is a sealed newslot method, it will be put in the sealed vtable instead of the type's vtable. In this // case, the slot index return should be the index in the sealed vtable, plus the total number of vtable slots. @@ -71,7 +69,7 @@ namespace ILCompiler int numSealedVTableEntries = 0; for (int slot = 0; slot < virtualSlots.Count; slot++) { - if (virtualSlots[slot].CanMethodBeInSealedVTable() && !factory.IsCppCodegenTemporaryWorkaround) + if (virtualSlots[slot].CanMethodBeInSealedVTable()) { numSealedVTableEntries++; continue; @@ -133,7 +131,7 @@ namespace ILCompiler foreach (var vtableMethod in baseVirtualSlots) { // Methods in the sealed vtable should be excluded from the count - if (vtableMethod.CanMethodBeInSealedVTable() && !factory.IsCppCodegenTemporaryWorkaround) + if (vtableMethod.CanMethodBeInSealedVTable()) continue; baseSlots++; } diff --git a/src/ILCompiler.CppCodeGen/src/CppCodeGen/CppWriter.cs b/src/ILCompiler.CppCodeGen/src/CppCodeGen/CppWriter.cs index 7472c0893..c813492bf 100644 --- a/src/ILCompiler.CppCodeGen/src/CppCodeGen/CppWriter.cs +++ b/src/ILCompiler.CppCodeGen/src/CppCodeGen/CppWriter.cs @@ -815,7 +815,7 @@ namespace ILCompiler.CppCodeGen string mangledName = ((ISymbolNode)node).GetMangledName(factory.NameMangler); // Rename generic composition and optional fields nodes to avoid name clash with types - bool shouldReplaceNamespaceQualifier = node is GenericCompositionNode || node is EETypeOptionalFieldsNode; + bool shouldReplaceNamespaceQualifier = node is GenericCompositionNode || node is EETypeOptionalFieldsNode || node is SealedVTableNode; nodeCode.Append(shouldReplaceNamespaceQualifier ? mangledName.Replace("::", "_") : mangledName); } nodeCode.Append("()"); @@ -903,10 +903,15 @@ namespace ILCompiler.CppCodeGen relocCode.Append("()"); } // Node is either an non-emitted type or a generic composition - both are ignored for CPP codegen - else if ((reloc.Target is TypeManagerIndirectionNode || reloc.Target is InterfaceDispatchMapNode || reloc.Target is EETypeOptionalFieldsNode || reloc.Target is GenericCompositionNode) && !(reloc.Target as ObjectNode).ShouldSkipEmittingObjectNode(factory)) + else if ((reloc.Target is TypeManagerIndirectionNode || + reloc.Target is InterfaceDispatchMapNode || + reloc.Target is EETypeOptionalFieldsNode || + reloc.Target is GenericCompositionNode || + reloc.Target is SealedVTableNode + ) && !(reloc.Target as ObjectNode).ShouldSkipEmittingObjectNode(factory)) { string mangledTargetName = reloc.Target.GetMangledName(factory.NameMangler); - bool shouldReplaceNamespaceQualifier = reloc.Target is GenericCompositionNode || reloc.Target is EETypeOptionalFieldsNode; + bool shouldReplaceNamespaceQualifier = reloc.Target is GenericCompositionNode || reloc.Target is EETypeOptionalFieldsNode || reloc.Target is SealedVTableNode; relocCode.Append(shouldReplaceNamespaceQualifier ? mangledTargetName.Replace("::", "_") : mangledTargetName); relocCode.Append("()"); } @@ -1037,7 +1042,11 @@ namespace ILCompiler.CppCodeGen { if (node is EETypeNode) OutputTypeNode(node as EETypeNode, factory, typeDefinitions, methodTables); - else if ((node is EETypeOptionalFieldsNode || node is TypeManagerIndirectionNode || node is GenericCompositionNode || node is BlobNode) && !(node as ObjectNode).ShouldSkipEmittingObjectNode(factory)) + else if ((node is EETypeOptionalFieldsNode || + node is TypeManagerIndirectionNode || + node is GenericCompositionNode || + node is BlobNode || + node is SealedVTableNode) && !(node as ObjectNode).ShouldSkipEmittingObjectNode(factory)) additionalNodes.Append(GetCodeForObjectNode(node as ObjectNode, factory)); else if (node is ArrayOfEmbeddedPointersNode<InterfaceDispatchMapNode> dispatchMap) { diff --git a/src/ILCompiler/src/Program.cs b/src/ILCompiler/src/Program.cs index f31b485f4..90e5a4572 100644 --- a/src/ILCompiler/src/Program.cs +++ b/src/ILCompiler/src/Program.cs @@ -50,6 +50,7 @@ namespace ILCompiler private string _mapFileName; private string _metadataLogFileName; private bool _noMetadataBlocking; + private bool _completeTypesMetadata; private string _singleMethodTypeName; private string _singleMethodName; @@ -159,6 +160,7 @@ namespace ILCompiler syntax.DefineOption("map", ref _mapFileName, "Generate a map file"); syntax.DefineOption("metadatalog", ref _metadataLogFileName, "Generate a metadata log file"); syntax.DefineOption("nometadatablocking", ref _noMetadataBlocking, "Ignore metadata blocking for internal implementation details"); + syntax.DefineOption("completetypemetadata", ref _completeTypesMetadata, "Generate complete metadata for types"); syntax.DefineOption("scan", ref _useScanner, "Use IL scanner to generate optimized code (implied by -O)"); syntax.DefineOption("noscan", ref _noScanner, "Do not use IL scanner to generate optimized code"); syntax.DefineOption("ildump", ref _ilDump, "Dump IL assembly listing for compiler-generated IL"); @@ -418,13 +420,19 @@ namespace ILCompiler ManifestResourceBlockingPolicy resBlockingPolicy = new NoManifestResourceBlockingPolicy(); + UsageBasedMetadataGenerationOptions metadataGenerationOptions = UsageBasedMetadataGenerationOptions.None; + if (_completeTypesMetadata) + metadataGenerationOptions |= UsageBasedMetadataGenerationOptions.CompleteTypesOnly; + UsageBasedMetadataManager metadataManager = new UsageBasedMetadataManager( compilationGroup, typeSystemContext, mdBlockingPolicy, resBlockingPolicy, _metadataLogFileName, - stackTracePolicy); + stackTracePolicy, + metadataGenerationOptions + ); // Unless explicitly opted in at the command line, we enable scanner for retail builds by default. // We don't do this for CppCodegen and Wasm, because those codegens are behind. diff --git a/src/System.Private.CoreLib/shared/System/Buffers/Text/FormattingHelpers.CountDigits.cs b/src/System.Private.CoreLib/shared/System/Buffers/Text/FormattingHelpers.CountDigits.cs index 709ac4fba..b6140adba 100644 --- a/src/System.Private.CoreLib/shared/System/Buffers/Text/FormattingHelpers.CountDigits.cs +++ b/src/System.Private.CoreLib/shared/System/Buffers/Text/FormattingHelpers.CountDigits.cs @@ -128,5 +128,34 @@ namespace System.Buffers.Text return digits; } + + + // Counts the number of trailing '0' digits in a decimal number. + // e.g., value = 0 => retVal = 0, valueWithoutTrailingZeros = 0 + // value = 1234 => retVal = 0, valueWithoutTrailingZeros = 1234 + // value = 320900 => retVal = 2, valueWithoutTrailingZeros = 3209 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int CountDecimalTrailingZeros(uint value, out uint valueWithoutTrailingZeros) + { + int zeroCount = 0; + + if (value != 0) + { + while (true) + { + uint temp = value / 10; + if (value != (temp * 10)) + { + break; + } + + value = temp; + zeroCount++; + } + } + + valueWithoutTrailingZeros = value; + return zeroCount; + } } } diff --git a/src/System.Private.CoreLib/shared/System/Collections/Generic/Dictionary.cs b/src/System.Private.CoreLib/shared/System/Collections/Generic/Dictionary.cs index 8f7ad70e2..69363d6c0 100644 --- a/src/System.Private.CoreLib/shared/System/Collections/Generic/Dictionary.cs +++ b/src/System.Private.CoreLib/shared/System/Collections/Generic/Dictionary.cs @@ -970,6 +970,7 @@ namespace System.Collections.Generic int currentCapacity = _entries == null ? 0 : _entries.Length; if (currentCapacity >= capacity) return currentCapacity; + _version++; if (_buckets == null) return Initialize(capacity); int newSize = HashHelpers.GetPrime(capacity); @@ -1009,6 +1010,7 @@ namespace System.Collections.Generic return; int oldCount = _count; + _version++; Initialize(newSize); Entry[] entries = _entries; int[] buckets = _buckets; diff --git a/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/EventSource.cs b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/EventSource.cs index dd2543163..55b19058d 100644 --- a/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/EventSource.cs +++ b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/EventSource.cs @@ -4049,7 +4049,12 @@ namespace System.Diagnostics.Tracing if (!s_EventSourceShutdownRegistered) { s_EventSourceShutdownRegistered = true; +#if ES_BUILD_PN AppContext.ProcessExit += DisposeOnShutdown; +#else + AppDomain.CurrentDomain.ProcessExit += DisposeOnShutdown; + AppDomain.CurrentDomain.DomainUnload += DisposeOnShutdown; +#endif } diff --git a/src/System.Private.CoreLib/shared/System/Double.cs b/src/System.Private.CoreLib/shared/System/Double.cs index 181086477..d85b4bca9 100644 --- a/src/System.Private.CoreLib/shared/System/Double.cs +++ b/src/System.Private.CoreLib/shared/System/Double.cs @@ -24,7 +24,7 @@ namespace System [Serializable] [StructLayout(LayoutKind.Sequential)] [TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")] - public struct Double : IComparable, IConvertible, IFormattable, IComparable<double>, IEquatable<double>, ISpanFormattable + public readonly struct Double : IComparable, IConvertible, IFormattable, IComparable<double>, IEquatable<double>, ISpanFormattable { private readonly double m_value; // Do not rename (binary serialization) diff --git a/src/System.Private.CoreLib/shared/System/Globalization/CultureData.Windows.cs b/src/System.Private.CoreLib/shared/System/Globalization/CultureData.Windows.cs index 393f983bb..75f78620d 100644 --- a/src/System.Private.CoreLib/shared/System/Globalization/CultureData.Windows.cs +++ b/src/System.Private.CoreLib/shared/System/Globalization/CultureData.Windows.cs @@ -517,7 +517,7 @@ namespace System.Globalization // Context for EnumCalendarInfoExEx callback. - private class EnumLocaleData + private struct EnumLocaleData { public string regionName; public string cultureName; diff --git a/src/System.Private.CoreLib/shared/System/Globalization/DateTimeFormatInfo.cs b/src/System.Private.CoreLib/shared/System/Globalization/DateTimeFormatInfo.cs index 1b47c372c..8fc6f7beb 100644 --- a/src/System.Private.CoreLib/shared/System/Globalization/DateTimeFormatInfo.cs +++ b/src/System.Private.CoreLib/shared/System/Globalization/DateTimeFormatInfo.cs @@ -2010,6 +2010,25 @@ namespace System.Globalization } // + // Decimal separator used by positive TimeSpan pattern + // + private string _decimalSeparator; + internal string DecimalSeparator + { + get + { + if (_decimalSeparator == null) + { + CultureData cultureDataWithoutUserOverrides = _cultureData.UseUserOverride ? + CultureData.GetCultureData(_cultureData.CultureName, false) : + _cultureData; + _decimalSeparator = new NumberFormatInfo(cultureDataWithoutUserOverrides).NumberDecimalSeparator; + } + return _decimalSeparator; + } + } + + // // Positive TimeSpan Pattern // private string _fullTimeSpanPositivePattern; @@ -2019,14 +2038,7 @@ namespace System.Globalization { if (_fullTimeSpanPositivePattern == null) { - CultureData cultureDataWithoutUserOverrides; - if (_cultureData.UseUserOverride) - cultureDataWithoutUserOverrides = CultureData.GetCultureData(_cultureData.CultureName, false); - else - cultureDataWithoutUserOverrides = _cultureData; - string decimalSeparator = new NumberFormatInfo(cultureDataWithoutUserOverrides).NumberDecimalSeparator; - - _fullTimeSpanPositivePattern = "d':'h':'mm':'ss'" + decimalSeparator + "'FFFFFFF"; + _fullTimeSpanPositivePattern = "d':'h':'mm':'ss'" + DecimalSeparator + "'FFFFFFF"; } return _fullTimeSpanPositivePattern; } diff --git a/src/System.Private.CoreLib/shared/System/Globalization/TimeSpanFormat.cs b/src/System.Private.CoreLib/shared/System/Globalization/TimeSpanFormat.cs index a66e4600a..5e553d56b 100644 --- a/src/System.Private.CoreLib/shared/System/Globalization/TimeSpanFormat.cs +++ b/src/System.Private.CoreLib/shared/System/Globalization/TimeSpanFormat.cs @@ -2,9 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System.Text; +using System.Buffers.Text; using System.Diagnostics; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Text; namespace System.Globalization { @@ -35,159 +37,300 @@ namespace System.Globalization internal static readonly FormatLiterals PositiveInvariantFormatLiterals = TimeSpanFormat.FormatLiterals.InitInvariant(isNegative: false); internal static readonly FormatLiterals NegativeInvariantFormatLiterals = TimeSpanFormat.FormatLiterals.InitInvariant(isNegative: true); - internal enum Pattern - { - None = 0, - Minimum = 1, - Full = 2, - } /// <summary>Main method called from TimeSpan.ToString.</summary> - internal static string Format(TimeSpan value, string format, IFormatProvider formatProvider) => - StringBuilderCache.GetStringAndRelease(FormatToBuilder(value, format, formatProvider)); - - /// <summary>Main method called from TimeSpan.TryFormat.</summary> - internal static bool TryFormat(TimeSpan value, Span<char> destination, out int charsWritten, ReadOnlySpan<char> format, IFormatProvider formatProvider) + internal static string Format(TimeSpan value, string format, IFormatProvider formatProvider) { - StringBuilder sb = FormatToBuilder(value, format, formatProvider); - if (sb.Length <= destination.Length) + if (string.IsNullOrEmpty(format)) { - charsWritten = sb.Length; - sb.CopyTo(0, destination, sb.Length); - StringBuilderCache.Release(sb); - return true; + return FormatC(value); // formatProvider ignored, as "c" is invariant } - else + + if (format.Length == 1) { - StringBuilderCache.Release(sb); - charsWritten = 0; - return false; + char c = format[0]; + + if (c == 'c' || (c | 0x20) == 't') // special-case to optimize the default TimeSpan format + { + return FormatC(value); // formatProvider ignored, as "c" is invariant + } + + if ((c | 0x20) == 'g') // special-case to optimize the remaining 'g'/'G' standard formats + { + return FormatG(value, DateTimeFormatInfo.GetInstance(formatProvider), c == 'G' ? StandardFormat.G : StandardFormat.g); + } + + throw new FormatException(SR.Format_InvalidString); } + + return StringBuilderCache.GetStringAndRelease(FormatCustomized(value, format, DateTimeFormatInfo.GetInstance(formatProvider), result: null)); } - private static StringBuilder FormatToBuilder(TimeSpan value, ReadOnlySpan<char> format, IFormatProvider formatProvider) + /// <summary>Main method called from TimeSpan.TryFormat.</summary> + internal static bool TryFormat(TimeSpan value, Span<char> destination, out int charsWritten, ReadOnlySpan<char> format, IFormatProvider formatProvider) { if (format.Length == 0) { - format = "c"; + return TryFormatStandard(value, StandardFormat.C, null, destination, out charsWritten); } - // Standard formats if (format.Length == 1) { - char f = format[0]; - switch (f) + char c = format[0]; + if (c == 'c' || ((c | 0x20) == 't')) { - case 'c': - case 't': - case 'T': - return FormatStandard( - value, - isInvariant: true, - format: format, - pattern: Pattern.Minimum); - - case 'g': - case 'G': - DateTimeFormatInfo dtfi = DateTimeFormatInfo.GetInstance(formatProvider); - return FormatStandard( - value, - isInvariant: false, - format: value.Ticks < 0 ? dtfi.FullTimeSpanNegativePattern : dtfi.FullTimeSpanPositivePattern, - pattern: f == 'g' ? Pattern.Minimum : Pattern.Full); - - default: + return TryFormatStandard(value, StandardFormat.C, null, destination, out charsWritten); + } + else + { + StandardFormat sf = + c == 'g' ? StandardFormat.g : + c == 'G' ? StandardFormat.G : throw new FormatException(SR.Format_InvalidString); + return TryFormatStandard(value, sf, DateTimeFormatInfo.GetInstance(formatProvider).DecimalSeparator, destination, out charsWritten); } } - // Custom formats - return FormatCustomized(value, format, DateTimeFormatInfo.GetInstance(formatProvider), result: null); + StringBuilder sb = FormatCustomized(value, format, DateTimeFormatInfo.GetInstance(formatProvider), result: null); + + if (sb.Length <= destination.Length) + { + sb.CopyTo(0, destination, sb.Length); + charsWritten = sb.Length; + StringBuilderCache.Release(sb); + return true; + } + + charsWritten = 0; + StringBuilderCache.Release(sb); + return false; } - /// <summary>Format the TimeSpan instance using the specified format.</summary> - private static StringBuilder FormatStandard(TimeSpan value, bool isInvariant, ReadOnlySpan<char> format, Pattern pattern) + internal static string FormatC(TimeSpan value) { - StringBuilder sb = StringBuilderCache.Acquire(InternalGlobalizationHelper.StringBuilderDefaultCapacity); - int day = (int)(value.Ticks / TimeSpan.TicksPerDay); - long time = value.Ticks % TimeSpan.TicksPerDay; + Span<char> destination = stackalloc char[26]; // large enough for any "c" TimeSpan + TryFormatStandard(value, StandardFormat.C, null, destination, out int charsWritten); + return new string(destination.Slice(0, charsWritten)); + } - if (value.Ticks < 0) + private static string FormatG(TimeSpan value, DateTimeFormatInfo dtfi, StandardFormat format) + { + string decimalSeparator = dtfi.DecimalSeparator; + int maxLength = 25 + decimalSeparator.Length; // large enough for any "g"/"G" TimeSpan + Span<char> destination = maxLength < 128 ? + stackalloc char[maxLength] : + new char[maxLength]; // the chances of needing this case are almost 0, as DecimalSeparator.Length will basically always == 1 + TryFormatStandard(value, format, decimalSeparator, destination, out int charsWritten); + return new string(destination.Slice(0, charsWritten)); + } + + private enum StandardFormat { C, G, g } + + private static bool TryFormatStandard(TimeSpan value, StandardFormat format, string decimalSeparator, Span<char> destination, out int charsWritten) + { + Debug.Assert(format == StandardFormat.C || format == StandardFormat.G || format == StandardFormat.g); + + // First, calculate how large an output buffer is needed to hold the entire output. + int requiredOutputLength = 8; // start with "hh:mm:ss" and adjust as necessary + + uint fraction; + ulong totalSecondsRemaining; { - day = -day; - time = -time; + // Turn this into a non-negative TimeSpan if possible. + long ticks = value.Ticks; + if (ticks < 0) + { + requiredOutputLength = 9; // requiredOutputLength + 1 for the leading '-' sign + ticks = -ticks; + if (ticks < 0) + { + Debug.Assert(ticks == long.MinValue /* -9223372036854775808 */); + + // We computed these ahead of time; they're straight from the decimal representation of Int64.MinValue. + fraction = 4775808; + totalSecondsRemaining = 922337203685; + goto AfterComputeFraction; + } + } + + totalSecondsRemaining = Math.DivRem((ulong)ticks, TimeSpan.TicksPerSecond, out ulong fraction64); + fraction = (uint)fraction64; } - int hours = (int)(time / TimeSpan.TicksPerHour % 24); - int minutes = (int)(time / TimeSpan.TicksPerMinute % 60); - int seconds = (int)(time / TimeSpan.TicksPerSecond % 60); - int fraction = (int)(time % TimeSpan.TicksPerSecond); - FormatLiterals literal; - if (isInvariant) + AfterComputeFraction: + // Only write out the fraction if it's non-zero, and in that + // case write out the entire fraction (all digits). + Debug.Assert(fraction < 10_000_000); + int fractionDigits = 0; + switch (format) { - literal = value.Ticks < 0 ? - NegativeInvariantFormatLiterals : - PositiveInvariantFormatLiterals; + case StandardFormat.C: + // "c": Write out a fraction only if it's non-zero, and write out all 7 digits of it. + if (fraction != 0) + { + fractionDigits = DateTimeFormat.MaxSecondsFractionDigits; + requiredOutputLength += fractionDigits + 1; // digits plus leading decimal separator + } + break; + + case StandardFormat.G: + // "G": Write out a fraction regardless of whether it's 0, and write out all 7 digits of it. + fractionDigits = DateTimeFormat.MaxSecondsFractionDigits; + requiredOutputLength += fractionDigits + 1; // digits plus leading decimal separator + break; + + default: + // "g": Write out a fraction only if it's non-zero, and write out only the most significant digits. + Debug.Assert(format == StandardFormat.g); + if (fraction != 0) + { + fractionDigits = DateTimeFormat.MaxSecondsFractionDigits - FormattingHelpers.CountDecimalTrailingZeros(fraction, out fraction); + requiredOutputLength += fractionDigits + 1; // digits plus leading decimal separator + } + break; } - else + + ulong totalMinutesRemaining = 0, seconds = 0; + if (totalSecondsRemaining > 0) { - literal = new FormatLiterals(); - literal.Init(format, pattern == Pattern.Full); + // Only compute minutes if the TimeSpan has an absolute value of >= 1 minute. + totalMinutesRemaining = Math.DivRem(totalSecondsRemaining, 60 /* seconds per minute */, out seconds); + Debug.Assert(seconds < 60); } - if (fraction != 0) + ulong totalHoursRemaining = 0, minutes = 0; + if (totalMinutesRemaining > 0) { - // truncate the partial second to the specified length - fraction = (int)(fraction / TimeSpanParse.Pow10(DateTimeFormat.MaxSecondsFractionDigits - literal.ff)); + // Only compute hours if the TimeSpan has an absolute value of >= 1 hour. + totalHoursRemaining = Math.DivRem(totalMinutesRemaining, 60 /* minutes per hour */, out minutes); + Debug.Assert(minutes < 60); } - // Pattern.Full: [-]dd.hh:mm:ss.fffffff - // Pattern.Minimum: [-][d.]hh:mm:ss[.fffffff] + // At this point, we can switch over to 32-bit DivRem since the data has shrunk far enough. + Debug.Assert(totalHoursRemaining <= uint.MaxValue); - sb.Append(literal.Start); // [-] - if (pattern == Pattern.Full || day != 0) + uint days = 0, hours = 0; + if (totalHoursRemaining > 0) { - sb.Append(day); // [dd] - sb.Append(literal.DayHourSep); // [.] - } // - AppendNonNegativeInt32(sb, hours, literal.hh); // hh - sb.Append(literal.HourMinuteSep); // : - AppendNonNegativeInt32(sb, minutes, literal.mm); // mm - sb.Append(literal.MinuteSecondSep); // : - AppendNonNegativeInt32(sb, seconds, literal.ss); // ss - if (!isInvariant && pattern == Pattern.Minimum) + // Only compute days if the TimeSpan has an absolute value of >= 1 day. + days = Math.DivRem((uint)totalHoursRemaining, 24 /* hours per day */, out hours); + Debug.Assert(hours < 24); + } + + int hourDigits = 2; + if (format == StandardFormat.g && hours < 10) + { + // "g": Only writing a one-digit hour, rather than expected two-digit hour + hourDigits = 1; + requiredOutputLength--; + } + + int dayDigits = 0; + if (days > 0) + { + dayDigits = FormattingHelpers.CountDigits(days); + Debug.Assert(dayDigits <= 8); + requiredOutputLength += dayDigits + 1; // for the leading "d." + } + else if (format == StandardFormat.G) + { + // "G": has a leading "0:" if days is 0 + requiredOutputLength += 2; + dayDigits = 1; + } + + if (destination.Length < requiredOutputLength) { - int effectiveDigits = literal.ff; - while (effectiveDigits > 0) + charsWritten = 0; + return false; + } + + // Write leading '-' if necessary + int idx = 0; + if (value.Ticks < 0) + { + destination[idx++] = '-'; + } + + // Write day and separator, if necessary + if (dayDigits != 0) + { + WriteDigits(days, destination.Slice(idx, dayDigits)); + idx += dayDigits; + destination[idx++] = format == StandardFormat.C ? '.' : ':'; + } + + // Write "[h]h:mm:ss + Debug.Assert(hourDigits == 1 || hourDigits == 2); + if (hourDigits == 2) + { + WriteTwoDigits(hours, destination.Slice(idx)); + idx += 2; + } + else + { + destination[idx++] = (char)('0' + hours); + } + destination[idx++] = ':'; + WriteTwoDigits((uint)minutes, destination.Slice(idx)); + idx += 2; + destination[idx++] = ':'; + WriteTwoDigits((uint)seconds, destination.Slice(idx)); + idx += 2; + + // Write fraction and separator, if necessary + if (fractionDigits != 0) + { + if (format == StandardFormat.C) { - if (fraction % 10 == 0) - { - fraction = fraction / 10; - effectiveDigits--; - } - else - { - break; - } + destination[idx++] = '.'; } - if (effectiveDigits > 0) + else if (decimalSeparator.Length == 1) { - sb.Append(literal.SecondFractionSep); // [.FFFFFFF] - sb.Append((fraction).ToString(DateTimeFormat.fixedNumberFormats[effectiveDigits - 1], CultureInfo.InvariantCulture)); + destination[idx++] = decimalSeparator[0]; } + else + { + decimalSeparator.AsSpan().CopyTo(destination); + idx += decimalSeparator.Length; + } + WriteDigits(fraction, destination.Slice(idx, fractionDigits)); + idx += fractionDigits; } - else if (pattern == Pattern.Full || fraction != 0) + + Debug.Assert(idx == requiredOutputLength); + charsWritten = requiredOutputLength; + return true; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void WriteTwoDigits(uint value, Span<char> buffer) + { + Debug.Assert(buffer.Length >= 2); + uint temp = '0' + value; + value /= 10; + buffer[1] = (char)(temp - (value * 10)); + buffer[0] = (char)('0' + value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void WriteDigits(uint value, Span<char> buffer) + { + Debug.Assert(buffer.Length > 0); + + for (int i = buffer.Length - 1; i >= 1; i--) { - sb.Append(literal.SecondFractionSep); // [.] - AppendNonNegativeInt32(sb, fraction, literal.ff); // [fffffff] + uint temp = '0' + value; + value /= 10; + buffer[i] = (char)(temp - (value * 10)); } - sb.Append(literal.End); - return sb; + Debug.Assert(value < 10); + buffer[0] = (char)('0' + value); } /// <summary>Format the TimeSpan instance using the specified format.</summary> - private static StringBuilder FormatCustomized(TimeSpan value, ReadOnlySpan<char> format, DateTimeFormatInfo dtfi, StringBuilder result) + private static StringBuilder FormatCustomized(TimeSpan value, ReadOnlySpan<char> format, DateTimeFormatInfo dtfi, StringBuilder result = null) { Debug.Assert(dtfi != null); diff --git a/src/System.Private.CoreLib/shared/System/Math.cs b/src/System.Private.CoreLib/shared/System/Math.cs index a175103f8..ef46869e2 100644 --- a/src/System.Private.CoreLib/shared/System/Math.cs +++ b/src/System.Private.CoreLib/shared/System/Math.cs @@ -123,15 +123,25 @@ namespace System public static long DivRem(long a, long b, out long result) { - // TODO https://github.com/dotnet/coreclr/issues/3439: - // Restore to using % and / when the JIT is able to eliminate one of the idivs. - // In the meantime, a * and - is measurably faster than an extra /. - long div = a / b; result = a - (div * b); return div; } + internal static uint DivRem(uint a, uint b, out uint result) + { + uint div = a / b; + result = a - (div * b); + return div; + } + + internal static ulong DivRem(ulong a, ulong b, out ulong result) + { + ulong div = a / b; + result = a - (div * b); + return div; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static decimal Ceiling(decimal d) { diff --git a/src/System.Private.CoreLib/shared/System/MemoryExtensions.Fast.cs b/src/System.Private.CoreLib/shared/System/MemoryExtensions.Fast.cs index 4e84223b2..efd11c9c5 100644 --- a/src/System.Private.CoreLib/shared/System/MemoryExtensions.Fast.cs +++ b/src/System.Private.CoreLib/shared/System/MemoryExtensions.Fast.cs @@ -226,6 +226,7 @@ namespace System /// <param name="culture">An object that supplies culture-specific casing rules.</param> /// <remarks>If the source and destinations overlap, this method behaves as if the original values are in /// a temporary location before the destination is overwritten.</remarks> + /// <returns>The number of characters written into the destination span. If the destination is too small, returns -1.</returns> /// <exception cref="System.ArgumentNullException"> /// Thrown when <paramref name="culture"/> is null. /// </exception> @@ -253,6 +254,7 @@ namespace System /// <param name="destination">The destination span which contains the transformed characters.</param> /// <remarks>If the source and destinations overlap, this method behaves as if the original values are in /// a temporary location before the destination is overwritten.</remarks> + /// <returns>The number of characters written into the destination span. If the destination is too small, returns -1.</returns> public static int ToLowerInvariant(this ReadOnlySpan<char> source, Span<char> destination) { // Assuming that changing case does not affect length @@ -275,6 +277,7 @@ namespace System /// <param name="culture">An object that supplies culture-specific casing rules.</param> /// <remarks>If the source and destinations overlap, this method behaves as if the original values are in /// a temporary location before the destination is overwritten.</remarks> + /// <returns>The number of characters written into the destination span. If the destination is too small, returns -1.</returns> /// <exception cref="System.ArgumentNullException"> /// Thrown when <paramref name="culture"/> is null. /// </exception> @@ -302,6 +305,7 @@ namespace System /// <param name="destination">The destination span which contains the transformed characters.</param> /// <remarks>If the source and destinations overlap, this method behaves as if the original values are in /// a temporary location before the destination is overwritten.</remarks> + /// <returns>The number of characters written into the destination span. If the destination is too small, returns -1.</returns> public static int ToUpperInvariant(this ReadOnlySpan<char> source, Span<char> destination) { // Assuming that changing case does not affect length diff --git a/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/MemoryMarshal.Fast.cs b/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/MemoryMarshal.Fast.cs index a95452954..e3cf0a84e 100644 --- a/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/MemoryMarshal.Fast.cs +++ b/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/MemoryMarshal.Fast.cs @@ -102,7 +102,7 @@ namespace System.Runtime.InteropServices /// These types may not contain pointers or references. This is checked at runtime in order to preserve type safety. /// </summary> /// <remarks> - /// Supported only for platforms that support misaligned memory access. + /// Supported only for platforms that support misaligned memory access or when the memory block is aligned by other means. /// </remarks> /// <param name="span">The source slice, of type <typeparamref name="TFrom"/>.</param> /// <exception cref="System.ArgumentException"> @@ -157,7 +157,7 @@ namespace System.Runtime.InteropServices /// These types may not contain pointers or references. This is checked at runtime in order to preserve type safety. /// </summary> /// <remarks> - /// Supported only for platforms that support misaligned memory access. + /// Supported only for platforms that support misaligned memory access or when the memory block is aligned by other means. /// </remarks> /// <param name="span">The source slice, of type <typeparamref name="TFrom"/>.</param> /// <exception cref="System.ArgumentException"> diff --git a/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/MemoryMarshal.cs b/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/MemoryMarshal.cs index 5e33ced6b..fcc9c4f41 100644 --- a/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/MemoryMarshal.cs +++ b/src/System.Private.CoreLib/shared/System/Runtime/InteropServices/MemoryMarshal.cs @@ -212,6 +212,50 @@ namespace System.Runtime.InteropServices } /// <summary> + /// Re-interprets a span of bytes as a reference to structure of type T. + /// The type may not contain pointers or references. This is checked at runtime in order to preserve type safety. + /// </summary> + /// <remarks> + /// Supported only for platforms that support misaligned memory access or when the memory block is aligned by other means. + /// </remarks> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref T AsRef<T>(Span<byte> span) + where T : struct + { + if (RuntimeHelpers.IsReferenceOrContainsReferences<T>()) + { + ThrowHelper.ThrowInvalidTypeWithPointersNotSupported(typeof(T)); + } + if (Unsafe.SizeOf<T>() > (uint)span.Length) + { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.length); + } + return ref Unsafe.As<byte, T>(ref GetReference(span)); + } + + /// <summary> + /// Re-interprets a span of bytes as a reference to structure of type T. + /// The type may not contain pointers or references. This is checked at runtime in order to preserve type safety. + /// </summary> + /// <remarks> + /// Supported only for platforms that support misaligned memory access or when the memory block is aligned by other means. + /// </remarks> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref readonly T AsRef<T>(ReadOnlySpan<byte> span) + where T : struct + { + if (RuntimeHelpers.IsReferenceOrContainsReferences<T>()) + { + ThrowHelper.ThrowInvalidTypeWithPointersNotSupported(typeof(T)); + } + if (Unsafe.SizeOf<T>() > (uint)span.Length) + { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.length); + } + return ref Unsafe.As<byte, T>(ref GetReference(span)); + } + + /// <summary> /// Creates a new memory over the portion of the pre-pinned target array beginning /// at 'start' index and ending at 'end' index (exclusive). /// </summary> diff --git a/src/System.Private.CoreLib/shared/System/Single.cs b/src/System.Private.CoreLib/shared/System/Single.cs index 6b8440d40..8d1788f73 100644 --- a/src/System.Private.CoreLib/shared/System/Single.cs +++ b/src/System.Private.CoreLib/shared/System/Single.cs @@ -23,7 +23,7 @@ namespace System [Serializable] [StructLayout(LayoutKind.Sequential)] [TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")] - public struct Single : IComparable, IConvertible, IFormattable, IComparable<float>, IEquatable<float>, ISpanFormattable + public readonly struct Single : IComparable, IConvertible, IFormattable, IComparable<float>, IEquatable<float>, ISpanFormattable { private readonly float m_value; // Do not rename (binary serialization) diff --git a/src/System.Private.CoreLib/shared/System/TimeSpan.cs b/src/System.Private.CoreLib/shared/System/TimeSpan.cs index 4716ab6c1..1b94c9f15 100644 --- a/src/System.Private.CoreLib/shared/System/TimeSpan.cs +++ b/src/System.Private.CoreLib/shared/System/TimeSpan.cs @@ -450,7 +450,7 @@ namespace System } public override string ToString() { - return TimeSpanFormat.Format(this, null, null); + return TimeSpanFormat.FormatC(this); } public string ToString(string format) { |