From 262f4c2ad2bdc0848f58e84bbf5d22abd53c6d05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Tue, 9 Aug 2022 14:01:27 +0900 Subject: [PATCH 01/41] Delete the "Generating compatible code" message (#73554) We no longer root all assemblies by default and we don't need to advertise the size switches to much. Also, we need to establish what switches we actually want to support (document on docs.microsoft.com) for real. --- .../BuildIntegration/Microsoft.NETCore.Native.targets | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.targets b/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.targets index f39805cc9eddfe..5ded3960f2795e 100644 --- a/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.targets +++ b/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.targets @@ -39,8 +39,6 @@ The .NET Foundation licenses this file to you under the MIT license. - <_BuildingInCompatibleMode Condition="$(TrimmerDefaultAction) == '' and $(IlcGenerateStackTraceData) == '' and $(IlcDisableReflection) == ''">true - true true @@ -266,8 +264,7 @@ The .NET Foundation licenses this file to you under the MIT license. Inputs="@(IlcCompileInput);@(RdXmlFile);%(ManagedBinary.IlcRspFile)" Outputs="%(ManagedBinary.IlcOutputFile)" DependsOnTargets="WriteIlcRspFileForCompilation;$(IlcCompileDependsOn)"> - - + From 3c389db900f949122f4ad51f02cadc709bf12815 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz <32700855+ilonatommy@users.noreply.github.com> Date: Tue, 9 Aug 2022 08:20:59 +0200 Subject: [PATCH 02/41] [wasm][debugger] Detect `new` keyword in derived members on GetProperties (#73230) * Fix of VHO for most test cases. * Cleanup. * New testcases with auto-props. * Adjusted ProtectionLvlTest to new changes. --- .../MemberObjectsExplorer.cs | 90 +++++++++++++------ .../DebuggerTestSuite/GetPropertiesTests.cs | 17 +++- .../debugger-get-properties-test.cs | 21 +++++ 3 files changed, 98 insertions(+), 30 deletions(-) diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MemberObjectsExplorer.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MemberObjectsExplorer.cs index 30bf3127bd5995..6e8049e8666bcf 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MemberObjectsExplorer.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MemberObjectsExplorer.cs @@ -35,7 +35,17 @@ private static string GetNamePrefixForValues(string memberName, string typeName, return $"{memberName} ({justClassName})"; } - private static async Task ReadFieldValue(MonoSDBHelper sdbHelper, MonoBinaryReader reader, FieldTypeClass field, int objectId, TypeInfoWithDebugInformation typeInfo, int fieldValueType, bool isOwn, GetObjectCommandOptions getObjectOptions, CancellationToken token) + private static async Task ReadFieldValue( + MonoSDBHelper sdbHelper, + MonoBinaryReader reader, + FieldTypeClass field, + int objectId, + TypeInfoWithDebugInformation typeInfo, + int fieldValueType, + bool isOwn, + int parentTypeId, + GetObjectCommandOptions getObjectOptions, + CancellationToken token) { var fieldValue = await sdbHelper.ValueCreator.ReadAsVariableValue( reader, @@ -62,7 +72,10 @@ private static async Task ReadFieldValue(MonoSDBHelper sdbHelper, MonoB _ => "internal" }; if (field.IsBackingField) + { fieldValue["__isBackingField"] = true; + fieldValue["__parentTypeId"] = parentTypeId; + } if (field.Attributes.HasFlag(FieldAttributes.Static)) fieldValue["__isStatic"] = true; @@ -189,6 +202,7 @@ public static async Task ExpandFieldValues( MonoSDBHelper sdbHelper, DotnetObjectId id, int containerTypeId, + int parentTypeId, IReadOnlyList fields, GetObjectCommandOptions getCommandOptions, bool isOwn, @@ -220,7 +234,7 @@ public static async Task ExpandFieldValues( int valtype = retDebuggerCmdReader.ReadByte(); retDebuggerCmdReader.BaseStream.Position = initialPos; - JObject fieldValue = await ReadFieldValue(sdbHelper, retDebuggerCmdReader, field, id.Value, typeInfo, valtype, isOwn, getCommandOptions, token); + JObject fieldValue = await ReadFieldValue(sdbHelper, retDebuggerCmdReader, field, id.Value, typeInfo, valtype, isOwn, parentTypeId, getCommandOptions, token); numFieldsRead++; if (!Enum.TryParse(fieldValue["__state"].Value(), out DebuggerBrowsableState fieldState) @@ -302,7 +316,8 @@ public static async Task> ExpandPropertyValues( bool isOwn, CancellationToken token, Dictionary allMembers, - bool includeStatic) + bool includeStatic, + int parentTypeId = -1) { using var retDebuggerCmdReader = await sdbHelper.GetTypePropertiesReader(typeId, token); if (retDebuggerCmdReader == null) @@ -330,6 +345,8 @@ public static async Task> ExpandPropertyValues( MethodInfoWithDebugInformation getterInfo = await sdbHelper.GetMethodInfo(getMethodId, token); MethodAttributes getterAttrs = getterInfo.Info.Attributes; MethodAttributes getterMemberAccessAttrs = getterAttrs & MethodAttributes.MemberAccessMask; + MethodAttributes vtableLayout = getterAttrs & MethodAttributes.VtableLayoutMask; + bool isNewSlot = (vtableLayout & MethodAttributes.NewSlot) == MethodAttributes.NewSlot; typePropertiesBrowsableInfo.TryGetValue(propName, out DebuggerBrowsableState? state); @@ -337,7 +354,7 @@ public static async Task> ExpandPropertyValues( if (!allMembers.TryGetValue(propName, out JObject existingMember)) { // new member - await AddProperty(getMethodId, state, propName, getterMemberAccessAttrs, isStatic); + await AddProperty(getMethodId, parentTypeId, state, propName, getterMemberAccessAttrs, isStatic, isNewSlot: isNewSlot); continue; } @@ -358,24 +375,29 @@ public static async Task> ExpandPropertyValues( } var overriddenOrHiddenPropName = $"{propName} ({parentSuffix})"; - MethodAttributes vtableLayout = getterAttrs & MethodAttributes.VtableLayoutMask; - bool wasOverriddenByDerivedType = (vtableLayout & MethodAttributes.NewSlot) == MethodAttributes.NewSlot; - if (wasOverriddenByDerivedType) + if (isNewSlot) { - /* - * property was overridden by a derived type member. We want to show - * only the overridden members. So, remove the backing field - * for this auto-property that was added, with the type name suffix - * - * Two cases: - * 1. auto-prop in base, overridden by auto-prop in derived - * 2. auto-prop in base, overridden by prop in derived - * - * And in both cases we want to remove the backing field for the auto-prop for - * *this* base type - */ - allMembers.Remove(overriddenOrHiddenPropName); - continue; + // this has `new` keyword if it is newSlot but direct child was not a newSlot: + var child = allMembers.FirstOrDefault( + kvp => (kvp.Key == propName || kvp.Key.StartsWith($"{propName} (")) && kvp.Value["__parentTypeId"]?.Value() == typeId).Value; + bool wasOverriddenByDerivedType = child != null && child["__isNewSlot"]?.Value() != true; + if (wasOverriddenByDerivedType) + { + /* + * property was overridden by a derived type member. We want to show + * only the overridden members. So, remove the backing field + * for this auto-property that was added, with the type name suffix + * + * Two cases: + * 1. auto-prop in base, overridden by auto-prop in derived + * 2. auto-prop in base, overridden by prop in derived + * + * And in both cases we want to remove the backing field for the auto-prop for + * *this* base type + */ + allMembers.Remove(overriddenOrHiddenPropName); + continue; + } } /* @@ -388,7 +410,7 @@ public static async Task> ExpandPropertyValues( { // hiding with a non-auto property, so nothing to adjust // add the new property - await AddProperty(getMethodId, state, overriddenOrHiddenPropName, getterMemberAccessAttrs, isStatic); + await AddProperty(getMethodId, parentTypeId, state, overriddenOrHiddenPropName, getterMemberAccessAttrs, isStatic, isNewSlot: isNewSlot); continue; } @@ -420,7 +442,14 @@ async Task UpdateBackingFieldWithPropertyAttributes(JObject backingField, string allMembers[evalue["name"].Value()] = evalue; } - async Task AddProperty(int getMethodId, DebuggerBrowsableState? state, string propNameWithSufix, MethodAttributes getterAttrs, bool isPropertyStatic) + async Task AddProperty( + int getMethodId, + int parentTypeId, + DebuggerBrowsableState? state, + string propNameWithSufix, + MethodAttributes getterAttrs, + bool isPropertyStatic, + bool isNewSlot) { string returnTypeName = await sdbHelper.GetReturnType(getMethodId, token); JObject propRet = null; @@ -448,6 +477,11 @@ async Task AddProperty(int getMethodId, DebuggerBrowsableState? state, string pr _ => "internal" }; propRet["__state"] = state?.ToString(); + if (parentTypeId != -1) + { + propRet["__parentTypeId"] = parentTypeId; + propRet["__isNewSlot"] = isNewSlot; + } string namePrefix = GetNamePrefixForValues(propNameWithSufix, typeName, isOwn, state); var expandedMembers = await GetExpandedMemberValues( @@ -529,9 +563,11 @@ public static async Task GetObjectMemberValues( ArraySegment getPropertiesParamBuffer = commandParamsObjWriter.GetParameterBuffer(); var allMembers = new Dictionary(); - for (int i = 0; i < typeIdsIncludingParents.Count; i++) + int typeIdsCnt = typeIdsIncludingParents.Count; + for (int i = 0; i < typeIdsCnt; i++) { int typeId = typeIdsIncludingParents[i]; + int parentTypeId = i + 1 < typeIdsCnt ? typeIdsIncludingParents[i + 1] : -1; string typeName = await sdbHelper.GetTypeName(typeId, token); // 0th id is for the object itself, and then its ancestors bool isOwn = i == 0; @@ -541,7 +577,7 @@ public static async Task GetObjectMemberValues( if (thisTypeFields.Count > 0) { var allFields = await ExpandFieldValues( - sdbHelper, id, typeId, thisTypeFields, getCommandType, isOwn, includeStatic, token); + sdbHelper, id, typeId, parentTypeId, thisTypeFields, getCommandType, isOwn, includeStatic, token); if (getCommandType.HasFlag(GetObjectCommandOptions.AccessorPropertiesOnly)) { @@ -566,7 +602,8 @@ public static async Task GetObjectMemberValues( isOwn, token, allMembers, - includeStatic); + includeStatic, + parentTypeId); // ownProperties // Note: ownProperties should mean that we return members of the klass itself, @@ -577,6 +614,7 @@ public static async Task GetObjectMemberValues( /*if (accessorPropertiesOnly) break;*/ } + return GetMembersResult.FromValues(allMembers.Values, sortByAccessLevel); static void AddOnlyNewFieldValuesByNameTo(JArray namedValues, IDictionary valuesDict, string typeName, bool isOwn) diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/GetPropertiesTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/GetPropertiesTests.cs index 9ce001e1e2843a..d9fc2098dc07ec 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/GetPropertiesTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/GetPropertiesTests.cs @@ -21,7 +21,6 @@ public GetPropertiesTests(ITestOutputHelper testOutput) : base(testOutput) public static TheoryData, bool> ClassGetPropertiesTestData(bool is_async) { // FIXME: invoking getter on the hidden(base) properties - is that supported?? - // FIXME: add case - v -> o -> n, v -> n -> o var data = new TheoryData, bool>(); var type_name = "DerivedClass2"; @@ -34,6 +33,9 @@ public GetPropertiesTests(ITestOutputHelper testOutput) : base(testOutput) {"BaseBase_PropertyForVHO", (TGetter("BaseBase_PropertyForVHO", TString("Derived#BaseBase_PropertyForVHO")), true)}, {"BaseBase_PropertyForVOH", (TGetter("BaseBase_PropertyForVOH", TString("Derived#BaseBase_PropertyForVOH")), true)}, // {"BaseBase_PropertyForVOO", (TGetter("BaseBase_PropertyForVOO", TString("Derived#BaseBase_PropertyForVOO")), true)}, //FixMe: Issue #69788 + {"BaseBase_AutoPropertyForVHO", (TString("Derived#BaseBase_AutoPropertyForVHO"), true)}, + {"BaseBase_AutoPropertyForVOH", (TString("Derived#BaseBase_AutoPropertyForVOH"), true)}, + // {"BaseBase_AutoPropertyForVOO", (TGetter("BaseBase_AutoPropertyForVOO", TString("Derived#BaseBase_AutoPropertyForVOO")), true)}, //FixMe: Issue #69788 // protected / internal: {"BaseBase_AutoPropertyForHidingWithProperty", (TGetter("BaseBase_AutoPropertyForHidingWithProperty", TString("Derived#BaseBase_AutoPropertyForHidingWithProperty")), true)}, @@ -53,6 +55,7 @@ public GetPropertiesTests(ITestOutputHelper testOutput) : base(testOutput) {"FirstName", (TGetter("FirstName", TString("BaseClass#FirstName")), false)}, {"LastName", (TGetter("LastName", TString("BaseClass#LastName")), false)}, {"BaseBase_PropertyForVOH (BaseClass2)", (TGetter("BaseBase_PropertyForVOH (BaseClass2)", TString("Base#BaseBase_PropertyForVOH")), false)}, + {"BaseBase_AutoPropertyForVOH (BaseClass2)", (TString("Base#BaseBase_AutoPropertyForVOH"), false)}, // protected / internal: {"BaseBase_PropertyForHidingWithField (BaseClass2)", (TNumber(110), false)}, @@ -75,7 +78,8 @@ public GetPropertiesTests(ITestOutputHelper testOutput) : base(testOutput) {"BaseBase_FieldForHidingWithAutoProperty (BaseBaseClass2)", (TString("BaseBase#BaseBase_FieldForHidingWithAutoProperty"), false)}, {"BaseBase_PropertyForHidingWithAutoProperty (BaseBaseClass2)", (TGetter("BaseBase_PropertyForHidingWithAutoProperty (BaseBaseClass2)", TString("BaseBase#BaseBase_PropertyForHidingWithAutoProperty")), false)}, {"BaseBase_AutoPropertyForHidingWithAutoProperty (BaseBaseClass2)", (TString("BaseBase#BaseBase_AutoPropertyForHidingWithAutoProperty"), false)}, - // {"BaseBase_PropertyForVHO (BaseBaseClass2)", (TGetter("BaseBase_PropertyForVHO (BaseBaseClass2)", TString("BaseBase#BaseBase_PropertyForVHO")), false)}, // FixMe: Issue #69788 + {"BaseBase_PropertyForVHO (BaseBaseClass2)", (TGetter("BaseBase_PropertyForVHO (BaseBaseClass2)", TString("BaseBase#BaseBase_PropertyForVHO")), false)}, + {"BaseBase_AutoPropertyForVHO (BaseBaseClass2)", (TString("BaseBase#BaseBase_AutoPropertyForVHO"), false)}, }; // default, all properties @@ -117,7 +121,7 @@ public GetPropertiesTests(ITestOutputHelper testOutput) : base(testOutput) "BaseBase_PropertyForHidingWithProperty (BaseBaseClass2)", "BaseBase_PropertyForHidingWithAutoProperty (BaseBaseClass2)", "Base_VirtualPropertyNotOverriddenOrHidden", - // "BaseBase_PropertyForVHO (BaseBaseClass2)" // FixMe: Issue #69788 + "BaseBase_PropertyForVHO (BaseBaseClass2)" }; var only_own_accessors = new[] @@ -457,6 +461,9 @@ public static TheoryData, Dictionary, Dictionary, Dictionary(){ diff --git a/src/mono/wasm/debugger/tests/debugger-test/debugger-get-properties-test.cs b/src/mono/wasm/debugger/tests/debugger-test/debugger-get-properties-test.cs index 5ed2a5b7cca331..e7359ee8c3320b 100644 --- a/src/mono/wasm/debugger/tests/debugger-test/debugger-get-properties-test.cs +++ b/src/mono/wasm/debugger/tests/debugger-test/debugger-get-properties-test.cs @@ -136,12 +136,19 @@ public class BaseBaseClass2 public virtual string BaseBase_PropertyForVOH => "BaseBase#BaseBase_PropertyForVOH"; public virtual string BaseBase_PropertyForVHO => "BaseBase#BaseBase_PropertyForVHO"; // public virtual string BaseBase_PropertyForVOO => "BaseBase#BaseBase_PropertyForVOO"; // FixMe: Issue #69788 + public virtual string BaseBase_AutoPropertyForVOH { get; set; } + public virtual string BaseBase_AutoPropertyForVHO { get; set; } + // public virtual string BaseBase_AutoPropertyForVOO { get; set; } // FixMe: Issue #69788 public BaseBaseClass2() { BaseBase_AutoPropertyForHidingWithField = 15; BaseBase_AutoPropertyForHidingWithProperty = "BaseBase#BaseBase_AutoPropertyForHidingWithProperty"; BaseBase_AutoPropertyForHidingWithAutoProperty = "BaseBase#BaseBase_AutoPropertyForHidingWithAutoProperty"; + + BaseBase_AutoPropertyForVOH = "BaseBase#BaseBase_AutoPropertyForVOH"; + BaseBase_AutoPropertyForVHO = "BaseBase#BaseBase_AutoPropertyForVHO"; + // BaseBase_AutoPropertyForVOO = "BaseBase#BaseBase_AutoPropertyForVOO"; // FixMe: Issue #69788 } } @@ -181,6 +188,9 @@ public class BaseClass2 : BaseBaseClass2, IName public override string BaseBase_PropertyForVOH => "Base#BaseBase_PropertyForVOH"; public new virtual string BaseBase_PropertyForVHO => "Base#BaseBase_PropertyForVHO"; // public override string BaseBase_PropertyForVOO => "BaseBase#BaseBase_PropertyForVOO"; // FixMe: Issue #69788 + public override string BaseBase_AutoPropertyForVOH { get; set; } + public new virtual string BaseBase_AutoPropertyForVHO { get; set; } + // public override string BaseBase_AutoPropertyForVOO { get; set; }// FixMe: Issue #69788 public BaseClass2() { @@ -190,6 +200,10 @@ public BaseClass2() BaseBase_AutoPropertyForHidingWithAutoProperty = "Base#BaseBase_AutoPropertyForHidingWithAutoProperty"; Base_AutoPropertyForOverridingWithProperty = new (2134, 5, 7, 1, 9, 2); Base_AutoPropertyForOverridingWithAutoProperty = new (2144, 5, 7, 1, 9, 2); + + BaseBase_AutoPropertyForVOH = "Base#BaseBase_AutoPropertyForVOH"; + BaseBase_AutoPropertyForVHO = "Base#BaseBase_AutoPropertyForVHO"; + // BaseBase_AutoPropertyForVOO = "Base#BaseBase_AutoPropertyForVOO"; // FixMe: Issue #69788 } } @@ -212,12 +226,19 @@ public class DerivedClass2 : BaseClass2 public new string BaseBase_PropertyForVOH => "Derived#BaseBase_PropertyForVOH"; public override string BaseBase_PropertyForVHO => "Derived#BaseBase_PropertyForVHO"; // public override string BaseBase_PropertyForVOO => "Derived#BaseBase_PropertyForVOO"; // FixMe: Issue #69788 + public new string BaseBase_AutoPropertyForVOH { get; set; } + public override string BaseBase_AutoPropertyForVHO { get; set; } + // public override string BaseBase_AutoPropertyForVOO { get; set; } // FixMe: Issue #69788 public DerivedClass2() { Base_PropertyForOverridingWithAutoProperty = new (2022, 7, 6, 5, 4, 3); Base_AutoPropertyForOverridingWithAutoProperty = new (2023, 7, 6, 5, 4, 3); BaseBase_FieldForHidingWithAutoProperty = "Derived#BaseBase_FieldForHidingWithAutoProperty"; + + BaseBase_AutoPropertyForVOH = "Derived#BaseBase_AutoPropertyForVOH"; + BaseBase_AutoPropertyForVHO = "Derived#BaseBase_AutoPropertyForVHO"; + // BaseBase_AutoPropertyForVOO = "Derived#BaseBase_AutoPropertyForVOO"; // FixMe: Issue #69788 } public static void run() From f58a7902a0b4fc9914494de820a7aae8045abd48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Tue, 9 Aug 2022 15:41:10 +0900 Subject: [PATCH 03/41] Delete some .NET Nativisms (#73553) * Replace MissingInteropDataException with NotSupportedException * Replace MissingRuntimeArtifactException with MissingMetadataException (it was already used as a MME in some spots anyway). I'll try to get rid of MME later. * Change the messages to refer to static analysis as the fix, not RD.XML. --- .../CompilerHelpers/RuntimeInteropData.cs | 14 +++++----- .../src/System.Private.CoreLib.csproj | 1 - .../MissingInteropDataException.cs | 20 -------------- .../MissingMetadataExceptionCreator.cs | 27 ++++--------------- .../ReflectionDomainSetupImplementation.cs | 2 +- .../DelegateMethodInfoRetriever.cs | 10 +++---- .../src/Resources/Strings.resx | 4 +-- ...System.Private.Reflection.Execution.csproj | 1 - .../MissingRuntimeArtifactException.cs | 25 ----------------- .../src/Resources/Strings.resx | 4 +-- 10 files changed, 21 insertions(+), 87 deletions(-) delete mode 100644 src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/MissingInteropDataException.cs delete mode 100644 src/coreclr/nativeaot/System.Private.Reflection.Execution/src/System/Reflection/MissingRuntimeArtifactException.cs diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/CompilerHelpers/RuntimeInteropData.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/CompilerHelpers/RuntimeInteropData.cs index 53b6dd53df1d78..a4e0d74957ad67 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/CompilerHelpers/RuntimeInteropData.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/CompilerHelpers/RuntimeInteropData.cs @@ -26,7 +26,7 @@ public static uint GetStructFieldOffset(RuntimeTypeHandle structureTypeHandle, s throw new ArgumentException(SR.Format(SR.Argument_OffsetOfFieldNotFound, RuntimeAugments.GetLastResortString(structureTypeHandle)), nameof(fieldName)); } - throw new MissingInteropDataException(SR.StructMarshalling_MissingInteropData, Type.GetTypeFromHandle(structureTypeHandle)); + throw new NotSupportedException(SR.Format(SR.StructMarshalling_MissingInteropData, Type.GetTypeFromHandle(structureTypeHandle))); } public static int GetStructUnsafeStructSize(RuntimeTypeHandle structureTypeHandle) @@ -44,7 +44,7 @@ public static int GetStructUnsafeStructSize(RuntimeTypeHandle structureTypeHandl return structureTypeHandle.GetValueTypeSize(); } - throw new MissingInteropDataException(SR.StructMarshalling_MissingInteropData, Type.GetTypeFromHandle(structureTypeHandle)); + throw new NotSupportedException(SR.Format(SR.StructMarshalling_MissingInteropData, Type.GetTypeFromHandle(structureTypeHandle))); } public static IntPtr GetStructUnmarshalStub(RuntimeTypeHandle structureTypeHandle) @@ -54,7 +54,7 @@ public static IntPtr GetStructUnmarshalStub(RuntimeTypeHandle structureTypeHandl return stub; } - throw new MissingInteropDataException(SR.StructMarshalling_MissingInteropData, Type.GetTypeFromHandle(structureTypeHandle)); + throw new NotSupportedException(SR.Format(SR.StructMarshalling_MissingInteropData, Type.GetTypeFromHandle(structureTypeHandle))); } public static IntPtr GetStructMarshalStub(RuntimeTypeHandle structureTypeHandle) @@ -64,7 +64,7 @@ public static IntPtr GetStructMarshalStub(RuntimeTypeHandle structureTypeHandle) return stub; } - throw new MissingInteropDataException(SR.StructMarshalling_MissingInteropData, Type.GetTypeFromHandle(structureTypeHandle)); + throw new NotSupportedException(SR.Format(SR.StructMarshalling_MissingInteropData, Type.GetTypeFromHandle(structureTypeHandle))); } public static IntPtr GetDestroyStructureStub(RuntimeTypeHandle structureTypeHandle, out bool hasInvalidLayout) @@ -74,7 +74,7 @@ public static IntPtr GetDestroyStructureStub(RuntimeTypeHandle structureTypeHand return stub; } - throw new MissingInteropDataException(SR.StructMarshalling_MissingInteropData, Type.GetTypeFromHandle(structureTypeHandle)); + throw new NotSupportedException(SR.Format(SR.StructMarshalling_MissingInteropData, Type.GetTypeFromHandle(structureTypeHandle))); } @@ -82,7 +82,7 @@ public static IntPtr GetForwardDelegateCreationStub(RuntimeTypeHandle delegateTy { GetMarshallersForDelegate(delegateTypeHandle, out _, out _, out IntPtr delegateCreationStub); if (delegateCreationStub == IntPtr.Zero) - throw new MissingInteropDataException(SR.DelegateMarshalling_MissingInteropData, Type.GetTypeFromHandle(delegateTypeHandle)); + throw new NotSupportedException(SR.Format(SR.DelegateMarshalling_MissingInteropData, Type.GetTypeFromHandle(delegateTypeHandle))); return delegateCreationStub; } @@ -91,7 +91,7 @@ public static IntPtr GetDelegateMarshallingStub(RuntimeTypeHandle delegateTypeHa GetMarshallersForDelegate(delegateTypeHandle, out IntPtr openStub, out IntPtr closedStub, out _); IntPtr pStub = openStaticDelegate ? openStub : closedStub; if (pStub == IntPtr.Zero) - throw new MissingInteropDataException(SR.DelegateMarshalling_MissingInteropData, Type.GetTypeFromHandle(delegateTypeHandle)); + throw new NotSupportedException(SR.Format(SR.DelegateMarshalling_MissingInteropData, Type.GetTypeFromHandle(delegateTypeHandle))); return pStub; } diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj b/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj index 9075bb0cc5de08..3a113147f4eac1 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj @@ -211,7 +211,6 @@ - diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/MissingInteropDataException.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/MissingInteropDataException.cs deleted file mode 100644 index 437c9e41bf6acf..00000000000000 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/MissingInteropDataException.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - - -namespace System.Runtime.InteropServices -{ - /// - /// Thrown when a manual marshalling method is called, but the type was not found - /// by static analysis or in the rd.xml file. - /// - class MissingInteropDataException : Exception - { - public Type MissingType { get; private set; } - public MissingInteropDataException(string resourceFormat, Type pertainantType): - base(SR.Format(resourceFormat, pertainantType.ToString())) - { - MissingType = pertainantType; - } - } -} diff --git a/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Execution/PayForPlayExperience/MissingMetadataExceptionCreator.cs b/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Execution/PayForPlayExperience/MissingMetadataExceptionCreator.cs index 46b245f9db7438..ebe6da8349665a 100644 --- a/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Execution/PayForPlayExperience/MissingMetadataExceptionCreator.cs +++ b/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Execution/PayForPlayExperience/MissingMetadataExceptionCreator.cs @@ -32,11 +32,11 @@ internal static MissingMetadataException Create(TypeInfo? pertainant, string nes string usefulPertainant = ComputeUsefulPertainantIfPossible(pertainant); if (usefulPertainant == null) - return new MissingMetadataException(Format(SR.Reflection_InsufficientMetadata_NoHelpAvailable, pertainant.ToString())); + return new MissingMetadataException(SR.Format(SR.Reflection_InsufficientMetadata_NoHelpAvailable, pertainant.ToString())); else { usefulPertainant = usefulPertainant + "." + DiagnosticMappingTables.ConvertBackTickNameToNameWithReducerInputFormat(nestedTypeName, null); - return new MissingMetadataException(Format(SR.Reflection_InsufficientMetadata_EdbNeeded, usefulPertainant)); + return new MissingMetadataException(SR.Format(SR.Reflection_InsufficientMetadata_EdbNeeded, usefulPertainant)); } } @@ -55,7 +55,7 @@ private static MissingMetadataException CreateFromString(string? pertainant) if (pertainant == null) return new MissingMetadataException(SR.Format(SR.Reflection_InsufficientMetadata_NoHelpAvailable, "")); else - return new MissingMetadataException(Format(SR.Reflection_InsufficientMetadata_EdbNeeded, pertainant)); + return new MissingMetadataException(SR.Format(SR.Reflection_InsufficientMetadata_EdbNeeded, pertainant)); } internal static MissingMetadataException CreateMissingArrayTypeException(Type elementType, bool isMultiDim, int rank) @@ -78,9 +78,9 @@ internal static MissingMetadataException CreateFromMetadataObject(string resourc string usefulPertainant = ComputeUsefulPertainantIfPossible(pertainant); if (usefulPertainant == null) - return new MissingMetadataException(Format(SR.Reflection_InsufficientMetadata_NoHelpAvailable, pertainant.ToString())); + return new MissingMetadataException(SR.Format(SR.Reflection_InsufficientMetadata_NoHelpAvailable, pertainant.ToString())); else - return new MissingMetadataException(Format(resourceId, usefulPertainant)); + return new MissingMetadataException(SR.Format(resourceId, usefulPertainant)); } public static string ComputeUsefulPertainantIfPossible(object pertainant) @@ -299,22 +299,5 @@ private static string CreateConstructedGenericTypeStringIfAvailable(Type generic return genericTypeName.ToString(); } - // - // This is a workaround to prevent crucial information being lost when compiling console apps using the retail ILC. - // This combination turns rich error messages from the framework into resource keys without the substitution strings. - // We'll detect this case here and append the substitution string manually. - // - private static string Format(string resourceMessage, object parameter) - { - if (resourceMessage.Contains("{0}")) - return SR.Format(resourceMessage, parameter); - - // If the rich exception message was eaten by the IL2IL transform, make sure the resulting message - // has a link pointing the user towards the .NET Native debugging guide. These get normally appended - // to the restricted message by the transform, but the pattern here is not recognized by the rewriter. - // At this point we know the message doesn't come from resources (because message == resource key), so - // we can't do much to make this localizable. - return resourceMessage + ": " + parameter + ". For more information, visit http://go.microsoft.com/fwlink/?LinkId=623485"; - } } } diff --git a/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Execution/ReflectionDomainSetupImplementation.cs b/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Execution/ReflectionDomainSetupImplementation.cs index 439be772211663..143dbf0b2011e7 100644 --- a/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Execution/ReflectionDomainSetupImplementation.cs +++ b/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Execution/ReflectionDomainSetupImplementation.cs @@ -52,7 +52,7 @@ public sealed override Exception CreateNonInvokabilityException(MemberInfo perta } string pertainantString = MissingMetadataExceptionCreator.ComputeUsefulPertainantIfPossible(pertainant); - return new MissingRuntimeArtifactException(SR.Format(resourceName, pertainantString ?? "?")); + return new MissingMetadataException(SR.Format(resourceName, pertainantString ?? "?")); } public sealed override Exception CreateMissingArrayTypeException(Type elementType, bool isMultiDim, int rank) diff --git a/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Extensions/NonPortable/DelegateMethodInfoRetriever.cs b/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Extensions/NonPortable/DelegateMethodInfoRetriever.cs index d8b2b88fb39ed7..68cbc9f19918bd 100644 --- a/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Extensions/NonPortable/DelegateMethodInfoRetriever.cs +++ b/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Extensions/NonPortable/DelegateMethodInfoRetriever.cs @@ -63,10 +63,8 @@ public static MethodInfo GetDelegateMethodInfo(Delegate del) callTryGetMethod = false; methodHandle = QMethodDefinition.FromObjectAndInt(resolver->Reader, resolver->Handle); - RuntimeTypeHandle declaringTypeHandleIgnored; - MethodNameAndSignature nameAndSignatureIgnored; - if (!TypeLoaderEnvironment.Instance.TryGetRuntimeMethodHandleComponents(resolver->GVMMethodHandle, out declaringTypeHandleIgnored, out nameAndSignatureIgnored, out genericMethodTypeArgumentHandles)) - throw new MissingRuntimeArtifactException(SR.DelegateGetMethodInfo_NoInstantiation); + if (!TypeLoaderEnvironment.Instance.TryGetRuntimeMethodHandleComponents(resolver->GVMMethodHandle, out _, out _, out genericMethodTypeArgumentHandles)) + throw new MissingMetadataException(SR.DelegateGetMethodInfo_NoInstantiation); } } } @@ -79,9 +77,9 @@ public static MethodInfo GetDelegateMethodInfo(Delegate del) string methodDisplayString = RuntimeAugments.TryGetMethodDisplayStringFromIp(ip); if (methodDisplayString == null) - throw new MissingRuntimeArtifactException(SR.DelegateGetMethodInfo_NoDynamic); + throw new MissingMetadataException(SR.DelegateGetMethodInfo_NoDynamic); else - throw new MissingRuntimeArtifactException(SR.Format(SR.DelegateGetMethodInfo_NoDynamic_WithDisplayString, methodDisplayString)); + throw new MissingMetadataException(SR.Format(SR.DelegateGetMethodInfo_NoDynamic_WithDisplayString, methodDisplayString)); } } MethodBase methodBase = ReflectionCoreExecution.ExecutionDomain.GetMethod(typeOfFirstParameterIfInstanceDelegate, methodHandle, genericMethodTypeArgumentHandles); diff --git a/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Resources/Strings.resx b/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Resources/Strings.resx index dfb2f92a4b27a2..e76aecc48eaad7 100644 --- a/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Resources/Strings.resx +++ b/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Resources/Strings.resx @@ -121,7 +121,7 @@ This operation cannot be carried out because metadata for the following object was removed for performance reasons:\n\n {0}\n\nNo further information is available. Rebuild in debug mode for better information.\n\n - '{0}' is missing metadata. For more information, please visit http://go.microsoft.com/fwlink/?LinkID=392859 + '{0}' is missing native code or metadata. This can happen for code that is not compatible with trimming or AOT. Inspect and fix trimming and AOT related warnings that were generated when the app was published. For more information see https://aka.ms/nativeaot-compatibility Cannot bind to the target method because its signature is not compatible with that of the delegate type. @@ -151,7 +151,7 @@ This object cannot be invoked because no code was generated for it: '{0}'. - MakeGenericMethod() cannot create this generic method instantiation because no code was generated for it: '{0}'. + '{0}' is missing native code. MethodInfo.MakeGenericMethod() is not compatible with AOT compilation. Inspect and fix AOT related warnings that were generated when the app was published. For more information see https://aka.ms/nativeaot-compatibility Dynamic invocation of delegate constructors is not supported on this runtime. diff --git a/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/System.Private.Reflection.Execution.csproj b/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/System.Private.Reflection.Execution.csproj index d2c2c954dcf0a2..81302d18af9deb 100644 --- a/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/System.Private.Reflection.Execution.csproj +++ b/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/System.Private.Reflection.Execution.csproj @@ -51,7 +51,6 @@ - diff --git a/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/System/Reflection/MissingRuntimeArtifactException.cs b/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/System/Reflection/MissingRuntimeArtifactException.cs deleted file mode 100644 index 3690212c33b3db..00000000000000 --- a/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/System/Reflection/MissingRuntimeArtifactException.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -/*============================================================ -** - Type: MissingRuntimeArtifactException -** -==============================================================*/ - -using global::System; - -namespace System.Reflection -{ - internal sealed class MissingRuntimeArtifactException : MemberAccessException - { - public MissingRuntimeArtifactException() - { - } - - public MissingRuntimeArtifactException(string message) - : base(message) - { - } - } -} diff --git a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx index 702b23a4b608dd..22490fd75b2a94 100644 --- a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx +++ b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx @@ -3957,10 +3957,10 @@ Must not be greater than the length of the buffer. - {0} is missing structure marshalling data. To enable structure marshalling data, add a MarshalStructure directive to the application rd.xml file. For more information, please visit http://go.microsoft.com/fwlink/?LinkID=393965 + '{0}' is missing structure marshalling data. This can happen for code that is not compatible with AOT. Inspect and fix AOT related warnings that were generated when the app was published. For more information see https://aka.ms/nativeaot-compatibility - {0} is missing delegate marshalling data. To enable delegate marshalling data, add a MarshalDelegate directive to the application rd.xml file. For more information, please visit http://go.microsoft.com/fwlink/?LinkID=393965 + '{0}' is missing delegate marshalling data. This can happen for code that is not compatible with AOT. Inspect and fix AOT related warnings that were generated when the app was published. For more information see https://aka.ms/nativeaot-compatibility Type names passed to Assembly.GetType() must not specify an assembly. From ffd79485b90d03b5aa9a8df0e7b34a2b7ac27504 Mon Sep 17 00:00:00 2001 From: Viktor Hofer Date: Tue, 9 Aug 2022 09:39:14 +0200 Subject: [PATCH 04/41] Avoid additional Pipes msbuild invocation and clean-up project (#73588) * Avoid additional Pipes invocation and clean-up project * Update System.IO.Pipes.AccessControl.csproj --- .../src/System.IO.Pipes.AccessControl.csproj | 8 +++++--- .../System.IO.Pipes/src/System.IO.Pipes.csproj | 13 +++++++++---- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/libraries/System.IO.Pipes.AccessControl/src/System.IO.Pipes.AccessControl.csproj b/src/libraries/System.IO.Pipes.AccessControl/src/System.IO.Pipes.AccessControl.csproj index f562bfbaa3f904..3dd3c8a455bf0e 100644 --- a/src/libraries/System.IO.Pipes.AccessControl/src/System.IO.Pipes.AccessControl.csproj +++ b/src/libraries/System.IO.Pipes.AccessControl/src/System.IO.Pipes.AccessControl.csproj @@ -12,9 +12,11 @@ - - true - + + diff --git a/src/libraries/System.IO.Pipes/src/System.IO.Pipes.csproj b/src/libraries/System.IO.Pipes/src/System.IO.Pipes.csproj index 93e20745fd3c0a..b0956d6ba7bf31 100644 --- a/src/libraries/System.IO.Pipes/src/System.IO.Pipes.csproj +++ b/src/libraries/System.IO.Pipes/src/System.IO.Pipes.csproj @@ -1,15 +1,15 @@ - + - true - true $(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-Unix;$(NetCoreAppCurrent) + true + $([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) SR.Pipes_PlatformNotSupported - + @@ -27,6 +27,7 @@ + @@ -123,6 +124,7 @@ + @@ -170,6 +172,7 @@ + @@ -180,11 +183,13 @@ + + From 063f7b761dcd855f11fffaa8dcf31956d330cf54 Mon Sep 17 00:00:00 2001 From: Peter Sollich Date: Tue, 9 Aug 2022 10:37:54 +0200 Subject: [PATCH 05/41] More precise writebarrier for regions (#67389) This introduces a lookup table for regions where we can find the current generation, the planned generation and a couple flags efficiently, i.e. by simple indexing into table. The table has byte-sized elements where the low 2 bits represent the current generation and the high 2 bits represent the planned generation. The 4 bits in between are available for flags, 2 are used so far, one to show that a region was swept in the plan phase, and one to show that a region was demoted. The table is used in the mark phase, in the mark_through_cards_helper (which is also called in the relocate_phase), and in the write barriers (for now only the most frequently used ones, Array.Copy has its own way of setting cards that I haven't fixed). I have changed the write barrier to only set single bits for the case where a pointer to younger generation is stored into an object in an older generation. This costs an interlocked operation in the case the bit is not already set. In the test cases we looked at this is more than compensated by lower cost in card marking. For cases where the cost of the interlocked instruction is too high, an escape hatch has been provided the form of the COMPLUS_GCWriteBarrier setting which can be used to select another flavor of write barrier - either a more precise write barrier for regions, or an imprecise write barrier. Both set whole bytes and thus need no interlocked operations. The write barrier uses a global ephemeral range to decide quickly whether a stored pointer can be in an ephemeral generation at all. For now, this global ephemeral range is simply set to the address range of the whole heap, to avoid having to update the write barrier on the fly. A slightly more sophisticated approach that takes into account low gen 2 regions is present in the code, but disabled for now. --- src/coreclr/gc/gc.cpp | 512 +++++++++++++++--- src/coreclr/gc/gcconfig.h | 10 +- src/coreclr/gc/gcinterface.h | 9 + src/coreclr/gc/gcpriv.h | 68 ++- src/coreclr/vm/amd64/JitHelpers_Fast.asm | 85 +++ .../vm/amd64/JitHelpers_FastWriteBarriers.asm | 312 +++++++++++ src/coreclr/vm/amd64/jithelpers_fast.S | 88 ++- .../vm/amd64/jithelpers_fastwritebarriers.S | 359 +++++++++++- src/coreclr/vm/amd64/jitinterfaceamd64.cpp | 302 ++++++++++- src/coreclr/vm/gcenv.ee.cpp | 6 + src/coreclr/vm/gcheaputilities.cpp | 3 + src/coreclr/vm/gcheaputilities.h | 4 + src/coreclr/vm/jitinterface.h | 19 +- 13 files changed, 1675 insertions(+), 102 deletions(-) diff --git a/src/coreclr/gc/gc.cpp b/src/coreclr/gc/gc.cpp index 3843eab77fc5c3..377092f16d2695 100644 --- a/src/coreclr/gc/gc.cpp +++ b/src/coreclr/gc/gc.cpp @@ -2004,7 +2004,46 @@ void stomp_write_barrier_resize(bool is_runtime_suspended, bool requires_upper_b GCToEEInterface::StompWriteBarrier(&args); } -void stomp_write_barrier_ephemeral(uint8_t* ephemeral_low, uint8_t* ephemeral_high) +#ifdef USE_REGIONS +void region_write_barrier_settings (WriteBarrierParameters* args, + gc_heap::region_info* map_region_to_generation_skewed, + uint8_t region_shr) +{ + switch (GCConfig::GetGCWriteBarrier()) + { + default: + case GCConfig::WRITE_BARRIER_DEFAULT: + case GCConfig::WRITE_BARRIER_REGION_BIT: + // bitwise region write barrier is the default now + args->region_to_generation_table = (uint8_t*)map_region_to_generation_skewed; + args->region_shr = region_shr; + args->region_use_bitwise_write_barrier = true; + break; + + case GCConfig::WRITE_BARRIER_REGION_BYTE: + // bytewise region write barrier + args->region_to_generation_table = (uint8_t*)map_region_to_generation_skewed; + args->region_shr = region_shr; + assert (args->region_use_bitwise_write_barrier == false); + break; + + case GCConfig::WRITE_BARRIER_SERVER: + // server write barrier + // args should have been zero initialized + assert (args->region_use_bitwise_write_barrier == false); + assert (args->region_to_generation_table == nullptr); + assert (args->region_shr == 0); + break; + } +} +#endif //USE_REGIONS + +void stomp_write_barrier_ephemeral (uint8_t* ephemeral_low, uint8_t* ephemeral_high +#ifdef USE_REGIONS + , gc_heap::region_info* map_region_to_generation_skewed + , uint8_t region_shr +#endif //USE_REGIONS + ) { initGCShadow(); @@ -2013,10 +2052,18 @@ void stomp_write_barrier_ephemeral(uint8_t* ephemeral_low, uint8_t* ephemeral_hi args.is_runtime_suspended = true; args.ephemeral_low = ephemeral_low; args.ephemeral_high = ephemeral_high; +#ifdef USE_REGIONS + region_write_barrier_settings (&args, map_region_to_generation_skewed, region_shr); +#endif //USE_REGIONS GCToEEInterface::StompWriteBarrier(&args); } -void stomp_write_barrier_initialize(uint8_t* ephemeral_low, uint8_t* ephemeral_high) +void stomp_write_barrier_initialize(uint8_t* ephemeral_low, uint8_t* ephemeral_high +#ifdef USE_REGIONS + , gc_heap::region_info* map_region_to_generation_skewed + , uint8_t region_shr +#endif //USE_REGIONS + ) { WriteBarrierParameters args = {}; args.operation = WriteBarrierOp::Initialize; @@ -2032,6 +2079,11 @@ void stomp_write_barrier_initialize(uint8_t* ephemeral_low, uint8_t* ephemeral_h args.highest_address = g_gc_highest_address; args.ephemeral_low = ephemeral_low; args.ephemeral_high = ephemeral_high; + +#ifdef USE_REGIONS + region_write_barrier_settings (&args, map_region_to_generation_skewed, region_shr); +#endif //USE_REGIONS + GCToEEInterface::StompWriteBarrier(&args); } @@ -2289,6 +2341,9 @@ region_allocator global_region_allocator; uint8_t*(*initial_regions)[total_generation_count][2] = nullptr; size_t gc_heap::region_count = 0; +gc_heap::region_info* gc_heap::map_region_to_generation = nullptr; +gc_heap::region_info* gc_heap::map_region_to_generation_skewed = nullptr; + #endif //USE_REGIONS #ifdef BACKGROUND_GC @@ -2327,6 +2382,15 @@ VOLATILE(c_gc_state) gc_heap::current_c_gc_state = c_gc_state_free; VOLATILE(BOOL) gc_heap::gc_background_running = FALSE; #endif //BACKGROUND_GC +#ifdef USE_REGIONS +#ifdef MULTIPLE_HEAPS +uint8_t* gc_heap::gc_low; +uint8_t* gc_heap::gc_high; +#endif //MULTIPLE_HEAPS +VOLATILE(uint8_t*) gc_heap::ephemeral_low; +VOLATILE(uint8_t*) gc_heap::ephemeral_high; +#endif //USE_REGIONS + #ifndef MULTIPLE_HEAPS #ifdef SPINLOCK_HISTORY int gc_heap::spinlock_info_index = 0; @@ -3501,11 +3565,19 @@ sorted_table::clear() #endif //FEATURE_BASICFREEZE #ifdef USE_REGIONS +inline +size_t get_skewed_basic_region_index_for_address (uint8_t* address) +{ + assert ((g_gc_lowest_address <= address) && (address < g_gc_highest_address)); + size_t skewed_basic_region_index = (size_t)address >> gc_heap::min_segment_size_shr; + return skewed_basic_region_index; +} + inline size_t get_basic_region_index_for_address (uint8_t* address) { - size_t basic_region_index = (size_t)address >> gc_heap::min_segment_size_shr; - return (basic_region_index - ((size_t)g_gc_lowest_address >> gc_heap::min_segment_size_shr)); + size_t skewed_basic_region_index = get_skewed_basic_region_index_for_address (address); + return (skewed_basic_region_index - get_skewed_basic_region_index_for_address (g_gc_lowest_address)); } // Go from a random address to its region info. The random address could be @@ -4007,6 +4079,13 @@ size_t size_seg_mapping_table_of (uint8_t* from, uint8_t* end) return sizeof (seg_mapping)*((size_t)(end - from) >> gc_heap::min_segment_size_shr); } +size_t size_region_to_generation_table_of (uint8_t* from, uint8_t* end) +{ + dprintf (1, ("from: %Ix, end: %Ix, size: %Ix", from, end, + sizeof (uint8_t)*(((size_t)(end - from) >> gc_heap::min_segment_size_shr)))); + return sizeof (uint8_t)*((size_t)(end - from) >> gc_heap::min_segment_size_shr); +} + inline size_t seg_mapping_word_of (uint8_t* add) { @@ -7802,27 +7881,10 @@ bool gc_heap::should_check_brick_for_reloc (uint8_t* o) { assert ((o >= g_gc_lowest_address) && (o < g_gc_highest_address)); - int condemned_gen = settings.condemned_generation; - if (condemned_gen < max_generation) - { - heap_segment* region = region_of (o); - int gen = get_region_gen_num (region); - if ((gen > condemned_gen) || (heap_segment_swept_in_plan (region))) - { - if (heap_segment_swept_in_plan (region)) - { - dprintf (4444, ("-Rsip %Ix", o)); - } - - return false; - } - } - else if (heap_segment_swept_in_plan (region_of (o))) - { - return false; - } + size_t skewed_basic_region_index = get_skewed_basic_region_index_for_address (o); - return true; + // return true if the region is not SIP and the generation is <= condemned generation + return (map_region_to_generation_skewed[skewed_basic_region_index] & (RI_SIP|RI_GEN_MASK)) <= settings.condemned_generation; } #endif //USE_REGIONS @@ -8559,6 +8621,9 @@ void gc_heap::get_card_table_element_sizes (uint8_t* start, uint8_t* end, size_t sizes[software_write_watch_table_element] = SoftwareWriteWatch::GetTableByteSize(start, end); } #endif //FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP && BACKGROUND_GC +#ifdef USE_REGIONS + sizes[region_to_generation_table_element] = size_region_to_generation_table_of (start, end); +#endif //USE_REGIONS sizes[seg_mapping_table_element] = size_seg_mapping_table_of (start, end); #ifdef BACKGROUND_GC if (gc_can_use_concurrent) @@ -8583,6 +8648,9 @@ void gc_heap::get_card_table_element_layout (uint8_t* start, uint8_t* end, size_ #ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP sizeof(size_t), // software_write_watch_table_element #endif //FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP +#ifdef USE_REGIONS + sizeof (uint8_t), // region_to_generation_table_element +#endif //USE_REGIONS sizeof (uint8_t*), // seg_mapping_table_element #ifdef BACKGROUND_GC // In order to avoid a dependency between commit_mark_array_by_range and this logic, it is easier to make sure @@ -8861,6 +8929,11 @@ uint32_t* gc_heap::make_card_table (uint8_t* start, uint8_t* end) } #endif //FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP && BACKGROUND_GC +#ifdef USE_REGIONS + map_region_to_generation = (region_info*)(mem + card_table_element_layout[region_to_generation_table_element]); + map_region_to_generation_skewed = map_region_to_generation - size_region_to_generation_table_of (0, g_gc_lowest_address); +#endif //USE_REGIONS + seg_mapping_table = (seg_mapping*)(mem + card_table_element_layout[seg_mapping_table_element]); seg_mapping_table = (seg_mapping*)((uint8_t*)seg_mapping_table - size_seg_mapping_table_of (0, (align_lower_segment (g_gc_lowest_address)))); @@ -11322,17 +11395,28 @@ int gc_heap::get_region_gen_num (heap_segment* region) int gc_heap::get_region_gen_num (uint8_t* obj) { - return heap_segment_gen_num (region_of (obj)); + size_t skewed_basic_region_index = get_skewed_basic_region_index_for_address (obj); + int gen_num = map_region_to_generation_skewed[skewed_basic_region_index] & gc_heap::RI_GEN_MASK; + assert ((soh_gen0 <= gen_num) && (gen_num <= soh_gen2)); + assert (gen_num == heap_segment_gen_num (region_of (obj))); + return gen_num; } int gc_heap::get_region_plan_gen_num (uint8_t* obj) { - return heap_segment_plan_gen_num (region_of (obj)); + size_t skewed_basic_region_index = get_skewed_basic_region_index_for_address (obj); + int plan_gen_num = map_region_to_generation_skewed[skewed_basic_region_index] >> gc_heap::RI_PLAN_GEN_SHR; + assert ((soh_gen0 <= plan_gen_num) && (plan_gen_num <= soh_gen2)); + assert (plan_gen_num == heap_segment_plan_gen_num (region_of (obj))); + return plan_gen_num; } bool gc_heap::is_region_demoted (uint8_t* obj) { - return heap_segment_demoted_p (region_of (obj)); + size_t skewed_basic_region_index = get_skewed_basic_region_index_for_address (obj); + bool demoted_p = (map_region_to_generation_skewed[skewed_basic_region_index] & gc_heap::RI_DEMOTED) != 0; + assert (demoted_p == heap_segment_demoted_p (region_of (obj))); + return demoted_p; } inline @@ -11341,6 +11425,77 @@ void gc_heap::set_region_gen_num (heap_segment* region, int gen_num) assert (gen_num < (1 << (sizeof (uint8_t) * 8))); assert (gen_num >= 0); heap_segment_gen_num (region) = (uint8_t)gen_num; + + uint8_t* region_start = get_region_start (region); + uint8_t* region_end = heap_segment_reserved (region); + + size_t region_index_start = get_basic_region_index_for_address (region_start); + size_t region_index_end = get_basic_region_index_for_address (region_end); + region_info entry = (region_info)((gen_num << RI_PLAN_GEN_SHR) | gen_num); + for (size_t region_index = region_index_start; region_index < region_index_end; region_index++) + { + assert (gen_num <= max_generation); + map_region_to_generation[region_index] = entry; + } + if (gen_num <= soh_gen1) + { + if ((region_start < ephemeral_low) || (ephemeral_high < region_end)) + { + static GCSpinLock write_barrier_spin_lock; + + while (true) + { + if (Interlocked::CompareExchange(&write_barrier_spin_lock.lock, 0, -1) < 0) + break; + + if ((ephemeral_low <= region_start) && (region_end <= ephemeral_high)) + return; + + while (write_barrier_spin_lock.lock >= 0) + { + YieldProcessor(); // indicate to the processor that we are spinning + } + } +#ifdef _DEBUG + write_barrier_spin_lock.holding_thread = GCToEEInterface::GetThread(); +#endif //_DEBUG + + if ((region_start < ephemeral_low) || (ephemeral_high < region_end)) + { + uint8_t* new_ephemeral_low = min (region_start, (uint8_t*)ephemeral_low); + uint8_t* new_ephemeral_high = max (region_end, (uint8_t*)ephemeral_high); + + dprintf (REGIONS_LOG, ("about to set ephemeral_low = %Ix ephemeral_high = %Ix", new_ephemeral_low, new_ephemeral_high)); + + stomp_write_barrier_ephemeral (new_ephemeral_low, new_ephemeral_high, + map_region_to_generation_skewed, (uint8_t)min_segment_size_shr); + + // we should only *decrease* ephemeral_low and only *increase* ephemeral_high + if (ephemeral_low < new_ephemeral_low) + GCToOSInterface::DebugBreak (); + if (new_ephemeral_high < ephemeral_high) + GCToOSInterface::DebugBreak (); + + // only set the globals *after* we have updated the write barrier + ephemeral_low = new_ephemeral_low; + ephemeral_high = new_ephemeral_high; + + dprintf (REGIONS_LOG, ("set ephemeral_low = %Ix ephemeral_high = %Ix", new_ephemeral_low, new_ephemeral_high)); + } + else + { + dprintf (REGIONS_LOG, ("leaving lock - no need to update ephemeral range [%Ix,%Ix[ for region [%Ix,%Ix]", (uint8_t*)ephemeral_low, (uint8_t*)ephemeral_high, region_start, region_end)); + } +#ifdef _DEBUG + write_barrier_spin_lock.holding_thread = (Thread*)-1; +#endif //_DEBUG + write_barrier_spin_lock.lock = -1; + } + else + { + dprintf (REGIONS_LOG, ("no need to update ephemeral range [%Ix,%Ix[ for region [%Ix,%Ix]", (uint8_t*)ephemeral_low, (uint8_t*)ephemeral_high, region_start, region_end)); + } + } } inline @@ -11354,7 +11509,8 @@ void gc_heap::set_region_plan_gen_num (heap_segment* region, int plan_gen_num) gen_num, plan_gen_num, supposed_plan_gen_num, ((plan_gen_num < supposed_plan_gen_num) ? "DEMOTED" : "ND"))); - if (plan_gen_num < supposed_plan_gen_num) + region_info region_info_bits_to_set = (region_info)(plan_gen_num << RI_PLAN_GEN_SHR); + if ((plan_gen_num < supposed_plan_gen_num) && (heap_segment_pinned_survived (region) != 0)) { if (!settings.demotion) { @@ -11362,6 +11518,7 @@ void gc_heap::set_region_plan_gen_num (heap_segment* region, int plan_gen_num) } get_gc_data_per_heap()->set_mechanism_bit (gc_demotion_bit); region->flags |= heap_segment_flags_demoted; + region_info_bits_to_set = (region_info)(region_info_bits_to_set | RI_DEMOTED); } else { @@ -11369,6 +11526,17 @@ void gc_heap::set_region_plan_gen_num (heap_segment* region, int plan_gen_num) } heap_segment_plan_gen_num (region) = plan_gen_num; + + uint8_t* region_start = get_region_start (region); + uint8_t* region_end = heap_segment_reserved (region); + + size_t region_index_start = get_basic_region_index_for_address (region_start); + size_t region_index_end = get_basic_region_index_for_address (region_end); + for (size_t region_index = region_index_start; region_index < region_index_end; region_index++) + { + assert (plan_gen_num <= max_generation); + map_region_to_generation[region_index] = (region_info)(region_info_bits_to_set | (map_region_to_generation[region_index] & ~(RI_PLAN_GEN_MASK|RI_DEMOTED))); + } } inline @@ -11379,6 +11547,42 @@ void gc_heap::set_region_plan_gen_num_sip (heap_segment* region, int plan_gen_nu set_region_plan_gen_num (region, plan_gen_num); } } + +void gc_heap::set_region_sweep_in_plan (heap_segment*region) +{ + heap_segment_swept_in_plan (region) = true; + + // this should be a basic region + assert (get_region_size (region) == global_region_allocator.get_region_alignment()); + + uint8_t* region_start = get_region_start (region); + size_t region_index = get_basic_region_index_for_address (region_start); + map_region_to_generation[region_index] = (region_info)(map_region_to_generation[region_index] | RI_SIP); +} + +void gc_heap::clear_region_sweep_in_plan (heap_segment*region) +{ + heap_segment_swept_in_plan (region) = false; + + // this should be a basic region + assert (get_region_size (region) == global_region_allocator.get_region_alignment()); + + uint8_t* region_start = get_region_start (region); + size_t region_index = get_basic_region_index_for_address (region_start); + map_region_to_generation[region_index] = (region_info)(map_region_to_generation[region_index] & ~RI_SIP); +} + +void gc_heap::clear_region_demoted (heap_segment* region) +{ + region->flags &= ~heap_segment_flags_demoted; + + // this should be a basic region + assert (get_region_size (region) == global_region_allocator.get_region_alignment()); + + uint8_t* region_start = get_region_start (region); + size_t region_index = get_basic_region_index_for_address (region_start); + map_region_to_generation[region_index] = (region_info)(map_region_to_generation[region_index] & ~RI_DEMOTED); +} #endif //USE_REGIONS int gc_heap::get_plan_gen_num (int gen_number) @@ -11517,7 +11721,7 @@ void gc_heap::init_heap_segment (heap_segment* seg, gc_heap* hp #ifdef USE_REGIONS int gen_num_for_region = min (gen_num, max_generation); - heap_segment_gen_num (seg) = (uint8_t)gen_num_for_region; + set_region_gen_num (seg, gen_num_for_region); heap_segment_plan_gen_num (seg) = gen_num_for_region; heap_segment_swept_in_plan (seg) = false; #endif //USE_REGIONS @@ -14122,11 +14326,14 @@ gc_heap::init_gc_heap (int h_number) if (heap_number == 0) { stomp_write_barrier_initialize( -#if defined(MULTIPLE_HEAPS) || defined(USE_REGIONS) +#if defined(USE_REGIONS) + ephemeral_low, ephemeral_high, + map_region_to_generation_skewed, (uint8_t)min_segment_size_shr +#elif defined(MULTIPLE_HEAPS) reinterpret_cast(1), reinterpret_cast(~0) #else ephemeral_low, ephemeral_high -#endif //!MULTIPLE_HEAPS || USE_REGIONS +#endif //MULTIPLE_HEAPS || USE_REGIONS ); } @@ -21173,6 +21380,10 @@ void gc_heap::gc1() } #ifdef USE_REGIONS distribute_free_regions(); + verify_region_to_generation_map (); + compute_gc_and_ephemeral_range (settings.condemned_generation, true); + stomp_write_barrier_ephemeral (ephemeral_low, ephemeral_high, + map_region_to_generation_skewed, (uint8_t)min_segment_size_shr); #endif //USE_REGIONS #ifdef FEATURE_LOH_COMPACTION @@ -21237,6 +21448,10 @@ void gc_heap::gc1() { #ifdef USE_REGIONS distribute_free_regions(); + verify_region_to_generation_map (); + compute_gc_and_ephemeral_range (settings.condemned_generation, true); + stomp_write_barrier_ephemeral (ephemeral_low, ephemeral_high, + map_region_to_generation_skewed, (uint8_t)min_segment_size_shr); if (settings.condemned_generation == max_generation) { // age and print all kinds of free regions @@ -23206,30 +23421,40 @@ inline bool is_in_heap_range (uint8_t* o) return (o != nullptr); #endif //FEATURE_BASICFREEZE } + +inline bool gc_heap::is_in_gc_range (uint8_t* o) +{ +#ifdef FEATURE_BASICFREEZE + // we may have frozen objects in read only segments + // outside of the reserved address range of the gc heap + assert (((g_gc_lowest_address <= o) && (o < g_gc_highest_address)) || + (o == nullptr) || (ro_segment_lookup (o) != nullptr)); +#else //FEATURE_BASICFREEZE + // without frozen objects, every non-null pointer must be + // within the heap + assert ((o == nullptr) || (g_gc_lowest_address <= o) && (o < g_gc_highest_address)); +#endif //FEATURE_BASICFREEZE + return ((gc_low <= o) && (o < gc_high)); +} #endif //USE_REGIONS inline BOOL gc_heap::gc_mark (uint8_t* o, uint8_t* low, uint8_t* high, int condemned_gen) { #ifdef USE_REGIONS - assert (low == 0); - assert (high == 0); - if (is_in_heap_range (o)) + if ((o >= low) && (o < high)) { - BOOL already_marked = marked (o); - if (already_marked) - return FALSE; - if (condemned_gen == max_generation) + if (condemned_gen != max_generation && get_region_gen_num (o) > condemned_gen) { - set_marked (o); - return TRUE; + return FALSE; } - int gen = get_region_gen_num (o); - if (gen <= condemned_gen) + BOOL already_marked = marked (o); + if (already_marked) { - set_marked (o); - return TRUE; + return FALSE; } + set_marked(o); + return TRUE; } return FALSE; #else //USE_REGIONS @@ -24240,7 +24465,7 @@ inline void gc_heap::mark_object (uint8_t* o THREAD_NUMBER_DCL) { #ifdef USE_REGIONS - if (is_in_heap_range (o) && is_in_condemned_gc (o)) + if (is_in_gc_range (o) && is_in_condemned_gc (o)) { mark_object_simple (&o THREAD_NUMBER_ARG); } @@ -25628,6 +25853,113 @@ void gc_heap::record_mark_time (uint64_t& mark_time, } #endif // FEATURE_EVENT_TRACE +#ifdef USE_REGIONS +void gc_heap::verify_region_to_generation_map() +{ +#ifdef _DEBUG + uint8_t* local_ephemeral_low = MAX_PTR; + uint8_t* local_ephemeral_high = nullptr; + for (int gen_number = soh_gen0; gen_number < total_generation_count; gen_number++) + { +#ifdef MULTIPLE_HEAPS + for (int i = 0; i < n_heaps; i++) + { + gc_heap* hp = g_heaps[i]; +#else //MULTIPLE_HEAPS + { + gc_heap* hp = pGenGCHeap; +#endif //MULTIPLE_HEAPS + generation *gen = hp->generation_of (gen_number); + for (heap_segment *region = generation_start_segment (gen); region != nullptr; region = heap_segment_next (region)) + { + size_t region_index_start = get_basic_region_index_for_address (get_region_start (region)); + size_t region_index_end = get_basic_region_index_for_address (heap_segment_reserved (region)); + int gen_num = min (gen_number, soh_gen2); + assert (gen_num == heap_segment_gen_num (region)); + int plan_gen_num = heap_segment_plan_gen_num (region); + bool is_demoted = (region->flags & heap_segment_flags_demoted) != 0; + bool is_sweep_in_plan = heap_segment_swept_in_plan (region); + for (size_t region_index = region_index_start; region_index < region_index_end; region_index++) + { + region_info region_info_bits = map_region_to_generation[region_index]; + assert ((region_info_bits & RI_GEN_MASK) == gen_num); + assert ((region_info_bits >> RI_PLAN_GEN_SHR) == plan_gen_num); + assert (((region_info_bits & RI_SIP) != 0) == is_sweep_in_plan); + assert (((region_info_bits & RI_DEMOTED) != 0) == is_demoted); + } + } + } + } +#endif //_DEBUG +} + +// recompute ephemeral range - it may have become too large because of temporary allocation +// and deallocation of regions +void gc_heap::compute_gc_and_ephemeral_range (int condemned_gen_number, bool end_of_gc_p) +{ + ephemeral_low = MAX_PTR; + ephemeral_high = nullptr; + gc_low = MAX_PTR; + gc_high = nullptr; + if (condemned_gen_number >= soh_gen2 || end_of_gc_p) + { + gc_low = g_gc_lowest_address; + gc_high = g_gc_highest_address; + } + if (end_of_gc_p) + { +#if 1 + // simple and safe value + ephemeral_low = g_gc_lowest_address; +#else + // conservative value - should still avoid changing + // ephemeral bounds in the write barrier while app is running + // scan our address space for a region that is either free + // or in an ephemeral generation + uint8_t* addr = g_gc_lowest_address; + while (true) + { + heap_segment* region = get_region_info (addr); + if (is_free_region (region)) + break; + if (heap_segment_gen_num (region) <= soh_gen1) + break; + addr += ((size_t)1) << min_segment_size_shr; + } + ephemeral_low = addr; +#endif + ephemeral_high = g_gc_highest_address; + } + else + { + for (int gen_number = soh_gen0; gen_number <= soh_gen1; gen_number++) + { + #ifdef MULTIPLE_HEAPS + for (int i = 0; i < n_heaps; i++) + { + gc_heap* hp = g_heaps[i]; + #else //MULTIPLE_HEAPS + { + gc_heap* hp = pGenGCHeap; + #endif //MULTIPLE_HEAPS + generation *gen = hp->generation_of (gen_number); + for (heap_segment *region = generation_start_segment (gen); region != nullptr; region = heap_segment_next (region)) + { + ephemeral_low = min ((uint8_t*)ephemeral_low, get_region_start (region)); + ephemeral_high = max ((uint8_t*)ephemeral_high, heap_segment_reserved (region)); + if (gen_number <= condemned_gen_number) + { + gc_low = min (gc_low, get_region_start (region)); + gc_high = max (gc_high, heap_segment_reserved (region)); + } + } + } + } + } + dprintf (2, ("ephemeral_low = %Ix, ephemeral_high = %Ix, gc_low = %Ix, gc_high = %Ix", (uint8_t*)ephemeral_low, (uint8_t*)ephemeral_high, gc_low, gc_high)); +} +#endif //USE_REGIONS + void gc_heap::mark_phase (int condemned_gen_number, BOOL mark_only_p) { assert (settings.concurrent == FALSE); @@ -25761,6 +26093,8 @@ void gc_heap::mark_phase (int condemned_gen_number, BOOL mark_only_p) special_sweep_p = false; region_count = global_region_allocator.get_used_region_count(); grow_mark_list_piece(); + verify_region_to_generation_map(); + compute_gc_and_ephemeral_range (condemned_gen_number, false); #endif //USE_REGIONS GCToEEInterface::BeforeGcScanRoots(condemned_gen_number, /* is_bgc */ false, /* is_concurrent */ false); @@ -30229,6 +30563,10 @@ void gc_heap::plan_phase (int condemned_gen_number) } #endif //!USE_REGIONS +#ifdef USE_REGIONS + verify_region_to_generation_map (); +#endif //USE_REGIONS + #ifdef MULTIPLE_HEAPS //join all threads to make sure they are synchronized dprintf(3, ("Restarting after Promotion granted")); @@ -30532,8 +30870,8 @@ heap_segment* gc_heap::find_first_valid_region (heap_segment* region, bool compa } // Take this opportunity to make sure all the regions left with flags only for this GC are reset. - heap_segment_swept_in_plan (current_region) = false; - current_region->flags &= ~heap_segment_flags_demoted; + clear_region_sweep_in_plan (current_region); + clear_region_demoted (current_region); return current_region; } @@ -30951,7 +31289,7 @@ void gc_heap::sweep_region_in_plan (heap_segment* region, uint8_t**& mark_list_next, uint8_t** mark_list_index) { - heap_segment_swept_in_plan (region) = true; + set_region_sweep_in_plan (region); region->init_free_list(); @@ -31084,7 +31422,7 @@ void gc_heap::check_demotion_helper_sip (uint8_t** pval, int parent_gen_num, uin uint8_t* child_object = *pval; if (!is_in_heap_range (child_object)) return; - if (!child_object) return; + assert (child_object != nullptr); int child_object_plan_gen = get_region_plan_gen_num (child_object); if (child_object_plan_gen < parent_gen_num) @@ -31628,7 +31966,7 @@ void gc_heap::relocate_address (uint8_t** pold_address THREAD_NUMBER_DCL) { uint8_t* old_address = *pold_address; #ifdef USE_REGIONS - if (!is_in_heap_range (old_address) || !should_check_brick_for_reloc (old_address)) + if (!is_in_gc_range (old_address) || !should_check_brick_for_reloc (old_address)) { return; } @@ -32599,6 +32937,10 @@ void gc_heap::relocate_phase (int condemned_gen_number, } #endif //FEATURE_EVENT_TRACE +#ifdef USE_REGIONS + verify_region_to_generation_map(); +#endif //USE_REGIONS + #ifdef MULTIPLE_HEAPS //join all threads to make sure they are synchronized dprintf(3, ("Restarting for relocation")); @@ -36852,11 +37194,21 @@ BOOL gc_heap::find_card_dword (size_t& cardw, size_t cardw_end) while (1) { // Find a non-zero bundle - while ((cardb < end_cardb) && (card_bundle_set_p (cardb) == 0)) + while (cardb < end_cardb) { - cardb++; + uint32_t cbw = card_bundle_table[card_bundle_word(cardb)] >> card_bundle_bit (cardb); + DWORD bit_index; + if (BitScanForward (&bit_index, cbw)) + { + cardb += bit_index; + break; + } + else + { + cardb += sizeof(cbw)*8 - card_bundle_bit (cardb); + } } - if (cardb == end_cardb) + if (cardb >= end_cardb) return FALSE; uint32_t* card_word = &card_table[max(card_bundle_cardw (cardb),cardw)]; @@ -36871,8 +37223,19 @@ BOOL gc_heap::find_card_dword (size_t& cardw, size_t cardw_end) cardw = (card_word - &card_table[0]); return TRUE; } - else if ((cardw <= card_bundle_cardw (cardb)) && - (card_word == &card_table [card_bundle_cardw (cardb+1)])) + // explore the beginning of the card bundle so we can possibly clear it + if (cardw == (card_bundle_cardw (cardb) + 1) && !card_table[cardw-1]) + { + cardw--; + } + // explore the end of the card bundle so we can possibly clear it + card_word_end = &card_table[card_bundle_cardw (cardb+1)]; + while ((card_word < card_word_end) && !(*card_word)) + { + card_word++; + } + if ((cardw <= card_bundle_cardw (cardb)) && + (card_word == card_word_end)) { // a whole bundle was explored and is empty dprintf (3, ("gc: %d, find_card_dword clear bundle: %Ix cardw:[%Ix,%Ix[", @@ -36928,14 +37291,23 @@ BOOL gc_heap::find_card(uint32_t* card_table, // Find the first card which is set last_card_word = &card_table [card_word (card)]; bit_position = card_bit (card); - card_word_value = (*last_card_word) >> bit_position; +#ifdef CARD_BUNDLE + // if we have card bundles, consult them before fetching a new card word + if (bit_position == 0) + { + card_word_value = 0; + } + else +#endif + { + card_word_value = (*last_card_word) >> bit_position; + } if (!card_word_value) { - bit_position = 0; #ifdef CARD_BUNDLE // Using the card bundle, go through the remaining card words between here and // card_word_end until we find one that is non-zero. - size_t lcw = card_word(card) + 1; + size_t lcw = card_word(card) + (bit_position != 0); if (gc_heap::find_card_dword (lcw, card_word_end) == FALSE) { return FALSE; @@ -36945,6 +37317,7 @@ BOOL gc_heap::find_card(uint32_t* card_table, last_card_word = &card_table [lcw]; card_word_value = *last_card_word; } + bit_position = 0; #else //CARD_BUNDLE // Go through the remaining card words between here and card_word_end until we find // one that is non-zero. @@ -36969,11 +37342,11 @@ BOOL gc_heap::find_card(uint32_t* card_table, // Look for the lowest bit set if (card_word_value) { - while (!(card_word_value & 1)) - { - bit_position++; - card_word_value = card_word_value / 2; - } + DWORD bit_index; + uint8_t res = BitScanForward (&bit_index, card_word_value); + assert (res != 0); + card_word_value >>= bit_index; + bit_position += bit_index; } // card is the card word index * card size + the bit index within the card @@ -37068,8 +37441,9 @@ gc_heap::mark_through_cards_helper (uint8_t** poo, size_t& n_gen, assert (nhigh == 0); assert (next_boundary == 0); uint8_t* child_object = *poo; - if (!is_in_heap_range (child_object)) + if ((child_object < ephemeral_low) || (ephemeral_high <= child_object)) return; + int child_object_gen = get_region_gen_num (child_object); int saved_child_object_gen = child_object_gen; uint8_t* saved_child_object = child_object; @@ -37089,7 +37463,7 @@ gc_heap::mark_through_cards_helper (uint8_t** poo, size_t& n_gen, { cg_pointers_found++; dprintf (4, ("cg pointer %Ix found, %Id so far", - (size_t)*poo, cg_pointers_found )); + (size_t)*poo, cg_pointers_found )); } #else //USE_REGIONS assert (condemned_gen == -1); @@ -44432,6 +44806,12 @@ HRESULT GCHeap::Initialize() initGCShadow(); // If we are debugging write barriers, initialize heap shadow +#ifdef USE_REGIONS + gc_heap::ephemeral_low = MAX_PTR; + + gc_heap::ephemeral_high = nullptr; +#endif //!USE_REGIONS + #ifdef MULTIPLE_HEAPS for (uint32_t i = 0; i < nhp; i++) @@ -44588,7 +44968,7 @@ bool GCHeap::IsPromoted(Object* object) else { #ifdef USE_REGIONS - return (is_in_heap_range (o) ? (gc_heap::is_in_condemned_gc (o) ? gc_heap::is_mark_set (o) : true) : true); + return (gc_heap::is_in_gc_range (o) ? (gc_heap::is_in_condemned_gc (o) ? gc_heap::is_mark_set (o) : true) : true); #else gc_heap* hp = gc_heap::heap_of (o); return (!((o < hp->gc_high) && (o >= hp->gc_low)) diff --git a/src/coreclr/gc/gcconfig.h b/src/coreclr/gc/gcconfig.h index 92fe5a49ac6420..f1111a99eae5f6 100644 --- a/src/coreclr/gc/gcconfig.h +++ b/src/coreclr/gc/gcconfig.h @@ -135,7 +135,7 @@ class GCConfigStringHolder INT_CONFIG (GCHeapHardLimitPOHPercent, "GCHeapHardLimitPOHPercent", "System.GC.HeapHardLimitPOHPercent", 0, "Specifies the GC heap POH usage as a percentage of the total memory") \ INT_CONFIG (GCEnabledInstructionSets, "GCEnabledInstructionSets", NULL, -1, "Specifies whether GC can use AVX2 or AVX512F - 0 for neither, 1 for AVX2, 3 for AVX512F")\ INT_CONFIG (GCConserveMem, "GCConserveMemory", "System.GC.ConserveMemory", 0, "Specifies how hard GC should try to conserve memory - values 0-9") \ - + INT_CONFIG (GCWriteBarrier, "GCWriteBarrier", NULL, 0, "Specifies whether GC should use more precise but slower write barrier") // This class is responsible for retreiving configuration information // for how the GC should operate. class GCConfig @@ -183,6 +183,14 @@ enum HeapVerifyFlags { HEAPVERIFY_DEEP_ON_COMPACT = 0x80 // Performs deep object verfication only on compacting GCs. }; +enum WriteBarrierFlavor +{ + WRITE_BARRIER_DEFAULT = 0, + WRITE_BARRIER_REGION_BIT = 1, + WRITE_BARRIER_REGION_BYTE = 2, + WRITE_BARRIER_SERVER = 3, +}; + // Initializes the GCConfig subsystem. Must be called before accessing any // configuration information. static void Initialize(); diff --git a/src/coreclr/gc/gcinterface.h b/src/coreclr/gc/gcinterface.h index 8bdcf0c4aa22b8..023f257d10fa58 100644 --- a/src/coreclr/gc/gcinterface.h +++ b/src/coreclr/gc/gcinterface.h @@ -108,6 +108,15 @@ struct WriteBarrierParameters // The new write watch table, if we are using our own write watch // implementation. Used for WriteBarrierOp::SwitchToWriteWatch only. uint8_t* write_watch_table; + + // mapping table from region index to generation + uint8_t* region_to_generation_table; + + // shift count - how many bits to shift right to obtain region index from address + uint8_t region_shr; + + // whether to use the more precise but slower write barrier + bool region_use_bitwise_write_barrier; }; struct EtwGCSettingsInfo diff --git a/src/coreclr/gc/gcpriv.h b/src/coreclr/gc/gcpriv.h index d834dcbcc4f29e..72ff309ffb076e 100644 --- a/src/coreclr/gc/gcpriv.h +++ b/src/coreclr/gc/gcpriv.h @@ -1206,6 +1206,9 @@ enum bookkeeping_element #ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP software_write_watch_table_element, #endif +#ifdef USE_REGIONS + region_to_generation_table_element, +#endif //USE_REGIONS seg_mapping_table_element, #ifdef BACKGROUND_GC mark_array_element, @@ -1378,6 +1381,12 @@ class gc_heap PER_HEAP void set_region_plan_gen_num_sip (heap_segment* region, int plan_gen_num); PER_HEAP + void set_region_sweep_in_plan (heap_segment* region); + PER_HEAP + void clear_region_sweep_in_plan (heap_segment* region); + PER_HEAP + void clear_region_demoted (heap_segment* region); + PER_HEAP void decide_on_demotion_pin_surv (heap_segment* region); PER_HEAP void skip_pins_in_alloc_region (generation* consing_gen, int plan_gen_num); @@ -1445,6 +1454,12 @@ class gc_heap // This relocates the SIP regions and return the next non SIP region. PER_HEAP heap_segment* relocate_advance_to_non_sip (heap_segment* region); + + PER_HEAP_ISOLATED + void verify_region_to_generation_map(); + + PER_HEAP_ISOLATED + void compute_gc_and_ephemeral_range (int condemned_gen_number, bool end_of_gc_p); #ifdef STRESS_REGIONS PER_HEAP void pin_by_gc (uint8_t* object); @@ -3083,6 +3098,8 @@ class gc_heap #endif //BACKGROUND_GC #ifdef USE_REGIONS + PER_HEAP_ISOLATED + bool is_in_gc_range (uint8_t* o); // o is guaranteed to be in the heap range. PER_HEAP_ISOLATED bool is_in_condemned_gc (uint8_t* o); @@ -3673,6 +3690,39 @@ class gc_heap size_t* old_card_survived_per_region; PER_HEAP_ISOLATED size_t region_count; + + // table mapping region number to generation + // there are actually two generation numbers per entry: + // - the region's current generation + // - the region's planned generation, i.e. after the GC + // and there are flags + // - whether the region is sweep in plan + // - and whether the region is demoted + enum region_info : uint8_t + { + // lowest 2 bits are current generation number + RI_GEN_0 = 0x0, + RI_GEN_1 = 0x1, + RI_GEN_2 = 0x2, + RI_GEN_MASK = 0x3, + + // we have 4 bits available for flags, of which 2 are used + RI_SIP = 0x4, + RI_DEMOTED = 0x8, + + // top 2 bits are planned generation number + RI_PLAN_GEN_SHR = 0x6, // how much to shift the value right to obtain plan gen + RI_PLAN_GEN_0 = 0x00, + RI_PLAN_GEN_1 = 0x40, + RI_PLAN_GEN_2 = 0x80, + RI_PLAN_GEN_MASK= 0xC0, + }; + PER_HEAP_ISOLATED + region_info* map_region_to_generation; + // same table as above, but skewed so that we can index + // directly with address >> min_segment_size_shr + PER_HEAP_ISOLATED + region_info* map_region_to_generation_skewed; #endif //USE_REGIONS #define max_oom_history_count 4 @@ -3723,7 +3773,13 @@ class gc_heap PER_HEAP void exit_gc_done_event_lock(); -#ifndef USE_REGIONS +#ifdef USE_REGIONS + PER_HEAP_ISOLATED + VOLATILE(uint8_t*) ephemeral_low; //lowest ephemeral address + + PER_HEAP_ISOLATED + VOLATILE(uint8_t*) ephemeral_high; //highest ephemeral address +#else //!USE_REGIONS PER_HEAP uint8_t* ephemeral_low; //lowest ephemeral address @@ -4092,13 +4148,19 @@ class gc_heap PER_HEAP uint64_t time_bgc_last; -//#ifndef USE_REGIONS +#ifdef USE_REGIONS + PER_HEAP_ISOLATED + uint8_t* gc_low; // low end of the lowest region being condemned + + PER_HEAP_ISOLATED + uint8_t* gc_high; // high end of the highest region being condemned +#else // USE_REGIONS PER_HEAP uint8_t* gc_low; // lowest address being condemned PER_HEAP uint8_t* gc_high; // highest address being condemned -//#endif //USE_REGIONS +#endif //USE_REGIONS PER_HEAP size_t mark_stack_tos; diff --git a/src/coreclr/vm/amd64/JitHelpers_Fast.asm b/src/coreclr/vm/amd64/JitHelpers_Fast.asm index 9ec3a8617dac3b..5c654ab72a0438 100644 --- a/src/coreclr/vm/amd64/JitHelpers_Fast.asm +++ b/src/coreclr/vm/amd64/JitHelpers_Fast.asm @@ -20,6 +20,9 @@ EXTERN g_ephemeral_high:QWORD EXTERN g_lowest_address:QWORD EXTERN g_highest_address:QWORD EXTERN g_card_table:QWORD +EXTERN g_region_shr:BYTE +EXTERN g_region_use_bitwise_write_barrier:BYTE +EXTERN g_region_to_generation_table:QWORD ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES EXTERN g_card_bundle_table:QWORD @@ -135,6 +138,31 @@ endif align 16 Exit: REPRET + + NOP_3_BYTE + NOP_3_BYTE + NOP_3_BYTE + NOP_3_BYTE + NOP_3_BYTE + + NOP_3_BYTE + NOP_3_BYTE + NOP_3_BYTE + NOP_3_BYTE + NOP_3_BYTE + + NOP_3_BYTE + NOP_3_BYTE + NOP_3_BYTE + NOP_3_BYTE + NOP_3_BYTE + + NOP_3_BYTE + NOP_3_BYTE + NOP_3_BYTE + NOP_3_BYTE + NOP_3_BYTE + else ; JIT_WriteBarrier_PostGrow64 @@ -305,6 +333,60 @@ endif cmp rcx, [g_ephemeral_high] jnb Exit + ; do the following checks only if we are allowed to trash rax + ; otherwise we don't have enough registers +ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP + mov rax, rcx + + mov cl, [g_region_shr] + test cl, cl + je SkipCheck + + ; check if the source is in gen 2 - then it's not an ephemeral pointer + shr rax, cl + add rax, [g_region_to_generation_table] + cmp byte ptr [rax], 82h + je Exit + + ; check if the destination happens to be in gen 0 + mov rax, rdi + shr rax, cl + add rax, [g_region_to_generation_table] + cmp byte ptr [rax], 0 + je Exit + SkipCheck: + + cmp [g_region_use_bitwise_write_barrier], 0 + je CheckCardTableByte + + ; compute card table bit + mov rcx, rdi + mov al, 1 + shr rcx, 8 + and cl, 7 + shl al, cl + + ; move current rdi value into rcx and then increment the pointers + mov rcx, rdi + add rsi, 8h + add rdi, 8h + + ; Check if we need to update the card table + ; Calc pCardByte + shr rcx, 0Bh + add rcx, [g_card_table] + + ; Check if this card table bit is already set + test byte ptr [rcx], al + je SetCardTableBit + REPRET + + SetCardTableBit: + lock or byte ptr [rcx], al + jmp CheckCardBundle +endif +CheckCardTableByte: + ; move current rdi value into rcx and then increment the pointers mov rcx, rdi add rsi, 8h @@ -322,6 +404,9 @@ endif UpdateCardTable: mov byte ptr [rcx], 0FFh + + CheckCardBundle: + ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES ; check if we need to update the card bundle table ; restore destination address from rdi - rdi has been incremented by 8 already diff --git a/src/coreclr/vm/amd64/JitHelpers_FastWriteBarriers.asm b/src/coreclr/vm/amd64/JitHelpers_FastWriteBarriers.asm index 1bd47cae1fd8a2..68ab221278442b 100644 --- a/src/coreclr/vm/amd64/JitHelpers_FastWriteBarriers.asm +++ b/src/coreclr/vm/amd64/JitHelpers_FastWriteBarriers.asm @@ -196,6 +196,152 @@ endif ret LEAF_END_MARKED JIT_WriteBarrier_SVR64, _TEXT +LEAF_ENTRY JIT_WriteBarrier_Byte_Region64, _TEXT + align 8 + + ; Do the move into the GC . It is correct to take an AV here, the EH code + ; figures out that this came from a WriteBarrier and correctly maps it back + ; to the managed method which called the WriteBarrier (see setup in + ; InitializeExceptionHandling, vm\exceptionhandling.cpp). + mov [rcx], rdx + + mov r8, rcx + +PATCH_LABEL JIT_WriteBarrier_Byte_Region64_Patch_Label_RegionToGeneration + mov rax, 0F0F0F0F0F0F0F0F0h + +PATCH_LABEL JIT_WriteBarrier_Byte_Region64_Patch_Label_RegionShrDest + shr rcx, 16h ; compute region index + + ; Check whether the region we're storing into is gen 0 - nothing to do in this case + cmp byte ptr [rcx + rax], 0 + jne NotGen0 + REPRET + + NOP_2_BYTE ; padding for alignment of constant + + NotGen0: +PATCH_LABEL JIT_WriteBarrier_Byte_Region64_Patch_Label_Lower + mov r9, 0F0F0F0F0F0F0F0F0h + cmp rdx, r9 + jae NotLow + ret + NotLow: +PATCH_LABEL JIT_WriteBarrier_Byte_Region64_Patch_Label_Upper + mov r9, 0F0F0F0F0F0F0F0F0h + cmp rdx, r9 + jb NotHigh + REPRET + NotHigh: +PATCH_LABEL JIT_WriteBarrier_Byte_Region64_Patch_Label_RegionShrSrc + shr rdx, 16h ; compute region index + mov dl, [rdx + rax] + cmp dl, [rcx + rax] + jb isOldToYoung + REPRET + nop + + IsOldToYoung: +PATCH_LABEL JIT_WriteBarrier_Byte_Region64_Patch_Label_CardTable + mov rax, 0F0F0F0F0F0F0F0F0h + + shr r8, 0Bh + cmp byte ptr [r8 + rax], 0FFh + jne UpdateCardTable + REPRET + + UpdateCardTable: + mov byte ptr [r8 + rax], 0FFh +ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES + shr r8, 0Ah +PATCH_LABEL JIT_WriteBarrier_Byte_Region64_Patch_Label_CardBundleTable + mov rax, 0F0F0F0F0F0F0F0F0h + cmp byte ptr [r8 + rax], 0FFh + jne UpdateCardBundleTable + REPRET + + UpdateCardBundleTable: + mov byte ptr [r8 + rax], 0FFh +endif + ret +LEAF_END_MARKED JIT_WriteBarrier_Byte_Region64, _TEXT + +LEAF_ENTRY JIT_WriteBarrier_Bit_Region64, _TEXT + align 8 + + ; Do the move into the GC . It is correct to take an AV here, the EH code + ; figures out that this came from a WriteBarrier and correctly maps it back + ; to the managed method which called the WriteBarrier (see setup in + ; InitializeExceptionHandling, vm\exceptionhandling.cpp). + mov [rcx], rdx + + mov r8, rcx + +PATCH_LABEL JIT_WriteBarrier_Bit_Region64_Patch_Label_RegionToGeneration + mov rax, 0F0F0F0F0F0F0F0F0h + +PATCH_LABEL JIT_WriteBarrier_Bit_Region64_Patch_Label_RegionShrDest + shr rcx, 16h ; compute region index + + ; Check whether the region we're storing into is gen 0 - nothing to do in this case + cmp byte ptr [rcx + rax], 0 + jne NotGen0 + REPRET + + NOP_2_BYTE ; padding for alignment of constant + + NotGen0: +PATCH_LABEL JIT_WriteBarrier_Bit_Region64_Patch_Label_Lower + mov r9, 0F0F0F0F0F0F0F0F0h + cmp rdx, r9 + jae NotLow + ret + NotLow: +PATCH_LABEL JIT_WriteBarrier_Bit_Region64_Patch_Label_Upper + mov r9, 0F0F0F0F0F0F0F0F0h + cmp rdx, r9 + jb NotHigh + REPRET + NotHigh: +PATCH_LABEL JIT_WriteBarrier_Bit_Region64_Patch_Label_RegionShrSrc + shr rdx, 16h ; compute region index + mov dl, [rdx + rax] + cmp dl, [rcx + rax] + jb isOldToYoung + REPRET + nop + + IsOldToYoung: +PATCH_LABEL JIT_WriteBarrier_Bit_Region64_Patch_Label_CardTable + mov rax, 0F0F0F0F0F0F0F0F0h + + mov ecx, r8d + shr r8, 0Bh + shr ecx, 8 + and ecx, 7 + mov dl, 1 + shl dl, cl + test byte ptr [r8 + rax], dl + je UpdateCardTable + REPRET + + UpdateCardTable: + lock or byte ptr [r8 + rax], dl + +ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES +PATCH_LABEL JIT_WriteBarrier_Bit_Region64_Patch_Label_CardBundleTable + mov rax, 0F0F0F0F0F0F0F0F0h + shr r8, 0Ah + cmp byte ptr [r8 + rax], 0FFh + jne UpdateCardBundleTable + REPRET + + UpdateCardBundleTable: + mov byte ptr [r8 + rax], 0FFh +endif + ret +LEAF_END_MARKED JIT_WriteBarrier_Bit_Region64, _TEXT + endif @@ -405,6 +551,172 @@ endif LEAF_END_MARKED JIT_WriteBarrier_WriteWatch_SVR64, _TEXT endif + +LEAF_ENTRY JIT_WriteBarrier_WriteWatch_Byte_Region64, _TEXT + align 8 + + ; Do the move into the GC . It is correct to take an AV here, the EH code + ; figures out that this came from a WriteBarrier and correctly maps it back + ; to the managed method which called the WriteBarrier (see setup in + ; InitializeExceptionHandling, vm\exceptionhandling.cpp). + mov [rcx], rdx + + ; Update the write watch table if necessary + mov rax, rcx +PATCH_LABEL JIT_WriteBarrier_WriteWatch_Byte_Region64_Patch_Label_WriteWatchTable + mov r8, 0F0F0F0F0F0F0F0F0h + shr rax, 0Ch ; SoftwareWriteWatch::AddressToTableByteIndexShift + add rax, r8 + mov r8, rcx +PATCH_LABEL JIT_WriteBarrier_WriteWatch_Byte_Region64_Patch_Label_RegionShrDest + shr rcx, 16h ; compute region index + cmp byte ptr [rax], 0h + jne JIT_WriteBarrier_WriteWatch_Byte_Region64_Patch_Label_RegionToGeneration + mov byte ptr [rax], 0FFh + +PATCH_LABEL JIT_WriteBarrier_WriteWatch_Byte_Region64_Patch_Label_RegionToGeneration + mov rax, 0F0F0F0F0F0F0F0F0h + + ; Check whether the region we're storing into is gen 0 - nothing to do in this case + cmp byte ptr [rcx + rax], 0 + jne NotGen0 + REPRET + + NOP_2_BYTE ; padding for alignment of constant + NOP_2_BYTE ; padding for alignment of constant + NOP_2_BYTE ; padding for alignment of constant + + NotGen0: +PATCH_LABEL JIT_WriteBarrier_WriteWatch_Byte_Region64_Patch_Label_Lower + mov r9, 0F0F0F0F0F0F0F0F0h + cmp rdx, r9 + jae NotLow + ret + NotLow: +PATCH_LABEL JIT_WriteBarrier_WriteWatch_Byte_Region64_Patch_Label_Upper + mov r9, 0F0F0F0F0F0F0F0F0h + cmp rdx, r9 + jb NotHigh + REPRET + NotHigh: +PATCH_LABEL JIT_WriteBarrier_WriteWatch_Byte_Region64_Patch_Label_RegionShrSrc + shr rdx, 16h ; compute region index + mov dl, [rdx + rax] + cmp dl, [rcx + rax] + jb isOldToYoung + REPRET + nop + + IsOldToYoung: +PATCH_LABEL JIT_WriteBarrier_WriteWatch_Byte_Region64_Patch_Label_CardTable + mov rax, 0F0F0F0F0F0F0F0F0h + + shr r8, 0Bh + cmp byte ptr [r8 + rax], 0FFh + jne UpdateCardTable + REPRET + + UpdateCardTable: + mov byte ptr [r8 + rax], 0FFh +ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES + shr r8, 0Ah +PATCH_LABEL JIT_WriteBarrier_WriteWatch_Byte_Region64_Patch_Label_CardBundleTable + mov rax, 0F0F0F0F0F0F0F0F0h + cmp byte ptr [r8 + rax], 0FFh + jne UpdateCardBundleTable + REPRET + + UpdateCardBundleTable: + mov byte ptr [r8 + rax], 0FFh +endif + ret +LEAF_END_MARKED JIT_WriteBarrier_WriteWatch_Byte_Region64, _TEXT + +LEAF_ENTRY JIT_WriteBarrier_WriteWatch_Bit_Region64, _TEXT + align 8 + + ; Do the move into the GC . It is correct to take an AV here, the EH code + ; figures out that this came from a WriteBarrier and correctly maps it back + ; to the managed method which called the WriteBarrier (see setup in + ; InitializeExceptionHandling, vm\exceptionhandling.cpp). + mov [rcx], rdx + + ; Update the write watch table if necessary + mov rax, rcx +PATCH_LABEL JIT_WriteBarrier_WriteWatch_Bit_Region64_Patch_Label_WriteWatchTable + mov r8, 0F0F0F0F0F0F0F0F0h + shr rax, 0Ch ; SoftwareWriteWatch::AddressToTableByteIndexShift + add rax, r8 + mov r8, rcx +PATCH_LABEL JIT_WriteBarrier_WriteWatch_Bit_Region64_Patch_Label_RegionShrDest + shr rcx, 16h ; compute region index + cmp byte ptr [rax], 0h + jne JIT_WriteBarrier_WriteWatch_Bit_Region64_Patch_Label_RegionToGeneration + mov byte ptr [rax], 0FFh + +PATCH_LABEL JIT_WriteBarrier_WriteWatch_Bit_Region64_Patch_Label_RegionToGeneration + mov rax, 0F0F0F0F0F0F0F0F0h + + ; Check whether the region we're storing into is gen 0 - nothing to do in this case + cmp byte ptr [rcx + rax], 0 + jne NotGen0 + REPRET + + NOP_2_BYTE ; padding for alignment of constant + NOP_2_BYTE ; padding for alignment of constant + NOP_2_BYTE ; padding for alignment of constant + + NotGen0: +PATCH_LABEL JIT_WriteBarrier_WriteWatch_Bit_Region64_Patch_Label_Lower + mov r9, 0F0F0F0F0F0F0F0F0h + cmp rdx, r9 + jae NotLow + ret + NotLow: +PATCH_LABEL JIT_WriteBarrier_WriteWatch_Bit_Region64_Patch_Label_Upper + mov r9, 0F0F0F0F0F0F0F0F0h + cmp rdx, r9 + jb NotHigh + REPRET + NotHigh: +PATCH_LABEL JIT_WriteBarrier_WriteWatch_Bit_Region64_Patch_Label_RegionShrSrc + shr rdx, 16h ; compute region index + mov dl, [rdx + rax] + cmp dl, [rcx + rax] + jb isOldToYoung + REPRET + nop + + IsOldToYoung: +PATCH_LABEL JIT_WriteBarrier_WriteWatch_Bit_Region64_Patch_Label_CardTable + mov rax, 0F0F0F0F0F0F0F0F0h + + mov ecx, r8d + shr r8, 0Bh + shr ecx, 8 + and ecx, 7 + mov dl, 1 + shl dl, cl + test byte ptr [r8 + rax], dl + je UpdateCardTable + REPRET + + UpdateCardTable: + lock or byte ptr [r8 + rax], dl +ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES +PATCH_LABEL JIT_WriteBarrier_WriteWatch_Bit_Region64_Patch_Label_CardBundleTable + mov rax, 0F0F0F0F0F0F0F0F0h + shr r8, 0Ah + cmp byte ptr [r8 + rax], 0FFh + jne UpdateCardBundleTable + REPRET + + UpdateCardBundleTable: + mov byte ptr [r8 + rax], 0FFh +endif + ret +LEAF_END_MARKED JIT_WriteBarrier_WriteWatch_Bit_Region64, _TEXT + endif diff --git a/src/coreclr/vm/amd64/jithelpers_fast.S b/src/coreclr/vm/amd64/jithelpers_fast.S index 5d7cb379ce96d1..9b582103509c2a 100644 --- a/src/coreclr/vm/amd64/jithelpers_fast.S +++ b/src/coreclr/vm/amd64/jithelpers_fast.S @@ -138,6 +138,31 @@ LEAF_ENTRY JIT_WriteBarrier, _TEXT .balign 16 Exit: REPRET + + NOP_3_BYTE + NOP_3_BYTE + NOP_3_BYTE + NOP_3_BYTE + NOP_3_BYTE + + NOP_3_BYTE + NOP_3_BYTE + NOP_3_BYTE + NOP_3_BYTE + NOP_3_BYTE + + NOP_3_BYTE + NOP_3_BYTE + NOP_3_BYTE + NOP_3_BYTE + NOP_3_BYTE + + NOP_3_BYTE + NOP_3_BYTE + NOP_3_BYTE + NOP_3_BYTE + NOP_3_BYTE + #else // JIT_WriteBarrier_PostGrow64 @@ -330,6 +355,41 @@ LEAF_ENTRY JIT_ByRefWriteBarrier, _TEXT cmp rcx, [rax] jnb Exit_ByRefWriteBarrier + mov rax, rcx + + PREPARE_EXTERNAL_VAR g_region_shr, rcx + mov cl, [rcx] + test cl, cl + je SkipCheck_ByRefWriteBarrier + + // check if the source is in gen 2 - then it's not an ephemeral pointer + shr rax, cl + PREPARE_EXTERNAL_VAR g_region_to_generation_table, r10 + mov r10, [r10] + cmp byte ptr [rax + r10], 0x82 + je Exit_ByRefWriteBarrier + + // check if the destination happens to be in gen 0 + mov rax, rdi + shr rax, cl + cmp byte ptr [rax + r10], 0 + je Exit_ByRefWriteBarrier + SkipCheck_ByRefWriteBarrier: + + PREPARE_EXTERNAL_VAR g_card_table, r10 + mov r10, [r10] + + PREPARE_EXTERNAL_VAR g_region_use_bitwise_write_barrier, rax + cmp byte ptr [rax], 0 + je CheckCardTableByte_ByRefWriteBarrier + + // compute card table bit + mov ecx, edi + mov al, 1 + shr ecx, 8 + and cl, 7 + shl al, cl + // move current rdi value into rcx and then increment the pointers mov rcx, rdi add rsi, 0x8 @@ -337,19 +397,31 @@ LEAF_ENTRY JIT_ByRefWriteBarrier, _TEXT // Check if we need to update the card table // Calc pCardByte - shr rcx, 0x0B + shr rcx, 0xB + // Check if this card table bit is already set + test byte ptr [rcx + r10], al + je SetCardTableBit_ByRefWriteBarrier + REPRET + + SetCardTableBit_ByRefWriteBarrier: + lock or byte ptr [rcx + r10], al - PREPARE_EXTERNAL_VAR g_card_table, rax - mov rax, [rax] + jmp CheckCardBundle_ByRefWriteBarrier - // Check if this card is dirty - cmp byte ptr [rcx + rax], 0xFF + CheckCardTableByte_ByRefWriteBarrier: + // move current rdi value into rcx and then increment the pointers + mov rcx, rdi + add rsi, 0x8 + add rdi, 0x8 - jne UpdateCardTable_ByRefWriteBarrier + shr rcx, 0xB + cmp byte ptr [rcx + r10], 0xFF + jne SetCardTableByte_ByRefWriteBarrier REPRET + SetCardTableByte_ByRefWriteBarrier: + mov byte ptr [rcx + r10], 0xFF - UpdateCardTable_ByRefWriteBarrier: - mov byte ptr [rcx + rax], 0xFF + CheckCardBundle_ByRefWriteBarrier: #ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES // Shift rcx by 0x0A more to get the card bundle byte (we shifted by 0x0B already) diff --git a/src/coreclr/vm/amd64/jithelpers_fastwritebarriers.S b/src/coreclr/vm/amd64/jithelpers_fastwritebarriers.S index 70d2f2072aa51a..f987751bdcb358 100644 --- a/src/coreclr/vm/amd64/jithelpers_fastwritebarriers.S +++ b/src/coreclr/vm/amd64/jithelpers_fastwritebarriers.S @@ -5,7 +5,7 @@ #include "unixasmmacros.inc" - .balign 8 + .balign 16 LEAF_ENTRY JIT_WriteBarrier_PreGrow64, _TEXT // Do the move into the GC . It is correct to take an AV here, the EH code // figures out that this came from a WriteBarrier and correctly maps it back @@ -26,7 +26,7 @@ PATCH_LABEL JIT_WriteBarrier_PreGrow64_Patch_Label_Lower #ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES .byte 0x72, 0x4B #else - .byte 0x72, 0x23 + .byte 0x72, 0x2b #endif // jb Exit_PreGrow64 @@ -72,7 +72,7 @@ PATCH_LABEL JIT_WriteBarrier_PreGrow64_Patch_Label_CardBundleTable LEAF_END_MARKED JIT_WriteBarrier_PreGrow64, _TEXT - .balign 8 + .balign 16 // See comments for JIT_WriteBarrier_PreGrow (above). LEAF_ENTRY JIT_WriteBarrier_PostGrow64, _TEXT // Do the move into the GC . It is correct to take an AV here, the EH code @@ -95,9 +95,9 @@ PATCH_LABEL JIT_WriteBarrier_PostGrow64_Patch_Label_Lower cmp rsi, rax #ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES - .byte 0x72, 0x53 + .byte 0x72, 0x5b #else - .byte 0x72, 0x33 + .byte 0x72, 0x3b #endif // jb Exit_PostGrow64 @@ -109,9 +109,9 @@ PATCH_LABEL JIT_WriteBarrier_PostGrow64_Patch_Label_Upper cmp rsi, r8 #ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES - .byte 0x73, 0x43 + .byte 0x73, 0x4b #else - .byte 0x73, 0x23 + .byte 0x73, 0x2b #endif // jae Exit_PostGrow64 @@ -159,7 +159,7 @@ LEAF_END_MARKED JIT_WriteBarrier_PostGrow64, _TEXT #ifdef FEATURE_SVR_GC - .balign 8 + .balign 16 LEAF_ENTRY JIT_WriteBarrier_SVR64, _TEXT // // SVR GC has multiple heaps, so it cannot provide one single @@ -213,10 +213,165 @@ LEAF_END_MARKED JIT_WriteBarrier_SVR64, _TEXT #endif + .balign 16 +LEAF_ENTRY JIT_WriteBarrier_Byte_Region64, _TEXT + + // Do the move into the GC . It is correct to take an AV here, the EH code + // figures out that this came from a WriteBarrier and correctly maps it back + // to the managed method which called the WriteBarrier (see setup in + // InitializeExceptionHandling, vm\exceptionhandling.cpp). + mov [rdi], rsi + + mov r8, rdi + +PATCH_LABEL JIT_WriteBarrier_Byte_Region64_Patch_Label_RegionToGeneration + movabs rax, 0xF0F0F0F0F0F0F0F0 + +PATCH_LABEL JIT_WriteBarrier_Byte_Region64_Patch_Label_RegionShrDest + shr rdi, 0x16 // compute region index + + // Check whether the region we're storing into is gen 0 - nothing to do in this case + cmp byte ptr [rdi + rax], 0 + .byte 0x75, 0x04 + //jne NotGen0_Byte_Region64 + REPRET + + NOP_2_BYTE // padding for alignment of constant + + NotGen0_Byte_Region64: +PATCH_LABEL JIT_WriteBarrier_Byte_Region64_Patch_Label_Lower + movabs r9, 0xF0F0F0F0F0F0F0F0 + cmp rsi, r9 + .byte 0x73, 0x01 + // jae NotLow_Byte_Region64 + ret + NotLow_Byte_Region64: +PATCH_LABEL JIT_WriteBarrier_Byte_Region64_Patch_Label_Upper + movabs r9, 0xF0F0F0F0F0F0F0F0 + cmp rsi, r9 + .byte 0x72, 0x02 + // jb NotHigh_Byte_Region64 + REPRET + NotHigh_Byte_Region64: +PATCH_LABEL JIT_WriteBarrier_Byte_Region64_Patch_Label_RegionShrSrc + shr rsi, 0x16 // compute region index + mov dl, [rsi + rax] + cmp dl, [rdi + rax] + .byte 0x72, 0x03 + // jb IsOldToYoung_Byte_Region64 + REPRET + nop + + IsOldToYoung_Byte_Region64: +PATCH_LABEL JIT_WriteBarrier_Byte_Region64_Patch_Label_CardTable + movabs rax, 0xF0F0F0F0F0F0F0F0 + + shr r8, 0xB + cmp byte ptr [r8 + rax], 0xFF + .byte 0x75, 0x02 + // jne UpdateCardTable_Byte_Region64 + REPRET + + UpdateCardTable_Byte_Region64: + mov byte ptr [r8 + rax], 0xFF +#ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES + shr r8, 0x0A +PATCH_LABEL JIT_WriteBarrier_Byte_Region64_Patch_Label_CardBundleTable + movabs rax, 0xF0F0F0F0F0F0F0F0 + cmp byte ptr [r8 + rax], 0xFF + .byte 0x75, 0x02 + // jne UpdateCardBundleTable_Byte_Region64 + REPRET + + UpdateCardBundleTable_Byte_Region64: + mov byte ptr [r8 + rax], 0xFF +#endif + ret +LEAF_END_MARKED JIT_WriteBarrier_Byte_Region64, _TEXT + .balign 16 +LEAF_ENTRY JIT_WriteBarrier_Bit_Region64, _TEXT + + // Do the move into the GC . It is correct to take an AV here, the EH code + // figures out that this came from a WriteBarrier and correctly maps it back + // to the managed method which called the WriteBarrier (see setup in + // InitializeExceptionHandling, vm\exceptionhandling.cpp). + mov [rdi], rsi + + mov r8, rdi + +PATCH_LABEL JIT_WriteBarrier_Bit_Region64_Patch_Label_RegionToGeneration + movabs rax, 0xF0F0F0F0F0F0F0F0 + +PATCH_LABEL JIT_WriteBarrier_Bit_Region64_Patch_Label_RegionShrDest + shr rdi, 0x16 // compute region index + + // Check whether the region we're storing into is gen 0 - nothing to do in this case + cmp byte ptr [rdi + rax], 0 + .byte 0x75, 0x04 + //jne NotGen0_Bit_Region64 + REPRET + + NOP_2_BYTE // padding for alignment of constant + + NotGen0_Bit_Region64: +PATCH_LABEL JIT_WriteBarrier_Bit_Region64_Patch_Label_Lower + movabs r9, 0xF0F0F0F0F0F0F0F0 + cmp rsi, r9 + .byte 0x73, 0x01 + // jae NotLow_Bit_Region64 + ret + NotLow_Bit_Region64: +PATCH_LABEL JIT_WriteBarrier_Bit_Region64_Patch_Label_Upper + movabs r9, 0xF0F0F0F0F0F0F0F0 + cmp rsi, r9 + .byte 0x72, 0x02 + // jb NotHigh_Bit_Region64 + REPRET + NotHigh_Bit_Region64: +PATCH_LABEL JIT_WriteBarrier_Bit_Region64_Patch_Label_RegionShrSrc + shr rsi, 0x16 // compute region index + mov dl, [rsi + rax] + cmp dl, [rdi + rax] + .byte 0x72, 0x03 + // jb IsOldToYoung_Bit_Region64 + REPRET + nop + + IsOldToYoung_Bit_Region64: +PATCH_LABEL JIT_WriteBarrier_Bit_Region64_Patch_Label_CardTable + movabs rax, 0xF0F0F0F0F0F0F0F0 + + mov ecx, r8d + shr r8, 0xB + shr ecx, 8 + and ecx, 7 + mov dl, 1 + shl dl, cl + test byte ptr [r8 + rax], dl + .byte 0x74, 0x02 + // je UpdateCardTable_Bit_Region64 + REPRET + + UpdateCardTable_Bit_Region64: + lock or byte ptr [r8 + rax], dl +#ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES +PATCH_LABEL JIT_WriteBarrier_Bit_Region64_Patch_Label_CardBundleTable + movabs rax, 0xF0F0F0F0F0F0F0F0 + shr r8, 0x0A + cmp byte ptr [r8 + rax], 0xFF + .byte 0x75, 0x02 + // jne UpdateCardBundleTable_Bit_Region64 + REPRET + + UpdateCardBundleTable_Bit_Region64: + mov byte ptr [r8 + rax], 0xFF +#endif + ret +LEAF_END_MARKED JIT_WriteBarrier_Bit_Region64, _TEXT #ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP - .balign 8 + .balign 16 LEAF_ENTRY JIT_WriteBarrier_WriteWatch_PreGrow64, _TEXT // Regarding patchable constants: // - 64-bit constants have to be loaded into a register @@ -294,7 +449,7 @@ PATCH_LABEL JIT_WriteBarrier_WriteWatch_PreGrow64_Patch_Label_CardBundleTable LEAF_END_MARKED JIT_WriteBarrier_WriteWatch_PreGrow64, _TEXT - .balign 8 + .balign 16 LEAF_ENTRY JIT_WriteBarrier_WriteWatch_PostGrow64, _TEXT // Regarding patchable constants: // - 64-bit constants have to be loaded into a register @@ -330,7 +485,7 @@ PATCH_LABEL JIT_WriteBarrier_WriteWatch_PostGrow64_Patch_Label_Lower cmp rsi, r11 #ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES - .byte 0x72, 0x55 + .byte 0x72, 0x4d #else .byte 0x72, 0x3d #endif @@ -344,7 +499,7 @@ PATCH_LABEL JIT_WriteBarrier_WriteWatch_PostGrow64_Patch_Label_Upper cmp rsi, r10 #ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES - .byte 0x73, 0x43 + .byte 0x73, 0x3b #else .byte 0x73, 0x2b #endif @@ -390,7 +545,7 @@ LEAF_END_MARKED JIT_WriteBarrier_WriteWatch_PostGrow64, _TEXT #ifdef FEATURE_SVR_GC - .balign 8 + .balign 16 LEAF_ENTRY JIT_WriteBarrier_WriteWatch_SVR64, _TEXT // Regarding patchable constants: // - 64-bit constants have to be loaded into a register @@ -457,4 +612,182 @@ PATCH_LABEL JIT_WriteBarrier_WriteWatch_SVR64_PatchLabel_CardBundleTable LEAF_END_MARKED JIT_WriteBarrier_WriteWatch_SVR64, _TEXT #endif + .balign 16 +LEAF_ENTRY JIT_WriteBarrier_WriteWatch_Byte_Region64, _TEXT + + // Do the move into the GC . It is correct to take an AV here, the EH code + // figures out that this came from a WriteBarrier and correctly maps it back + // to the managed method which called the WriteBarrier (see setup in + // InitializeExceptionHandling, vm\exceptionhandling.cpp). + mov [rdi], rsi + + // Update the write watch table if necessary + mov rax, rdi +PATCH_LABEL JIT_WriteBarrier_WriteWatch_Byte_Region64_Patch_Label_WriteWatchTable + movabs r8, 0xF0F0F0F0F0F0F0F0 + shr rax, 0x0C // SoftwareWriteWatch::AddressToTableByteIndexShift + add rax, r8 + mov r8, rdi +PATCH_LABEL JIT_WriteBarrier_WriteWatch_Byte_Region64_Patch_Label_RegionShrDest + shr rdi, 0x16 // compute region index + cmp byte ptr [rax], 0x0 + .byte 0x75, 0x03 + // jne CheckGen0_WriteWatch_Byte_Region64 + mov byte ptr [rax], 0xFF + CheckGen0_WriteWatch_Byte_Region64: +PATCH_LABEL JIT_WriteBarrier_WriteWatch_Byte_Region64_Patch_Label_RegionToGeneration + mov rax, 0xF0F0F0F0F0F0F0F0 + + // Check whether the region we're storing into is gen 0 - nothing to do in this case + cmp byte ptr [rdi + rax], 0 + .byte 0x75, 0x08 + // jne NotGen0_WriteWatch_Byte_Region64 + REPRET + + NOP_2_BYTE // padding for alignment of constant + NOP_2_BYTE // padding for alignment of constant + NOP_2_BYTE // padding for alignment of constant + + NotGen0_WriteWatch_Byte_Region64: +PATCH_LABEL JIT_WriteBarrier_WriteWatch_Byte_Region64_Patch_Label_Lower + movabs r9, 0xF0F0F0F0F0F0F0F0 + cmp rsi, r9 + .byte 0x73, 0x01 + // jae NotLow_WriteWatch_Byte_Region64 + ret + NotLow_WriteWatch_Byte_Region64: +PATCH_LABEL JIT_WriteBarrier_WriteWatch_Byte_Region64_Patch_Label_Upper + mov r9, 0xF0F0F0F0F0F0F0F0 + cmp rsi, r9 + .byte 0x72, 0x02 + // jb NotHigh_WriteWatch_Byte_Region64 + REPRET + NotHigh_WriteWatch_Byte_Region64: +PATCH_LABEL JIT_WriteBarrier_WriteWatch_Byte_Region64_Patch_Label_RegionShrSrc + shr rsi, 0x16 // compute region index + mov dl, [rsi + rax] + cmp dl, [rdi + rax] + .byte 0x72, 0x03 + // jb IsOldToYoung_WriteWatch_Byte_Region64 + REPRET + nop + + IsOldToYoung_WriteWatch_Byte_Region64: +PATCH_LABEL JIT_WriteBarrier_WriteWatch_Byte_Region64_Patch_Label_CardTable + mov rax, 0xF0F0F0F0F0F0F0F0 + + shr r8, 0xB + cmp byte ptr [r8 + rax], 0xFF + .byte 0x75, 0x02 + // jne UpdateCardTable_WriteWatch_Byte_Region64 + REPRET + + UpdateCardTable_WriteWatch_Byte_Region64: + mov byte ptr [r8 + rax], 0xFF +#ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES + shr r8, 0x0A +PATCH_LABEL JIT_WriteBarrier_WriteWatch_Byte_Region64_Patch_Label_CardBundleTable + mov rax, 0xF0F0F0F0F0F0F0F0 + cmp byte ptr [r8 + rax], 0xFF + .byte 0x75, 0x02 + // jne UpdateCardBundleTable_WriteWatch_Byte_Region64 + REPRET + + UpdateCardBundleTable_WriteWatch_Byte_Region64: + mov byte ptr [r8 + rax], 0xFF +#endif + ret +LEAF_END_MARKED JIT_WriteBarrier_WriteWatch_Byte_Region64, _TEXT + .balign 16 +LEAF_ENTRY JIT_WriteBarrier_WriteWatch_Bit_Region64, _TEXT + + // Do the move into the GC . It is correct to take an AV here, the EH code + // figures out that this came from a WriteBarrier and correctly maps it back + // to the managed method which called the WriteBarrier (see setup in + // InitializeExceptionHandling, vm\exceptionhandling.cpp). + mov [rdi], rsi + + // Update the write watch table if necessary + mov rax, rdi +PATCH_LABEL JIT_WriteBarrier_WriteWatch_Bit_Region64_Patch_Label_WriteWatchTable + movabs r8, 0xF0F0F0F0F0F0F0F0 + shr rax, 0x0C // SoftwareWriteWatch::AddressToTableByteIndexShift + add rax, r8 + mov r8, rdi +PATCH_LABEL JIT_WriteBarrier_WriteWatch_Bit_Region64_Patch_Label_RegionShrDest + shr rdi, 0x16 // compute region index + cmp byte ptr [rax], 0x0 + .byte 0x75, 0x03 + // jne CheckGen0_WriteWatch_Bit_Region64 + mov byte ptr [rax], 0xFF + CheckGen0_WriteWatch_Bit_Region64: +PATCH_LABEL JIT_WriteBarrier_WriteWatch_Bit_Region64_Patch_Label_RegionToGeneration + mov rax, 0xF0F0F0F0F0F0F0F0 + + // Check whether the region we're storing into is gen 0 - nothing to do in this case + cmp byte ptr [rdi + rax], 0 + .byte 0x75, 0x08 + // jne NotGen0_WriteWatch_Bit_Region64 + REPRET + + NOP_2_BYTE // padding for alignment of constant + NOP_2_BYTE // padding for alignment of constant + NOP_2_BYTE // padding for alignment of constant + + NotGen0_WriteWatch_Bit_Region64: +PATCH_LABEL JIT_WriteBarrier_WriteWatch_Bit_Region64_Patch_Label_Lower + movabs r9, 0xF0F0F0F0F0F0F0F0 + cmp rsi, r9 + .byte 0x73, 0x01 + // jae NotLow_WriteWatch_Bit_Region64 + ret + NotLow_WriteWatch_Bit_Region64: +PATCH_LABEL JIT_WriteBarrier_WriteWatch_Bit_Region64_Patch_Label_Upper + mov r9, 0xF0F0F0F0F0F0F0F0 + cmp rsi, r9 + .byte 0x72, 0x02 + // jb NotHigh_WriteWatch_Bit_Region64 + REPRET + NotHigh_WriteWatch_Bit_Region64: +PATCH_LABEL JIT_WriteBarrier_WriteWatch_Bit_Region64_Patch_Label_RegionShrSrc + shr rsi, 0x16 // compute region index + mov dl, [rsi + rax] + cmp dl, [rdi + rax] + .byte 0x72, 0x03 + // jb IsOldToYoung_WriteWatch_Bit_Region64 + REPRET + nop + + IsOldToYoung_WriteWatch_Bit_Region64: +PATCH_LABEL JIT_WriteBarrier_WriteWatch_Bit_Region64_Patch_Label_CardTable + mov rax, 0xF0F0F0F0F0F0F0F0 + + mov ecx, r8d + shr r8, 0xB + shr ecx, 8 + and ecx, 7 + mov dl, 1 + shl dl, cl + test byte ptr [r8 + rax], dl + .byte 0x74, 0x02 + // je UpdateCardTable_WriteWatch_Bit_Region64 + REPRET + + UpdateCardTable_WriteWatch_Bit_Region64: + lock or byte ptr [r8 + rax], dl +#ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES +PATCH_LABEL JIT_WriteBarrier_WriteWatch_Bit_Region64_Patch_Label_CardBundleTable + mov rax, 0xF0F0F0F0F0F0F0F0 + shr r8, 0x0A + cmp byte ptr [r8 + rax], 0xFF + .byte 0x75, 0x02 + // jne UpdateCardBundleTable_WriteWatch_Bit_Region64 + REPRET + + UpdateCardBundleTable_WriteWatch_Bit_Region64: + mov byte ptr [r8 + rax], 0xFF +#endif + ret +LEAF_END_MARKED JIT_WriteBarrier_WriteWatch_Bit_Region64, _TEXT + #endif diff --git a/src/coreclr/vm/amd64/jitinterfaceamd64.cpp b/src/coreclr/vm/amd64/jitinterfaceamd64.cpp index 29f2bb7a5afb18..08d5e9493f6c22 100644 --- a/src/coreclr/vm/amd64/jitinterfaceamd64.cpp +++ b/src/coreclr/vm/amd64/jitinterfaceamd64.cpp @@ -50,6 +50,31 @@ EXTERN_C void JIT_WriteBarrier_SVR64_PatchLabel_CardBundleTable(); EXTERN_C void JIT_WriteBarrier_SVR64_End(); #endif // FEATURE_SVR_GC +EXTERN_C void JIT_WriteBarrier_Byte_Region64(Object **dst, Object *ref); +EXTERN_C void JIT_WriteBarrier_Byte_Region64_Patch_Label_RegionToGeneration(); +EXTERN_C void JIT_WriteBarrier_Byte_Region64_Patch_Label_RegionShrDest(); +EXTERN_C void JIT_WriteBarrier_Byte_Region64_Patch_Label_Lower(); +EXTERN_C void JIT_WriteBarrier_Byte_Region64_Patch_Label_Upper(); +EXTERN_C void JIT_WriteBarrier_Byte_Region64_Patch_Label_RegionShrSrc(); +EXTERN_C void JIT_WriteBarrier_Byte_Region64_Patch_Label_CardTable(); +#ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES +EXTERN_C void JIT_WriteBarrier_Byte_Region64_Patch_Label_CardBundleTable(); +#endif +EXTERN_C void JIT_WriteBarrier_Byte_Region64_End(); + +EXTERN_C void JIT_WriteBarrier_Bit_Region64(Object **dst, Object *ref); +EXTERN_C void JIT_WriteBarrier_Bit_Region64_Patch_Label_RegionToGeneration(); +EXTERN_C void JIT_WriteBarrier_Bit_Region64_Patch_Label_RegionShrDest(); +EXTERN_C void JIT_WriteBarrier_Bit_Region64_Patch_Label_Lower(); +EXTERN_C void JIT_WriteBarrier_Bit_Region64_Patch_Label_Upper(); +EXTERN_C void JIT_WriteBarrier_Bit_Region64_Patch_Label_RegionShrSrc(); +EXTERN_C void JIT_WriteBarrier_Bit_Region64_Patch_Label_CardTable(); +#ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES +EXTERN_C void JIT_WriteBarrier_Bit_Region64_Patch_Label_CardBundleTable(); +#endif +EXTERN_C void JIT_WriteBarrier_Bit_Region64_End(); + + #ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP EXTERN_C void JIT_WriteBarrier_WriteWatch_PreGrow64(Object **dst, Object *ref); EXTERN_C void JIT_WriteBarrier_WriteWatch_PreGrow64_Patch_Label_WriteWatchTable(); @@ -79,6 +104,33 @@ EXTERN_C void JIT_WriteBarrier_WriteWatch_SVR64_PatchLabel_CardBundleTable(); #endif EXTERN_C void JIT_WriteBarrier_WriteWatch_SVR64_End(); #endif // FEATURE_SVR_GC + +EXTERN_C void JIT_WriteBarrier_WriteWatch_Byte_Region64(Object **dst, Object *ref); +EXTERN_C void JIT_WriteBarrier_WriteWatch_Byte_Region64_Patch_Label_WriteWatchTable(); +EXTERN_C void JIT_WriteBarrier_WriteWatch_Byte_Region64_Patch_Label_RegionToGeneration(); +EXTERN_C void JIT_WriteBarrier_WriteWatch_Byte_Region64_Patch_Label_RegionShrDest(); +EXTERN_C void JIT_WriteBarrier_WriteWatch_Byte_Region64_Patch_Label_Lower(); +EXTERN_C void JIT_WriteBarrier_WriteWatch_Byte_Region64_Patch_Label_Upper(); +EXTERN_C void JIT_WriteBarrier_WriteWatch_Byte_Region64_Patch_Label_RegionShrSrc(); +EXTERN_C void JIT_WriteBarrier_WriteWatch_Byte_Region64_Patch_Label_CardTable(); +#ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES +EXTERN_C void JIT_WriteBarrier_WriteWatch_Byte_Region64_Patch_Label_CardBundleTable(); +#endif +EXTERN_C void JIT_WriteBarrier_WriteWatch_Byte_Region64_End(); + +EXTERN_C void JIT_WriteBarrier_WriteWatch_Bit_Region64(Object **dst, Object *ref); +EXTERN_C void JIT_WriteBarrier_WriteWatch_Bit_Region64_Patch_Label_WriteWatchTable(); +EXTERN_C void JIT_WriteBarrier_WriteWatch_Bit_Region64_Patch_Label_RegionToGeneration(); +EXTERN_C void JIT_WriteBarrier_WriteWatch_Bit_Region64_Patch_Label_RegionShrDest(); +EXTERN_C void JIT_WriteBarrier_WriteWatch_Bit_Region64_Patch_Label_Lower(); +EXTERN_C void JIT_WriteBarrier_WriteWatch_Bit_Region64_Patch_Label_Upper(); +EXTERN_C void JIT_WriteBarrier_WriteWatch_Bit_Region64_Patch_Label_RegionShrSrc(); +EXTERN_C void JIT_WriteBarrier_WriteWatch_Bit_Region64_Patch_Label_CardTable(); +#ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES +EXTERN_C void JIT_WriteBarrier_WriteWatch_Bit_Region64_Patch_Label_CardBundleTable(); +#endif +EXTERN_C void JIT_WriteBarrier_WriteWatch_Bit_Region64_End(); + #endif // FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP WriteBarrierManager g_WriteBarrierManager; @@ -152,6 +204,36 @@ void WriteBarrierManager::Validate() #endif // FEATURE_MANUALLY_MANAGED_CARD_BUNDLES #endif // FEATURE_SVR_GC + PBYTE pRegionToGenTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_Byte_Region64, Patch_Label_RegionToGeneration, 2); + _ASSERTE_ALL_BUILDS((reinterpret_cast(pRegionToGenTableImmediate) & 0x7) == 0); + + pLowerBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_Byte_Region64, Patch_Label_Lower, 2); + pUpperBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_Byte_Region64, Patch_Label_Upper, 2); + pCardTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_Byte_Region64, Patch_Label_CardTable, 2); + _ASSERTE_ALL_BUILDS((reinterpret_cast(pLowerBoundImmediate) & 0x7) == 0); + _ASSERTE_ALL_BUILDS((reinterpret_cast(pUpperBoundImmediate) & 0x7) == 0); + _ASSERTE_ALL_BUILDS((reinterpret_cast(pCardTableImmediate) & 0x7) == 0); + +#ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES + pCardBundleTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_Byte_Region64, Patch_Label_CardBundleTable, 2); + _ASSERTE_ALL_BUILDS((reinterpret_cast(pCardBundleTableImmediate) & 0x7) == 0); +#endif + + pRegionToGenTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_Bit_Region64, Patch_Label_RegionToGeneration, 2); + _ASSERTE_ALL_BUILDS((reinterpret_cast(pRegionToGenTableImmediate) & 0x7) == 0); + + pLowerBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_Bit_Region64, Patch_Label_Lower, 2); + pUpperBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_Bit_Region64, Patch_Label_Upper, 2); + pCardTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_Bit_Region64, Patch_Label_CardTable, 2); + _ASSERTE_ALL_BUILDS((reinterpret_cast(pLowerBoundImmediate) & 0x7) == 0); + _ASSERTE_ALL_BUILDS((reinterpret_cast(pUpperBoundImmediate) & 0x7) == 0); + _ASSERTE_ALL_BUILDS((reinterpret_cast(pCardTableImmediate) & 0x7) == 0); + +#ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES + pCardBundleTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_Bit_Region64, Patch_Label_CardBundleTable, 2); + _ASSERTE_ALL_BUILDS((reinterpret_cast(pCardBundleTableImmediate) & 0x7) == 0); +#endif + #ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP PBYTE pWriteWatchTableImmediate; @@ -194,6 +276,37 @@ void WriteBarrierManager::Validate() _ASSERTE_ALL_BUILDS((reinterpret_cast(pCardBundleTableImmediate) & 0x7) == 0); #endif // FEATURE_MANUALLY_MANAGED_CARD_BUNDLES #endif // FEATURE_SVR_GC + + pRegionToGenTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Byte_Region64, Patch_Label_RegionToGeneration, 2); + _ASSERTE_ALL_BUILDS((reinterpret_cast(pRegionToGenTableImmediate) & 0x7) == 0); + + pLowerBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Byte_Region64, Patch_Label_Lower, 2); + pUpperBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Byte_Region64, Patch_Label_Upper, 2); + pCardTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Byte_Region64, Patch_Label_CardTable, 2); + _ASSERTE_ALL_BUILDS((reinterpret_cast(pLowerBoundImmediate) & 0x7) == 0); + _ASSERTE_ALL_BUILDS((reinterpret_cast(pUpperBoundImmediate) & 0x7) == 0); + _ASSERTE_ALL_BUILDS((reinterpret_cast(pCardTableImmediate) & 0x7) == 0); + +#ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES + pCardBundleTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Byte_Region64, Patch_Label_CardBundleTable, 2); + _ASSERTE_ALL_BUILDS((reinterpret_cast(pCardBundleTableImmediate) & 0x7) == 0); +#endif + + pRegionToGenTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Bit_Region64, Patch_Label_RegionToGeneration, 2); + _ASSERTE_ALL_BUILDS((reinterpret_cast(pRegionToGenTableImmediate) & 0x7) == 0); + + pLowerBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Bit_Region64, Patch_Label_Lower, 2); + pUpperBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Bit_Region64, Patch_Label_Upper, 2); + pCardTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Bit_Region64, Patch_Label_CardTable, 2); + _ASSERTE_ALL_BUILDS((reinterpret_cast(pLowerBoundImmediate) & 0x7) == 0); + _ASSERTE_ALL_BUILDS((reinterpret_cast(pUpperBoundImmediate) & 0x7) == 0); + _ASSERTE_ALL_BUILDS((reinterpret_cast(pCardTableImmediate) & 0x7) == 0); + +#ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES + pCardBundleTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Bit_Region64, Patch_Label_CardBundleTable, 2); + _ASSERTE_ALL_BUILDS((reinterpret_cast(pCardBundleTableImmediate) & 0x7) == 0); +#endif + #endif // FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP } @@ -214,6 +327,10 @@ PCODE WriteBarrierManager::GetCurrentWriteBarrierCode() case WRITE_BARRIER_SVR64: return GetEEFuncEntryPoint(JIT_WriteBarrier_SVR64); #endif // FEATURE_SVR_GC + case WRITE_BARRIER_BYTE_REGIONS64: + return GetEEFuncEntryPoint(JIT_WriteBarrier_Byte_Region64); + case WRITE_BARRIER_BIT_REGIONS64: + return GetEEFuncEntryPoint(JIT_WriteBarrier_Bit_Region64); #ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP case WRITE_BARRIER_WRITE_WATCH_PREGROW64: return GetEEFuncEntryPoint(JIT_WriteBarrier_WriteWatch_PreGrow64); @@ -223,6 +340,10 @@ PCODE WriteBarrierManager::GetCurrentWriteBarrierCode() case WRITE_BARRIER_WRITE_WATCH_SVR64: return GetEEFuncEntryPoint(JIT_WriteBarrier_WriteWatch_SVR64); #endif // FEATURE_SVR_GC + case WRITE_BARRIER_WRITE_WATCH_BYTE_REGIONS64: + return GetEEFuncEntryPoint(JIT_WriteBarrier_WriteWatch_Byte_Region64); + case WRITE_BARRIER_WRITE_WATCH_BIT_REGIONS64: + return GetEEFuncEntryPoint(JIT_WriteBarrier_WriteWatch_Bit_Region64); #endif // FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP default: UNREACHABLE_MSG("unexpected m_currentWriteBarrier!"); @@ -246,6 +367,10 @@ size_t WriteBarrierManager::GetSpecificWriteBarrierSize(WriteBarrierType writeBa case WRITE_BARRIER_SVR64: return MARKED_FUNCTION_SIZE(JIT_WriteBarrier_SVR64); #endif // FEATURE_SVR_GC + case WRITE_BARRIER_BYTE_REGIONS64: + return MARKED_FUNCTION_SIZE(JIT_WriteBarrier_Byte_Region64); + case WRITE_BARRIER_BIT_REGIONS64: + return MARKED_FUNCTION_SIZE(JIT_WriteBarrier_Bit_Region64); #ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP case WRITE_BARRIER_WRITE_WATCH_PREGROW64: return MARKED_FUNCTION_SIZE(JIT_WriteBarrier_WriteWatch_PreGrow64); @@ -255,6 +380,10 @@ size_t WriteBarrierManager::GetSpecificWriteBarrierSize(WriteBarrierType writeBa case WRITE_BARRIER_WRITE_WATCH_SVR64: return MARKED_FUNCTION_SIZE(JIT_WriteBarrier_WriteWatch_SVR64); #endif // FEATURE_SVR_GC + case WRITE_BARRIER_WRITE_WATCH_BYTE_REGIONS64: + return MARKED_FUNCTION_SIZE(JIT_WriteBarrier_WriteWatch_Byte_Region64); + case WRITE_BARRIER_WRITE_WATCH_BIT_REGIONS64: + return MARKED_FUNCTION_SIZE(JIT_WriteBarrier_WriteWatch_Bit_Region64); #endif // FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP case WRITE_BARRIER_BUFFER: return MARKED_FUNCTION_SIZE(JIT_WriteBarrier); @@ -351,6 +480,50 @@ int WriteBarrierManager::ChangeWriteBarrierTo(WriteBarrierType newWriteBarrier, } #endif // FEATURE_SVR_GC + case WRITE_BARRIER_BYTE_REGIONS64: + m_pRegionToGenTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_Byte_Region64, Patch_Label_RegionToGeneration, 2); + m_pRegionShrDest = CALC_PATCH_LOCATION(JIT_WriteBarrier_Byte_Region64, Patch_Label_RegionShrDest, 3); + m_pRegionShrSrc = CALC_PATCH_LOCATION(JIT_WriteBarrier_Byte_Region64, Patch_Label_RegionShrSrc, 3); + m_pLowerBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_Byte_Region64, Patch_Label_Lower, 2); + m_pUpperBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_Byte_Region64, Patch_Label_Upper, 2); + m_pCardTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_Byte_Region64, Patch_Label_CardTable, 2); + + // Make sure that we will be bashing the right places (immediates should be hardcoded to 0x0f0f0f0f0f0f0f0f0). + _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pRegionToGenTableImmediate); + _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pLowerBoundImmediate); + _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pUpperBoundImmediate); + _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pCardTableImmediate); + _ASSERTE_ALL_BUILDS( 0x16 == *(UINT8 *)m_pRegionShrDest); + _ASSERTE_ALL_BUILDS( 0x16 == *(UINT8 *)m_pRegionShrSrc); + +#ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES + m_pCardBundleTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_Byte_Region64, Patch_Label_CardBundleTable, 2); + _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pCardBundleTableImmediate); +#endif + break; + + case WRITE_BARRIER_BIT_REGIONS64: + m_pRegionToGenTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_Bit_Region64, Patch_Label_RegionToGeneration, 2); + m_pRegionShrDest = CALC_PATCH_LOCATION(JIT_WriteBarrier_Bit_Region64, Patch_Label_RegionShrDest, 3); + m_pRegionShrSrc = CALC_PATCH_LOCATION(JIT_WriteBarrier_Bit_Region64, Patch_Label_RegionShrSrc, 3); + m_pLowerBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_Bit_Region64, Patch_Label_Lower, 2); + m_pUpperBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_Bit_Region64, Patch_Label_Upper, 2); + m_pCardTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_Bit_Region64, Patch_Label_CardTable, 2); + + // Make sure that we will be bashing the right places (immediates should be hardcoded to 0x0f0f0f0f0f0f0f0f0). + _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pRegionToGenTableImmediate); + _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pLowerBoundImmediate); + _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pUpperBoundImmediate); + _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pCardTableImmediate); + _ASSERTE_ALL_BUILDS( 0x16 == *(UINT8 *)m_pRegionShrDest); + _ASSERTE_ALL_BUILDS( 0x16 == *(UINT8 *)m_pRegionShrSrc); + +#ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES + m_pCardBundleTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_Bit_Region64, Patch_Label_CardBundleTable, 2); + _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pCardBundleTableImmediate); +#endif + break; + #ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP case WRITE_BARRIER_WRITE_WATCH_PREGROW64: { @@ -407,6 +580,56 @@ int WriteBarrierManager::ChangeWriteBarrierTo(WriteBarrierType newWriteBarrier, break; } #endif // FEATURE_SVR_GC + + case WRITE_BARRIER_WRITE_WATCH_BYTE_REGIONS64: + m_pWriteWatchTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Byte_Region64, Patch_Label_WriteWatchTable, 2); + m_pRegionToGenTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Byte_Region64, Patch_Label_RegionToGeneration, 2); + m_pRegionShrDest = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Byte_Region64, Patch_Label_RegionShrDest, 3); + m_pRegionShrSrc = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Byte_Region64, Patch_Label_RegionShrSrc, 3); + m_pLowerBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Byte_Region64, Patch_Label_Lower, 2); + m_pUpperBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Byte_Region64, Patch_Label_Upper, 2); + m_pCardTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Byte_Region64, Patch_Label_CardTable, 2); + + // Make sure that we will be bashing the right places (immediates should be hardcoded to 0x0f0f0f0f0f0f0f0f0). + _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pWriteWatchTableImmediate); + _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pRegionToGenTableImmediate); + _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pLowerBoundImmediate); + _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pUpperBoundImmediate); + _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pCardTableImmediate); + _ASSERTE_ALL_BUILDS( 0x16 == *(UINT8 *)m_pRegionShrDest); + _ASSERTE_ALL_BUILDS( 0x16 == *(UINT8 *)m_pRegionShrSrc); + +#ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES + m_pCardBundleTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Byte_Region64, Patch_Label_CardBundleTable, 2); + _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pCardBundleTableImmediate); +#endif + break; + + case WRITE_BARRIER_WRITE_WATCH_BIT_REGIONS64: + m_pWriteWatchTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Bit_Region64, Patch_Label_WriteWatchTable, 2); + m_pRegionToGenTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Bit_Region64, Patch_Label_RegionToGeneration, 2); + m_pRegionShrDest = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Bit_Region64, Patch_Label_RegionShrDest, 3); + m_pRegionShrSrc = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Bit_Region64, Patch_Label_RegionShrSrc, 3); + m_pLowerBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Bit_Region64, Patch_Label_Lower, 2); + m_pUpperBoundImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Bit_Region64, Patch_Label_Upper, 2); + m_pCardTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Bit_Region64, Patch_Label_CardTable, 2); + + // Make sure that we will be bashing the right places (immediates should be hardcoded to 0x0f0f0f0f0f0f0f0f0). + _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pWriteWatchTableImmediate); + _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pRegionToGenTableImmediate); + _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pLowerBoundImmediate); + _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pUpperBoundImmediate); + _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pCardTableImmediate); + _ASSERTE_ALL_BUILDS( 0x16 == *(UINT8 *)m_pRegionShrDest); + _ASSERTE_ALL_BUILDS( 0x16 == *(UINT8 *)m_pRegionShrSrc); + +#ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES + m_pCardBundleTableImmediate = CALC_PATCH_LOCATION(JIT_WriteBarrier_WriteWatch_Bit_Region64, Patch_Label_CardBundleTable, 2); + _ASSERTE_ALL_BUILDS(0xf0f0f0f0f0f0f0f0 == *(UINT64*)m_pCardBundleTableImmediate); +#endif + break; + + #endif // FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP default: @@ -441,12 +664,16 @@ void WriteBarrierManager::Initialize() #ifdef FEATURE_SVR_GC _ASSERTE_ALL_BUILDS(cbWriteBarrierBuffer >= GetSpecificWriteBarrierSize(WRITE_BARRIER_SVR64)); #endif // FEATURE_SVR_GC + _ASSERTE_ALL_BUILDS(cbWriteBarrierBuffer >= GetSpecificWriteBarrierSize(WRITE_BARRIER_BYTE_REGIONS64)); + _ASSERTE_ALL_BUILDS(cbWriteBarrierBuffer >= GetSpecificWriteBarrierSize(WRITE_BARRIER_BIT_REGIONS64)); #ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP _ASSERTE_ALL_BUILDS(cbWriteBarrierBuffer >= GetSpecificWriteBarrierSize(WRITE_BARRIER_WRITE_WATCH_PREGROW64)); _ASSERTE_ALL_BUILDS(cbWriteBarrierBuffer >= GetSpecificWriteBarrierSize(WRITE_BARRIER_WRITE_WATCH_POSTGROW64)); #ifdef FEATURE_SVR_GC _ASSERTE_ALL_BUILDS(cbWriteBarrierBuffer >= GetSpecificWriteBarrierSize(WRITE_BARRIER_WRITE_WATCH_SVR64)); #endif // FEATURE_SVR_GC + _ASSERTE_ALL_BUILDS(cbWriteBarrierBuffer >= GetSpecificWriteBarrierSize(WRITE_BARRIER_WRITE_WATCH_BYTE_REGIONS64)); + _ASSERTE_ALL_BUILDS(cbWriteBarrierBuffer >= GetSpecificWriteBarrierSize(WRITE_BARRIER_WRITE_WATCH_BIT_REGIONS64)); #endif // FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP #if !defined(CODECOVERAGE) @@ -454,7 +681,7 @@ void WriteBarrierManager::Initialize() #endif } -bool WriteBarrierManager::NeedDifferentWriteBarrier(bool bReqUpperBoundsCheck, WriteBarrierType* pNewWriteBarrierType) +bool WriteBarrierManager::NeedDifferentWriteBarrier(bool bReqUpperBoundsCheck, bool bUseBitwiseWriteBarrier, WriteBarrierType* pNewWriteBarrierType) { // Init code for the JIT_WriteBarrier assembly routine. Since it will be bashed everytime the GC Heap // changes size, we want to do most of the work just once. @@ -476,8 +703,14 @@ bool WriteBarrierManager::NeedDifferentWriteBarrier(bool bReqUpperBoundsCheck, W break; } #endif - - writeBarrierType = GCHeapUtilities::IsServerHeap() ? WRITE_BARRIER_SVR64 : WRITE_BARRIER_PREGROW64; + if (g_region_shr != 0) + { + writeBarrierType = bUseBitwiseWriteBarrier ? WRITE_BARRIER_BIT_REGIONS64: WRITE_BARRIER_BYTE_REGIONS64; + } + else + { + writeBarrierType = GCHeapUtilities::IsServerHeap() ? WRITE_BARRIER_SVR64 : WRITE_BARRIER_PREGROW64; + } continue; case WRITE_BARRIER_PREGROW64: @@ -495,6 +728,10 @@ bool WriteBarrierManager::NeedDifferentWriteBarrier(bool bReqUpperBoundsCheck, W break; #endif // FEATURE_SVR_GC + case WRITE_BARRIER_BYTE_REGIONS64: + case WRITE_BARRIER_BIT_REGIONS64: + break; + #ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP case WRITE_BARRIER_WRITE_WATCH_PREGROW64: if (bReqUpperBoundsCheck) @@ -510,6 +747,9 @@ bool WriteBarrierManager::NeedDifferentWriteBarrier(bool bReqUpperBoundsCheck, W case WRITE_BARRIER_WRITE_WATCH_SVR64: break; #endif // FEATURE_SVR_GC + case WRITE_BARRIER_WRITE_WATCH_BYTE_REGIONS64: + case WRITE_BARRIER_WRITE_WATCH_BIT_REGIONS64: + break; #endif // FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP default: @@ -525,7 +765,7 @@ bool WriteBarrierManager::NeedDifferentWriteBarrier(bool bReqUpperBoundsCheck, W int WriteBarrierManager::UpdateEphemeralBounds(bool isRuntimeSuspended) { WriteBarrierType newType; - if (NeedDifferentWriteBarrier(false, &newType)) + if (NeedDifferentWriteBarrier(false, g_region_use_bitwise_write_barrier, &newType)) { return ChangeWriteBarrierTo(newType, isRuntimeSuspended); } @@ -541,8 +781,12 @@ int WriteBarrierManager::UpdateEphemeralBounds(bool isRuntimeSuspended) switch (m_currentWriteBarrier) { case WRITE_BARRIER_POSTGROW64: + case WRITE_BARRIER_BYTE_REGIONS64: + case WRITE_BARRIER_BIT_REGIONS64: #ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP case WRITE_BARRIER_WRITE_WATCH_POSTGROW64: + case WRITE_BARRIER_WRITE_WATCH_BYTE_REGIONS64: + case WRITE_BARRIER_WRITE_WATCH_BIT_REGIONS64: #endif // FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP { // Change immediate if different from new g_ephermeral_high. @@ -592,7 +836,7 @@ int WriteBarrierManager::UpdateWriteWatchAndCardTableLocations(bool isRuntimeSus // we need to switch to the WriteBarrier_PostGrow function for good. WriteBarrierType newType; - if (NeedDifferentWriteBarrier(bReqUpperBoundsCheck, &newType)) + if (NeedDifferentWriteBarrier(bReqUpperBoundsCheck, g_region_use_bitwise_write_barrier, &newType)) { return ChangeWriteBarrierTo(newType, isRuntimeSuspended); } @@ -613,6 +857,8 @@ int WriteBarrierManager::UpdateWriteWatchAndCardTableLocations(bool isRuntimeSus #ifdef FEATURE_SVR_GC case WRITE_BARRIER_WRITE_WATCH_SVR64: #endif // FEATURE_SVR_GC + case WRITE_BARRIER_WRITE_WATCH_BYTE_REGIONS64: + case WRITE_BARRIER_WRITE_WATCH_BIT_REGIONS64: if (*(UINT64*)m_pWriteWatchTableImmediate != (size_t)g_sw_ww_table) { ExecutableWriterHolder writeWatchTableImmediateWriterHolder((UINT64*)m_pWriteWatchTableImmediate, sizeof(UINT64)); @@ -626,6 +872,36 @@ int WriteBarrierManager::UpdateWriteWatchAndCardTableLocations(bool isRuntimeSus } #endif // FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP + switch (m_currentWriteBarrier) + { + case WRITE_BARRIER_BYTE_REGIONS64: + case WRITE_BARRIER_BIT_REGIONS64: + case WRITE_BARRIER_WRITE_WATCH_BYTE_REGIONS64: + case WRITE_BARRIER_WRITE_WATCH_BIT_REGIONS64: + if (*(UINT64*)m_pRegionToGenTableImmediate != (size_t)g_region_to_generation_table) + { + ExecutableWriterHolder writeWatchTableImmediateWriterHolder((UINT64*)m_pRegionToGenTableImmediate, sizeof(UINT64)); + *writeWatchTableImmediateWriterHolder.GetRW() = (size_t)g_region_to_generation_table; + stompWBCompleteActions |= SWB_ICACHE_FLUSH; + } + if (*m_pRegionShrDest != g_region_shr) + { + ExecutableWriterHolder writeWatchTableImmediateWriterHolder(m_pRegionShrDest, sizeof(UINT8)); + *writeWatchTableImmediateWriterHolder.GetRW() = g_region_shr; + stompWBCompleteActions |= SWB_ICACHE_FLUSH; + } + if (*m_pRegionShrSrc != g_region_shr) + { + ExecutableWriterHolder writeWatchTableImmediateWriterHolder(m_pRegionShrSrc, sizeof(UINT8)); + *writeWatchTableImmediateWriterHolder.GetRW() = g_region_shr; + stompWBCompleteActions |= SWB_ICACHE_FLUSH; + } + break; + + default: + break; // clang seems to require all enum values to be covered for some reason + } + if (*(UINT64*)m_pCardTableImmediate != (size_t)g_card_table) { ExecutableWriterHolder cardTableImmediateWriterHolder((UINT64*)m_pCardTableImmediate, sizeof(UINT64)); @@ -669,6 +945,14 @@ int WriteBarrierManager::SwitchToWriteWatchBarrier(bool isRuntimeSuspended) break; #endif // FEATURE_SVR_GC + case WRITE_BARRIER_BYTE_REGIONS64: + newWriteBarrierType = WRITE_BARRIER_WRITE_WATCH_BYTE_REGIONS64; + break; + + case WRITE_BARRIER_BIT_REGIONS64: + newWriteBarrierType = WRITE_BARRIER_WRITE_WATCH_BIT_REGIONS64; + break; + default: UNREACHABLE(); } @@ -699,6 +983,14 @@ int WriteBarrierManager::SwitchToNonWriteWatchBarrier(bool isRuntimeSuspended) break; #endif // FEATURE_SVR_GC + case WRITE_BARRIER_WRITE_WATCH_BYTE_REGIONS64: + newWriteBarrierType = WRITE_BARRIER_BYTE_REGIONS64; + break; + + case WRITE_BARRIER_WRITE_WATCH_BIT_REGIONS64: + newWriteBarrierType = WRITE_BARRIER_BIT_REGIONS64; + break; + default: UNREACHABLE(); } diff --git a/src/coreclr/vm/gcenv.ee.cpp b/src/coreclr/vm/gcenv.ee.cpp index 42c85aa788770c..27c6cad6443fa9 100644 --- a/src/coreclr/vm/gcenv.ee.cpp +++ b/src/coreclr/vm/gcenv.ee.cpp @@ -1002,6 +1002,9 @@ void GCToEEInterface::StompWriteBarrier(WriteBarrierParameters* args) assert(args->ephemeral_high != nullptr); g_ephemeral_low = args->ephemeral_low; g_ephemeral_high = args->ephemeral_high; + g_region_to_generation_table = args->region_to_generation_table; + g_region_shr = args->region_shr; + g_region_use_bitwise_write_barrier = args->region_use_bitwise_write_barrier; stompWBCompleteActions |= ::StompWriteBarrierEphemeral(args->is_runtime_suspended); break; case WriteBarrierOp::Initialize: @@ -1026,6 +1029,9 @@ void GCToEEInterface::StompWriteBarrier(WriteBarrierParameters* args) g_lowest_address = args->lowest_address; g_highest_address = args->highest_address; + g_region_to_generation_table = args->region_to_generation_table; + g_region_shr = args->region_shr; + g_region_use_bitwise_write_barrier = args->region_use_bitwise_write_barrier; stompWBCompleteActions |= ::StompWriteBarrierResize(true, false); // StompWriteBarrierResize does not necessarily bash g_ephemeral_low diff --git a/src/coreclr/vm/gcheaputilities.cpp b/src/coreclr/vm/gcheaputilities.cpp index 088af916fc5bd0..d3301df9ff51fa 100644 --- a/src/coreclr/vm/gcheaputilities.cpp +++ b/src/coreclr/vm/gcheaputilities.cpp @@ -17,6 +17,9 @@ GPTR_IMPL_INIT(uint8_t, g_highest_address, nullptr); GVAL_IMPL_INIT(GCHeapType, g_heap_type, GC_HEAP_INVALID); uint8_t* g_ephemeral_low = (uint8_t*)1; uint8_t* g_ephemeral_high = (uint8_t*)~0; +uint8_t* g_region_to_generation_table = nullptr; +uint8_t g_region_shr = 0; +bool g_region_use_bitwise_write_barrier = false; #ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES uint32_t* g_card_bundle_table = nullptr; diff --git a/src/coreclr/vm/gcheaputilities.h b/src/coreclr/vm/gcheaputilities.h index c6dbb54631b2b0..c652cc52bf417c 100644 --- a/src/coreclr/vm/gcheaputilities.h +++ b/src/coreclr/vm/gcheaputilities.h @@ -29,6 +29,10 @@ GVAL_DECL(gc_alloc_context, g_global_alloc_context); extern "C" uint32_t* g_card_bundle_table; extern "C" uint8_t* g_ephemeral_low; extern "C" uint8_t* g_ephemeral_high; +extern "C" uint8_t* g_region_to_generation_table; +extern "C" uint8_t g_region_shr; +extern "C" bool g_region_use_bitwise_write_barrier; + #ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP diff --git a/src/coreclr/vm/jitinterface.h b/src/coreclr/vm/jitinterface.h index d0a415a1f5f1d7..efb57186571eb3 100644 --- a/src/coreclr/vm/jitinterface.h +++ b/src/coreclr/vm/jitinterface.h @@ -262,12 +262,16 @@ class WriteBarrierManager #ifdef FEATURE_SVR_GC WRITE_BARRIER_SVR64, #endif // FEATURE_SVR_GC + WRITE_BARRIER_BYTE_REGIONS64, + WRITE_BARRIER_BIT_REGIONS64, #ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP WRITE_BARRIER_WRITE_WATCH_PREGROW64, WRITE_BARRIER_WRITE_WATCH_POSTGROW64, #ifdef FEATURE_SVR_GC WRITE_BARRIER_WRITE_WATCH_SVR64, #endif // FEATURE_SVR_GC + WRITE_BARRIER_WRITE_WATCH_BYTE_REGIONS64, + WRITE_BARRIER_WRITE_WATCH_BIT_REGIONS64, #endif // FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP WRITE_BARRIER_BUFFER }; @@ -289,18 +293,21 @@ class WriteBarrierManager PBYTE CalculatePatchLocation(LPVOID base, LPVOID label, int offset); PCODE GetCurrentWriteBarrierCode(); int ChangeWriteBarrierTo(WriteBarrierType newWriteBarrier, bool isRuntimeSuspended); - bool NeedDifferentWriteBarrier(bool bReqUpperBoundsCheck, WriteBarrierType* pNewWriteBarrierType); + bool NeedDifferentWriteBarrier(bool bReqUpperBoundsCheck, bool bUseBitwiseWriteBarrier, WriteBarrierType* pNewWriteBarrierType); private: void Validate(); WriteBarrierType m_currentWriteBarrier; - PBYTE m_pWriteWatchTableImmediate; // PREGROW | POSTGROW | SVR | WRITE_WATCH | - PBYTE m_pLowerBoundImmediate; // PREGROW | POSTGROW | | WRITE_WATCH | - PBYTE m_pCardTableImmediate; // PREGROW | POSTGROW | SVR | WRITE_WATCH | - PBYTE m_pCardBundleTableImmediate; // PREGROW | POSTGROW | SVR | WRITE_WATCH | - PBYTE m_pUpperBoundImmediate; // | POSTGROW | | WRITE_WATCH | + PBYTE m_pWriteWatchTableImmediate; // PREGROW | POSTGROW | SVR | WRITE_WATCH | REGION + PBYTE m_pLowerBoundImmediate; // PREGROW | POSTGROW | | WRITE_WATCH | REGION + PBYTE m_pCardTableImmediate; // PREGROW | POSTGROW | SVR | WRITE_WATCH | REGION + PBYTE m_pCardBundleTableImmediate; // PREGROW | POSTGROW | SVR | WRITE_WATCH | REGION + PBYTE m_pUpperBoundImmediate; // | POSTGROW | | WRITE_WATCH | REGION + PBYTE m_pRegionToGenTableImmediate; // | | | WRITE_WATCH | REGION + PBYTE m_pRegionShrDest; // | | | WRITE_WATCH | REGION + PBYTE m_pRegionShrSrc; // | | | WRITE_WATCH | RETION }; #endif // TARGET_AMD64 From c99545ca6458d0ff0c7facef8ec407e7d2bdf896 Mon Sep 17 00:00:00 2001 From: Krzysztof Wicher Date: Tue, 9 Aug 2022 12:29:47 +0200 Subject: [PATCH 06/41] Succeed rather than skip JSON tests (#73560) --- .../MetadataTests/DefaultJsonPropertyInfo.cs | 35 +++++++++++++++---- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/DefaultJsonPropertyInfo.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/DefaultJsonPropertyInfo.cs index 8b2dc8102b6377..eecc09afb24d73 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/DefaultJsonPropertyInfo.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/DefaultJsonPropertyInfo.cs @@ -31,11 +31,13 @@ protected override IJsonTypeInfoResolver CreateResolverWithModifiers(params Acti public class DefaultJsonPropertyInfoTests_SerializerContextNoWrapping : DefaultJsonPropertyInfoTests { + protected override bool ModifiersNotSupported => true; + protected override IJsonTypeInfoResolver CreateResolverWithModifiers(params Action[] modifiers) { if (modifiers.Length != 0) { - throw new SkipTestException($"Testing non wrapped JsonSerializerContext but modifier is provided."); + Assert.Fail($"Testing non wrapped JsonSerializerContext but modifier is provided. Make sure to check {nameof(ModifiersNotSupported)}."); } return Context.Default; @@ -75,6 +77,7 @@ public ContextWithModifiers(JsonSerializerContext context, Action[ public abstract partial class DefaultJsonPropertyInfoTests { + protected virtual bool ModifiersNotSupported => false; protected abstract IJsonTypeInfoResolver CreateResolverWithModifiers(params Action[] modifiers); private JsonSerializerOptions CreateOptionsWithModifiers(params Action[] modifiers) @@ -135,9 +138,12 @@ public void RequiredAttributesGetDetectedAndFailDeserializationWhenValuesNotPres Assert.Contains(nameof(ClassWithRequiredCustomAttributes.RequiredB), exception.Message); } - [ConditionalFact] + [Fact] public void RequiredMemberCanBeModifiedToNonRequired() { + if (ModifiersNotSupported) + return; + JsonSerializerOptions options = CreateOptionsWithModifiers(ti => { if (ti.Type == typeof(ClassWithRequiredCustomAttributes)) @@ -180,9 +186,12 @@ public void RequiredMemberCanBeModifiedToNonRequired() Assert.Contains(nameof(ClassWithRequiredCustomAttributes.RequiredB), exception.Message); } - [ConditionalFact] + [Fact] public void NonRequiredMemberCanBeModifiedToRequired() { + if (ModifiersNotSupported) + return; + JsonSerializerOptions options = CreateOptionsWithModifiers(ti => { if (ti.Type == typeof(ClassWithRequiredCustomAttributes)) @@ -234,9 +243,12 @@ public void RequiredExtensionDataPropertyThrows() Assert.Throws(() => JsonSerializer.Deserialize("""{"Data":{}}""", options)); } - [ConditionalFact] + [Fact] public void RequiredExtensionDataPropertyCanBeFixedToNotBeRequiredWithResolver() { + if (ModifiersNotSupported) + return; + JsonSerializerOptions options = CreateOptionsWithModifiers(ti => { if (ti.Type == typeof(ClassWithRequiredCustomAttributeAndDataExtensionProperty)) @@ -265,9 +277,12 @@ public void RequiredExtensionDataPropertyCanBeFixedToNotBeRequiredWithResolver() Assert.Equal("bar", ((JsonElement)deserialized.Data["foo"]).GetString()); } - [ConditionalFact] + [Fact] public void RequiredExtensionDataPropertyCanBeFixedToNotBeExtensionDataWithResolver() { + if (ModifiersNotSupported) + return; + JsonSerializerOptions options = CreateOptionsWithModifiers(ti => { if (ti.Type == typeof(ClassWithRequiredCustomAttributeAndDataExtensionProperty)) @@ -306,9 +321,12 @@ public void RequiredReadOnlyPropertyThrows() Assert.Throws(() => JsonSerializer.Deserialize("""{"Data":{}}""", options)); } - [ConditionalFact] + [Fact] public void RequiredReadOnlyPropertyCanBeFixedToNotBeRequiredWithResolver() { + if (ModifiersNotSupported) + return; + JsonSerializerOptions options = CreateOptionsWithModifiers(ti => { if (ti.Type == typeof(ClassWithRequiredCustomAttributeAndReadOnlyProperty)) @@ -338,9 +356,12 @@ public void RequiredReadOnlyPropertyCanBeFixedToNotBeRequiredWithResolver() Assert.Equal("SomePropertyInitialValue", deserialized.SomeProperty); } - [ConditionalFact] + [Fact] public void RequiredReadOnlyPropertyCanBeFixedToBeWritableWithResolver() { + if (ModifiersNotSupported) + return; + JsonSerializerOptions options = CreateOptionsWithModifiers(ti => { if (ti.Type == typeof(ClassWithRequiredCustomAttributeAndReadOnlyProperty)) From 8190fc6d5aa4482ad573cc3ad1848b87554bd157 Mon Sep 17 00:00:00 2001 From: Ovidiu Costea <39533748+ovidiucosteanet@users.noreply.github.com> Date: Tue, 9 Aug 2022 14:18:27 +0300 Subject: [PATCH 07/41] Add custom JsonConverter for BinaryData that converts to Base64 string (#73448) --- .../ref/System.Memory.Data.cs | 7 +++++ .../src/System.Memory.Data.csproj | 10 +++---- .../src/System/BinaryData.cs | 1 + .../src/System/BinaryDataConverter.cs | 21 +++++++++++++ .../tests/BinaryDataTests.cs | 30 +++++++++++++++++++ 5 files changed, 63 insertions(+), 6 deletions(-) create mode 100644 src/libraries/System.Memory.Data/src/System/BinaryDataConverter.cs diff --git a/src/libraries/System.Memory.Data/ref/System.Memory.Data.cs b/src/libraries/System.Memory.Data/ref/System.Memory.Data.cs index 82530904f76ae2..7a39e2c7354602 100644 --- a/src/libraries/System.Memory.Data/ref/System.Memory.Data.cs +++ b/src/libraries/System.Memory.Data/ref/System.Memory.Data.cs @@ -6,6 +6,7 @@ namespace System { + [System.Text.Json.Serialization.JsonConverter(typeof(BinaryDataConverter))] public partial class BinaryData { public BinaryData(byte[] data) { } @@ -37,4 +38,10 @@ public BinaryData(string data) { } public System.IO.Stream ToStream() { throw null; } public override string ToString() { throw null; } } + + internal sealed class BinaryDataConverter : System.Text.Json.Serialization.JsonConverter + { + public sealed override BinaryData? Read(ref System.Text.Json.Utf8JsonReader reader, System.Type typeToConvert, System.Text.Json.JsonSerializerOptions options) { throw null; } + public sealed override void Write(System.Text.Json.Utf8JsonWriter writer, BinaryData value, System.Text.Json.JsonSerializerOptions options) { } + } } diff --git a/src/libraries/System.Memory.Data/src/System.Memory.Data.csproj b/src/libraries/System.Memory.Data/src/System.Memory.Data.csproj index 5f04cf915b8c24..114ba3acc2460a 100644 --- a/src/libraries/System.Memory.Data/src/System.Memory.Data.csproj +++ b/src/libraries/System.Memory.Data/src/System.Memory.Data.csproj @@ -11,12 +11,10 @@ System.BinaryData - - - + + + + diff --git a/src/libraries/System.Memory.Data/src/System/BinaryData.cs b/src/libraries/System.Memory.Data/src/System/BinaryData.cs index e37f5307efc387..8ff184c462a79a 100644 --- a/src/libraries/System.Memory.Data/src/System/BinaryData.cs +++ b/src/libraries/System.Memory.Data/src/System/BinaryData.cs @@ -16,6 +16,7 @@ namespace System /// /// A lightweight abstraction for a payload of bytes that supports converting between string, stream, JSON, and bytes. /// + [JsonConverter(typeof(BinaryDataConverter))] public class BinaryData { private const string JsonSerializerRequiresUnreferencedCode = "JSON serialization and deserialization might require types that cannot be statically analyzed."; diff --git a/src/libraries/System.Memory.Data/src/System/BinaryDataConverter.cs b/src/libraries/System.Memory.Data/src/System/BinaryDataConverter.cs new file mode 100644 index 00000000000000..2e16ae1392cfe5 --- /dev/null +++ b/src/libraries/System.Memory.Data/src/System/BinaryDataConverter.cs @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace System +{ + internal sealed class BinaryDataConverter : JsonConverter + { + public sealed override BinaryData? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return BinaryData.FromBytes(reader.GetBytesFromBase64()); + } + + public sealed override void Write(Utf8JsonWriter writer, BinaryData value, JsonSerializerOptions options) + { + writer.WriteBase64StringValue(value.ToMemory().Span); + } + } +} diff --git a/src/libraries/System.Memory.Data/tests/BinaryDataTests.cs b/src/libraries/System.Memory.Data/tests/BinaryDataTests.cs index 737dbb924361da..aa2d18c8a3ca93 100644 --- a/src/libraries/System.Memory.Data/tests/BinaryDataTests.cs +++ b/src/libraries/System.Memory.Data/tests/BinaryDataTests.cs @@ -614,6 +614,31 @@ public void ToStringReturnEmptyStringWhenBinaryDataEmpty() Assert.Equal(string.Empty, BinaryData.Empty.ToString()); } + [Fact] + public void IsBinaryDataMemberPropertySerialized() + { + var data = new BinaryData("A test value"); + var dataBase64 = Convert.ToBase64String(data.ToArray()); + var jsonTestModel = $"{{\"A\":\"{dataBase64}\"}}"; + TestModelWithBinaryDataProperty testModel = new TestModelWithBinaryDataProperty { A = data }; + + var serializedTestModel = JsonSerializer.Serialize(testModel); + + Assert.Equal(jsonTestModel, serializedTestModel); + } + + [Fact] + public void IsBinaryDataMemberPropertyDeserialized() + { + var data = new BinaryData("A test value"); + var dataBase64 = Convert.ToBase64String(data.ToArray()); + var jsonTestModel = $"{{\"A\":\"{dataBase64}\"}}"; + + TestModelWithBinaryDataProperty deserializedModel = JsonSerializer.Deserialize(jsonTestModel); + + Assert.Equal(data.ToString(), deserializedModel.A.ToString()); + } + internal class TestModel { public string A { get; set; } @@ -661,5 +686,10 @@ private class NonSeekableStream : MemoryStream public NonSeekableStream(byte[] buffer) : base(buffer) { } public override bool CanSeek => false; } + + internal class TestModelWithBinaryDataProperty + { + public BinaryData A { get; set; } + } } } From 2cb6c1921bd21afe211c7248a450459d40bfa7ff Mon Sep 17 00:00:00 2001 From: Katya Sokolova Date: Tue, 9 Aug 2022 13:21:35 +0200 Subject: [PATCH 08/41] Set up `HttpClient` for `ClientWebSocket.ConnectAsync` (#73387) * Set up HttpClient for ClientWebSocket.ConnectAsync * more tests * Update src/libraries/System.Net.WebSockets.Client/src/System/Net/WebSockets/WebSocketHandle.Managed.cs Co-authored-by: Miha Zupan * Apply suggestions from code review Co-authored-by: Miha Zupan * Refactoring websockets test inheritance Co-authored-by: Miha Zupan --- .../Net/WebSockets/WebSocketHandle.Managed.cs | 5 +- .../tests/AbortTest.cs | 18 ++++++- .../tests/CancelTest.cs | 23 +++++++-- .../tests/ClientWebSocketTestBase.cs | 13 +++++ .../tests/CloseTest.cs | 44 ++++++++++------ .../tests/ConnectTest.Http2.cs | 15 ++++++ .../tests/ConnectTest.cs | 44 ++++++++++------ .../tests/DeflateTests.cs | 17 ++++++- .../tests/SendReceiveTest.cs | 50 +++++++++++++++---- .../tests/WebSocketHelper.cs | 11 ++-- 10 files changed, 190 insertions(+), 50 deletions(-) diff --git a/src/libraries/System.Net.WebSockets.Client/src/System/Net/WebSockets/WebSocketHandle.Managed.cs b/src/libraries/System.Net.WebSockets.Client/src/System/Net/WebSockets/WebSocketHandle.Managed.cs index d9dd9c1b1b1771..29ddf98cafeb72 100644 --- a/src/libraries/System.Net.WebSockets.Client/src/System/Net/WebSockets/WebSocketHandle.Managed.cs +++ b/src/libraries/System.Net.WebSockets.Client/src/System/Net/WebSockets/WebSocketHandle.Managed.cs @@ -109,7 +109,10 @@ public async Task ConnectAsync(Uri uri, HttpMessageInvoker? invoker, Cancellatio using (linkedCancellation) { - response = await invoker.SendAsync(request, externalAndAbortCancellation.Token).ConfigureAwait(false); + Task sendTask = invoker is HttpClient client + ? client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, externalAndAbortCancellation.Token) + : invoker.SendAsync(request, externalAndAbortCancellation.Token); + response = await sendTask.ConfigureAwait(false); externalAndAbortCancellation.Token.ThrowIfCancellationRequested(); // poll in case sends/receives in request/response didn't observe cancellation } diff --git a/src/libraries/System.Net.WebSockets.Client/tests/AbortTest.cs b/src/libraries/System.Net.WebSockets.Client/tests/AbortTest.cs index 209a6f10ff23b4..70cb6317f36635 100644 --- a/src/libraries/System.Net.WebSockets.Client/tests/AbortTest.cs +++ b/src/libraries/System.Net.WebSockets.Client/tests/AbortTest.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.Net.Http; using System.Net.Test.Common; using System.Threading; using System.Threading.Tasks; @@ -11,10 +12,25 @@ namespace System.Net.WebSockets.Client.Tests { + public sealed class InvokerAbortTest : AbortTest + { + public InvokerAbortTest(ITestOutputHelper output) : base(output) { } + + protected override HttpMessageInvoker? GetInvoker() => new HttpMessageInvoker(new SocketsHttpHandler()); + } + + public sealed class HttpClientAbortTest : AbortTest + { + public HttpClientAbortTest(ITestOutputHelper output) : base(output) { } + + protected override HttpMessageInvoker? GetInvoker() => new HttpClient(new HttpClientHandler()); + } + public class AbortTest : ClientWebSocketTestBase { public AbortTest(ITestOutputHelper output) : base(output) { } + [OuterLoop("Uses external servers", typeof(PlatformDetection), nameof(PlatformDetection.LocalEchoServerIsNotAvailable))] [ConditionalTheory(nameof(WebSocketsSupported)), MemberData(nameof(EchoServers))] public async Task Abort_ConnectAndAbort_ThrowsWebSocketExceptionWithmessage(Uri server) @@ -26,7 +42,7 @@ public async Task Abort_ConnectAndAbort_ThrowsWebSocketExceptionWithmessage(Uri var ub = new UriBuilder(server); ub.Query = "delay10sec"; - Task t = cws.ConnectAsync(ub.Uri, cts.Token); + Task t = ConnectAsync(cws, ub.Uri, cts.Token); cws.Abort(); WebSocketException ex = await Assert.ThrowsAsync(() => t); diff --git a/src/libraries/System.Net.WebSockets.Client/tests/CancelTest.cs b/src/libraries/System.Net.WebSockets.Client/tests/CancelTest.cs index 752ca8ece81897..5919f63157d705 100644 --- a/src/libraries/System.Net.WebSockets.Client/tests/CancelTest.cs +++ b/src/libraries/System.Net.WebSockets.Client/tests/CancelTest.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Net.Http; using System.Threading; using System.Threading.Tasks; @@ -9,6 +10,20 @@ namespace System.Net.WebSockets.Client.Tests { + public sealed class InvokerCancelTest : CancelTest + { + public InvokerCancelTest(ITestOutputHelper output) : base(output) { } + + protected override HttpMessageInvoker? GetInvoker() => new HttpMessageInvoker(new SocketsHttpHandler()); + } + + public sealed class HttpClientCancelTest : CancelTest + { + public HttpClientCancelTest(ITestOutputHelper output) : base(output) { } + + protected override HttpMessageInvoker? GetInvoker() => new HttpClient(new HttpClientHandler()); + } + public class CancelTest : ClientWebSocketTestBase { public CancelTest(ITestOutputHelper output) : base(output) { } @@ -24,7 +39,7 @@ public async Task ConnectAsync_Cancel_ThrowsCancellationException(Uri server) var ub = new UriBuilder(server); ub.Query = PlatformDetection.IsBrowser ? "delay20sec" : "delay10sec"; - var ex = await Assert.ThrowsAnyAsync(() => cws.ConnectAsync(ub.Uri, cts.Token)); + var ex = await Assert.ThrowsAnyAsync(() => ConnectAsync(cws, ub.Uri, cts.Token)); Assert.True(WebSocketState.Closed == cws.State, $"Actual {cws.State} when {ex}"); } } @@ -116,7 +131,7 @@ await cws.SendAsync( [ConditionalTheory(nameof(WebSocketsSupported)), MemberData(nameof(EchoServers))] public async Task ReceiveAsync_CancelThenReceive_ThrowsOperationCanceledException(Uri server) { - using (ClientWebSocket cws = await WebSocketHelper.GetConnectedWebSocket(server, TimeOutMilliseconds, _output)) + using (ClientWebSocket cws = await GetConnectedWebSocket(server, TimeOutMilliseconds, _output)) { var recvBuffer = new byte[100]; var segment = new ArraySegment(recvBuffer); @@ -132,7 +147,7 @@ public async Task ReceiveAsync_CancelThenReceive_ThrowsOperationCanceledExceptio [ConditionalTheory(nameof(WebSocketsSupported)), MemberData(nameof(EchoServers))] public async Task ReceiveAsync_ReceiveThenCancel_ThrowsOperationCanceledException(Uri server) { - using (ClientWebSocket cws = await WebSocketHelper.GetConnectedWebSocket(server, TimeOutMilliseconds, _output)) + using (ClientWebSocket cws = await GetConnectedWebSocket(server, TimeOutMilliseconds, _output)) { var recvBuffer = new byte[100]; var segment = new ArraySegment(recvBuffer); @@ -148,7 +163,7 @@ public async Task ReceiveAsync_ReceiveThenCancel_ThrowsOperationCanceledExceptio [ConditionalTheory(nameof(WebSocketsSupported)), MemberData(nameof(EchoServers))] public async Task ReceiveAsync_AfterCancellationDoReceiveAsync_ThrowsWebSocketException(Uri server) { - using (ClientWebSocket cws = await WebSocketHelper.GetConnectedWebSocket(server, TimeOutMilliseconds, _output)) + using (ClientWebSocket cws = await GetConnectedWebSocket(server, TimeOutMilliseconds, _output)) { var recvBuffer = new byte[100]; var segment = new ArraySegment(recvBuffer); diff --git a/src/libraries/System.Net.WebSockets.Client/tests/ClientWebSocketTestBase.cs b/src/libraries/System.Net.WebSockets.Client/tests/ClientWebSocketTestBase.cs index a03eff4c18c899..203040e4ffc2dd 100644 --- a/src/libraries/System.Net.WebSockets.Client/tests/ClientWebSocketTestBase.cs +++ b/src/libraries/System.Net.WebSockets.Client/tests/ClientWebSocketTestBase.cs @@ -9,6 +9,8 @@ using Xunit; using Xunit.Abstractions; +using System.Net.Http; +using System.Net.WebSockets.Client.Tests; namespace System.Net.WebSockets.Client.Tests { @@ -105,6 +107,17 @@ protected static async Task ReceiveEntireMessageAsync(We } } + protected virtual HttpMessageInvoker? GetInvoker() => null; + + protected Task GetConnectedWebSocket(Uri uri, int TimeOutMilliseconds, ITestOutputHelper output) => + WebSocketHelper.GetConnectedWebSocket(uri, TimeOutMilliseconds, output, invoker: GetInvoker()); + + protected Task ConnectAsync(ClientWebSocket cws, Uri uri, CancellationToken cancellationToken) => + cws.ConnectAsync(uri, GetInvoker(), cancellationToken); + + protected Task TestEcho(Uri uri, WebSocketMessageType type, int timeOutMilliseconds, ITestOutputHelper output) => + WebSocketHelper.TestEcho(uri, WebSocketMessageType.Text, TimeOutMilliseconds, _output, GetInvoker()); + public static bool WebSocketsSupported { get { return WebSocketHelper.WebSocketsSupported; } } } } diff --git a/src/libraries/System.Net.WebSockets.Client/tests/CloseTest.cs b/src/libraries/System.Net.WebSockets.Client/tests/CloseTest.cs index 38a53d550af0d4..c09df107c8538c 100644 --- a/src/libraries/System.Net.WebSockets.Client/tests/CloseTest.cs +++ b/src/libraries/System.Net.WebSockets.Client/tests/CloseTest.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Diagnostics; +using System.Net.Http; using System.Net.Test.Common; using System.Text; using System.Threading; @@ -13,10 +14,25 @@ namespace System.Net.WebSockets.Client.Tests { + public sealed class InvokerCloseTest : CloseTest + { + public InvokerCloseTest(ITestOutputHelper output) : base(output) { } + + protected override HttpMessageInvoker? GetInvoker() => new HttpMessageInvoker(new SocketsHttpHandler()); + } + + public sealed class HttpClientCloseTest : CloseTest + { + public HttpClientCloseTest(ITestOutputHelper output) : base(output) { } + + protected override HttpMessageInvoker? GetInvoker() => new HttpClient(new HttpClientHandler()); + } + public class CloseTest : ClientWebSocketTestBase { public CloseTest(ITestOutputHelper output) : base(output) { } + [ActiveIssue("https://github.com/dotnet/runtime/issues/28957")] [OuterLoop("Uses external servers", typeof(PlatformDetection), nameof(PlatformDetection.LocalEchoServerIsNotAvailable))] [ConditionalTheory(nameof(WebSocketsSupported)), MemberData(nameof(EchoServersAndBoolean))] @@ -24,7 +40,7 @@ public async Task CloseAsync_ServerInitiatedClose_Success(Uri server, bool useCl { const string closeWebSocketMetaCommand = ".close"; - using (ClientWebSocket cws = await WebSocketHelper.GetConnectedWebSocket(server, TimeOutMilliseconds, _output)) + using (ClientWebSocket cws = await GetConnectedWebSocket(server, TimeOutMilliseconds, _output)) { var cts = new CancellationTokenSource(TimeOutMilliseconds); @@ -70,7 +86,7 @@ await cws.SendAsync( [ConditionalTheory(nameof(WebSocketsSupported)), MemberData(nameof(EchoServers))] public async Task CloseAsync_ClientInitiatedClose_Success(Uri server) { - using (ClientWebSocket cws = await WebSocketHelper.GetConnectedWebSocket(server, TimeOutMilliseconds, _output)) + using (ClientWebSocket cws = await GetConnectedWebSocket(server, TimeOutMilliseconds, _output)) { var cts = new CancellationTokenSource(TimeOutMilliseconds); Assert.Equal(WebSocketState.Open, cws.State); @@ -94,7 +110,7 @@ public async Task CloseAsync_CloseDescriptionIsMaxLength_Success(Uri server) { string closeDescription = new string('C', CloseDescriptionMaxLength); - using (ClientWebSocket cws = await WebSocketHelper.GetConnectedWebSocket(server, TimeOutMilliseconds, _output)) + using (ClientWebSocket cws = await GetConnectedWebSocket(server, TimeOutMilliseconds, _output)) { var cts = new CancellationTokenSource(TimeOutMilliseconds); @@ -108,7 +124,7 @@ public async Task CloseAsync_CloseDescriptionIsMaxLengthPlusOne_ThrowsArgumentEx { string closeDescription = new string('C', CloseDescriptionMaxLength + 1); - using (ClientWebSocket cws = await WebSocketHelper.GetConnectedWebSocket(server, TimeOutMilliseconds, _output)) + using (ClientWebSocket cws = await GetConnectedWebSocket(server, TimeOutMilliseconds, _output)) { var cts = new CancellationTokenSource(TimeOutMilliseconds); @@ -131,7 +147,7 @@ public async Task CloseAsync_CloseDescriptionIsMaxLengthPlusOne_ThrowsArgumentEx [ConditionalTheory(nameof(WebSocketsSupported)), MemberData(nameof(EchoServers))] public async Task CloseAsync_CloseDescriptionHasUnicode_Success(Uri server) { - using (ClientWebSocket cws = await WebSocketHelper.GetConnectedWebSocket(server, TimeOutMilliseconds, _output)) + using (ClientWebSocket cws = await GetConnectedWebSocket(server, TimeOutMilliseconds, _output)) { var cts = new CancellationTokenSource(TimeOutMilliseconds); @@ -150,7 +166,7 @@ public async Task CloseAsync_CloseDescriptionHasUnicode_Success(Uri server) [ConditionalTheory(nameof(WebSocketsSupported)), MemberData(nameof(EchoServers))] public async Task CloseAsync_CloseDescriptionIsNull_Success(Uri server) { - using (ClientWebSocket cws = await WebSocketHelper.GetConnectedWebSocket(server, TimeOutMilliseconds, _output)) + using (ClientWebSocket cws = await GetConnectedWebSocket(server, TimeOutMilliseconds, _output)) { var cts = new CancellationTokenSource(TimeOutMilliseconds); @@ -166,7 +182,7 @@ public async Task CloseAsync_CloseDescriptionIsNull_Success(Uri server) [ConditionalTheory(nameof(WebSocketsSupported)), MemberData(nameof(EchoServers))] public async Task CloseOutputAsync_ExpectedStates(Uri server) { - using (ClientWebSocket cws = await WebSocketHelper.GetConnectedWebSocket(server, TimeOutMilliseconds, _output)) + using (ClientWebSocket cws = await GetConnectedWebSocket(server, TimeOutMilliseconds, _output)) { var cts = new CancellationTokenSource(TimeOutMilliseconds); @@ -185,7 +201,7 @@ public async Task CloseOutputAsync_ExpectedStates(Uri server) [ConditionalTheory(nameof(WebSocketsSupported)), MemberData(nameof(EchoServers))] public async Task CloseAsync_CloseOutputAsync_Throws(Uri server) { - using (ClientWebSocket cws = await WebSocketHelper.GetConnectedWebSocket(server, TimeOutMilliseconds, _output)) + using (ClientWebSocket cws = await GetConnectedWebSocket(server, TimeOutMilliseconds, _output)) { var cts = new CancellationTokenSource(TimeOutMilliseconds); @@ -212,7 +228,7 @@ public async Task CloseOutputAsync_ClientInitiated_CanReceive_CanClose(Uri serve { string message = "Hello WebSockets!"; - using (ClientWebSocket cws = await WebSocketHelper.GetConnectedWebSocket(server, TimeOutMilliseconds, _output)) + using (ClientWebSocket cws = await GetConnectedWebSocket(server, TimeOutMilliseconds, _output)) { var cts = new CancellationTokenSource(TimeOutMilliseconds); @@ -254,7 +270,7 @@ public async Task CloseOutputAsync_ServerInitiated_CanSend(Uri server) var expectedCloseStatus = WebSocketCloseStatus.NormalClosure; var expectedCloseDescription = ".shutdown"; - using (ClientWebSocket cws = await WebSocketHelper.GetConnectedWebSocket(server, TimeOutMilliseconds, _output)) + using (ClientWebSocket cws = await GetConnectedWebSocket(server, TimeOutMilliseconds, _output)) { var cts = new CancellationTokenSource(TimeOutMilliseconds); @@ -297,7 +313,7 @@ await cws.SendAsync( [ConditionalTheory(nameof(WebSocketsSupported)), MemberData(nameof(EchoServers))] public async Task CloseOutputAsync_CloseDescriptionIsNull_Success(Uri server) { - using (ClientWebSocket cws = await WebSocketHelper.GetConnectedWebSocket(server, TimeOutMilliseconds, _output)) + using (ClientWebSocket cws = await GetConnectedWebSocket(server, TimeOutMilliseconds, _output)) { var cts = new CancellationTokenSource(TimeOutMilliseconds); @@ -314,7 +330,7 @@ public async Task CloseOutputAsync_CloseDescriptionIsNull_Success(Uri server) public async Task CloseOutputAsync_DuringConcurrentReceiveAsync_ExpectedStates(Uri server) { var receiveBuffer = new byte[1024]; - using (ClientWebSocket cws = await WebSocketHelper.GetConnectedWebSocket(server, TimeOutMilliseconds, _output)) + using (ClientWebSocket cws = await GetConnectedWebSocket(server, TimeOutMilliseconds, _output)) { // Issue a receive but don't wait for it. var t = cws.ReceiveAsync(new ArraySegment(receiveBuffer), CancellationToken.None); @@ -351,7 +367,7 @@ public async Task CloseOutputAsync_DuringConcurrentReceiveAsync_ExpectedStates(U public async Task CloseAsync_DuringConcurrentReceiveAsync_ExpectedStates(Uri server) { var receiveBuffer = new byte[1024]; - using (ClientWebSocket cws = await WebSocketHelper.GetConnectedWebSocket(server, TimeOutMilliseconds, _output)) + using (ClientWebSocket cws = await GetConnectedWebSocket(server, TimeOutMilliseconds, _output)) { var t = cws.ReceiveAsync(new ArraySegment(receiveBuffer), CancellationToken.None); Assert.False(t.IsCompleted); @@ -387,7 +403,7 @@ await LoopbackServer.CreateClientAndServerAsync(async uri => using (var cws = new ClientWebSocket()) using (var cts = new CancellationTokenSource(TimeOutMilliseconds)) { - await cws.ConnectAsync(uri, cts.Token); + await ConnectAsync(cws, uri, cts.Token); Task receiveTask = cws.ReceiveAsync(new byte[1], CancellationToken.None); diff --git a/src/libraries/System.Net.WebSockets.Client/tests/ConnectTest.Http2.cs b/src/libraries/System.Net.WebSockets.Client/tests/ConnectTest.Http2.cs index d59b2e5a8721ef..aa14fd93ab2927 100644 --- a/src/libraries/System.Net.WebSockets.Client/tests/ConnectTest.Http2.cs +++ b/src/libraries/System.Net.WebSockets.Client/tests/ConnectTest.Http2.cs @@ -15,10 +15,25 @@ namespace System.Net.WebSockets.Client.Tests { + public sealed class InvokerConnectTest_Http2 : ConnectTest_Http2 + { + public InvokerConnectTest_Http2(ITestOutputHelper output) : base(output) { } + + protected override HttpMessageInvoker? GetInvoker() => new HttpClient(new HttpClientHandler()); + } + + public sealed class HttpClientConnectTest_Http2 : ConnectTest_Http2 + { + public HttpClientConnectTest_Http2(ITestOutputHelper output) : base(output) { } + + protected override HttpMessageInvoker? GetInvoker() => new HttpClient(new HttpClientHandler()); + } + public class ConnectTest_Http2 : ClientWebSocketTestBase { public ConnectTest_Http2(ITestOutputHelper output) : base(output) { } + [Theory] [InlineData(false)] [InlineData(true)] diff --git a/src/libraries/System.Net.WebSockets.Client/tests/ConnectTest.cs b/src/libraries/System.Net.WebSockets.Client/tests/ConnectTest.cs index 5097206c13c330..fd0eee1265cfa1 100644 --- a/src/libraries/System.Net.WebSockets.Client/tests/ConnectTest.cs +++ b/src/libraries/System.Net.WebSockets.Client/tests/ConnectTest.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Net.Http; using System.Net.Test.Common; using System.Threading; using System.Threading.Tasks; @@ -13,6 +14,19 @@ namespace System.Net.WebSockets.Client.Tests { + public sealed class InvokerConnectTest : ConnectTest + { + public InvokerConnectTest(ITestOutputHelper output) : base(output) { } + protected override HttpMessageInvoker? GetInvoker() => new HttpMessageInvoker(new SocketsHttpHandler()); + } + + public sealed class HttpClientConnectTest : ConnectTest + { + public HttpClientConnectTest(ITestOutputHelper output) : base(output) { } + + protected override HttpMessageInvoker? GetInvoker() => new HttpClient(new HttpClientHandler()); + } + public class ConnectTest : ClientWebSocketTestBase { public ConnectTest(ITestOutputHelper output) : base(output) { } @@ -26,7 +40,7 @@ public async Task ConnectAsync_NotWebSocketServer_ThrowsWebSocketExceptionWithMe { var cts = new CancellationTokenSource(TimeOutMilliseconds); WebSocketException ex = await Assert.ThrowsAsync(() => - cws.ConnectAsync(server, cts.Token)); + ConnectAsync(cws, server, cts.Token)); if (PlatformDetection.IsNetCore && !PlatformDetection.IsInAppContainer) // bug fix in netcoreapp: https://github.com/dotnet/corefx/pull/35960 { @@ -47,14 +61,14 @@ public async Task ConnectAsync_NotWebSocketServer_ThrowsWebSocketExceptionWithMe [ConditionalTheory(nameof(WebSocketsSupported)), MemberData(nameof(EchoServers))] public async Task EchoBinaryMessage_Success(Uri server) { - await WebSocketHelper.TestEcho(server, WebSocketMessageType.Binary, TimeOutMilliseconds, _output); + await TestEcho(server, WebSocketMessageType.Binary, TimeOutMilliseconds, _output); } [OuterLoop("Uses external servers", typeof(PlatformDetection), nameof(PlatformDetection.LocalEchoServerIsNotAvailable))] [ConditionalTheory(nameof(WebSocketsSupported)), MemberData(nameof(EchoServers))] public async Task EchoTextMessage_Success(Uri server) { - await WebSocketHelper.TestEcho(server, WebSocketMessageType.Text, TimeOutMilliseconds, _output); + await TestEcho(server, WebSocketMessageType.Text, TimeOutMilliseconds, _output); } [OuterLoop("Uses external servers", typeof(PlatformDetection), nameof(PlatformDetection.LocalEchoServerIsNotAvailable))] @@ -68,7 +82,7 @@ public async Task ConnectAsync_AddCustomHeaders_Success(Uri server) cws.Options.SetRequestHeader("X-CustomHeader2", "Value2"); using (var cts = new CancellationTokenSource(TimeOutMilliseconds)) { - Task taskConnect = cws.ConnectAsync(server, cts.Token); + Task taskConnect = ConnectAsync(cws, server, cts.Token); Assert.True( (cws.State == WebSocketState.None) || (cws.State == WebSocketState.Connecting) || @@ -108,7 +122,7 @@ await LoopbackServer.CreateClientAndServerAsync(async uri => using (var cts = new CancellationTokenSource(TimeOutMilliseconds)) { cws.Options.SetRequestHeader("Host", expectedHost); - await cws.ConnectAsync(uri, cts.Token); + await ConnectAsync(cws, uri, cts.Token); } }, server => server.AcceptConnectionAsync(async connection => { @@ -183,7 +197,7 @@ public async Task ConnectAsync_PassNoSubProtocol_ServerRequires_ThrowsWebSocketE ub.Query = "subprotocol=" + AcceptedProtocol; WebSocketException ex = await Assert.ThrowsAsync(() => - cws.ConnectAsync(ub.Uri, cts.Token)); + ConnectAsync(cws, ub.Uri, cts.Token)); _output.WriteLine(ex.Message); if (PlatformDetection.IsNetCore) // bug fix in netcoreapp: https://github.com/dotnet/corefx/pull/35960 { @@ -210,7 +224,7 @@ public async Task ConnectAsync_PassMultipleSubProtocols_ServerRequires_Connectio var ub = new UriBuilder(server); ub.Query = "subprotocol=" + AcceptedProtocol; - await cws.ConnectAsync(ub.Uri, cts.Token); + await ConnectAsync(cws, ub.Uri, cts.Token); Assert.Equal(WebSocketState.Open, cws.State); Assert.Equal(AcceptedProtocol, cws.SubProtocol); } @@ -227,7 +241,7 @@ await LoopbackServer.CreateClientAndServerAsync(async uri => using (var cts = new CancellationTokenSource(TimeOutMilliseconds)) { clientSocket.Options.SetRequestHeader("Authorization", "AWS4-HMAC-SHA256 Credential=PLACEHOLDER /20190301/us-east-2/neptune-db/aws4_request, SignedHeaders=host;x-amz-date, Signature=b8155de54d9faab00000000000000000000000000a07e0d7dda49902e4d9202"); - await clientSocket.ConnectAsync(uri, cts.Token); + await ConnectAsync(clientSocket, uri, cts.Token); } }, server => server.AcceptConnectionAsync(async connection => { @@ -245,7 +259,7 @@ public async Task ConnectAndCloseAsync_UseProxyServer_ExpectedClosedState(Uri se using (LoopbackProxyServer proxyServer = LoopbackProxyServer.Create()) { cws.Options.Proxy = new WebProxy(proxyServer.Uri); - await cws.ConnectAsync(server, cts.Token); + await ConnectAsync(cws, server, cts.Token); string expectedCloseStatusDescription = "Client close status"; await cws.CloseAsync(WebSocketCloseStatus.NormalClosure, expectedCloseStatusDescription, cts.Token); @@ -263,7 +277,7 @@ public async Task ConnectAsync_CancellationRequestedBeforeConnect_ThrowsOperatio { var cts = new CancellationTokenSource(); cts.Cancel(); - Task t = clientSocket.ConnectAsync(new Uri("ws://" + Guid.NewGuid().ToString("N")), cts.Token); + Task t = ConnectAsync(clientSocket, new Uri("ws://" + Guid.NewGuid().ToString("N")), cts.Token); await Assert.ThrowsAnyAsync(() => t); } } @@ -274,7 +288,7 @@ public async Task ConnectAsync_CancellationRequestedInflightConnect_ThrowsOperat using (var clientSocket = new ClientWebSocket()) { var cts = new CancellationTokenSource(); - Task t = clientSocket.ConnectAsync(new Uri("ws://" + Guid.NewGuid().ToString("N")), cts.Token); + Task t = ConnectAsync(clientSocket, new Uri("ws://" + Guid.NewGuid().ToString("N")), cts.Token); cts.Cancel(); await Assert.ThrowsAnyAsync(() => t); } @@ -291,7 +305,7 @@ await LoopbackServer.CreateClientAndServerAsync(async uri => try { var cts = new CancellationTokenSource(); - Task t = clientSocket.ConnectAsync(uri, cts.Token); + Task t = ConnectAsync(clientSocket, uri, cts.Token); Assert.False(t.IsCompleted); cts.Cancel(); await Assert.ThrowsAnyAsync(() => t); @@ -326,7 +340,7 @@ await LoopbackServer.CreateClientAndServerAsync(async uri => using (var cts = new CancellationTokenSource(TimeOutMilliseconds)) { clientWebSocket.Options.CollectHttpResponseDetails = true; - Task t = clientWebSocket.ConnectAsync(uri, cts.Token); + Task t = ConnectAsync(clientWebSocket, uri, cts.Token); await Assert.ThrowsAnyAsync(() => t); Assert.Equal(HttpStatusCode.Unauthorized, clientWebSocket.HttpStatusCode); @@ -346,7 +360,7 @@ await LoopbackServer.CreateClientAndServerAsync(async uri => using (var cts = new CancellationTokenSource(TimeOutMilliseconds)) { clientWebSocket.Options.CollectHttpResponseDetails = true; - Task t = clientWebSocket.ConnectAsync(uri, cts.Token); + Task t = ConnectAsync(clientWebSocket, uri, cts.Token); await Assert.ThrowsAnyAsync(() => t); Assert.Equal(HttpStatusCode.Unauthorized, clientWebSocket.HttpStatusCode); @@ -369,7 +383,7 @@ await LoopbackServer.CreateClientAndServerAsync(async uri => using (var cts = new CancellationTokenSource(TimeOutMilliseconds)) { clientWebSocket.Options.CollectHttpResponseDetails = true; - await clientWebSocket.ConnectAsync(uri, cts.Token); + await ConnectAsync(clientWebSocket, uri, cts.Token); Assert.Equal(HttpStatusCode.SwitchingProtocols, clientWebSocket.HttpStatusCode); Assert.NotEmpty(clientWebSocket.HttpResponseHeaders); diff --git a/src/libraries/System.Net.WebSockets.Client/tests/DeflateTests.cs b/src/libraries/System.Net.WebSockets.Client/tests/DeflateTests.cs index e0a0e1e59fd846..262e45ae414db8 100644 --- a/src/libraries/System.Net.WebSockets.Client/tests/DeflateTests.cs +++ b/src/libraries/System.Net.WebSockets.Client/tests/DeflateTests.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.Net.Http; using System.Net.Test.Common; using System.Reflection; using System.Text; @@ -13,6 +14,20 @@ namespace System.Net.WebSockets.Client.Tests { + public sealed class InvokerDeflateTests : DeflateTests + { + public InvokerDeflateTests(ITestOutputHelper output) : base(output) { } + + protected override HttpMessageInvoker? GetInvoker() => new HttpMessageInvoker(new SocketsHttpHandler()); + } + + public sealed class HttpClientDeflateTests : DeflateTests + { + public HttpClientDeflateTests(ITestOutputHelper output) : base(output) { } + + protected override HttpMessageInvoker? GetInvoker() => new HttpClient(new HttpClientHandler()); + } + [PlatformSpecific(~TestPlatforms.Browser)] public class DeflateTests : ClientWebSocketTestBase { @@ -45,7 +60,7 @@ await LoopbackServer.CreateClientAndServerAsync(async uri => ServerContextTakeover = serverContextTakover }; - await client.ConnectAsync(uri, cancellation.Token); + await ConnectAsync(client, uri, cancellation.Token); object webSocketHandle = client.GetType().GetField("_innerWebSocket", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(client); WebSocketDeflateOptions negotiatedDeflateOptions = (WebSocketDeflateOptions)webSocketHandle.GetType() diff --git a/src/libraries/System.Net.WebSockets.Client/tests/SendReceiveTest.cs b/src/libraries/System.Net.WebSockets.Client/tests/SendReceiveTest.cs index 63e59b07e8cd06..6597b6f9ec6315 100644 --- a/src/libraries/System.Net.WebSockets.Client/tests/SendReceiveTest.cs +++ b/src/libraries/System.Net.WebSockets.Client/tests/SendReceiveTest.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Net.Http; using System.Net.Sockets; using System.Net.Test.Common; using System.Threading; @@ -11,7 +12,36 @@ namespace System.Net.WebSockets.Client.Tests { - public sealed class MemorySendReceiveTest : SendReceiveTest + + public sealed class InvokerMemorySendReceiveTest : MemorySendReceiveTest + { + public InvokerMemorySendReceiveTest(ITestOutputHelper output) : base(output) { } + + protected override HttpMessageInvoker? GetInvoker() => new HttpMessageInvoker(new SocketsHttpHandler()); + } + + public sealed class HttpClientMemorySendReceiveTest : MemorySendReceiveTest + { + public HttpClientMemorySendReceiveTest(ITestOutputHelper output) : base(output) { } + + protected override HttpMessageInvoker? GetInvoker() => new HttpClient(new HttpClientHandler()); + } + + public sealed class InvokerArraySegmentSendReceiveTest : ArraySegmentSendReceiveTest + { + public InvokerArraySegmentSendReceiveTest(ITestOutputHelper output) : base(output) { } + + protected override HttpMessageInvoker? GetInvoker() => new HttpMessageInvoker(new SocketsHttpHandler()); + } + + public sealed class HttpClientArraySegmentSendReceiveTest : ArraySegmentSendReceiveTest + { + public HttpClientArraySegmentSendReceiveTest(ITestOutputHelper output) : base(output) { } + + protected override HttpMessageInvoker? GetInvoker() => new HttpClient(new HttpClientHandler()); + } + + public class MemorySendReceiveTest : SendReceiveTest { public MemorySendReceiveTest(ITestOutputHelper output) : base(output) { } @@ -31,7 +61,7 @@ protected override Task SendAsync(WebSocket ws, ArraySegment arraySegment, cancellationToken).AsTask(); } - public sealed class ArraySegmentSendReceiveTest : SendReceiveTest + public class ArraySegmentSendReceiveTest : SendReceiveTest { public ArraySegmentSendReceiveTest(ITestOutputHelper output) : base(output) { } @@ -60,7 +90,7 @@ public async Task SendReceive_PartialMessageDueToSmallReceiveBuffer_Success(Uri var receiveBuffer = new byte[SendBufferSize / 2]; var receiveSegment = new ArraySegment(receiveBuffer); - using (ClientWebSocket cws = await WebSocketHelper.GetConnectedWebSocket(server, TimeOutMilliseconds, _output)) + using (ClientWebSocket cws = await GetConnectedWebSocket(server, TimeOutMilliseconds, _output)) { var ctsDefault = new CancellationTokenSource(TimeOutMilliseconds); @@ -132,7 +162,7 @@ public async Task SendReceive_PartialMessageBeforeCompleteMessageArrives_Success [ConditionalTheory(nameof(WebSocketsSupported)), MemberData(nameof(EchoServers))] public async Task SendAsync_SendCloseMessageType_ThrowsArgumentExceptionWithMessage(Uri server) { - using (ClientWebSocket cws = await WebSocketHelper.GetConnectedWebSocket(server, TimeOutMilliseconds, _output)) + using (ClientWebSocket cws = await GetConnectedWebSocket(server, TimeOutMilliseconds, _output)) { var cts = new CancellationTokenSource(TimeOutMilliseconds); @@ -161,7 +191,7 @@ public async Task SendAsync_SendCloseMessageType_ThrowsArgumentExceptionWithMess // This will also pass when no exception is thrown. Current implementation doesn't throw. public async Task SendAsync_MultipleOutstandingSendOperations_Throws(Uri server) { - using (ClientWebSocket cws = await WebSocketHelper.GetConnectedWebSocket(server, TimeOutMilliseconds, _output)) + using (ClientWebSocket cws = await GetConnectedWebSocket(server, TimeOutMilliseconds, _output)) { var cts = new CancellationTokenSource(TimeOutMilliseconds); @@ -221,7 +251,7 @@ public async Task SendAsync_MultipleOutstandingSendOperations_Throws(Uri server) // This will also pass when no exception is thrown. Current implementation doesn't throw. public async Task ReceiveAsync_MultipleOutstandingReceiveOperations_Throws(Uri server) { - using (ClientWebSocket cws = await WebSocketHelper.GetConnectedWebSocket(server, TimeOutMilliseconds, _output)) + using (ClientWebSocket cws = await GetConnectedWebSocket(server, TimeOutMilliseconds, _output)) { var cts = new CancellationTokenSource(TimeOutMilliseconds); @@ -285,7 +315,7 @@ await SendAsync( [ConditionalTheory(nameof(WebSocketsSupported)), MemberData(nameof(EchoServers))] public async Task SendAsync_SendZeroLengthPayloadAsEndOfMessage_Success(Uri server) { - using (ClientWebSocket cws = await WebSocketHelper.GetConnectedWebSocket(server, TimeOutMilliseconds, _output)) + using (ClientWebSocket cws = await GetConnectedWebSocket(server, TimeOutMilliseconds, _output)) { var cts = new CancellationTokenSource(TimeOutMilliseconds); string message = "hello"; @@ -364,7 +394,7 @@ public async Task SendReceive_VaryingLengthBuffers_Success(Uri server) [ConditionalTheory(nameof(WebSocketsSupported)), MemberData(nameof(EchoServers))] public async Task SendReceive_Concurrent_Success(Uri server) { - using (ClientWebSocket cws = await WebSocketHelper.GetConnectedWebSocket(server, TimeOutMilliseconds, _output)) + using (ClientWebSocket cws = await GetConnectedWebSocket(server, TimeOutMilliseconds, _output)) { CancellationTokenSource ctsDefault = new CancellationTokenSource(TimeOutMilliseconds); @@ -416,7 +446,7 @@ public async Task SendReceive_ConnectionClosedPrematurely_ReceiveAsyncFailsAndWe // Initiate a connection attempt. var cts = new CancellationTokenSource(TimeOutMilliseconds); - await clientSocket.ConnectAsync(url, cts.Token); + await ConnectAsync(clientSocket, url, cts.Token); // Post a pending ReceiveAsync before the TCP connection is torn down. var recvBuffer = new byte[100]; @@ -463,7 +493,7 @@ await LoopbackServer.CreateServerAsync(async (server, url) => [ConditionalTheory(nameof(WebSocketsSupported)), MemberData(nameof(EchoServers))] public async Task ZeroByteReceive_CompletesWhenDataAvailable(Uri server) { - using (ClientWebSocket cws = await WebSocketHelper.GetConnectedWebSocket(server, TimeOutMilliseconds, _output)) + using (ClientWebSocket cws = await GetConnectedWebSocket(server, TimeOutMilliseconds, _output)) { var ctsDefault = new CancellationTokenSource(TimeOutMilliseconds); diff --git a/src/libraries/System.Net.WebSockets.Client/tests/WebSocketHelper.cs b/src/libraries/System.Net.WebSockets.Client/tests/WebSocketHelper.cs index 0782f9d5232e42..d409007f9995d0 100644 --- a/src/libraries/System.Net.WebSockets.Client/tests/WebSocketHelper.cs +++ b/src/libraries/System.Net.WebSockets.Client/tests/WebSocketHelper.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Net.Http; using System.Net.Test.Common; using System.Threading; using System.Threading.Tasks; @@ -19,7 +20,8 @@ public static async Task TestEcho( Uri server, WebSocketMessageType type, int timeOutMilliseconds, - ITestOutputHelper output) + ITestOutputHelper output, + HttpMessageInvoker? invoker = null) { var cts = new CancellationTokenSource(timeOutMilliseconds); string message = "Hello WebSockets!"; @@ -27,7 +29,7 @@ public static async Task TestEcho( var receiveBuffer = new byte[100]; var receiveSegment = new ArraySegment(receiveBuffer); - using (ClientWebSocket cws = await GetConnectedWebSocket(server, timeOutMilliseconds, output)) + using (ClientWebSocket cws = await GetConnectedWebSocket(server, timeOutMilliseconds, output, invoker: invoker)) { output.WriteLine("TestEcho: SendAsync starting."); await cws.SendAsync(WebSocketData.GetBufferFromText(message), type, true, cts.Token); @@ -66,7 +68,8 @@ public static Task GetConnectedWebSocket( int timeOutMilliseconds, ITestOutputHelper output, TimeSpan keepAliveInterval = default, - IWebProxy proxy = null) => + IWebProxy proxy = null, + HttpMessageInvoker? invoker = null) => Retry(output, async () => { var cws = new ClientWebSocket(); @@ -83,7 +86,7 @@ public static Task GetConnectedWebSocket( using (var cts = new CancellationTokenSource(timeOutMilliseconds)) { output.WriteLine("GetConnectedWebSocket: ConnectAsync starting."); - Task taskConnect = cws.ConnectAsync(server, cts.Token); + Task taskConnect = invoker == null ? cws.ConnectAsync(server, cts.Token) : cws.ConnectAsync(server, invoker, cts.Token); Assert.True( (cws.State == WebSocketState.None) || (cws.State == WebSocketState.Connecting) || From f19591801305bc61f353ab91c65e50360175b44c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aleksey=20Kliger=20=28=CE=BBgeek=29?= Date: Tue, 9 Aug 2022 07:25:41 -0400 Subject: [PATCH 09/41] [wasm-ep] Remove old sample and JS-based diagnostics (#73586) * [wasm-ep] remove in-JS VFS tracing support use the websocket diagnostic server support instead * remove browser-mt-eventpipe sample use the browser-threading sample for working with threading use the browser-eventpipe sample for working with tracing * remove the EventPipeSessionOptions, too --- src/libraries/tests.proj | 1 - .../sample/wasm/browser-eventpipe/main.js | 46 +------ .../sample/wasm/browser-mt-eventpipe/Makefile | 11 -- .../wasm/browser-mt-eventpipe/Program.cs | 51 -------- .../Wasm.Browser.ThreadsEP.Sample.csproj | 42 ------ .../wasm/browser-mt-eventpipe/index.html | 21 --- .../sample/wasm/browser-mt-eventpipe/main.js | 63 --------- .../diagnostics/browser/file-session.ts | 109 ---------------- .../browser/session-options-builder.ts | 122 ------------------ src/mono/wasm/runtime/diagnostics/index.ts | 85 +----------- .../wasm/runtime/diagnostics/shared/types.ts | 11 ++ src/mono/wasm/runtime/startup.ts | 4 +- src/mono/wasm/runtime/types.ts | 26 +--- 13 files changed, 22 insertions(+), 570 deletions(-) delete mode 100644 src/mono/sample/wasm/browser-mt-eventpipe/Makefile delete mode 100644 src/mono/sample/wasm/browser-mt-eventpipe/Program.cs delete mode 100644 src/mono/sample/wasm/browser-mt-eventpipe/Wasm.Browser.ThreadsEP.Sample.csproj delete mode 100644 src/mono/sample/wasm/browser-mt-eventpipe/index.html delete mode 100644 src/mono/sample/wasm/browser-mt-eventpipe/main.js delete mode 100644 src/mono/wasm/runtime/diagnostics/browser/file-session.ts delete mode 100644 src/mono/wasm/runtime/diagnostics/browser/session-options-builder.ts diff --git a/src/libraries/tests.proj b/src/libraries/tests.proj index 77e53d7c06e4d3..6c222ac05cbd12 100644 --- a/src/libraries/tests.proj +++ b/src/libraries/tests.proj @@ -57,7 +57,6 @@ - diff --git a/src/mono/sample/wasm/browser-eventpipe/main.js b/src/mono/sample/wasm/browser-eventpipe/main.js index 2d5b99e8560c9e..51b638b21bf4a9 100644 --- a/src/mono/sample/wasm/browser-eventpipe/main.js +++ b/src/mono/sample/wasm/browser-eventpipe/main.js @@ -1,25 +1,5 @@ import createDotnetRuntime from "./dotnet.js"; -function downloadData(dataURL, filename) { - // make an `` link and click on it to trigger a download with the given name - const elt = document.createElement('a'); - elt.download = filename; - elt.href = dataURL; - - document.body.appendChild(elt); - - elt.click(); - - document.body.removeChild(elt); -} - -function makeTimestamp() { - // ISO date string, but with : and . replaced by - - const t = new Date(); - const s = t.toISOString(); - return s.replace(/[:.]/g, '-'); -} - const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms)) async function doWork(startWork, stopWork, getIterationsDone) { @@ -56,31 +36,7 @@ async function doWork(startWork, stopWork, getIterationsDone) { function getOnClickHandler(startWork, stopWork, getIterationsDone) { return async function () { - let sessions = MONO.diagnostics.getStartupSessions(); - - if (typeof (sessions) !== "object" || sessions.length === "undefined") - console.error("expected an array of sessions, got ", sessions); - let eventSession = null; - if (sessions.length !== 0) { - if (sessions.length != 1) - console.error("expected one startup session, got ", sessions); - eventSession = sessions[0]; - console.debug("eventSession state is ", eventSession._state); // ooh protected member access - } - - const ret = await doWork(startWork, stopWork, getIterationsDone); - - if (eventSession !== null) { - eventSession.stop(); - - const filename = "dotnet-wasm-" + makeTimestamp() + ".nettrace"; - - const blob = eventSession.getTraceBlob(); - const uri = URL.createObjectURL(blob); - downloadData(uri, filename); - } - - console.debug("sample onclick handler done"); + await doWork(startWork, stopWork, getIterationsDone); } } diff --git a/src/mono/sample/wasm/browser-mt-eventpipe/Makefile b/src/mono/sample/wasm/browser-mt-eventpipe/Makefile deleted file mode 100644 index 8fddd8371b830e..00000000000000 --- a/src/mono/sample/wasm/browser-mt-eventpipe/Makefile +++ /dev/null @@ -1,11 +0,0 @@ -TOP=../../../../.. - -include ../wasm.mk - -ifneq ($(AOT),) -override MSBUILD_ARGS+=/p:RunAOTCompilation=true -endif - -PROJECT_NAME=Wasm.Browser.ThreadsEP.Sample.csproj - -run: run-browser diff --git a/src/mono/sample/wasm/browser-mt-eventpipe/Program.cs b/src/mono/sample/wasm/browser-mt-eventpipe/Program.cs deleted file mode 100644 index 8683f673d92ef1..00000000000000 --- a/src/mono/sample/wasm/browser-mt-eventpipe/Program.cs +++ /dev/null @@ -1,51 +0,0 @@ -// 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; -using System.Collections.Generic; -using System.Threading; -using System.Runtime.Versioning; - -namespace Sample -{ - public class Test - { - public static void Main(string[] args) - { - Console.WriteLine ("Hello, World!"); - } - - [MethodImpl(MethodImplOptions.NoInlining)] - [SupportedOSPlatform("browser")] // ask the analyzer to warn if we use APIs not supported on browser-wasm - public static int TestMeaning() - { - List myList = new List{ 1, 2, 3, 4 }; - Console.WriteLine(myList); - - Thread t = new Thread(new ThreadStart(ThreadFuncTest)); - t.Start(); - - for (int i = 0; i < 5; i++) - { - Console.WriteLine("Main Thread is doing stuff too... " + i.ToString()); - Thread.Sleep(100); - GC.Collect(); - } - - return 42; - } - - public static void ThreadFuncTest() - { - Console.WriteLine("Hello from another thread"); - - for (int i = 0; i < 5; i++) - { - Console.WriteLine("Sleeping from another thread: " + i.ToString()); - Thread.Sleep(100); - GC.Collect(); - } - } - } -} diff --git a/src/mono/sample/wasm/browser-mt-eventpipe/Wasm.Browser.ThreadsEP.Sample.csproj b/src/mono/sample/wasm/browser-mt-eventpipe/Wasm.Browser.ThreadsEP.Sample.csproj deleted file mode 100644 index 514d8ca176e915..00000000000000 --- a/src/mono/sample/wasm/browser-mt-eventpipe/Wasm.Browser.ThreadsEP.Sample.csproj +++ /dev/null @@ -1,42 +0,0 @@ - - - true - main.js - true - embedded - 1 - true - true - $(ExecXHarnessCmd) wasm test-browser --app=. --browser=Chrome $(XHarnessBrowserPathArg) --html-file=index.html --output-directory=$(XHarnessOutput) -- $(MSBuildProjectName).dll - true - - - - - - - - - <_SampleProject>Wasm.Browser.ThreadsEP.Sample.csproj - - - - - true - - - - - - - - - - - - - diff --git a/src/mono/sample/wasm/browser-mt-eventpipe/index.html b/src/mono/sample/wasm/browser-mt-eventpipe/index.html deleted file mode 100644 index e3b30951b6571e..00000000000000 --- a/src/mono/sample/wasm/browser-mt-eventpipe/index.html +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - Sample event pipe - - - - - - - - - Answer to the Ultimate Question of Life, the Universe, and Everything is : - - - - - \ No newline at end of file diff --git a/src/mono/sample/wasm/browser-mt-eventpipe/main.js b/src/mono/sample/wasm/browser-mt-eventpipe/main.js deleted file mode 100644 index a3c863349ae5cb..00000000000000 --- a/src/mono/sample/wasm/browser-mt-eventpipe/main.js +++ /dev/null @@ -1,63 +0,0 @@ -import createDotnetRuntime from "./dotnet.js"; - -function wasm_exit(exit_code, reason) { - /* Set result in a tests_done element, to be read by xharness in runonly CI test */ - const tests_done_elem = document.createElement("label"); - tests_done_elem.id = "tests_done"; - tests_done_elem.innerHTML = exit_code.toString(); - if (exit_code) tests_done_elem.style.background = "red"; - document.body.appendChild(tests_done_elem); - - if (reason) console.error(reason); - console.log(`WASM EXIT ${exit_code}`); -} - -function Uint8ToString(u8a) { - var CHUNK_SZ = 0x8000; - var c = []; - for (var i = 0; i < u8a.length; i += CHUNK_SZ) { - c.push(String.fromCharCode.apply(null, u8a.subarray(i, i + CHUNK_SZ))); - } - return c.join(""); -} - -async function main() { - const { MONO, BINDING, Module, runtimeBuildInfo } = await createDotnetRuntime(() => { - console.log('user code in createDotnetRuntime') - return { - disableDotnet6Compatibility: true, - configSrc: "./mono-config.json", - preInit: () => { console.log('user code Module.preInit') }, - preRun: () => { console.log('user code Module.preRun') }, - onRuntimeInitialized: () => { console.log('user code Module.onRuntimeInitialized') }, - postRun: () => { console.log('user code Module.postRun') }, - } - }); - globalThis.__Module = Module; - globalThis.MONO = MONO; - console.log('after createDotnetRuntime') - - try { - const testMeaning = BINDING.bind_static_method("[Wasm.Browser.ThreadsEP.Sample] Sample.Test:TestMeaning"); - const ret = testMeaning(); - document.getElementById("out").innerHTML = `${ret} as computed on dotnet ver ${runtimeBuildInfo.productVersion}`; - - console.debug(`ret: ${ret}`); - - let exit_code = ret == 42 ? 0 : 1; - Module._mono_wasm_exit(exit_code); - - wasm_exit(exit_code); - } catch (err) { - console.log(`WASM ERROR ${err}`); - - var b = Module.FS.readFile('/trace.nettrace'); - var bits = btoa((Uint8ToString(b))); - - window.open("data:application/octet-stream;base64," + bits); - - wasm_exit(2, err); - } -} - -setTimeout(main, 10000); diff --git a/src/mono/wasm/runtime/diagnostics/browser/file-session.ts b/src/mono/wasm/runtime/diagnostics/browser/file-session.ts deleted file mode 100644 index 0e935eb471066f..00000000000000 --- a/src/mono/wasm/runtime/diagnostics/browser/file-session.ts +++ /dev/null @@ -1,109 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// An EventPipe session object represents a single diagnostic tracing session that is collecting -/// events from the runtime and managed libraries. There may be multiple active sessions at the same time. -/// Each session subscribes to a number of providers and will collect events from the time that start() is called, until stop() is called. -/// Upon completion the session saves the events to a file on the VFS. -/// The data can then be retrieved as Blob. -import { EventPipeSessionID, EventPipeSessionOptions } from "../../types"; -import { EventPipeSessionIDImpl } from "../shared/types"; -import { createEventPipeFileSession } from "../shared/create-session"; -import { Module } from "../../imports"; -import cwraps from "../../cwraps"; - -export interface EventPipeSession { - // session ID for debugging logging only - get sessionID(): EventPipeSessionID; - start(): void; - stop(): void; - getTraceBlob(): Blob; -} - -// internal session state of the JS instance -enum State { - Initialized, - Started, - Done, -} - -/// An EventPipe session that saves the event data to a file in the VFS. -class EventPipeFileSession implements EventPipeSession { - protected _state: State; - private _sessionID: EventPipeSessionIDImpl; - private _tracePath: string; // VFS file path to the trace file - - get sessionID(): bigint { return BigInt(this._sessionID); } - - constructor(sessionID: EventPipeSessionIDImpl, tracePath: string) { - this._state = State.Initialized; - this._sessionID = sessionID; - this._tracePath = tracePath; - console.debug(`MONO_WASM: EventPipe session ${this.sessionID} created`); - } - - start = () => { - if (this._state !== State.Initialized) { - throw new Error(`MONO_WASM: EventPipe session ${this.sessionID} already started`); - } - this._state = State.Started; - start_streaming(this._sessionID); - console.debug(`MONO_WASM: EventPipe session ${this.sessionID} started`); - }; - - stop = () => { - if (this._state !== State.Started) { - throw new Error(`cannot stop an EventPipe session in state ${this._state}, not 'Started'`); - } - this._state = State.Done; - stop_streaming(this._sessionID); - console.debug(`MONO_WASM: EventPipe session ${this.sessionID} stopped`); - }; - - getTraceBlob = () => { - if (this._state !== State.Done) { - throw new Error(`session is in state ${this._state}, not 'Done'`); - } - const data = Module.FS_readFile(this._tracePath, { encoding: "binary" }) as Uint8Array; - return new Blob([data], { type: "application/octet-stream" }); - }; -} - -function start_streaming(sessionID: EventPipeSessionIDImpl): void { - cwraps.mono_wasm_event_pipe_session_start_streaming(sessionID); -} - -function stop_streaming(sessionID: EventPipeSessionIDImpl): void { - cwraps.mono_wasm_event_pipe_session_disable(sessionID); -} - -// a conter for the number of sessions created -let totalSessions = 0; - -export function makeEventPipeSession(options?: EventPipeSessionOptions): EventPipeSession | null { - const defaultRundownRequested = true; - const defaultProviders = ""; // empty string means use the default providers - const defaultBufferSizeInMB = 1; - - const rundown = options?.collectRundownEvents ?? defaultRundownRequested; - const providers = options?.providers ?? defaultProviders; - - // The session trace is saved to a file in the VFS. The file name doesn't matter, - // but we'd like it to be distinct from other traces. - const tracePath = `/trace-${totalSessions++}.nettrace`; - - const sessionOptions = { - rundownRequested: rundown, - providers: providers, - bufferSizeInMB: defaultBufferSizeInMB, - }; - - const success = createEventPipeFileSession(tracePath, sessionOptions); - - if (success === false) - return null; - const sessionID = success; - - return new EventPipeFileSession(sessionID, tracePath); -} - - diff --git a/src/mono/wasm/runtime/diagnostics/browser/session-options-builder.ts b/src/mono/wasm/runtime/diagnostics/browser/session-options-builder.ts deleted file mode 100644 index 5aa76b04ae6e13..00000000000000 --- a/src/mono/wasm/runtime/diagnostics/browser/session-options-builder.ts +++ /dev/null @@ -1,122 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -import { EventPipeSessionOptions } from "../../types"; - -export const eventLevel = { - LogAlways: 0, - Critical: 1, - Error: 2, - Warning: 3, - Informational: 4, - Verbose: 5, -} as const; - -export type EventLevel = typeof eventLevel; - -type UnnamedProviderConfiguration = Partial<{ - keywordMask: string | 0; - level: number; - args: string; -}> - -/// The configuration for an individual provider. Each provider configuration has the name of the provider, -/// the level of events to collect, and a string containing a 32-bit hexadecimal mask (without an "0x" prefix) of -/// the "keywords" to filter a subset of the events. The keyword mask may be the number 0 or "" to skips the filtering. -/// See https://docs.microsoft.com/en-us/dotnet/core/diagnostics/well-known-event-providers for a list of known providers. -/// Additional providers may be added by applications or libraries that implement an EventSource subclass. -/// See https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.tracing.eventsource?view=net-6.0 -/// -/// Some providers also have an "args" string in an arbitrary format. For example the EventSource providers that -/// include EventCounters have a "EventCounterIntervalSec=NNN" argument that specified how often the counters of -/// the event source should be polled. -export interface ProviderConfiguration extends UnnamedProviderConfiguration { - name: string; -} - -const runtimeProviderName = "Microsoft-Windows-DotNETRuntime"; -const runtimePrivateProviderName = "Microsoft-Windows-DotNETRuntimePrivate"; -const sampleProfilerProviderName = "Microsoft-DotNETCore-SampleProfiler"; - -const runtimeProviderDefault: ProviderConfiguration = { - name: runtimeProviderName, - keywordMask: "4c14fccbd", - level: eventLevel.Verbose, -}; - -const runtimePrivateProviderDefault: ProviderConfiguration = { - name: runtimePrivateProviderName, - keywordMask: "4002000b", - level: eventLevel.Verbose, -}; - -const sampleProfilerProviderDefault: ProviderConfiguration = { - name: sampleProfilerProviderName, - keywordMask: "0", - level: eventLevel.Verbose, -}; - -/// A helper class to create EventPipeSessionOptions -export class SessionOptionsBuilder { - private _rundown?: boolean; - private _providers: ProviderConfiguration[]; - /// Create an empty builder. Prefer to use SessionOptionsBuilder.Empty - constructor() { - this._providers = []; - } - /// Gets a builder with no providers. - static get Empty(): SessionOptionsBuilder { return new SessionOptionsBuilder(); } - /// Gets a builder with default providers and rundown events enabled. - /// See https://docs.microsoft.com/en-us/dotnet/core/diagnostics/eventpipe#trace-using-environment-variables - static get DefaultProviders(): SessionOptionsBuilder { - return this.Empty.addRuntimeProvider().addRuntimePrivateProvider().addSampleProfilerProvider(); - } - /// Change whether to collect rundown events. - /// Certain providers may need rundown events to be collected in order to provide useful diagnostic information. - setRundownEnabled(enabled: boolean): SessionOptionsBuilder { - this._rundown = enabled; - return this; - } - /// Add a provider configuration to the builder. - addProvider(provider: ProviderConfiguration): SessionOptionsBuilder { - this._providers.push(provider); - return this; - } - /// Add the Microsoft-Windows-DotNETRuntime provider. Use override options to change the event level or keyword mask. - /// The default is { keywordMask: "4c14fccbd", level: eventLevel.Verbose } - addRuntimeProvider(overrideOptions?: UnnamedProviderConfiguration): SessionOptionsBuilder { - const options = { ...runtimeProviderDefault, ...overrideOptions }; - this._providers.push(options); - return this; - } - /// Add the Microsoft-Windows-DotNETRuntimePrivate provider. Use override options to change the event level or keyword mask. - /// The default is { keywordMask: "4002000b", level: eventLevel.Verbose} - addRuntimePrivateProvider(overrideOptions?: UnnamedProviderConfiguration): SessionOptionsBuilder { - const options = { ...runtimePrivateProviderDefault, ...overrideOptions }; - this._providers.push(options); - return this; - } - /// Add the Microsoft-DotNETCore-SampleProfiler. Use override options to change the event level or keyword mask. - // The default is { keywordMask: 0, level: eventLevel.Verbose } - addSampleProfilerProvider(overrideOptions?: UnnamedProviderConfiguration): SessionOptionsBuilder { - const options = { ...sampleProfilerProviderDefault, ...overrideOptions }; - this._providers.push(options); - return this; - } - /// Create an EventPipeSessionOptions from the builder. - build(): EventPipeSessionOptions { - const providers = this._providers.map(p => { - const name = p.name; - const keywordMask = "" + (p?.keywordMask ?? ""); - const level = p?.level ?? eventLevel.Verbose; - const args = p?.args ?? ""; - const maybeArgs = args != "" ? `:${args}` : ""; - return `${name}:${keywordMask}:${level}${maybeArgs}`; - }); - return { - collectRundownEvents: this._rundown, - providers: providers.join(",") - }; - } -} - diff --git a/src/mono/wasm/runtime/diagnostics/index.ts b/src/mono/wasm/runtime/diagnostics/index.ts index 68f79ed9f0163e..4987795e001677 100644 --- a/src/mono/wasm/runtime/diagnostics/index.ts +++ b/src/mono/wasm/runtime/diagnostics/index.ts @@ -4,83 +4,20 @@ import monoWasmThreads from "consts:monoWasmThreads"; import type { DiagnosticOptions, - EventPipeSessionOptions, -} from "../types"; +} from "./shared/types"; import { is_nullish } from "../types"; import type { VoidPtr } from "../types/emscripten"; import { getController, startDiagnosticServer } from "./browser/controller"; import * as memory from "../memory"; -export type { ProviderConfiguration } from "./browser/session-options-builder"; -import { - eventLevel, EventLevel, - SessionOptionsBuilder, -} from "./browser/session-options-builder"; -import { EventPipeSession, makeEventPipeSession } from "./browser/file-session"; - -export interface Diagnostics { - eventLevel: EventLevel; - SessionOptionsBuilder: typeof SessionOptionsBuilder; - - createEventPipeSession(options?: EventPipeSessionOptions): EventPipeSession | null; - getStartupSessions(): (EventPipeSession | null)[]; -} - -let startup_session_configs: EventPipeSessionOptions[] = []; -let startup_sessions: (EventPipeSession | null)[] | null = null; // called from C on the main thread export function mono_wasm_event_pipe_early_startup_callback(): void { if (monoWasmThreads) { - if (startup_session_configs === null || startup_session_configs.length == 0) { - return; - } - console.debug("MONO_WASM: diagnostics: setting startup sessions based on startup session configs", startup_session_configs); - startup_sessions = startup_session_configs.map(config => createAndStartEventPipeSession(config)); - startup_session_configs = []; - } -} - - -function createAndStartEventPipeSession(options: (EventPipeSessionOptions)): EventPipeSession | null { - const session = makeEventPipeSession(options); - if (session === null) { - return null; - } - session.start(); - - return session; -} - -function getDiagnostics(): Diagnostics { - if (monoWasmThreads) { - return { - /// An enumeration of the level (higher value means more detail): - /// LogAlways: 0, - /// Critical: 1, - /// Error: 2, - /// Warning: 3, - /// Informational: 4, - /// Verbose: 5, - eventLevel: eventLevel, - /// A builder for creating an EventPipeSessionOptions instance. - SessionOptionsBuilder: SessionOptionsBuilder, - /// Creates a new EventPipe session that will collect trace events from the runtime and managed libraries. - /// Use the options to control the kinds of events to be collected. - /// Multiple sessions may be created and started at the same time. - createEventPipeSession: makeEventPipeSession, - getStartupSessions(): (EventPipeSession | null)[] { - return Array.from(startup_sessions || []); - }, - }; - } else { - return undefined as unknown as Diagnostics; + return; } } -/// APIs for working with .NET diagnostics from JavaScript. -export const diagnostics: Diagnostics = getDiagnostics(); - // Initialization flow /// * The runtime calls configure_diagnostics with options from MonoConfig /// * We start the diagnostic server which connects to the host and waits for some configurations (an IPC CollectTracing command) @@ -96,21 +33,16 @@ let diagnosticsServerEnabled = false; let diagnosticsInitialized = false; -export async function mono_wasm_init_diagnostics(opts: "env" | DiagnosticOptions): Promise { +export async function mono_wasm_init_diagnostics(): Promise { if (diagnosticsInitialized) return; if (!monoWasmThreads) { - console.warn("MONO_WASM: ignoring diagnostics options because this runtime does not support diagnostics", opts); + console.warn("MONO_WASM: ignoring diagnostics options because this runtime does not support diagnostics"); return; } else { - let options: DiagnosticOptions | null; - if (opts === "env") { - options = diagnostic_options_from_environment(); - if (!options) - return; - } else { - options = opts; - } + const options = diagnostic_options_from_environment(); + if (!options) + return; diagnosticsInitialized = true; if (!is_nullish(options?.server)) { if (options.server.connectUrl === undefined || typeof (options.server.connectUrl) !== "string") { @@ -126,8 +58,6 @@ export async function mono_wasm_init_diagnostics(opts: "env" | DiagnosticOptions } } } - const sessions = options?.sessions ?? []; - startup_session_configs.push(...sessions); } } @@ -221,4 +151,3 @@ export function mono_wasm_diagnostic_server_on_runtime_server_init(out_options: } } -export default diagnostics; diff --git a/src/mono/wasm/runtime/diagnostics/shared/types.ts b/src/mono/wasm/runtime/diagnostics/shared/types.ts index af5de7c4729374..ef2526ff8e9809 100644 --- a/src/mono/wasm/runtime/diagnostics/shared/types.ts +++ b/src/mono/wasm/runtime/diagnostics/shared/types.ts @@ -3,3 +3,14 @@ export type EventPipeSessionIDImpl = number; +/// Options to configure EventPipe sessions that will be created and started at runtime startup +export type DiagnosticOptions = { + /// If true, the diagnostic server will be started. If "wait", the runtime will wait at startup until a diagnsotic session connects to the server + server?: DiagnosticServerOptions, +} + +/// Options to configure the diagnostic server +export type DiagnosticServerOptions = { + connectUrl: string, // websocket URL to connect to. + suspend: string | boolean, // if true, the server will suspend the app when it starts until a diagnostic tool tells the runtime to resume. +} diff --git a/src/mono/wasm/runtime/startup.ts b/src/mono/wasm/runtime/startup.ts index 051c0a796c5e87..c26bdf352a0bb3 100644 --- a/src/mono/wasm/runtime/startup.ts +++ b/src/mono/wasm/runtime/startup.ts @@ -330,7 +330,7 @@ async function mono_wasm_after_user_runtime_initialized(): Promise { } // for Blazor, init diagnostics after their "onRuntimeInitalized" sets env variables, but before their postRun callback (which calls mono_wasm_load_runtime) if (MonoWasmThreads) { - await mono_wasm_init_diagnostics("env"); + await mono_wasm_init_diagnostics(); } if (runtimeHelpers.diagnosticTracing) console.debug("MONO_WASM: Initializing mono runtime"); @@ -540,7 +540,7 @@ async function _apply_configuration_from_args() { mono_wasm_init_coverage_profiler(config.coverageProfilerOptions); // for non-Blazor, init diagnostics after environment variables are set if (MonoWasmThreads) { - await mono_wasm_init_diagnostics("env"); + await mono_wasm_init_diagnostics(); } } diff --git a/src/mono/wasm/runtime/types.ts b/src/mono/wasm/runtime/types.ts index e11d7efe6a36f6..271edd7f14595f 100644 --- a/src/mono/wasm/runtime/types.ts +++ b/src/mono/wasm/runtime/types.ts @@ -111,7 +111,7 @@ export interface AssetEntry extends ResourceRequest { export type AssetBehaviours = "resource" // load asset as a managed resource assembly - | "assembly" // load asset as a managed assembly + | "assembly" // load asset as a managed assembly | "pdb" // load asset as a managed debugging information | "heap" // store asset into the native heap | "icu" // load asset as an ICU data archive @@ -159,30 +159,6 @@ export type CoverageProfilerOptions = { sendTo?: string // should be in the format ::, default: 'WebAssembly.Runtime::DumpCoverageProfileData' (DumpCoverageProfileData stores the data into INTERNAL.coverage_profile_data.) } -/// Options to configure EventPipe sessions that will be created and started at runtime startup -export type DiagnosticOptions = { - /// An array of sessions to start at runtime startup - sessions?: EventPipeSessionOptions[], - /// If true, the diagnostic server will be started. If "wait", the runtime will wait at startup until a diagnsotic session connects to the server - server?: DiagnosticServerOptions, -} - -/// Options to configure the event pipe session -/// The recommended method is to MONO.diagnostics.SesisonOptionsBuilder to create an instance of this type -export interface EventPipeSessionOptions { - /// Whether to collect additional details (such as method and type names) at EventPipeSession.stop() time (default: true) - /// This is required for some use cases, and may allow some tools to better understand the events. - collectRundownEvents?: boolean; - /// The providers that will be used by this session. - /// See https://docs.microsoft.com/en-us/dotnet/core/diagnostics/eventpipe#trace-using-environment-variables - providers: string; -} - -/// Options to configure the diagnostic server -export type DiagnosticServerOptions = { - connectUrl: string, // websocket URL to connect to. - suspend: string | boolean, // if true, the server will suspend the app when it starts until a diagnostic tool tells the runtime to resume. -} // how we extended emscripten Module export type DotnetModule = EmscriptenModule & DotnetModuleConfig; From acc4f23049365c8e1b1dbae8379f3be96bceaaa7 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Tue, 9 Aug 2022 14:48:16 +0200 Subject: [PATCH 10/41] JIT: Fix liveness for dependently promoted TYP_LONGs on x86 (#73562) We were only handling uses of promoted locals when varTypeIsStruct(lcl) was true. For TYP_LONG promoted locals on x86 it is not. Fix #73559 --- src/coreclr/jit/codegencommon.cpp | 2 +- src/coreclr/jit/compiler.hpp | 4 +-- src/coreclr/jit/liveness.cpp | 6 ++-- .../JitBlue/Runtime_73559/Runtime_73559.cs | 33 +++++++++++++++++++ .../Runtime_73559/Runtime_73559.csproj | 10 ++++++ 5 files changed, 49 insertions(+), 6 deletions(-) create mode 100644 src/tests/JIT/Regression/JitBlue/Runtime_73559/Runtime_73559.cs create mode 100644 src/tests/JIT/Regression/JitBlue/Runtime_73559/Runtime_73559.csproj diff --git a/src/coreclr/jit/codegencommon.cpp b/src/coreclr/jit/codegencommon.cpp index 0ea9da403636c9..db8f867b3daf56 100644 --- a/src/coreclr/jit/codegencommon.cpp +++ b/src/coreclr/jit/codegencommon.cpp @@ -3384,7 +3384,7 @@ void CodeGen::genFnPrologCalleeRegArgs(regNumber xtraReg, bool* pXtraRegClobbere regNumber regNum = genMapRegArgNumToRegNum(argNum, regType); regNumber destRegNum = REG_NA; - if (varTypeIsStruct(varDsc) && + if (varTypeIsPromotable(varDsc) && (compiler->lvaGetPromotionType(varDsc) == Compiler::PROMOTION_TYPE_INDEPENDENT)) { assert(regArgTab[argNum].slot <= varDsc->lvFieldCnt); diff --git a/src/coreclr/jit/compiler.hpp b/src/coreclr/jit/compiler.hpp index 9fa24922d32144..7f0c5c5686fd06 100644 --- a/src/coreclr/jit/compiler.hpp +++ b/src/coreclr/jit/compiler.hpp @@ -1903,7 +1903,7 @@ inline void LclVarDsc::incRefCnts(weight_t weight, Compiler* comp, RefCountState } Compiler::lvaPromotionType promotionType = DUMMY_INIT(Compiler::PROMOTION_TYPE_NONE); - if (varTypeIsStruct(lvType)) + if (varTypeIsPromotable(lvType)) { promotionType = comp->lvaGetPromotionType(this); } @@ -1950,7 +1950,7 @@ inline void LclVarDsc::incRefCnts(weight_t weight, Compiler* comp, RefCountState } } - if (varTypeIsStruct(lvType) && propagate) + if (varTypeIsPromotable(lvType) && propagate) { // For promoted struct locals, increment lvRefCnt on its field locals as well. if (promotionType == Compiler::PROMOTION_TYPE_INDEPENDENT || diff --git a/src/coreclr/jit/liveness.cpp b/src/coreclr/jit/liveness.cpp index 50c90555279420..8b499069c78f1c 100644 --- a/src/coreclr/jit/liveness.cpp +++ b/src/coreclr/jit/liveness.cpp @@ -87,7 +87,7 @@ void Compiler::fgMarkUseDef(GenTreeLclVarCommon* tree) } } - if (varTypeIsStruct(varDsc)) + if (varTypeIsPromotable(varDsc)) { lvaPromotionType promotionType = lvaGetPromotionType(varDsc); @@ -1631,7 +1631,7 @@ bool Compiler::fgComputeLifeUntrackedLocal(VARSET_TP& life, return true; } } - else if (varTypeIsStruct(varDsc.lvType)) + else if (varTypeIsPromotable(varDsc.lvType)) { if (lvaGetPromotionType(&varDsc) != PROMOTION_TYPE_INDEPENDENT) { @@ -1644,7 +1644,7 @@ bool Compiler::fgComputeLifeUntrackedLocal(VARSET_TP& life, } } - if (!varTypeIsStruct(varDsc.lvType) || (lvaGetPromotionType(&varDsc) == PROMOTION_TYPE_NONE)) + if (!varTypeIsPromotable(varDsc.TypeGet()) || (lvaGetPromotionType(&varDsc) == PROMOTION_TYPE_NONE)) { return false; } diff --git a/src/tests/JIT/Regression/JitBlue/Runtime_73559/Runtime_73559.cs b/src/tests/JIT/Regression/JitBlue/Runtime_73559/Runtime_73559.cs new file mode 100644 index 00000000000000..5bb247ed9b6cfa --- /dev/null +++ b/src/tests/JIT/Regression/JitBlue/Runtime_73559/Runtime_73559.cs @@ -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. + +using System; +using System.Runtime.CompilerServices; + +public unsafe class Runtime_73559 +{ + public static int Main() + { + long value = 0x1234567891011121; + return Verify(*(S8*)&value); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static int Verify(S8 val) + { + long asLong = *(long*)&val; + if (asLong == 0x1234567891011121) + { + Console.WriteLine("PASS"); + return 100; + } + + Console.WriteLine("FAIL: Value is {0:X}", asLong); + return -1; + } + + private struct S8 + { + public int A, B; + } +} \ No newline at end of file diff --git a/src/tests/JIT/Regression/JitBlue/Runtime_73559/Runtime_73559.csproj b/src/tests/JIT/Regression/JitBlue/Runtime_73559/Runtime_73559.csproj new file mode 100644 index 00000000000000..cf94135633b19a --- /dev/null +++ b/src/tests/JIT/Regression/JitBlue/Runtime_73559/Runtime_73559.csproj @@ -0,0 +1,10 @@ + + + Exe + True + true + + + + + \ No newline at end of file From 3fcbcc2acb74473b32a99e05968b793e4901e9cb Mon Sep 17 00:00:00 2001 From: Fan Yang <52458914+fanyang-mono@users.noreply.github.com> Date: Tue, 9 Aug 2022 10:33:55 -0400 Subject: [PATCH 11/41] [Mono] Enable X86Base.Pause test only when LLVM is enabled (#73318) * Enable test only when LLVM is enabled * Add back SIMD intrinsics support for X86Base.Pause * Disable test for llvm --- src/mono/mono/mini/simd-intrinsics.c | 1 + src/mono/mono/mini/simd-methods.h | 1 + src/tests/issues.targets | 7 +++---- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/mono/mono/mini/simd-intrinsics.c b/src/mono/mono/mini/simd-intrinsics.c index c9e95c058d9654..4ce3b622a47679 100644 --- a/src/mono/mono/mini/simd-intrinsics.c +++ b/src/mono/mono/mini/simd-intrinsics.c @@ -3296,6 +3296,7 @@ static SimdIntrinsic bmi2_methods [] = { static SimdIntrinsic x86base_methods [] = { {SN_BitScanForward}, {SN_BitScanReverse}, + {SN_Pause, OP_XOP, INTRINS_SSE_PAUSE}, {SN_get_IsSupported} }; diff --git a/src/mono/mono/mini/simd-methods.h b/src/mono/mono/mini/simd-methods.h index d996916e228dcb..aa7d3a82df8890 100644 --- a/src/mono/mono/mini/simd-methods.h +++ b/src/mono/mono/mini/simd-methods.h @@ -278,6 +278,7 @@ METHOD(ComputeCrc32C) // X86Base METHOD(BitScanForward) METHOD(BitScanReverse) +METHOD(Pause) // Crypto METHOD(FixedRotate) METHOD(HashUpdateChoose) diff --git a/src/tests/issues.targets b/src/tests/issues.targets index c2e6cc66043921..c4dd696bdbca12 100644 --- a/src/tests/issues.targets +++ b/src/tests/issues.targets @@ -1434,10 +1434,6 @@ https://github.com/dotnet/runtime/issues/71656 - - https://github.com/dotnet/runtime/issues/61693 - - https://github.com/dotnet/runtime/issues/56887 @@ -3052,6 +3048,9 @@ https://github.com/dotnet/runtime/issues/48914 + + https://github.com/dotnet/runtime/issues/73454;https://github.com/dotnet/runtime/pull/61707#issuecomment-973122341 + needs triage From 8020c89b1ea5fdf95253341fbce80323c80dd272 Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Tue, 9 Aug 2022 08:06:12 -0700 Subject: [PATCH 12/41] Disable useless warnings (#73598) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Disable useless warnings - Detect use of dynamic modules in PGO data processing and skip them instead of generating useless, non-actionable warnings - Infrastructure currently used only for dynamic modules, but could be extended in the future to provide a means to ignore assemblies based on the set of assemblies specified on the commandline. Fixes #68000 * Update src/coreclr/tools/dotnet-pgo/Program.cs Co-authored-by: Michał Petryka <35800402+MichalPetryka@users.noreply.github.com> Co-authored-by: Michał Petryka <35800402+MichalPetryka@users.noreply.github.com> --- .../tools/dotnet-pgo/MethodMemoryMap.cs | 6 +- src/coreclr/tools/dotnet-pgo/Program.cs | 20 ++++--- .../TraceRuntimeDescToTypeSystemDesc.cs | 57 +++++++++++++++---- 3 files changed, 61 insertions(+), 22 deletions(-) diff --git a/src/coreclr/tools/dotnet-pgo/MethodMemoryMap.cs b/src/coreclr/tools/dotnet-pgo/MethodMemoryMap.cs index 6175c897f5808e..9a1e89f439b7df 100644 --- a/src/coreclr/tools/dotnet-pgo/MethodMemoryMap.cs +++ b/src/coreclr/tools/dotnet-pgo/MethodMemoryMap.cs @@ -67,7 +67,7 @@ public MethodMemoryMap( MethodDesc method = null; try { - method = idParser.ResolveMethodID(e.MethodID); + method = idParser.ResolveMethodID(e.MethodID, out _); } catch { @@ -98,7 +98,7 @@ public MethodMemoryMap( MethodDesc method = null; try { - method = idParser.ResolveMethodID(e.MethodID, throwIfNotFound: false); + method = idParser.ResolveMethodID(e.MethodID, out _, throwIfNotFound: false); } catch { @@ -271,7 +271,7 @@ private static KeyValueMap CreateNativeToILMap(TraceRuntimeDesc void AddSubTree(InlineContext ctx) { - MethodDesc md = idParser.ResolveMethodID((long)ctx.MethodID, false); + MethodDesc md = idParser.ResolveMethodID((long)ctx.MethodID, out _, false); byOrdinal.Add(ctx.Ordinal, (ctx, md)); foreach (var child in ctx.Inlinees) diff --git a/src/coreclr/tools/dotnet-pgo/Program.cs b/src/coreclr/tools/dotnet-pgo/Program.cs index 4353183451895b..02cde991728a9e 100644 --- a/src/coreclr/tools/dotnet-pgo/Program.cs +++ b/src/coreclr/tools/dotnet-pgo/Program.cs @@ -73,7 +73,8 @@ public TypeSystemEntityOrUnknown TypeFromLong(long input) try { - type = _idParser.ResolveTypeHandle(input, false); + bool unusedNonLoadableModule = false; + type = _idParser.ResolveTypeHandle(input, ref unusedNonLoadableModule, false); } catch { } @@ -94,7 +95,7 @@ public TypeSystemEntityOrUnknown MethodFromLong(long input) try { - method = _idParser.ResolveMethodID(input, false); + method = _idParser.ResolveMethodID(input, out _, false); } catch { } @@ -1284,7 +1285,8 @@ static int InnerProcessTraceFileMain(CommandLineOptions commandLineOptions) ModuleDesc loadedModule = idParser.ResolveModuleID(e.ModuleID, false); if (loadedModule == null) { - PrintWarning($"Unable to find loaded module {e.ModuleILFileName} to verify match"); + if (!idParser.IsDynamicModuleID(e.ModuleID)) + PrintWarning($"Unable to find loaded module {e.ModuleILFileName} to verify match"); continue; } @@ -1365,9 +1367,10 @@ static int InnerProcessTraceFileMain(CommandLineOptions commandLineOptions) } MethodDesc method = null; string extraWarningText = null; + bool failedDueToNonloadableModule = false; try { - method = idParser.ResolveMethodID(e.MethodID, commandLineOptions.VerboseWarnings); + method = idParser.ResolveMethodID(e.MethodID, out failedDueToNonloadableModule, commandLineOptions.VerboseWarnings); } catch (Exception exception) { @@ -1376,7 +1379,7 @@ static int InnerProcessTraceFileMain(CommandLineOptions commandLineOptions) if (method == null) { - if ((e.MethodNamespace == "dynamicClass") || !commandLineOptions.Warnings) + if ((e.MethodNamespace == "dynamicClass") || failedDueToNonloadableModule || !commandLineOptions.Warnings) continue; PrintWarning($"Unable to parse {methodNameFromEventDirectly} when looking up R2R methods"); @@ -1410,9 +1413,10 @@ static int InnerProcessTraceFileMain(CommandLineOptions commandLineOptions) MethodDesc method = null; string extraWarningText = null; + bool failedDueToNonloadableModule = false; try { - method = idParser.ResolveMethodID(e.MethodID, commandLineOptions.VerboseWarnings); + method = idParser.ResolveMethodID(e.MethodID, out failedDueToNonloadableModule, commandLineOptions.VerboseWarnings); } catch (Exception exception) { @@ -1421,7 +1425,7 @@ static int InnerProcessTraceFileMain(CommandLineOptions commandLineOptions) if (method == null) { - if ((e.MethodNamespace == "dynamicClass") || !commandLineOptions.Warnings) + if ((e.MethodNamespace == "dynamicClass") || failedDueToNonloadableModule || !commandLineOptions.Warnings) continue; PrintWarning($"Unable to parse {methodNameFromEventDirectly}"); @@ -1549,7 +1553,7 @@ void AddToInstrumentationData(int eventClrInstanceId, long methodID, int methodF MethodDesc method = null; try { - method = idParser.ResolveMethodID(methodID, commandLineOptions.VerboseWarnings); + method = idParser.ResolveMethodID(methodID, out _, commandLineOptions.VerboseWarnings); } catch (Exception) { diff --git a/src/coreclr/tools/dotnet-pgo/TraceRuntimeDescToTypeSystemDesc.cs b/src/coreclr/tools/dotnet-pgo/TraceRuntimeDescToTypeSystemDesc.cs index a179292b4c21dd..8ed1857045d013 100644 --- a/src/coreclr/tools/dotnet-pgo/TraceRuntimeDescToTypeSystemDesc.cs +++ b/src/coreclr/tools/dotnet-pgo/TraceRuntimeDescToTypeSystemDesc.cs @@ -93,15 +93,17 @@ public override string ToString() class ModuleDescInfo { - public ModuleDescInfo(long id, string assemblyName) + public ModuleDescInfo(long id, string assemblyName, bool isDynamic) { ID = id; AssemblyName = assemblyName; + IsDynamic = isDynamic; } public readonly long ID; public ModuleDesc Module; public readonly string AssemblyName; + public readonly bool IsDynamic; } private readonly Dictionary _methods = new Dictionary(); @@ -223,10 +225,13 @@ public TraceRuntimeDescToTypeSystemDesc(TraceProcess traceProcess, TypeSystemCon Dictionary assemblyToCLRInstanceIDMap = new Dictionary(); Dictionary assemblyToFullyQualifiedAssemblyName = new Dictionary(); + Dictionary assemblyToIsDynamic = new Dictionary(); + foreach (var assemblyLoadTrace in _traceProcess.EventsInProcess.ByEventType()) { assemblyToCLRInstanceIDMap[assemblyLoadTrace.AssemblyID] = assemblyLoadTrace.ClrInstanceID; assemblyToFullyQualifiedAssemblyName[assemblyLoadTrace.AssemblyID] = assemblyLoadTrace.FullyQualifiedAssemblyName; + assemblyToIsDynamic[assemblyLoadTrace.AssemblyID] = ((assemblyLoadTrace.AssemblyFlags & Tracing.Parsers.Clr.AssemblyFlags.Dynamic) == Tracing.Parsers.Clr.AssemblyFlags.Dynamic); } foreach (var moduleFile in _traceProcess.LoadedModules) @@ -242,7 +247,7 @@ public TraceRuntimeDescToTypeSystemDesc(TraceProcess traceProcess, TypeSystemCon if (clrInstanceIDModule != _clrInstanceID) continue; - var currentInfo = new ModuleDescInfo(managedModule.ModuleID, assemblyToFullyQualifiedAssemblyName[managedModule.AssemblyID]); + var currentInfo = new ModuleDescInfo(managedModule.ModuleID, assemblyToFullyQualifiedAssemblyName[managedModule.AssemblyID], assemblyToIsDynamic[managedModule.AssemblyID]); if (!_modules.ContainsKey(managedModule.ModuleID)) _modules.Add(managedModule.ModuleID, currentInfo); } @@ -255,7 +260,8 @@ public void Init() // Fill in all the types foreach (var entry in _types) { - ResolveTypeHandle(entry.Key, false); + bool nonLoadableModule = false; + ResolveTypeHandle(entry.Key, ref nonLoadableModule, false); } } @@ -278,6 +284,13 @@ public ModuleDesc ResolveModuleID(long handle, bool throwIfNotFound = true) if (minfo.Module != null) return minfo.Module; + if (minfo.IsDynamic) + { + if (throwIfNotFound) + throw new Exception("Attempt to load dynamic module in pgo processing logic"); + return null; + } + minfo.Module = _context.ResolveAssembly(new AssemblyName(minfo.AssemblyName), throwIfNotFound); return minfo.Module; } @@ -290,7 +303,23 @@ public ModuleDesc ResolveModuleID(long handle, bool throwIfNotFound = true) } } - public TypeDesc ResolveTypeHandle(long handle, bool throwIfNotFound = true) + public bool IsDynamicModuleID(long handle) + { + lock (_lock) + { + ModuleDescInfo minfo; + if (_modules.TryGetValue(handle, out minfo)) + { + return minfo.IsDynamic; + } + else + { + return false; + } + } + } + + public TypeDesc ResolveTypeHandle(long handle, ref bool dependsOnKnownNonLoadableType, bool throwIfNotFound = true) { lock(_lock) { @@ -307,7 +336,7 @@ public TypeDesc ResolveTypeHandle(long handle, bool throwIfNotFound = true) throw new Exception("Bad format for BulkType"); } - TypeDesc elementType = ResolveTypeHandle((long)tinfo.TypeValue.TypeParameters[0], throwIfNotFound); + TypeDesc elementType = ResolveTypeHandle((long)tinfo.TypeValue.TypeParameters[0], ref dependsOnKnownNonLoadableType, throwIfNotFound); if (elementType == null) return null; @@ -328,7 +357,7 @@ public TypeDesc ResolveTypeHandle(long handle, bool throwIfNotFound = true) throw new Exception("Bad format for BulkType"); } - TypeDesc elementType = ResolveTypeHandle((long)tinfo.TypeValue.TypeParameters[0], throwIfNotFound); + TypeDesc elementType = ResolveTypeHandle((long)tinfo.TypeValue.TypeParameters[0], ref dependsOnKnownNonLoadableType, throwIfNotFound); if (elementType == null) return null; @@ -341,7 +370,7 @@ public TypeDesc ResolveTypeHandle(long handle, bool throwIfNotFound = true) throw new Exception("Bad format for BulkType"); } - TypeDesc elementType = ResolveTypeHandle((long)tinfo.TypeValue.TypeParameters[0], throwIfNotFound); + TypeDesc elementType = ResolveTypeHandle((long)tinfo.TypeValue.TypeParameters[0], ref dependsOnKnownNonLoadableType, throwIfNotFound); if (elementType == null) return null; @@ -356,7 +385,12 @@ public TypeDesc ResolveTypeHandle(long handle, bool throwIfNotFound = true) // Must be class type or instantiated type. ModuleDesc module = ResolveModuleID((long)tinfo.TypeValue.ModuleID, throwIfNotFound); if (module == null) + { + if (this.IsDynamicModuleID((long)tinfo.TypeValue.ModuleID)) + dependsOnKnownNonLoadableType = true; + return null; + } EcmaModule ecmaModule = module as EcmaModule; if (ecmaModule == null) @@ -384,7 +418,7 @@ public TypeDesc ResolveTypeHandle(long handle, bool throwIfNotFound = true) TypeDesc[] instantiation = new TypeDesc[tinfo.TypeValue.TypeParameters.Length]; for (int i = 0; i < instantiation.Length; i++) { - instantiation[i] = ResolveTypeHandle((long)tinfo.TypeValue.TypeParameters[i], throwIfNotFound); + instantiation[i] = ResolveTypeHandle((long)tinfo.TypeValue.TypeParameters[i], ref dependsOnKnownNonLoadableType, throwIfNotFound); if (instantiation[i] == null) return null; } @@ -419,8 +453,9 @@ public TypeDesc ResolveTypeHandle(long handle, bool throwIfNotFound = true) } } - public MethodDesc ResolveMethodID(long handle, bool throwIfNotFound = true) + public MethodDesc ResolveMethodID(long handle, out bool nonLoadableModuleInvolvedInMethod, bool throwIfNotFound = true) { + nonLoadableModuleInvolvedInMethod = false; lock (_lock) { MethodDescInfo minfo; @@ -429,7 +464,7 @@ public MethodDesc ResolveMethodID(long handle, bool throwIfNotFound = true) if (minfo.Method != null) return minfo.Method; - TypeDesc owningType = ResolveTypeHandle(minfo.MethodDetailsTraceData.TypeID, throwIfNotFound); + TypeDesc owningType = ResolveTypeHandle(minfo.MethodDetailsTraceData.TypeID, ref nonLoadableModuleInvolvedInMethod, throwIfNotFound); if (owningType == null) return null; @@ -482,7 +517,7 @@ public MethodDesc ResolveMethodID(long handle, bool throwIfNotFound = true) TypeDesc[] instantiation = new TypeDesc[minfo.MethodDetailsTraceData.TypeParameters.Length]; for (int i = 0; i < instantiation.Length; i++) { - instantiation[i] = ResolveTypeHandle((long)minfo.MethodDetailsTraceData.TypeParameters[i], throwIfNotFound); + instantiation[i] = ResolveTypeHandle((long)minfo.MethodDetailsTraceData.TypeParameters[i], ref nonLoadableModuleInvolvedInMethod, throwIfNotFound); if (instantiation[i] == null) return null; } From b8554beea57afb5d75c55c8ea19ec9965b8917fe Mon Sep 17 00:00:00 2001 From: Parker Bibus Date: Tue, 9 Aug 2022 10:15:57 -0500 Subject: [PATCH 13/41] [PERF] Fix "x$PERF_PREREQS_INSTALLED was unexpected at this time" Error for Microbenchmarks (#73584) * Try double brackets. * Only use the bash check if running on non-windows machine, added command for running on windows machines. * Undoing changes to if statement brackets (double to single now) as the double bracket is not necessary. * Use the correct, previously removed command. --- eng/testing/performance/microbenchmarks.proj | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/eng/testing/performance/microbenchmarks.proj b/eng/testing/performance/microbenchmarks.proj index 2f0123483028cf..1331e1e7a811c0 100644 --- a/eng/testing/performance/microbenchmarks.proj +++ b/eng/testing/performance/microbenchmarks.proj @@ -131,12 +131,13 @@ $(WorkItemDirectory) $(WorkItemCommand) --bdn-artifacts $(BaselineArtifactsDirectory) --bdn-arguments="--anyCategories $(BDNCategories) $(ExtraBenchmarkDotNetArguments) $(BaselineCoreRunArgument) --partition-count $(PartitionCount) --partition-index %(HelixWorkItem.Index)" - + if [ "x$PERF_PREREQS_INSTALLED" = "x1" ]; then $(WorkItemCommand) --bdn-artifacts $(ArtifactsDirectory) --bdn-arguments="--anyCategories $(BDNCategories) $(ExtraBenchmarkDotNetArguments) $(CoreRunArgument) --partition-count $(PartitionCount) --partition-index %(HelixWorkItem.Index)"; else echo "\n\n** Error: Failed to install prerequisites **\n\n"; export _commandExitCode=1; fi + $(WorkItemCommand) --bdn-artifacts $(ArtifactsDirectory) --bdn-arguments="--anyCategories $(BDNCategories) $(ExtraBenchmarkDotNetArguments) $(CoreRunArgument) --partition-count $(PartitionCount) --partition-index %(HelixWorkItem.Index)" $(DotnetExe) run -f $(PERFLAB_Framework) -p $(ResultsComparer) --base $(BaselineArtifactsDirectory) --diff $(ArtifactsDirectory) --threshold 2$(Percent) --xml $(XMLResults);$(FinalCommand) $(WorkItemTimeout) @@ -146,12 +147,13 @@ $(WorkItemDirectory) $(WorkItemCommand) --bdn-artifacts $(BaselineArtifactsDirectory) --bdn-arguments="--anyCategories $(BDNCategories) $(ExtraBenchmarkDotNetArguments) $(BaselineCoreRunArgument)" - + if [ "x$PERF_PREREQS_INSTALLED" = "x1" ]; then $(WorkItemCommand) --bdn-artifacts $(ArtifactsDirectory) --bdn-arguments="--anyCategories $(BDNCategories) $(ExtraBenchmarkDotNetArguments) $(CoreRunArgument)"; else echo "\n\n** Error: Failed to install prerequisites **\n\n"; export _commandExitCode=1; fi + $(WorkItemCommand) --bdn-artifacts $(ArtifactsDirectory) --bdn-arguments="--anyCategories $(BDNCategories) $(ExtraBenchmarkDotNetArguments) $(CoreRunArgument)" $(DotnetExe) run -f $(PERFLAB_Framework) -p $(ResultsComparer) --base $(BaselineArtifactsDirectory) --diff $(ArtifactsDirectory) --threshold 2$(Percent) --xml $(XMLResults) 4:00 From 45709118111b01fb6cf0ada103487e8185d195d2 Mon Sep 17 00:00:00 2001 From: Peter Sollich Date: Tue, 9 Aug 2022 17:51:17 +0200 Subject: [PATCH 14/41] Mark phase prefetching. (#73375) This adds prefetching to the mark phase. The idea is that once we have established that an object is in one of the generations we want to collect, we prefetch its memory before we determine whether we have marked it already. This is because the mark bit is in the object itself, and thus requires accessing the object's memory. As the prefetching will take some time to take effect, we park the object in a queue (see type mark_queue_t below). We then retrieve an older object from the queue, and test whether it has been marked. This should be faster, because we have issued a prefetch for this older object's memory a while back. In quite a few places we now need to drain the queue to ensure correctness - see calls to drain_mark_queue(). --- src/coreclr/gc/gc.cpp | 215 ++++++++++++++++++++++++++++++++++++---- src/coreclr/gc/gcpriv.h | 24 +++++ 2 files changed, 221 insertions(+), 18 deletions(-) diff --git a/src/coreclr/gc/gc.cpp b/src/coreclr/gc/gc.cpp index 377092f16d2695..69465bc59b0a86 100644 --- a/src/coreclr/gc/gc.cpp +++ b/src/coreclr/gc/gc.cpp @@ -2854,6 +2854,8 @@ size_t gc_heap::expand_mechanisms_per_heap[max_expand_mechanisms_count]; size_t gc_heap::interesting_mechanism_bits_per_heap[max_gc_mechanism_bits_count]; +mark_queue_t gc_heap::mark_queue; + #endif // MULTIPLE_HEAPS /* end of per heap static initialization */ @@ -23453,7 +23455,7 @@ BOOL gc_heap::gc_mark (uint8_t* o, uint8_t* low, uint8_t* high, int condemned_ge { return FALSE; } - set_marked(o); + set_marked (o); return TRUE; } return FALSE; @@ -23777,14 +23779,31 @@ void gc_heap::save_post_plug_info (uint8_t* last_pinned_plug, uint8_t* last_obje } } -//#define PREFETCH +// enable on processors known to have a useful prefetch instruction +#if defined(TARGET_AMD64) || defined(TARGET_X86) || defined(TARGET_ARM64) +#define PREFETCH +#endif + #ifdef PREFETCH -__declspec(naked) void __fastcall Prefetch(void* addr) +inline void Prefetch(void* addr) { - __asm { - PREFETCHT0 [ECX] - ret - }; +#ifdef TARGET_WINDOWS + +#if defined(TARGET_AMD64) || defined(TARGET_X86) + +#ifndef _MM_HINT_T0 +#define _MM_HINT_T0 1 +#endif + _mm_prefetch((const char*)addr, _MM_HINT_T0); +#elif defined(TARGET_ARM64) + __prefetch((const char*)addr); +#endif //defined(TARGET_AMD64) || defined(TARGET_X86) + +#elif defined(TARGET_UNIX) || defined(TARGET_OSX) + __builtin_prefetch(addr); +#else //!(TARGET_WINDOWS || TARGET_UNIX || TARGET_OSX) + UNREFERENCED_PARAMETER(addr); +#endif //TARGET_WINDOWS } #else //PREFETCH inline void Prefetch (void* addr) @@ -23840,6 +23859,114 @@ BOOL ref_p (uint8_t* r) return (straight_ref_p (r) || partial_object_p (r)); } +mark_queue_t::mark_queue_t() : curr_slot_index(0) +{ + for (size_t i = 0; i < slot_count; i++) + { + slot_table[i] = nullptr; + } +} + +// place an object in the mark queue +// returns a *different* object or nullptr +// if a non-null object is returned, that object is newly marked +// object o *must* be in a condemned generation +FORCEINLINE +uint8_t *mark_queue_t::queue_mark(uint8_t *o) +{ + Prefetch (o); + + // while the prefetch is taking effect, park our object in the queue + // and fetch an object that has been sitting in the queue for a while + // and where (hopefully) the memory is already in the cache + size_t slot_index = curr_slot_index; + uint8_t* old_o = slot_table[slot_index]; + slot_table[slot_index] = o; + + curr_slot_index = (slot_index + 1) % slot_count; + if (old_o == nullptr) + return nullptr; + + // this causes us to access the method table pointer of the old object + BOOL already_marked = marked (old_o); + if (already_marked) + { + return nullptr; + } + set_marked (old_o); + return old_o; +} + +// place an object in the mark queue +// returns a *different* object or nullptr +// if a non-null object is returned, that object is newly marked +// check first whether the object o is indeed in a condemned generation +FORCEINLINE +uint8_t *mark_queue_t::queue_mark(uint8_t *o, int condemned_gen) +{ +#ifdef USE_REGIONS + if (!is_in_heap_range (o)) + { + return nullptr; + } + if (condemned_gen != max_generation && gc_heap::get_region_gen_num (o) > condemned_gen) + { + return nullptr; + } + return queue_mark(o); +#else //USE_REGIONS + assert (condemned_gen == -1); + +#ifdef MULTIPLE_HEAPS + if (o) + { + gc_heap* hp = gc_heap::heap_of_gc (o); + assert (hp); + if ((o >= hp->gc_low) && (o < hp->gc_high)) + return queue_mark (o); + } +#else //MULTIPLE_HEAPS + if ((o >= gc_heap::gc_low) && (o < gc_heap::gc_high)) + return queue_mark (o); +#endif //MULTIPLE_HEAPS + return nullptr; +#endif //USE_REGIONS +} + +// retrieve a newly marked object from the queue +// returns nullptr if there is no such object +uint8_t* mark_queue_t::get_next_marked() +{ + size_t slot_index = curr_slot_index; + size_t empty_slot_count = 0; + while (empty_slot_count < slot_count) + { + uint8_t* o = slot_table[slot_index]; + slot_table[slot_index] = nullptr; + slot_index = (slot_index + 1) % slot_count; + if (o != nullptr) + { + BOOL already_marked = marked (o); + if (!already_marked) + { + set_marked (o); + curr_slot_index = slot_index; + return o; + } + } + empty_slot_count++; + } + return nullptr; +} + +mark_queue_t::~mark_queue_t() +{ + for (size_t slot_index = 0; slot_index < slot_count; slot_index++) + { + assert(slot_table[slot_index] == nullptr); + } +} + void gc_heap::mark_object_simple1 (uint8_t* oo, uint8_t* start THREAD_NUMBER_DCL) { SERVER_SC_MARK_VOLATILE(uint8_t*)* mark_stack_tos = (SERVER_SC_MARK_VOLATILE(uint8_t*)*)mark_stack_array; @@ -23899,9 +24026,8 @@ void gc_heap::mark_object_simple1 (uint8_t* oo, uint8_t* start THREAD_NUMBER_DCL go_through_object_cl (method_table(oo), oo, s, ppslot, { - uint8_t* o = *ppslot; - Prefetch(o); - if (gc_mark (o, gc_low, gc_high, condemned_gen)) + uint8_t* o = mark_queue.queue_mark(*ppslot, condemned_gen); + if (o != nullptr) { if (full_p) { @@ -23997,9 +24123,8 @@ void gc_heap::mark_object_simple1 (uint8_t* oo, uint8_t* start THREAD_NUMBER_DCL go_through_object (method_table(oo), oo, s, ppslot, start, use_start, (oo + s), { - uint8_t* o = *ppslot; - Prefetch(o); - if (gc_mark (o, gc_low, gc_high,condemned_gen)) + uint8_t* o = mark_queue.queue_mark(*ppslot, condemned_gen); + if (o != nullptr) { if (full_p) { @@ -24438,7 +24563,8 @@ gc_heap::mark_object_simple (uint8_t** po THREAD_NUMBER_DCL) snoop_stat.objects_checked_count++; #endif //SNOOP_STATS - if (gc_mark1 (o)) + o = mark_queue.queue_mark (o); + if (o != nullptr) { m_boundary (o); size_t s = size (o); @@ -24446,8 +24572,8 @@ gc_heap::mark_object_simple (uint8_t** po THREAD_NUMBER_DCL) { go_through_object_cl (method_table(o), o, s, poo, { - uint8_t* oo = *poo; - if (gc_mark (oo, gc_low, gc_high, condemned_gen)) + uint8_t* oo = mark_queue.queue_mark(*poo, condemned_gen); + if (oo != nullptr) { m_boundary (oo); add_to_promoted_bytes (oo, thread); @@ -24484,6 +24610,45 @@ void gc_heap::mark_object (uint8_t* o THREAD_NUMBER_DCL) #endif //USE_REGIONS } +void gc_heap::drain_mark_queue () +{ + int condemned_gen = +#ifdef USE_REGIONS + settings.condemned_generation; +#else + -1; +#endif //USE_REGIONS + +#ifdef MULTIPLE_HEAPS + THREAD_FROM_HEAP; +#else + const int thread = 0; +#endif //MULTIPLE_HEAPS + + uint8_t* o; + while ((o = mark_queue.get_next_marked()) != nullptr) + { + m_boundary (o); + size_t s = size (o); + add_to_promoted_bytes (o, s, thread); + if (contain_pointers_or_collectible (o)) + { + go_through_object_cl (method_table(o), o, s, poo, + { + uint8_t* oo = mark_queue.queue_mark(*poo, condemned_gen); + if (oo != nullptr) + { + m_boundary (oo); + add_to_promoted_bytes (oo, thread); + if (contain_pointers_or_collectible (oo)) + mark_object_simple1 (oo, oo THREAD_NUMBER_ARG); + } + } + ); + } + } +} + #ifdef BACKGROUND_GC #ifdef USE_REGIONS @@ -25660,6 +25825,8 @@ void gc_heap::scan_dependent_handles (int condemned_gen_number, ScanContext *sc, if (GCScan::GcDhUnpromotedHandlesExist(sc)) s_fUnpromotedHandles = TRUE; + drain_mark_queue(); + // Synchronize all the threads so we can read our state variables safely. The shared variable // s_fScanRequired, indicating whether we should scan the tables or terminate the loop, will be set by // a single thread inside the join. @@ -25761,6 +25928,8 @@ void gc_heap::scan_dependent_handles (int condemned_gen_number, ScanContext *sc, if (process_mark_overflow(condemned_gen_number)) fUnscannedPromotions = true; + drain_mark_queue(); + // Perform the scan and set the flag if any promotions resulted. if (GCScan::GcDhReScan(sc)) fUnscannedPromotions = true; @@ -26187,6 +26356,7 @@ void gc_heap::mark_phase (int condemned_gen_number, BOOL mark_only_p) if ((condemned_gen_number == max_generation) && (num_sizedrefs > 0)) { GCScan::GcScanSizedRefs(GCHeap::Promote, condemned_gen_number, max_generation, &sc); + drain_mark_queue(); fire_mark_event (ETW::GC_ROOT_SIZEDREF, current_promoted_bytes, last_promoted_bytes); #ifdef MULTIPLE_HEAPS @@ -26210,12 +26380,14 @@ void gc_heap::mark_phase (int condemned_gen_number, BOOL mark_only_p) GCScan::GcScanRoots(GCHeap::Promote, condemned_gen_number, max_generation, &sc); + drain_mark_queue(); fire_mark_event (ETW::GC_ROOT_STACK, current_promoted_bytes, last_promoted_bytes); #ifdef BACKGROUND_GC if (gc_heap::background_running_p()) { scan_background_roots (GCHeap::Promote, heap_number, &sc); + drain_mark_queue(); fire_mark_event (ETW::GC_ROOT_BGC, current_promoted_bytes, last_promoted_bytes); } #endif //BACKGROUND_GC @@ -26223,6 +26395,7 @@ void gc_heap::mark_phase (int condemned_gen_number, BOOL mark_only_p) #ifdef FEATURE_PREMORTEM_FINALIZATION dprintf(3, ("Marking finalization data")); finalize_queue->GcScanRoots(GCHeap::Promote, heap_number, 0); + drain_mark_queue(); fire_mark_event (ETW::GC_ROOT_FQ, current_promoted_bytes, last_promoted_bytes); #endif // FEATURE_PREMORTEM_FINALIZATION @@ -26230,6 +26403,7 @@ void gc_heap::mark_phase (int condemned_gen_number, BOOL mark_only_p) GCScan::GcScanHandles(GCHeap::Promote, condemned_gen_number, max_generation, &sc); + drain_mark_queue(); fire_mark_event (ETW::GC_ROOT_HANDLES, current_promoted_bytes, last_promoted_bytes); if (!full_p) @@ -26341,6 +26515,7 @@ void gc_heap::mark_phase (int condemned_gen_number, BOOL mark_only_p) update_old_card_survived(); #endif //USE_REGIONS + drain_mark_queue(); fire_mark_event (ETW::GC_ROOT_OLDER, current_promoted_bytes, last_promoted_bytes); } } @@ -26349,6 +26524,7 @@ void gc_heap::mark_phase (int condemned_gen_number, BOOL mark_only_p) if (do_mark_steal_p) { mark_steal(); + drain_mark_queue(); fire_mark_event (ETW::GC_ROOT_STEAL, current_promoted_bytes, last_promoted_bytes); } #endif //MH_SC_MARK @@ -26362,6 +26538,7 @@ void gc_heap::mark_phase (int condemned_gen_number, BOOL mark_only_p) // handle table has been fully promoted. GCScan::GcDhInitialScan(GCHeap::Promote, condemned_gen_number, max_generation, &sc); scan_dependent_handles(condemned_gen_number, &sc, true); + drain_mark_queue(); fire_mark_event (ETW::GC_ROOT_DH_HANDLES, current_promoted_bytes, last_promoted_bytes); #ifdef MULTIPLE_HEAPS @@ -26444,12 +26621,14 @@ void gc_heap::mark_phase (int condemned_gen_number, BOOL mark_only_p) #ifdef FEATURE_PREMORTEM_FINALIZATION dprintf (3, ("Finalize marking")); finalize_queue->ScanForFinalization (GCHeap::Promote, condemned_gen_number, mark_only_p, __this); + drain_mark_queue(); fire_mark_event (ETW::GC_ROOT_NEW_FQ, current_promoted_bytes, last_promoted_bytes); GCToEEInterface::DiagWalkFReachableObjects(__this); // Scan dependent handles again to promote any secondaries associated with primaries that were promoted // for finalization. As before scan_dependent_handles will also process any mark stack overflow. scan_dependent_handles(condemned_gen_number, &sc, false); + drain_mark_queue(); fire_mark_event (ETW::GC_ROOT_DH_HANDLES, current_promoted_bytes, last_promoted_bytes); #endif //FEATURE_PREMORTEM_FINALIZATION @@ -31927,7 +32106,7 @@ uint8_t* tree_search (uint8_t* tree, uint8_t* old_address) assert (candidate < tree); candidate = tree; tree = tree + cn; - Prefetch (tree - 8); + Prefetch (&((plug_and_pair*)tree)[-1].m_pair.left); continue; } else @@ -31938,7 +32117,7 @@ uint8_t* tree_search (uint8_t* tree, uint8_t* old_address) if ((cn = node_left_child (tree)) != 0) { tree = tree + cn; - Prefetch (tree - 8); + Prefetch (&((plug_and_pair*)tree)[-1].m_pair.left); continue; } else diff --git a/src/coreclr/gc/gcpriv.h b/src/coreclr/gc/gcpriv.h index 72ff309ffb076e..e0640a860400af 100644 --- a/src/coreclr/gc/gcpriv.h +++ b/src/coreclr/gc/gcpriv.h @@ -1216,6 +1216,23 @@ enum bookkeeping_element total_bookkeeping_elements }; +class mark_queue_t +{ + static const size_t slot_count = 16; + uint8_t* slot_table[slot_count]; + size_t curr_slot_index; + +public: + mark_queue_t(); + + uint8_t *queue_mark(uint8_t *o); + uint8_t *queue_mark(uint8_t *o, int condemned_gen); + + uint8_t* get_next_marked(); + + ~mark_queue_t(); +}; + //class definition of the internal class class gc_heap { @@ -1242,6 +1259,8 @@ class gc_heap friend void PopulateDacVars(GcDacVars *gcDacVars); + friend class mark_queue_t; + #ifdef MULTIPLE_HEAPS typedef void (gc_heap::* card_fn) (uint8_t**, int); #define call_fn(this_arg,fn) (this_arg->*fn) @@ -2411,6 +2430,9 @@ class gc_heap PER_HEAP void mark_object_simple1 (uint8_t* o, uint8_t* start THREAD_NUMBER_DCL); + PER_HEAP + void drain_mark_queue(); + #ifdef MH_SC_MARK PER_HEAP void mark_steal (); @@ -5131,6 +5153,8 @@ class gc_heap PER_HEAP_ISOLATED size_t bookkeeping_sizes[total_bookkeeping_elements]; #endif //USE_REGIONS + PER_HEAP + mark_queue_t mark_queue; }; // class gc_heap #ifdef FEATURE_PREMORTEM_FINALIZATION From b947dd6e1bb45b064e30159c9251add5c80914af Mon Sep 17 00:00:00 2001 From: Buyaa Namnan Date: Tue, 9 Aug 2022 09:09:49 -0700 Subject: [PATCH 15/41] Generic attributes handling in CustomAttributeDecoder (#72561) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add handling of generic attributes to CustomAttributeDecoder If the attribute constructor refers to the generic T in its signature, we would throw `BadImageFormatException`. This adds handling by capturing the generic context and interpreting the signature variables when needed. * Add tests Co-authored-by: Michal Strehovský --- .../Ecma335/CustomAttributeDecoder.cs | 155 +++++++- .../Decoding/CustomAttributeDecoderTests.cs | 364 +++++++++++++++++- 2 files changed, 501 insertions(+), 18 deletions(-) diff --git a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/CustomAttributeDecoder.cs b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/CustomAttributeDecoder.cs index a81396cbdef16c..45deeee7853047 100644 --- a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/CustomAttributeDecoder.cs +++ b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/CustomAttributeDecoder.cs @@ -22,6 +22,7 @@ public CustomAttributeDecoder(ICustomAttributeTypeProvider provider, Meta public CustomAttributeValue DecodeValue(EntityHandle constructor, BlobHandle value) { BlobHandle signature; + BlobHandle attributeOwningTypeSpec = default; switch (constructor.Kind) { case HandleKind.MethodDefinition: @@ -32,6 +33,13 @@ public CustomAttributeValue DecodeValue(EntityHandle constructor, BlobHan case HandleKind.MemberReference: MemberReference reference = _reader.GetMemberReference((MemberReferenceHandle)constructor); signature = reference.Signature; + + // If this is a generic attribute, we'll need its instantiation to decode the signatures + if (reference.Parent.Kind == HandleKind.TypeSpecification) + { + TypeSpecification genericOwner = _reader.GetTypeSpecification((TypeSpecificationHandle)reference.Parent); + attributeOwningTypeSpec = genericOwner.Signature; + } break; default: @@ -60,12 +68,38 @@ public CustomAttributeValue DecodeValue(EntityHandle constructor, BlobHan throw new BadImageFormatException(); } - ImmutableArray> fixedArguments = DecodeFixedArguments(ref signatureReader, ref valueReader, parameterCount); + BlobReader genericContextReader = default; + if (!attributeOwningTypeSpec.IsNil) + { + // If this is a generic attribute, grab the instantiation arguments so that we can + // interpret the constructor signature, should it refer to the generic context. + genericContextReader = _reader.GetBlobReader(attributeOwningTypeSpec); + if (genericContextReader.ReadSignatureTypeCode() == SignatureTypeCode.GenericTypeInstance) + { + int kind = genericContextReader.ReadCompressedInteger(); + if (kind != (int)SignatureTypeKind.Class && kind != (int)SignatureTypeKind.ValueType) + { + throw new BadImageFormatException(); + } + + genericContextReader.ReadTypeHandle(); + + // At this point, the reader points to the "GenArgCount Type Type*" part of the signature. + } + else + { + // Some other invalid TypeSpec. Don't accidentally allow resolving generic parameters + // from the constructor signature into a broken blob. + genericContextReader = default; + } + } + + ImmutableArray> fixedArguments = DecodeFixedArguments(ref signatureReader, ref valueReader, parameterCount, genericContextReader); ImmutableArray> namedArguments = DecodeNamedArguments(ref valueReader); return new CustomAttributeValue(fixedArguments, namedArguments); } - private ImmutableArray> DecodeFixedArguments(ref BlobReader signatureReader, ref BlobReader valueReader, int count) + private ImmutableArray> DecodeFixedArguments(ref BlobReader signatureReader, ref BlobReader valueReader, int count, BlobReader genericContextReader) { if (count == 0) { @@ -76,7 +110,7 @@ private ImmutableArray> DecodeFixedArguments for (int i = 0; i < count; i++) { - ArgumentTypeInfo info = DecodeFixedArgumentType(ref signatureReader); + ArgumentTypeInfo info = DecodeFixedArgumentType(ref signatureReader, genericContextReader); arguments.Add(DecodeArgument(ref valueReader, info)); } @@ -124,7 +158,7 @@ private struct ArgumentTypeInfo // better perf-wise, but even more important is that we can't actually reason about // a method signature with opaque TType values without adding some unnecessary chatter // with the provider. - private ArgumentTypeInfo DecodeFixedArgumentType(ref BlobReader signatureReader, bool isElementType = false) + private ArgumentTypeInfo DecodeFixedArgumentType(ref BlobReader signatureReader, BlobReader genericContextReader, bool isElementType = false) { SignatureTypeCode signatureTypeCode = signatureReader.ReadSignatureTypeCode(); @@ -170,12 +204,33 @@ private ArgumentTypeInfo DecodeFixedArgumentType(ref BlobReader signatureReader, throw new BadImageFormatException(); } - var elementInfo = DecodeFixedArgumentType(ref signatureReader, isElementType: true); + var elementInfo = DecodeFixedArgumentType(ref signatureReader, genericContextReader, isElementType: true); info.ElementType = elementInfo.Type; info.ElementTypeCode = elementInfo.TypeCode; info.Type = _provider.GetSZArrayType(info.ElementType); break; + case SignatureTypeCode.GenericTypeParameter: + if (genericContextReader.Length == 0) + { + throw new BadImageFormatException(); + } + + int parameterIndex = signatureReader.ReadCompressedInteger(); + int numGenericParameters = genericContextReader.ReadCompressedInteger(); + if (parameterIndex >= numGenericParameters) + { + throw new BadImageFormatException(); + } + + while (parameterIndex > 0) + { + SkipType(ref genericContextReader); + parameterIndex--; + } + + return DecodeFixedArgumentType(ref genericContextReader, default, isElementType); + default: throw new BadImageFormatException(); } @@ -363,5 +418,95 @@ private TType GetTypeFromHandle(EntityHandle handle) => HandleKind.TypeReference => _provider.GetTypeFromReference(_reader, (TypeReferenceHandle)handle, 0), _ => throw new BadImageFormatException(SR.NotTypeDefOrRefHandle), }; + + private static void SkipType(ref BlobReader blobReader) + { + int typeCode = blobReader.ReadCompressedInteger(); + + switch (typeCode) + { + case (int)SignatureTypeCode.Boolean: + case (int)SignatureTypeCode.Char: + case (int)SignatureTypeCode.SByte: + case (int)SignatureTypeCode.Byte: + case (int)SignatureTypeCode.Int16: + case (int)SignatureTypeCode.UInt16: + case (int)SignatureTypeCode.Int32: + case (int)SignatureTypeCode.UInt32: + case (int)SignatureTypeCode.Int64: + case (int)SignatureTypeCode.UInt64: + case (int)SignatureTypeCode.Single: + case (int)SignatureTypeCode.Double: + case (int)SignatureTypeCode.IntPtr: + case (int)SignatureTypeCode.UIntPtr: + case (int)SignatureTypeCode.Object: + case (int)SignatureTypeCode.String: + case (int)SignatureTypeCode.Void: + case (int)SignatureTypeCode.TypedReference: + return; + + case (int)SignatureTypeCode.Pointer: + case (int)SignatureTypeCode.ByReference: + case (int)SignatureTypeCode.Pinned: + case (int)SignatureTypeCode.SZArray: + SkipType(ref blobReader); + return; + + case (int)SignatureTypeCode.FunctionPointer: + SignatureHeader header = blobReader.ReadSignatureHeader(); + if (header.IsGeneric) + { + blobReader.ReadCompressedInteger(); // arity + } + + int paramCount = blobReader.ReadCompressedInteger(); + SkipType(ref blobReader); + for (int i = 0; i < paramCount; i++) + SkipType(ref blobReader); + return; + + case (int)SignatureTypeCode.Array: + SkipType(ref blobReader); + blobReader.ReadCompressedInteger(); // rank + int boundsCount = blobReader.ReadCompressedInteger(); + for (int i = 0; i < boundsCount; i++) + { + blobReader.ReadCompressedInteger(); + } + int lowerBoundsCount = blobReader.ReadCompressedInteger(); + for (int i = 0; i < lowerBoundsCount; i++) + { + blobReader.ReadCompressedSignedInteger(); + } + return; + + case (int)SignatureTypeCode.RequiredModifier: + case (int)SignatureTypeCode.OptionalModifier: + blobReader.ReadTypeHandle(); + SkipType(ref blobReader); + return; + + case (int)SignatureTypeCode.GenericTypeInstance: + SkipType(ref blobReader); + int count = blobReader.ReadCompressedInteger(); + for (int i = 0; i < count; i++) + { + SkipType(ref blobReader); + } + return; + + case (int)SignatureTypeCode.GenericTypeParameter: + blobReader.ReadCompressedInteger(); + return; + + case (int)SignatureTypeKind.Class: + case (int)SignatureTypeKind.ValueType: + SkipType(ref blobReader); + break; + + default: + throw new BadImageFormatException(); + } + } } } diff --git a/src/libraries/System.Reflection.Metadata/tests/Metadata/Decoding/CustomAttributeDecoderTests.cs b/src/libraries/System.Reflection.Metadata/tests/Metadata/Decoding/CustomAttributeDecoderTests.cs index f0aafa09ed66f9..a1f6355201053a 100644 --- a/src/libraries/System.Reflection.Metadata/tests/Metadata/Decoding/CustomAttributeDecoderTests.cs +++ b/src/libraries/System.Reflection.Metadata/tests/Metadata/Decoding/CustomAttributeDecoderTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Collections.Generic; using System.Collections.Immutable; using System.IO; using System.Reflection.Metadata.Tests; @@ -11,7 +12,7 @@ namespace System.Reflection.Metadata.Decoding.Tests { public class CustomAttributeDecoderTests { - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.HasAssemblyFiles))] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.HasAssemblyFiles), nameof(PlatformDetection.IsMonoRuntime))] [ActiveIssue("https://github.com/dotnet/runtime/issues/60579", TestPlatforms.iOS | TestPlatforms.tvOS)] public void TestCustomAttributeDecoder() { @@ -22,7 +23,6 @@ public void TestCustomAttributeDecoder() var provider = new CustomAttributeTypeProvider(); TypeDefinitionHandle typeDefHandle = TestMetadataResolver.FindTestType(reader, typeof(HasAttributes)); - int i = 0; foreach (CustomAttributeHandle attributeHandle in reader.GetCustomAttributes(typeDefHandle)) { @@ -75,16 +75,274 @@ public void TestCustomAttributeDecoder() break; default: - // TODO: https://github.com/dotnet/runtime/issues/16552 - // The other cases are missing corresponding assertions. This needs some refactoring to - // be data-driven. A better approach would probably be to generically compare reflection - // CustomAttributeData to S.R.M CustomAttributeValue for every test attribute applied. + // TODO: https://github.com/dotnet/runtime/issues/73593 + // This method only tests first 3 attriubtes because the complete test 'TestCustomAttributeDecoderUsingReflection' fails on mono + // Leaving this hard coded test only for mono, until the issue fixed on mono break; } } } } + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.HasAssemblyFiles))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/73593", TestRuntimes.Mono)] + public void TestCustomAttributeDecoderUsingReflection() + { + Type type = typeof(HasAttributes); + using (FileStream stream = File.OpenRead(AssemblyPathHelper.GetAssemblyLocation(type.GetTypeInfo().Assembly))) + using (PEReader peReader = new PEReader(stream)) + { + MetadataReader reader = peReader.GetMetadataReader(); + CustomAttributeTypeProvider provider = new CustomAttributeTypeProvider(); + TypeDefinitionHandle typeDefHandle = TestMetadataResolver.FindTestType(reader, type); + + IList attributes = type.GetCustomAttributesData(); + + int i = 0; + foreach (CustomAttributeHandle attributeHandle in reader.GetCustomAttributes(typeDefHandle)) + { + CustomAttribute attribute = reader.GetCustomAttribute(attributeHandle); + CustomAttributeValue value = attribute.DecodeValue(provider); + CustomAttributeData reflectionAttribute = attributes[i++]; + + Assert.Equal(reflectionAttribute.ConstructorArguments.Count, value.FixedArguments.Length); + Assert.Equal(reflectionAttribute.NamedArguments.Count, value.NamedArguments.Length); + + int j = 0; + foreach (CustomAttributeTypedArgument arguments in value.FixedArguments) + { + Type t = reflectionAttribute.ConstructorArguments[j].ArgumentType; + Assert.Equal(TypeToString(t), arguments.Type); + if (t.IsArray && arguments.Value is not null) + { + ImmutableArray> array = (ImmutableArray>)(arguments.Value); + IList refArray = (IList)reflectionAttribute.ConstructorArguments[j].Value; + int k = 0; + foreach (CustomAttributeTypedArgument element in array) + { + if (refArray[k].ArgumentType.IsArray) + { + ImmutableArray> innerArray = (ImmutableArray>)(element.Value); + IList refInnerArray = (IList)refArray[k].Value; + int a = 0; + foreach (CustomAttributeTypedArgument el in innerArray) + { + if (refInnerArray[a].Value?.ToString() != el.Value?.ToString()) + { + Assert.Equal(refInnerArray[a].Value, el.Value); + } + a++; + } + } + else if (refArray[k].Value?.ToString() != element.Value?.ToString()) + { + if (refArray[k].ArgumentType == typeof(Type)) // TODO: check if it is expected + { + Assert.Contains(refArray[k].Value.ToString(), element.Value.ToString()); + } + else + { + Assert.Equal(refArray[k].Value, element.Value); + } + } + k++; + } + } + else if (reflectionAttribute.ConstructorArguments[j].Value?.ToString() != arguments.Value?.ToString()) + { + if (reflectionAttribute.ConstructorArguments[j].ArgumentType == typeof(Type)) + { + Assert.Contains(reflectionAttribute.ConstructorArguments[j].Value.ToString(), arguments.Value.ToString()); + } + else + { + Assert.Equal(reflectionAttribute.ConstructorArguments[j].Value, arguments.Value); + } + } + j++; + } + j = 0; + foreach (CustomAttributeNamedArgument arguments in value.NamedArguments) + { + Type t = reflectionAttribute.NamedArguments[j].TypedValue.ArgumentType; + Assert.Equal(TypeToString(t), arguments.Type); + if (t.IsArray && arguments.Value is not null) + { + ImmutableArray> array = (ImmutableArray>)(arguments.Value); + IList refArray = (IList)reflectionAttribute.NamedArguments[j].TypedValue.Value; + int k = 0; + foreach (CustomAttributeTypedArgument element in array) + { + if (refArray[k].Value?.ToString() != element.Value?.ToString()) + { + Assert.Equal(refArray[k].Value, element.Value); + } + k++; + } + } + else if (reflectionAttribute.NamedArguments[j].TypedValue.Value?.ToString() != arguments.Value?.ToString()) + { + if (reflectionAttribute.NamedArguments[j].TypedValue.ArgumentType == typeof(Type)) // typeof operator used for named parameter, like [Test(TypeField = typeof(string))], check if it is expected + { + Assert.Contains(reflectionAttribute.NamedArguments[j].TypedValue.Value.ToString(), arguments.Value.ToString()); + } + else + { + Assert.Equal(reflectionAttribute.NamedArguments[j].TypedValue.Value, arguments.Value); + } + } + j++; + } + } + } + } + +#if NETCOREAPP // Generic attribute is not supported on .NET Framework. + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.HasAssemblyFiles))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/60579", TestPlatforms.iOS | TestPlatforms.tvOS)] + public void TestCustomAttributeDecoderGenericUsingReflection() + { + Type type = typeof(HasGenericAttributes); + using (FileStream stream = File.OpenRead(AssemblyPathHelper.GetAssemblyLocation(type.GetTypeInfo().Assembly))) + using (PEReader peReader = new PEReader(stream)) + { + MetadataReader reader = peReader.GetMetadataReader(); + CustomAttributeTypeProvider provider = new CustomAttributeTypeProvider(); + TypeDefinitionHandle typeDefHandle = TestMetadataResolver.FindTestType(reader, type); + + IList attributes= type.GetCustomAttributesData(); + + int i = 0; + foreach (CustomAttributeHandle attributeHandle in reader.GetCustomAttributes(typeDefHandle)) + { + CustomAttribute attribute = reader.GetCustomAttribute(attributeHandle); + CustomAttributeValue value = attribute.DecodeValue(provider); + CustomAttributeData reflectionAttribute = attributes[i++]; + + Assert.Equal(reflectionAttribute.ConstructorArguments.Count, value.FixedArguments.Length); + Assert.Equal(reflectionAttribute.NamedArguments.Count, value.NamedArguments.Length); + + int j = 0; + foreach (CustomAttributeTypedArgument arguments in value.FixedArguments) + { + Assert.Equal(TypeToString(reflectionAttribute.ConstructorArguments[j].ArgumentType), arguments.Type); + if (reflectionAttribute.ConstructorArguments[j].Value.ToString() != arguments.Value.ToString()) + { + Assert.Equal(reflectionAttribute.ConstructorArguments[j].Value, arguments.Value); + } + j++; + } + j = 0; + foreach (CustomAttributeNamedArgument arguments in value.NamedArguments) + { + Assert.Equal(TypeToString(reflectionAttribute.NamedArguments[j].TypedValue.ArgumentType), arguments.Type); + if (reflectionAttribute.NamedArguments[j].TypedValue.Value.ToString() != arguments.Value.ToString()) + { + Assert.Equal(reflectionAttribute.NamedArguments[j].TypedValue.Value, arguments.Value); + } + j++; + } + } + } + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.HasAssemblyFiles))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/60579", TestPlatforms.iOS | TestPlatforms.tvOS)] + public void TestCustomAttributeDecoderGenericArray() + { + Type type = typeof(HasGenericArrayAttributes); + using (FileStream stream = File.OpenRead(AssemblyPathHelper.GetAssemblyLocation(type.GetTypeInfo().Assembly))) + using (PEReader peReader = new PEReader(stream)) + { + MetadataReader reader = peReader.GetMetadataReader(); + CustomAttributeTypeProvider provider = new CustomAttributeTypeProvider(); + TypeDefinitionHandle typeDefHandle = TestMetadataResolver.FindTestType(reader, type); + + IList attributes = type.GetCustomAttributesData(); + + foreach (CustomAttributeHandle attributeHandle in reader.GetCustomAttributes(typeDefHandle)) + { + CustomAttribute attribute = reader.GetCustomAttribute(attributeHandle); + CustomAttributeValue value = attribute.DecodeValue(provider); + + if (value.FixedArguments.Length == 2) + { + Assert.Equal(2, value.FixedArguments.Length); + ImmutableArray> array1 = (ImmutableArray>)(value.FixedArguments[0].Value); + Assert.Equal("int32[]", value.FixedArguments[0].Type); + Assert.Equal(1, array1[0].Value); + Assert.Equal(3, array1[2].Value); + ImmutableArray> array2 = (ImmutableArray>)(value.FixedArguments[1].Value); + Assert.Equal("uint8[]", value.FixedArguments[1].Type); + Assert.Equal((byte)4, array2[0].Value); + Assert.Equal((byte)5, array2[1].Value); + + Assert.Empty(value.NamedArguments); + } + else + { + Assert.Equal(1, value.FixedArguments.Length); + + Assert.Equal("uint8", value.FixedArguments[0].Type); + Assert.Equal((byte)1, value.FixedArguments[0].Value); + + Assert.Equal(2, value.NamedArguments.Length); + + Assert.Equal("uint8", value.NamedArguments[0].Type); + Assert.Equal((byte)2, value.NamedArguments[0].Value); + + ImmutableArray> array = (ImmutableArray>)(value.NamedArguments[1].Value); + Assert.Equal("uint8[]", value.NamedArguments[1].Type); + Assert.Equal((byte)3, array[0].Value); + } + } + } + } + + [GenericAttribute] + [GenericAttribute("Hello")] + [GenericAttribute(12)] + [GenericAttribute("Hello", 12, TProperty = "Bye")] + [GenericAttribute(1, TProperty = 2)] + [GenericAttribute2(true, 13)] + // [GenericAttribute(MyEnum.Property)] TODO: https://github.com/dotnet/runtime/issues/16552 + [GenericAttribute(typeof(HasAttributes))] + [GenericAttribute(TProperty = typeof(HasAttributes))] + public class HasGenericAttributes { } + + [GenericAttribute2(new int[] { 1, 2, 3 }, new byte[] { 4, 5 })] + [GenericAttribute(1, TProperty = 2, TArrayProperty = new byte[] { 3, 4 })] + public class HasGenericArrayAttributes { } + + [AttributeUsage(AttributeTargets.All, AllowMultiple = true)] + internal class GenericAttribute : Attribute + { + public GenericAttribute() { } + public GenericAttribute(T value) + { + Field = value; + } + public GenericAttribute(T value, int count) + { + Field = value; + } + public T TProperty { get; set; } + public T[] TArrayProperty { get; set; } + public T Field; + } + + [AttributeUsage(AttributeTargets.All, AllowMultiple = true)] + internal class GenericAttribute2 : Attribute + { + public GenericAttribute2() { } + public GenericAttribute2(K key) { } + public GenericAttribute2(K key, V value) { } + public K Key { get; set; } + public V Value { get; set; } + public K[] ArrayProperty { get; set; } + } +#endif + // no arguments [Test] @@ -120,17 +378,17 @@ public void TestCustomAttributeDecoder() [Test(true)] [Test(false)] [Test(typeof(string))] - [Test(SByteEnum.Value)] - [Test(Int16Enum.Value)] - [Test(Int32Enum.Value)] + /* [Test(SByteEnum.Value)] // The FullName is (System.Reflection.Metadata.Decoding.Tests.CustomAttributeDecoderTests+SByteEnum) + [Test(Int16Enum.Value)] // but some enums '+' is replaced with '/' and causing inconsistency + [Test(Int32Enum.Value)] // Updaated https://github.com/dotnet/runtime/issues/16552 to resolve this scenario later [Test(Int64Enum.Value)] [Test(ByteEnum.Value)] [Test(UInt16Enum.Value)] [Test(UInt32Enum.Value)] - [Test(UInt64Enum.Value)] + [Test(UInt64Enum.Value)]*/ [Test(new string[] { })] [Test(new string[] { "x", "y", "z", null })] - [Test(new Int32Enum[] { Int32Enum.Value })] + // [Test(new Int32Enum[] { Int32Enum.Value })] TODO: https://github.com/dotnet/runtime/issues/16552 // same single fixed arguments as above, typed as object [Test((object)("string"))] @@ -176,7 +434,7 @@ public void TestCustomAttributeDecoder() (uint)4, true, false, - typeof(string), + typeof(string), // check if the produced value is expected SByteEnum.Value, Int16Enum.Value, Int32Enum.Value, @@ -217,7 +475,7 @@ public void TestCustomAttributeDecoder() [Test(UInt64EnumField = UInt64Enum.Value)] [Test(new string[] { })] [Test(new string[] { "x", "y", "z", null })] - [Test(new Int32Enum[] { Int32Enum.Value })] + // [Test(new Int32Enum[] { Int32Enum.Value })] TODO: https://github.com/dotnet/runtime/issues/16552 // null named arguments [Test(ObjectField = null)] @@ -333,6 +591,83 @@ public TestAttribute(UInt64Enum[] value) { } public UInt64Enum[] UInt64EnumArrayProperty { get; set; } } + private string TypeToString(Type type) + { + if (type == typeof(Type)) + return $"[{MetadataReaderTestHelpers.RuntimeAssemblyName}]System.Type"; + + if (type.IsArray) + { + if (type.GetElementType().IsEnum) + { + Type el = type.GetElementType(); + return type.FullName; + } + return GetPrimitiveType(type.GetElementType()) + "[]"; + } + + if (type.IsEnum) + return type.FullName; + + return GetPrimitiveType(type); + } + + private static string GetPrimitiveType(Type type) + { + switch (Type.GetTypeCode(type)) + { + case TypeCode.Boolean: + return "bool"; + + case TypeCode.Byte: + return "uint8"; + + case TypeCode.Char: + return "char"; + + case TypeCode.Double: + return "float64"; + + case TypeCode.Int16: + return "int16"; + + case TypeCode.Int32: + return "int32"; + + case TypeCode.Int64: + return "int64"; + + case TypeCode.Object: + return "object"; + + case TypeCode.SByte: + return "int8"; + + case TypeCode.Single: + return "float32"; + + case TypeCode.String: + return "string"; + + case TypeCode.UInt16: + return "uint16"; + + case TypeCode.UInt32: + return "uint32"; + + case TypeCode.UInt64: + return "uint64"; + + default: + throw new ArgumentOutOfRangeException(nameof(type)); + } + } + + public enum MyEnum + { + Ctor, + Property + } private class CustomAttributeTypeProvider : DisassemblingTypeProvider, ICustomAttributeTypeProvider { @@ -380,6 +715,9 @@ public PrimitiveTypeCode GetUnderlyingEnumType(string type) if (runtimeType == typeof(UInt64Enum)) return PrimitiveTypeCode.UInt64; + if (runtimeType == typeof(MyEnum)) + return PrimitiveTypeCode.Byte; + throw new ArgumentOutOfRangeException(); } } From e117479e2530ed4e0e83e3e27948b5675fda2b57 Mon Sep 17 00:00:00 2001 From: Buyaa Namnan Date: Tue, 9 Aug 2022 09:14:59 -0700 Subject: [PATCH 16/41] Rename ObsoletedInOSPlatform attribute (#73600) --- .../src/ILLink/ILLink.LinkAttributes.Shared.xml | 2 +- .../src/System/Runtime/Versioning/PlatformAttributes.cs | 6 +++--- src/libraries/System.Runtime/ref/System.Runtime.cs | 6 +++--- .../System/Runtime/Versioning/OSPlatformAttributeTests.cs | 8 ++++---- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/ILLink/ILLink.LinkAttributes.Shared.xml b/src/libraries/System.Private.CoreLib/src/ILLink/ILLink.LinkAttributes.Shared.xml index 1e1071c450ca6b..f10548a4a70dc4 100644 --- a/src/libraries/System.Private.CoreLib/src/ILLink/ILLink.LinkAttributes.Shared.xml +++ b/src/libraries/System.Private.CoreLib/src/ILLink/ILLink.LinkAttributes.Shared.xml @@ -219,7 +219,7 @@ - + diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/Versioning/PlatformAttributes.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/Versioning/PlatformAttributes.cs index ef235aae22261e..06359cf91171b6 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/Versioning/PlatformAttributes.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/Versioning/PlatformAttributes.cs @@ -132,12 +132,12 @@ public UnsupportedOSPlatformAttribute(string platformName, string? message) : ba #else internal #endif - sealed class ObsoletedInOSPlatformAttribute : OSPlatformAttribute + sealed class ObsoletedOSPlatformAttribute : OSPlatformAttribute { - public ObsoletedInOSPlatformAttribute(string platformName) : base(platformName) + public ObsoletedOSPlatformAttribute(string platformName) : base(platformName) { } - public ObsoletedInOSPlatformAttribute(string platformName, string? message) : base(platformName) + public ObsoletedOSPlatformAttribute(string platformName, string? message) : base(platformName) { Message = message; } diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index d62e3cbb60e941..37b23579408a79 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -13366,10 +13366,10 @@ public FrameworkName(string identifier, System.Version version, string? profile) public override string ToString() { throw null; } } [System.AttributeUsageAttribute(System.AttributeTargets.Assembly | System.AttributeTargets.Class | System.AttributeTargets.Constructor | System.AttributeTargets.Enum | System.AttributeTargets.Event | System.AttributeTargets.Field | System.AttributeTargets.Interface | System.AttributeTargets.Method | System.AttributeTargets.Module | System.AttributeTargets.Property | System.AttributeTargets.Struct, AllowMultiple=true, Inherited=false)] - public sealed partial class ObsoletedInOSPlatformAttribute : System.Runtime.Versioning.OSPlatformAttribute + public sealed partial class ObsoletedOSPlatformAttribute : System.Runtime.Versioning.OSPlatformAttribute { - public ObsoletedInOSPlatformAttribute(string platformName) : base(platformName) { } - public ObsoletedInOSPlatformAttribute(string platformName, string? message) : base(platformName) { } + public ObsoletedOSPlatformAttribute(string platformName) : base(platformName) { } + public ObsoletedOSPlatformAttribute(string platformName, string? message) : base(platformName) { } public string? Message { get { throw null; } } public string? Url { get { throw null; } set {} } } diff --git a/src/libraries/System.Runtime/tests/System/Runtime/Versioning/OSPlatformAttributeTests.cs b/src/libraries/System.Runtime/tests/System/Runtime/Versioning/OSPlatformAttributeTests.cs index 619bdc7c8f1134..8e682ec9e640c8 100644 --- a/src/libraries/System.Runtime/tests/System/Runtime/Versioning/OSPlatformAttributeTests.cs +++ b/src/libraries/System.Runtime/tests/System/Runtime/Versioning/OSPlatformAttributeTests.cs @@ -58,9 +58,9 @@ public void TestSupportedOSPlatformAttribute(string platformName) [InlineData("Windows8.0")] [InlineData("Android4.1")] [InlineData("")] - public void TestObsoletedInOSPlatformAttribute(string platformName) + public void TestObsoletedOSPlatformAttribute(string platformName) { - var tpa = new ObsoletedInOSPlatformAttribute(platformName); + var tpa = new ObsoletedOSPlatformAttribute(platformName); Assert.Equal(platformName, tpa.PlatformName); } @@ -69,9 +69,9 @@ public void TestObsoletedInOSPlatformAttribute(string platformName) [InlineData("Windows8.0", "Message in a bottle")] [InlineData("Android4.1", "Message on a pigeon")] [InlineData("", null)] - public void TestObsoletedInOSPlatformAttributeWithMessage(string platformName, string? message) + public void TestObsoletedOSPlatformAttributeWithMessage(string platformName, string? message) { - var tpa = new ObsoletedInOSPlatformAttribute(platformName, message); + var tpa = new ObsoletedOSPlatformAttribute(platformName, message); Assert.Equal(platformName, tpa.PlatformName); Assert.Equal(message, tpa.Message); From e4dd880ee8a709298a6e7679ac7b64b796d0faa9 Mon Sep 17 00:00:00 2001 From: Vlad Brezae Date: Tue, 9 Aug 2022 19:32:58 +0300 Subject: [PATCH 17/41] [mono][interp] Return null for localloc with len 0 (#73174) so that accessing the result address throws exception. Fixes https://github.com/dotnet/runtime/issues/54359 --- src/mono/mono/mini/interp/interp.c | 11 ++++++++--- src/tests/issues.targets | 15 --------------- 2 files changed, 8 insertions(+), 18 deletions(-) diff --git a/src/mono/mono/mini/interp/interp.c b/src/mono/mono/mini/interp/interp.c index fc7e4927784304..0c69d67bf6376b 100644 --- a/src/mono/mono/mini/interp/interp.c +++ b/src/mono/mono/mini/interp/interp.c @@ -7127,10 +7127,15 @@ MINT_IN_CASE(MINT_BRTRUE_I8_SP) ZEROP_SP(gint64, !=); MINT_IN_BREAK; MINT_IN_CASE(MINT_LOCALLOC) { int len = LOCAL_VAR (ip [2], gint32); - gpointer mem = frame_data_allocator_alloc (&context->data_stack, frame, ALIGN_TO (len, MINT_VT_ALIGNMENT)); + gpointer mem; + if (len > 0) { + mem = frame_data_allocator_alloc (&context->data_stack, frame, ALIGN_TO (len, MINT_VT_ALIGNMENT)); - if (frame->imethod->init_locals) - memset (mem, 0, len); + if (frame->imethod->init_locals) + memset (mem, 0, len); + } else { + mem = NULL; + } LOCAL_VAR (ip [1], gpointer) = mem; ip += 3; MINT_IN_BREAK; diff --git a/src/tests/issues.targets b/src/tests/issues.targets index c4dd696bdbca12..61b04ec3db157c 100644 --- a/src/tests/issues.targets +++ b/src/tests/issues.targets @@ -2504,9 +2504,6 @@ https://github.com/dotnet/runtime/issues/54393 - - https://github.com/dotnet/runtime/issues/54359 - needs triage @@ -2624,9 +2621,6 @@ needs triage - - https://github.com/dotnet/runtime/issues/54359 - needs triage @@ -2642,15 +2636,6 @@ https://github.com/dotnet/runtime/issues/54399 - - https://github.com/dotnet/runtime/issues/54359 - - - https://github.com/dotnet/runtime/issues/54359 - - - https://github.com/dotnet/runtime/issues/54359 - https://github.com/dotnet/runtime/issues/54393 From 01e2521dcee2a05791228decd2951eee1b9eb757 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Tue, 9 Aug 2022 09:43:10 -0700 Subject: [PATCH 18/41] Condition ExternalConsoleManipulation_RegistrationRemoved_UnregisterSucceeds on RemoteExecutor.IsSupported (#73603) --- .../InteropServices/PosixSignalRegistrationTests.Windows.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Runtime.InteropServices/tests/System.Runtime.InteropServices.UnitTests/System/Runtime/InteropServices/PosixSignalRegistrationTests.Windows.cs b/src/libraries/System.Runtime.InteropServices/tests/System.Runtime.InteropServices.UnitTests/System/Runtime/InteropServices/PosixSignalRegistrationTests.Windows.cs index bdc8adfd299eb4..7564a2b5c4afa7 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/System.Runtime.InteropServices.UnitTests/System/Runtime/InteropServices/PosixSignalRegistrationTests.Windows.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/System.Runtime.InteropServices.UnitTests/System/Runtime/InteropServices/PosixSignalRegistrationTests.Windows.cs @@ -29,7 +29,7 @@ public static IEnumerable UnsupportedSignals() private static IEnumerable SupportedPosixSignals => new[] { PosixSignal.SIGINT, PosixSignal.SIGQUIT, PosixSignal.SIGTERM, PosixSignal.SIGHUP }; - [Fact] + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] public void ExternalConsoleManipulation_RegistrationRemoved_UnregisterSucceeds() { RemoteExecutor.Invoke(() => From ce3acc35cfd57d7e127cca2acdcee60ce651bd22 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Tue, 9 Aug 2022 09:53:38 -0700 Subject: [PATCH 19/41] [main] Update dependencies from dotnet/arcade (#73517) * Update dependencies from https://github.com/dotnet/arcade build 20220805.2 Microsoft.DotNet.ApiCompat , Microsoft.DotNet.Arcade.Sdk , Microsoft.DotNet.Build.Tasks.Archives , Microsoft.DotNet.Build.Tasks.Feed , Microsoft.DotNet.Build.Tasks.Installers , Microsoft.DotNet.Build.Tasks.Packaging , Microsoft.DotNet.Build.Tasks.TargetFramework , Microsoft.DotNet.Build.Tasks.Templating , Microsoft.DotNet.Build.Tasks.Workloads , Microsoft.DotNet.CodeAnalysis , Microsoft.DotNet.GenAPI , Microsoft.DotNet.GenFacades , Microsoft.DotNet.Helix.Sdk , Microsoft.DotNet.PackageTesting , Microsoft.DotNet.RemoteExecutor , Microsoft.DotNet.SharedFramework.Sdk , Microsoft.DotNet.VersionTools.Tasks , Microsoft.DotNet.XUnitConsoleRunner , Microsoft.DotNet.XUnitExtensions From Version 7.0.0-beta.22403.1 -> To Version 7.0.0-beta.22405.2 * Update dependencies from https://github.com/dotnet/arcade build 20220805.6 Microsoft.DotNet.ApiCompat , Microsoft.DotNet.Arcade.Sdk , Microsoft.DotNet.Build.Tasks.Archives , Microsoft.DotNet.Build.Tasks.Feed , Microsoft.DotNet.Build.Tasks.Installers , Microsoft.DotNet.Build.Tasks.Packaging , Microsoft.DotNet.Build.Tasks.TargetFramework , Microsoft.DotNet.Build.Tasks.Templating , Microsoft.DotNet.Build.Tasks.Workloads , Microsoft.DotNet.CodeAnalysis , Microsoft.DotNet.GenAPI , Microsoft.DotNet.GenFacades , Microsoft.DotNet.Helix.Sdk , Microsoft.DotNet.PackageTesting , Microsoft.DotNet.RemoteExecutor , Microsoft.DotNet.SharedFramework.Sdk , Microsoft.DotNet.VersionTools.Tasks , Microsoft.DotNet.XUnitConsoleRunner , Microsoft.DotNet.XUnitExtensions From Version 7.0.0-beta.22403.1 -> To Version 7.0.0-beta.22405.6 * Update dependencies from https://github.com/dotnet/arcade build 20220808.3 Microsoft.DotNet.ApiCompat , Microsoft.DotNet.Arcade.Sdk , Microsoft.DotNet.Build.Tasks.Archives , Microsoft.DotNet.Build.Tasks.Feed , Microsoft.DotNet.Build.Tasks.Installers , Microsoft.DotNet.Build.Tasks.Packaging , Microsoft.DotNet.Build.Tasks.TargetFramework , Microsoft.DotNet.Build.Tasks.Templating , Microsoft.DotNet.Build.Tasks.Workloads , Microsoft.DotNet.CodeAnalysis , Microsoft.DotNet.GenAPI , Microsoft.DotNet.GenFacades , Microsoft.DotNet.Helix.Sdk , Microsoft.DotNet.PackageTesting , Microsoft.DotNet.RemoteExecutor , Microsoft.DotNet.SharedFramework.Sdk , Microsoft.DotNet.VersionTools.Tasks , Microsoft.DotNet.XUnitConsoleRunner , Microsoft.DotNet.XUnitExtensions From Version 7.0.0-beta.22403.1 -> To Version 7.0.0-beta.22408.3 Co-authored-by: dotnet-maestro[bot] --- eng/Version.Details.xml | 76 +++++++++---------- eng/Versions.props | 32 ++++---- .../templates/job/source-index-stage1.yml | 2 +- global.json | 6 +- 4 files changed, 58 insertions(+), 58 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 8fe3896a6e889d..504a4c8c50339f 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -54,77 +54,77 @@ - + https://github.com/dotnet/arcade - 4ee620cc1b57da45d93135e064d43a83e65bbb6e + 13b342360ba81ca3fdf911fda985dc8420d51627 - + https://github.com/dotnet/arcade - 4ee620cc1b57da45d93135e064d43a83e65bbb6e + 13b342360ba81ca3fdf911fda985dc8420d51627 - + https://github.com/dotnet/arcade - 4ee620cc1b57da45d93135e064d43a83e65bbb6e + 13b342360ba81ca3fdf911fda985dc8420d51627 - + https://github.com/dotnet/arcade - 4ee620cc1b57da45d93135e064d43a83e65bbb6e + 13b342360ba81ca3fdf911fda985dc8420d51627 - + https://github.com/dotnet/arcade - 4ee620cc1b57da45d93135e064d43a83e65bbb6e + 13b342360ba81ca3fdf911fda985dc8420d51627 - + https://github.com/dotnet/arcade - 4ee620cc1b57da45d93135e064d43a83e65bbb6e + 13b342360ba81ca3fdf911fda985dc8420d51627 - + https://github.com/dotnet/arcade - 4ee620cc1b57da45d93135e064d43a83e65bbb6e + 13b342360ba81ca3fdf911fda985dc8420d51627 - + https://github.com/dotnet/arcade - 4ee620cc1b57da45d93135e064d43a83e65bbb6e + 13b342360ba81ca3fdf911fda985dc8420d51627 - + https://github.com/dotnet/arcade - 4ee620cc1b57da45d93135e064d43a83e65bbb6e + 13b342360ba81ca3fdf911fda985dc8420d51627 - + https://github.com/dotnet/arcade - 4ee620cc1b57da45d93135e064d43a83e65bbb6e + 13b342360ba81ca3fdf911fda985dc8420d51627 - + https://github.com/dotnet/arcade - 4ee620cc1b57da45d93135e064d43a83e65bbb6e + 13b342360ba81ca3fdf911fda985dc8420d51627 - + https://github.com/dotnet/arcade - 4ee620cc1b57da45d93135e064d43a83e65bbb6e + 13b342360ba81ca3fdf911fda985dc8420d51627 - + https://github.com/dotnet/arcade - 4ee620cc1b57da45d93135e064d43a83e65bbb6e + 13b342360ba81ca3fdf911fda985dc8420d51627 - + https://github.com/dotnet/arcade - 4ee620cc1b57da45d93135e064d43a83e65bbb6e + 13b342360ba81ca3fdf911fda985dc8420d51627 - + https://github.com/dotnet/arcade - 4ee620cc1b57da45d93135e064d43a83e65bbb6e + 13b342360ba81ca3fdf911fda985dc8420d51627 - + https://github.com/dotnet/arcade - 4ee620cc1b57da45d93135e064d43a83e65bbb6e + 13b342360ba81ca3fdf911fda985dc8420d51627 - + https://github.com/dotnet/arcade - 4ee620cc1b57da45d93135e064d43a83e65bbb6e + 13b342360ba81ca3fdf911fda985dc8420d51627 - + https://github.com/dotnet/arcade - 4ee620cc1b57da45d93135e064d43a83e65bbb6e + 13b342360ba81ca3fdf911fda985dc8420d51627 https://github.com/dotnet/runtime-assets @@ -242,9 +242,9 @@ https://github.com/dotnet/xharness d43453b9981bfd8705025250caff58d18e2abc7e - + https://github.com/dotnet/arcade - 4ee620cc1b57da45d93135e064d43a83e65bbb6e + 13b342360ba81ca3fdf911fda985dc8420d51627 https://dev.azure.com/dnceng/internal/_git/dotnet-optimization diff --git a/eng/Versions.props b/eng/Versions.props index 8a0379bc1992a8..76258518b21965 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -54,22 +54,22 @@ 7.0.100-rc.1.22402.1 - 7.0.0-beta.22403.1 - 7.0.0-beta.22403.1 - 7.0.0-beta.22403.1 - 7.0.0-beta.22403.1 - 7.0.0-beta.22403.1 - 7.0.0-beta.22403.1 - 2.5.1-beta.22403.1 - 7.0.0-beta.22403.1 - 7.0.0-beta.22403.1 - 7.0.0-beta.22403.1 - 7.0.0-beta.22403.1 - 7.0.0-beta.22403.1 - 7.0.0-beta.22403.1 - 7.0.0-beta.22403.1 - 7.0.0-beta.22403.1 - 7.0.0-beta.22403.1 + 7.0.0-beta.22408.3 + 7.0.0-beta.22408.3 + 7.0.0-beta.22408.3 + 7.0.0-beta.22408.3 + 7.0.0-beta.22408.3 + 7.0.0-beta.22408.3 + 2.5.1-beta.22408.3 + 7.0.0-beta.22408.3 + 7.0.0-beta.22408.3 + 7.0.0-beta.22408.3 + 7.0.0-beta.22408.3 + 7.0.0-beta.22408.3 + 7.0.0-beta.22408.3 + 7.0.0-beta.22408.3 + 7.0.0-beta.22408.3 + 7.0.0-beta.22408.3 6.0.0-preview.1.102 diff --git a/eng/common/templates/job/source-index-stage1.yml b/eng/common/templates/job/source-index-stage1.yml index c2d51098d35cb1..4e37210857d154 100644 --- a/eng/common/templates/job/source-index-stage1.yml +++ b/eng/common/templates/job/source-index-stage1.yml @@ -1,6 +1,6 @@ parameters: runAsPublic: false - sourceIndexPackageVersion: 1.0.1-20210614.1 + sourceIndexPackageVersion: 1.0.1-20220804.1 sourceIndexPackageSource: https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json sourceIndexBuildCommand: powershell -NoLogo -NoProfile -ExecutionPolicy Bypass -Command "eng/common/build.ps1 -restore -build -binarylog -ci" preSteps: [] diff --git a/global.json b/global.json index 2e19eab0051450..81d425f8329cc2 100644 --- a/global.json +++ b/global.json @@ -8,9 +8,9 @@ "dotnet": "7.0.100-preview.5.22307.18" }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "7.0.0-beta.22403.1", - "Microsoft.DotNet.Helix.Sdk": "7.0.0-beta.22403.1", - "Microsoft.DotNet.SharedFramework.Sdk": "7.0.0-beta.22403.1", + "Microsoft.DotNet.Arcade.Sdk": "7.0.0-beta.22408.3", + "Microsoft.DotNet.Helix.Sdk": "7.0.0-beta.22408.3", + "Microsoft.DotNet.SharedFramework.Sdk": "7.0.0-beta.22408.3", "Microsoft.Build.NoTargets": "3.5.0", "Microsoft.Build.Traversal": "3.1.6", "Microsoft.NET.Sdk.IL": "7.0.0-rc.1.22374.4" From de03d8a8c84062e04c5c7ad574bd7bb7e58db9a9 Mon Sep 17 00:00:00 2001 From: Pavel Savara Date: Tue, 9 Aug 2022 18:54:49 +0200 Subject: [PATCH 20/41] [wasm] asset loading for workers (#73484) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - move asset related logic to separate file`assets.ts` - split logging code into separate file `logging.ts` - add `dotnet.wasm` and `dotnet-crypto-worker.js` to mono-config.json - moved `readSymbolMapFile()` call to `startup.ts` - fixed bug in `setup_proxy_console()` which didn't really copy the original `log` and `error` functions of the console and caused recursion on error. - moved `init_crypto` after `mono_wasm_load_config` in `mono_wasm_pre_init_essential_async` - added new resource type `js-module-crypto` and `js-module-threads` - changed the detection of blazor startup sequence to not be disabled by presence of config, but only by config.assets having some assembly in it. Co-authored-by: Marek Fišera --- src/mono/sample/wasm/Directory.Build.targets | 2 +- .../sample/wasm/browser-bench/frame-main.js | 4 +- src/mono/wasm/runtime/assets.ts | 415 ++++++++++++++ src/mono/wasm/runtime/crypto-worker.ts | 9 +- src/mono/wasm/runtime/debug.ts | 211 +------- src/mono/wasm/runtime/dotnet-legacy.d.ts | 2 +- src/mono/wasm/runtime/dotnet.d.ts | 6 +- src/mono/wasm/runtime/exports-internal.ts | 6 +- src/mono/wasm/runtime/exports-linker.ts | 3 +- src/mono/wasm/runtime/exports.ts | 2 +- src/mono/wasm/runtime/icu.ts | 3 +- src/mono/wasm/runtime/imports.ts | 1 + src/mono/wasm/runtime/invoke-js.ts | 2 +- src/mono/wasm/runtime/logging.ts | 210 ++++++++ .../wasm/runtime/net6-legacy/export-types.ts | 6 +- .../runtime/net6-legacy/exports-legacy.ts | 3 +- src/mono/wasm/runtime/polyfills.ts | 4 +- src/mono/wasm/runtime/startup.ts | 504 +++--------------- src/mono/wasm/runtime/types.ts | 5 +- src/mono/wasm/runtime/types/emscripten.ts | 5 +- .../runtime/workers/dotnet-crypto-worker.ts | 2 +- src/tasks/WasmAppBuilder/WasmAppBuilder.cs | 25 +- 22 files changed, 763 insertions(+), 667 deletions(-) create mode 100644 src/mono/wasm/runtime/assets.ts create mode 100644 src/mono/wasm/runtime/logging.ts diff --git a/src/mono/sample/wasm/Directory.Build.targets b/src/mono/sample/wasm/Directory.Build.targets index 6979b8536abaa5..be23863e311932 100644 --- a/src/mono/sample/wasm/Directory.Build.targets +++ b/src/mono/sample/wasm/Directory.Build.targets @@ -38,7 +38,7 @@ - + diff --git a/src/mono/sample/wasm/browser-bench/frame-main.js b/src/mono/sample/wasm/browser-bench/frame-main.js index bf767c9e7ebc5f..9045a9544988da 100644 --- a/src/mono/sample/wasm/browser-bench/frame-main.js +++ b/src/mono/sample/wasm/browser-bench/frame-main.js @@ -38,11 +38,11 @@ try { console.error(...arguments); } }, - onConfigLoaded: () => { + onConfigLoaded: (config) => { if (window.parent != window) { window.parent.resolveAppStartEvent("onConfigLoaded"); } - // Module.config.diagnosticTracing = true; + // config.diagnosticTracing = true; }, onAbort: (error) => { wasm_exit(1, error); diff --git a/src/mono/wasm/runtime/assets.ts b/src/mono/wasm/runtime/assets.ts new file mode 100644 index 00000000000000..362d6ba0f38a55 --- /dev/null +++ b/src/mono/wasm/runtime/assets.ts @@ -0,0 +1,415 @@ +import cwraps from "./cwraps"; +import { mono_wasm_load_icu_data } from "./icu"; +import { ENVIRONMENT_IS_WEB, Module, runtimeHelpers } from "./imports"; +import { mono_wasm_load_bytes_into_heap } from "./memory"; +import { MONO } from "./net6-legacy/imports"; +import { createPromiseController, PromiseAndController } from "./promise-controller"; +import { delay } from "./promise-utils"; +import { beforeOnRuntimeInitialized } from "./startup"; +import { AssetBehaviours, AssetEntry, LoadingResource, mono_assert, ResourceRequest } from "./types"; +import { InstantiateWasmSuccessCallback, VoidPtr } from "./types/emscripten"; + +const allAssetsInMemory = createPromiseController(); +const allDownloadsQueued = createPromiseController(); +let downloded_assets_count = 0; +let instantiated_assets_count = 0; +const loaded_files: { url: string, file: string }[] = []; +const loaded_assets: { [id: string]: [VoidPtr, number] } = Object.create(null); +// in order to prevent net::ERR_INSUFFICIENT_RESOURCES if we start downloading too many files at same time +let parallel_count = 0; +let throttling: PromiseAndController | undefined; +const skipDownloadsAssetTypes: { + [k: string]: boolean +} = { + "js-module-crypto": true, + "js-module-threads": true, +}; + +export function resolve_asset_path(behavior: AssetBehaviours) { + const asset: AssetEntry | undefined = runtimeHelpers.config.assets?.find(a => a.behavior == behavior); + mono_assert(asset, () => `Can't find asset for ${behavior}`); + if (!asset.resolvedUrl) { + asset.resolvedUrl = resolve_path(asset, ""); + } + return asset; +} + +export async function mono_download_assets(): Promise { + if (runtimeHelpers.diagnosticTracing) console.debug("MONO_WASM: mono_download_assets"); + runtimeHelpers.maxParallelDownloads = runtimeHelpers.config.maxParallelDownloads || runtimeHelpers.maxParallelDownloads; + try { + const download_promises: Promise[] = []; + // start fetching and instantiating all assets in parallel + for (const asset of runtimeHelpers.config.assets!) { + if (!asset.pending && !skipDownloadsAssetTypes[asset.behavior]) { + download_promises.push(start_asset_download(asset)); + } + } + allDownloadsQueued.promise_control.resolve(); + + const asset_promises: Promise[] = []; + for (const downloadPromise of download_promises) { + const downloadedAsset = await downloadPromise; + if (downloadedAsset) { + asset_promises.push((async () => { + const url = downloadedAsset.pending!.url; + const response = await downloadedAsset.pending!.response; + downloadedAsset.pending = undefined; //GC + const buffer = await response.arrayBuffer(); + await beforeOnRuntimeInitialized.promise; + // this is after onRuntimeInitialized + _instantiate_asset(downloadedAsset, url, new Uint8Array(buffer)); + })()); + } + } + + // this await will get past the onRuntimeInitialized because we are not blocking via addRunDependency + // and we are not awating it here + Promise.all(asset_promises).then(() => allAssetsInMemory.promise_control.resolve()); + // OPTIMIZATION explained: + // we do it this way so that we could allocate memory immediately after asset is downloaded (and after onRuntimeInitialized which happened already) + // spreading in time + // rather than to block all downloads after onRuntimeInitialized or block onRuntimeInitialized after all downloads are done. That would create allocation burst. + } catch (err: any) { + Module.printErr("MONO_WASM: Error in mono_download_assets: " + err); + throw err; + } +} + +export async function start_asset_download(asset: AssetEntry): Promise { + try { + return await start_asset_download_throttle(asset); + } catch (err: any) { + if (err && err.status == 404) { + throw err; + } + // second attempt only after all first attempts are queued + await allDownloadsQueued.promise; + try { + return await start_asset_download_throttle(asset); + } catch (err) { + // third attempt after small delay + await delay(100); + return await start_asset_download_throttle(asset); + } + } +} + +function resolve_path(asset: AssetEntry, sourcePrefix: string): string { + let attemptUrl; + const assemblyRootFolder = runtimeHelpers.config.assemblyRootFolder; + if (!asset.resolvedUrl) { + if (sourcePrefix === "") { + if (asset.behavior === "assembly" || asset.behavior === "pdb") + attemptUrl = assemblyRootFolder + "/" + asset.name; + else if (asset.behavior === "resource") { + const path = asset.culture !== "" ? `${asset.culture}/${asset.name}` : asset.name; + attemptUrl = assemblyRootFolder + "/" + path; + } + else { + attemptUrl = asset.name; + } + } else { + attemptUrl = sourcePrefix + asset.name; + } + attemptUrl = runtimeHelpers.locateFile(attemptUrl); + } + else { + attemptUrl = asset.resolvedUrl; + } + return attemptUrl; +} + +function download_resource(request: ResourceRequest): LoadingResource { + if (typeof Module.downloadResource === "function") { + const loading = Module.downloadResource(request); + if (loading) return loading; + } + const options: any = {}; + if (request.hash) { + options.integrity = request.hash; + } + const response = runtimeHelpers.fetch_like(request.resolvedUrl!, options); + return { + name: request.name, url: request.resolvedUrl!, response + }; +} + +async function start_asset_download_sources(asset: AssetEntry): Promise { + // we don't addRunDependency to allow download in parallel with onRuntimeInitialized event! + if (asset.buffer) { + ++downloded_assets_count; + const buffer = asset.buffer; + asset.buffer = undefined;//GC later + asset.pending = { + url: "undefined://" + asset.name, + name: asset.name, + response: Promise.resolve({ + arrayBuffer: () => buffer, + headers: { + get: () => undefined, + } + }) as any + }; + return Promise.resolve(asset); + } + if (asset.pending) { + ++downloded_assets_count; + return asset; + } + + const sourcesList = asset.loadRemote && runtimeHelpers.config.remoteSources ? runtimeHelpers.config.remoteSources : [""]; + let response: Response | undefined = undefined; + for (let sourcePrefix of sourcesList) { + sourcePrefix = sourcePrefix.trim(); + // HACK: Special-case because MSBuild doesn't allow "" as an attribute + if (sourcePrefix === "./") + sourcePrefix = ""; + + const attemptUrl = resolve_path(asset, sourcePrefix); + if (asset.name === attemptUrl) { + if (runtimeHelpers.diagnosticTracing) + console.debug(`MONO_WASM: Attempting to download '${attemptUrl}'`); + } else { + if (runtimeHelpers.diagnosticTracing) + console.debug(`MONO_WASM: Attempting to download '${attemptUrl}' for ${asset.name}`); + } + try { + const loadingResource = download_resource({ + name: asset.name, + resolvedUrl: attemptUrl, + hash: asset.hash, + behavior: asset.behavior + }); + asset.pending = loadingResource; + response = await loadingResource.response; + if (!response.ok) { + continue;// next source + } + ++downloded_assets_count; + return asset; + } + catch (err) { + continue; //next source + } + } + throw response; +} + +async function start_asset_download_throttle(asset: AssetEntry): Promise { + // we don't addRunDependency to allow download in parallel with onRuntimeInitialized event! + while (throttling) { + await throttling.promise; + } + try { + ++parallel_count; + if (parallel_count == runtimeHelpers.maxParallelDownloads) { + if (runtimeHelpers.diagnosticTracing) + console.debug("MONO_WASM: Throttling further parallel downloads"); + throttling = createPromiseController(); + } + return await start_asset_download_sources(asset); + } + catch (response: any) { + const isOkToFail = asset.isOptional || (asset.name.match(/\.pdb$/) && runtimeHelpers.config.ignorePdbLoadErrors); + if (!isOkToFail) { + const err: any = new Error(`MONO_WASM: download '${response.url}' for ${asset.name} failed ${response.status} ${response.statusText}`); + err.status = response.status; + throw err; + } + } + finally { + --parallel_count; + if (throttling && parallel_count == runtimeHelpers.maxParallelDownloads - 1) { + if (runtimeHelpers.diagnosticTracing) + console.debug("MONO_WASM: Resuming more parallel downloads"); + const old_throttling = throttling; + throttling = undefined; + old_throttling.promise_control.resolve(); + } + } +} + +// this need to be run only after onRuntimeInitialized event, when the memory is ready +function _instantiate_asset(asset: AssetEntry, url: string, bytes: Uint8Array) { + if (runtimeHelpers.diagnosticTracing) + console.debug(`MONO_WASM: Loaded:${asset.name} as ${asset.behavior} size ${bytes.length} from ${url}`); + + const virtualName: string = typeof (asset.virtualPath) === "string" + ? asset.virtualPath + : asset.name; + let offset: VoidPtr | null = null; + + switch (asset.behavior) { + case "dotnetwasm": + case "js-module-crypto": + case "js-module-threads": + // do nothing + break; + case "resource": + case "assembly": + case "pdb": + loaded_files.push({ url: url, file: virtualName }); + // falls through + case "heap": + case "icu": + offset = mono_wasm_load_bytes_into_heap(bytes); + loaded_assets[virtualName] = [offset, bytes.length]; + break; + + case "vfs": { + // FIXME + const lastSlash = virtualName.lastIndexOf("/"); + let parentDirectory = (lastSlash > 0) + ? virtualName.substr(0, lastSlash) + : null; + let fileName = (lastSlash > 0) + ? virtualName.substr(lastSlash + 1) + : virtualName; + if (fileName.startsWith("/")) + fileName = fileName.substr(1); + if (parentDirectory) { + if (runtimeHelpers.diagnosticTracing) + console.debug(`MONO_WASM: Creating directory '${parentDirectory}'`); + + Module.FS_createPath( + "/", parentDirectory, true, true // fixme: should canWrite be false? + ); + } else { + parentDirectory = "/"; + } + + if (runtimeHelpers.diagnosticTracing) + console.debug(`MONO_WASM: Creating file '${fileName}' in directory '${parentDirectory}'`); + + if (!mono_wasm_load_data_archive(bytes, parentDirectory)) { + Module.FS_createDataFile( + parentDirectory, fileName, + bytes, true /* canRead */, true /* canWrite */, true /* canOwn */ + ); + } + break; + } + default: + throw new Error(`Unrecognized asset behavior:${asset.behavior}, for asset ${asset.name}`); + } + + if (asset.behavior === "assembly") { + // this is reading flag inside the DLL about the existence of PDB + // it doesn't relate to whether the .pdb file is downloaded at all + const hasPpdb = cwraps.mono_wasm_add_assembly(virtualName, offset!, bytes.length); + + if (!hasPpdb) { + const index = loaded_files.findIndex(element => element.file == virtualName); + loaded_files.splice(index, 1); + } + } + else if (asset.behavior === "icu") { + if (!mono_wasm_load_icu_data(offset!)) + Module.printErr(`MONO_WASM: Error loading ICU asset ${asset.name}`); + } + else if (asset.behavior === "resource") { + cwraps.mono_wasm_add_satellite_assembly(virtualName, asset.culture!, offset!, bytes.length); + } + ++instantiated_assets_count; +} + +export async function instantiate_wasm_asset( + pendingAsset: AssetEntry, + wasmModuleImports: WebAssembly.Imports, + successCallback: InstantiateWasmSuccessCallback, +) { + mono_assert(pendingAsset && pendingAsset.pending, "Can't load dotnet.wasm"); + const response = await pendingAsset.pending.response; + const contentType = response.headers ? response.headers.get("Content-Type") : undefined; + let compiledInstance: WebAssembly.Instance; + let compiledModule: WebAssembly.Module; + if (typeof WebAssembly.instantiateStreaming === "function" && contentType === "application/wasm") { + if (runtimeHelpers.diagnosticTracing) console.debug("MONO_WASM: instantiate_wasm_module streaming"); + const streamingResult = await WebAssembly.instantiateStreaming(response, wasmModuleImports!); + compiledInstance = streamingResult.instance; + compiledModule = streamingResult.module; + } else { + if (ENVIRONMENT_IS_WEB && contentType !== "application/wasm") { + console.warn("MONO_WASM: WebAssembly resource does not have the expected content type \"application/wasm\", so falling back to slower ArrayBuffer instantiation."); + } + const arrayBuffer = await response.arrayBuffer(); + if (runtimeHelpers.diagnosticTracing) console.debug("MONO_WASM: instantiate_wasm_module buffered"); + const arrayBufferResult = await WebAssembly.instantiate(arrayBuffer, wasmModuleImports!); + compiledInstance = arrayBufferResult.instance; + compiledModule = arrayBufferResult.module; + } + ++instantiated_assets_count; + successCallback(compiledInstance, compiledModule); +} + +// used from Blazor +export function mono_wasm_load_data_archive(data: Uint8Array, prefix: string): boolean { + if (data.length < 8) + return false; + + const dataview = new DataView(data.buffer); + const magic = dataview.getUint32(0, true); + // get magic number + if (magic != 0x626c6174) { + return false; + } + const manifestSize = dataview.getUint32(4, true); + if (manifestSize == 0 || data.length < manifestSize + 8) + return false; + + let manifest; + try { + const manifestContent = Module.UTF8ArrayToString(data, 8, manifestSize); + manifest = JSON.parse(manifestContent); + if (!(manifest instanceof Array)) + return false; + } catch (exc) { + return false; + } + + data = data.slice(manifestSize + 8); + + // Create the folder structure + // /usr/share/zoneinfo + // /usr/share/zoneinfo/Africa + // /usr/share/zoneinfo/Asia + // .. + + const folders = new Set(); + manifest.filter(m => { + const file = m[0]; + const last = file.lastIndexOf("/"); + const directory = file.slice(0, last + 1); + folders.add(directory); + }); + folders.forEach(folder => { + Module["FS_createPath"](prefix, folder, true, true); + }); + + for (const row of manifest) { + const name = row[0]; + const length = row[1]; + const bytes = data.slice(0, length); + Module["FS_createDataFile"](prefix, name, bytes, true, true); + data = data.slice(length); + } + return true; +} + +export async function wait_for_all_assets() { + // wait for all assets in memory + await allAssetsInMemory.promise; + if (runtimeHelpers.config.assets) { + const expected_asset_count = runtimeHelpers.config.assets.filter(a => !skipDownloadsAssetTypes[a.behavior]).length; + mono_assert(downloded_assets_count == expected_asset_count, "Expected assets to be downloaded"); + mono_assert(instantiated_assets_count == expected_asset_count, "Expected assets to be in memory"); + loaded_files.forEach(value => MONO.loaded_files.push(value.url)); + if (runtimeHelpers.diagnosticTracing) console.debug("MONO_WASM: all assets are loaded in wasm memory"); + } +} + +// Used by the debugger to enumerate loaded dlls and pdbs +export function mono_wasm_get_loaded_files(): string[] { + return MONO.loaded_files; +} diff --git a/src/mono/wasm/runtime/crypto-worker.ts b/src/mono/wasm/runtime/crypto-worker.ts index adde81a9ec9bca..82fafbf3e6f050 100644 --- a/src/mono/wasm/runtime/crypto-worker.ts +++ b/src/mono/wasm/runtime/crypto-worker.ts @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +import { resolve_asset_path } from "./assets"; import { Module, runtimeHelpers } from "./imports"; import { mono_assert } from "./types"; @@ -107,22 +108,24 @@ export function init_crypto(): void { console.debug("MONO_WASM: Initializing Crypto WebWorker"); const chan = LibraryChannel.create(1024); // 1024 is the buffer size in char units. - const worker = new Worker("dotnet-crypto-worker.js"); + const asset = resolve_asset_path("js-module-crypto"); + mono_assert(asset && asset.resolvedUrl, "Can't find js-module-crypto"); + const worker = new Worker(asset.resolvedUrl); mono_wasm_crypto = { channel: chan, worker: worker, }; const messageData: InitCryptoMessageData = { - config: JSON.stringify(runtimeHelpers.config), + config: JSON.stringify(runtimeHelpers.config),// there could be things in config which could not be cloned to worker comm_buf: chan.get_comm_buffer(), msg_buf: chan.get_msg_buffer(), msg_char_len: chan.get_msg_len() }; - worker.postMessage(messageData); worker.onerror = event => { console.warn(`MONO_WASM: Error in Crypto WebWorker. Cryptography digest calls will fallback to managed implementation. Error: ${event.message}`); mono_wasm_crypto = null; }; + worker.postMessage(messageData); } } diff --git a/src/mono/wasm/runtime/debug.ts b/src/mono/wasm/runtime/debug.ts index 0367fc71f59620..43e9ffb83d3e11 100644 --- a/src/mono/wasm/runtime/debug.ts +++ b/src/mono/wasm/runtime/debug.ts @@ -1,14 +1,12 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import Configuration from "consts:configuration"; +import BuildConfiguration from "consts:configuration"; import { INTERNAL, Module, runtimeHelpers } from "./imports"; import { toBase64StringImpl } from "./base64"; import cwraps from "./cwraps"; import { VoidPtr, CharPtr } from "./types/emscripten"; -import { MONO } from "./net6-legacy/imports"; const commands_received: any = new Map(); -const wasm_func_map = new Map(); commands_received.remove = function (key: number): CommandResponse { const value = this.get(key); this.delete(key); return value; }; let _call_function_res_cache: any = {}; let _next_call_function_res_id = 0; @@ -17,23 +15,6 @@ let _debugger_buffer: VoidPtr; let _assembly_name_str: string; //keep this variable, it's used by BrowserDebugProxy let _entrypoint_method_token: number; //keep this variable, it's used by BrowserDebugProxy -const regexes: any[] = []; - -// V8 -// at :wasm-function[1900]:0x83f63 -// at dlfree (:wasm-function[18739]:0x2328ef) -regexes.push(/at (?[^:()]+:wasm-function\[(?\d+)\]:0x[a-fA-F\d]+)((?![^)a-fA-F\d])|$)/); - -//# 5: WASM [009712b2], function #111 (''), pc=0x7c16595c973 (+0x53), pos=38740 (+11) -regexes.push(/(?:WASM \[[\da-zA-Z]+\], (?function #(?[\d]+) \(''\)))/); - -//# chrome -//# at http://127.0.0.1:63817/dotnet.wasm:wasm-function[8963]:0x1e23f4 -regexes.push(/(?[a-z]+:\/\/[^ )]*:wasm-function\[(?\d+)\]:0x[a-fA-F\d]+)/); - -//# .wasm-function[8962] -regexes.push(/(?<[^ >]+>[.:]wasm-function\[(?[0-9]+)\])/); - export function mono_wasm_runtime_ready(): void { INTERNAL.mono_wasm_runtime_is_ready = runtimeHelpers.mono_wasm_runtime_is_ready = true; @@ -49,7 +30,6 @@ export function mono_wasm_runtime_ready(): void { else console.debug("mono_wasm_runtime_ready", "fe00e07a-5519-4dfe-b35a-f867dbaf2e28"); - _readSymbolMapFile("dotnet.js.symbols"); } export function mono_wasm_fire_debugger_agent_message(): void { @@ -143,11 +123,6 @@ export function mono_wasm_raise_debug_event(event: WasmEvent, args = {}): void { console.debug("mono_wasm_debug_event_raised:aef14bca-5519-4dfe-b35a-f867abc123ae", JSON.stringify(event), JSON.stringify(args)); } -// Used by the debugger to enumerate loaded dlls and pdbs -export function mono_wasm_get_loaded_files(): string[] { - return MONO.loaded_files; -} - export function mono_wasm_wait_for_debugger(): Promise { return new Promise((resolve) => { const interval = setInterval(() => { @@ -365,193 +340,11 @@ export function mono_wasm_debugger_log(level: number, message_ptr: CharPtr): voi return; } - if (Configuration === "Debug") { + if (BuildConfiguration === "Debug") { console.debug(`MONO_WASM: Debugger.Debug: ${message}`); } } -function _readSymbolMapFile(filename: string): void { - try { - const res = Module.FS_readFile(filename, { flags: "r", encoding: "utf8" }); - res.split(/[\r\n]/).forEach((line: string) => { - const parts: string[] = line.split(/:/); - if (parts.length < 2) - return; - - parts[1] = parts.splice(1).join(":"); - wasm_func_map.set(Number(parts[0]), parts[1]); - }); - if (Configuration === "Debug") { - console.debug(`MONO_WASM: Loaded ${wasm_func_map.size} symbols`); - } - } catch (error: any) { - if (error.errno == 44) {// NOENT - if (Configuration === "Debug") { - console.debug(`MONO_WASM: Could not find symbols file ${filename}. Ignoring.`); - } - } - else { - console.log(`MONO_WASM: Error loading symbol file ${filename}: ${JSON.stringify(error)}`); - } - return; - } -} - -export function mono_wasm_symbolicate_string(message: string): string { - try { - if (wasm_func_map.size == 0) - return message; - - const origMessage = message; - - for (let i = 0; i < regexes.length; i++) { - const newRaw = message.replace(new RegExp(regexes[i], "g"), (substring, ...args) => { - const groups = args.find(arg => { - return typeof (arg) == "object" && arg.replaceSection !== undefined; - }); - - if (groups === undefined) - return substring; - - const funcNum = groups.funcNum; - const replaceSection = groups.replaceSection; - const name = wasm_func_map.get(Number(funcNum)); - - if (name === undefined) - return substring; - - return substring.replace(replaceSection, `${name} (${replaceSection})`); - }); - - if (newRaw !== origMessage) - return newRaw; - } - - return origMessage; - } catch (error) { - console.debug(`MONO_WASM: failed to symbolicate: ${error}`); - return message; - } -} - -export function mono_wasm_stringify_as_error_with_stack(err: Error | string): string { - let errObj: any = err; - if (!(err instanceof Error)) - errObj = new Error(err); - - // Error - return mono_wasm_symbolicate_string(errObj.stack); -} - -export function mono_wasm_trace_logger(log_domain_ptr: CharPtr, log_level_ptr: CharPtr, message_ptr: CharPtr, fatal: number, user_data: VoidPtr): void { - const origMessage = Module.UTF8ToString(message_ptr); - const isFatal = !!fatal; - const domain = Module.UTF8ToString(log_domain_ptr); - const dataPtr = user_data; - const log_level = Module.UTF8ToString(log_level_ptr); - - const message = `[MONO] ${origMessage}`; - - if (INTERNAL["logging"] && typeof INTERNAL.logging["trace"] === "function") { - INTERNAL.logging.trace(domain, log_level, message, isFatal, dataPtr); - return; - } - - switch (log_level) { - case "critical": - case "error": - console.error(mono_wasm_stringify_as_error_with_stack(message)); - break; - case "warning": - console.warn(message); - break; - case "message": - console.log(message); - break; - case "info": - console.info(message); - break; - case "debug": - console.debug(message); - break; - default: - console.log(message); - break; - } -} - -export function setup_proxy_console(id: string, console: any, origin: string): void { - function proxyConsoleMethod(prefix: string, func: any, asJson: boolean) { - return function (...args: any[]) { - try { - let payload = args[0]; - if (payload === undefined) payload = "undefined"; - else if (payload === null) payload = "null"; - else if (typeof payload === "function") payload = payload.toString(); - else if (typeof payload !== "string") { - try { - payload = JSON.stringify(payload); - } catch (e) { - payload = payload.toString(); - } - } - - if (typeof payload === "string") - payload = `[${id}] ${payload}`; - - if (asJson) { - func(JSON.stringify({ - method: prefix, - payload: payload, - arguments: args - })); - } else { - func([prefix + payload, ...args.slice(1)]); - } - } catch (err) { - originalConsole.error(`proxyConsole failed: ${err}`); - } - }; - } - - const originalConsole = { - log: console.log, - error: console.error - }; - const methods = ["debug", "trace", "warn", "info", "error"]; - for (const m of methods) { - if (typeof (console[m]) !== "function") { - console[m] = proxyConsoleMethod(`console.${m}: `, originalConsole.log, false); - } - } - - const consoleUrl = `${origin}/console`.replace("https://", "wss://").replace("http://", "ws://"); - - const consoleWebSocket = new WebSocket(consoleUrl); - consoleWebSocket.onopen = function () { - originalConsole.log(`browser: [${id}] Console websocket connected.`); - }; - consoleWebSocket.onerror = function (event) { - originalConsole.error(`[${id}] websocket error: ${event}`, event); - }; - consoleWebSocket.onclose = function (event) { - originalConsole.error(`[${id}] websocket closed: ${event}`, event); - }; - - const send = (msg: string) => { - if (consoleWebSocket.readyState === WebSocket.OPEN) { - consoleWebSocket.send(msg); - } - else { - originalConsole.log(msg); - } - }; - - // redirect output early, so that when emscripten starts it's already redirected - for (const m of ["log", ...methods]) - console[m] = proxyConsoleMethod(`console.${m}`, send, true); -} - type CallDetails = { value: string } diff --git a/src/mono/wasm/runtime/dotnet-legacy.d.ts b/src/mono/wasm/runtime/dotnet-legacy.d.ts index a3b7591887d3ac..6bfdd64e14bcc7 100644 --- a/src/mono/wasm/runtime/dotnet-legacy.d.ts +++ b/src/mono/wasm/runtime/dotnet-legacy.d.ts @@ -304,4 +304,4 @@ declare type MONOType = { getF64: (offset: MemOffset) => number; }; -export { BINDINGType, MONOType }; +export { BINDINGType, MONOType, MonoArray, MonoObject, MonoString }; diff --git a/src/mono/wasm/runtime/dotnet.d.ts b/src/mono/wasm/runtime/dotnet.d.ts index 131f2bb4da5442..eacddc7a017bbe 100644 --- a/src/mono/wasm/runtime/dotnet.d.ts +++ b/src/mono/wasm/runtime/dotnet.d.ts @@ -47,7 +47,7 @@ declare interface EmscriptenModule { stackRestore(stack: VoidPtr): void; stackAlloc(size: number): VoidPtr; ready: Promise; - instantiateWasm?: (imports: WebAssembly.Imports, successCallback: (instance: WebAssembly.Instance, module: WebAssembly.Module) => void) => any; + instantiateWasm?: InstantiateWasmCallBack; preInit?: (() => any)[] | (() => any); preRun?: (() => any)[] | (() => any); onRuntimeInitialized?: () => any; @@ -56,6 +56,8 @@ declare interface EmscriptenModule { (error: any): void; }; } +declare type InstantiateWasmSuccessCallback = (instance: WebAssembly.Instance, module: WebAssembly.Module) => void; +declare type InstantiateWasmCallBack = (imports: WebAssembly.Imports, successCallback: InstantiateWasmSuccessCallback) => any; declare type TypedArray = Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array; declare type MonoConfig = { @@ -94,7 +96,7 @@ interface AssetEntry extends ResourceRequest { buffer?: ArrayBuffer; pending?: LoadingResource; } -declare type AssetBehaviours = "resource" | "assembly" | "pdb" | "heap" | "icu" | "vfs" | "dotnetwasm"; +declare type AssetBehaviours = "resource" | "assembly" | "pdb" | "heap" | "icu" | "vfs" | "dotnetwasm" | "js-module-crypto" | "js-module-threads"; declare type GlobalizationMode = "icu" | // load ICU globalization data from any runtime assets with behavior "icu". "invariant" | // operate in invariant globalization mode. "auto"; diff --git a/src/mono/wasm/runtime/exports-internal.ts b/src/mono/wasm/runtime/exports-internal.ts index a5c9219b2933e3..b2b8c4b958332c 100644 --- a/src/mono/wasm/runtime/exports-internal.ts +++ b/src/mono/wasm/runtime/exports-internal.ts @@ -1,6 +1,6 @@ import { mono_wasm_cancel_promise } from "./cancelable-promise"; import cwraps from "./cwraps"; -import { mono_wasm_symbolicate_string, mono_wasm_stringify_as_error_with_stack, mono_wasm_get_loaded_files, mono_wasm_send_dbg_command_with_parms, mono_wasm_send_dbg_command, mono_wasm_get_dbg_command_info, mono_wasm_get_details, mono_wasm_release_object, mono_wasm_call_function_on, mono_wasm_debugger_resume, mono_wasm_detach_debugger, mono_wasm_raise_debug_event, mono_wasm_change_debugger_log_level, mono_wasm_debugger_attached } from "./debug"; +import { mono_wasm_send_dbg_command_with_parms, mono_wasm_send_dbg_command, mono_wasm_get_dbg_command_info, mono_wasm_get_details, mono_wasm_release_object, mono_wasm_call_function_on, mono_wasm_debugger_resume, mono_wasm_detach_debugger, mono_wasm_raise_debug_event, mono_wasm_change_debugger_log_level, mono_wasm_debugger_attached } from "./debug"; import { get_dotnet_instance } from "./exports"; import { http_wasm_supports_streaming_response, http_wasm_create_abort_controler, http_wasm_abort_request, http_wasm_abort_response, http_wasm_fetch, http_wasm_fetch_bytes, http_wasm_get_response_header_names, http_wasm_get_response_header_values, http_wasm_get_response_bytes, http_wasm_get_response_length, http_wasm_get_streamed_response_bytes } from "./http"; import { Module, runtimeHelpers } from "./imports"; @@ -8,7 +8,9 @@ import { get_property, set_property, has_property, get_typeof_property, get_glob import { mono_method_resolve } from "./net6-legacy/method-binding"; import { mono_wasm_set_runtime_options } from "./startup"; import { mono_intern_string } from "./strings"; +import { mono_wasm_stringify_as_error_with_stack } from "./logging"; import { ws_wasm_create, ws_wasm_open, ws_wasm_send, ws_wasm_receive, ws_wasm_close, ws_wasm_abort } from "./web-socket"; +import { mono_wasm_get_loaded_files } from "./assets"; export function export_internal(): any { return { @@ -24,8 +26,6 @@ export function export_internal(): any { // with mono_wasm_debugger_log and mono_wasm_trace_logger logging: undefined, - // - mono_wasm_symbolicate_string, mono_wasm_stringify_as_error_with_stack, // used in debugger DevToolsHelper.cs diff --git a/src/mono/wasm/runtime/exports-linker.ts b/src/mono/wasm/runtime/exports-linker.ts index a8d2c08453d75d..6a97b55421ef73 100644 --- a/src/mono/wasm/runtime/exports-linker.ts +++ b/src/mono/wasm/runtime/exports-linker.ts @@ -1,6 +1,6 @@ import MonoWasmThreads from "consts:monoWasmThreads"; import { dotnet_browser_can_use_subtle_crypto_impl, dotnet_browser_simple_digest_hash, dotnet_browser_sign, dotnet_browser_encrypt_decrypt, dotnet_browser_derive_bits } from "./crypto-worker"; -import { mono_wasm_fire_debugger_agent_message, mono_wasm_debugger_log, mono_wasm_add_dbg_command_received, mono_wasm_trace_logger, mono_wasm_set_entrypoint_breakpoint } from "./debug"; +import { mono_wasm_fire_debugger_agent_message, mono_wasm_debugger_log, mono_wasm_add_dbg_command_received, mono_wasm_set_entrypoint_breakpoint } from "./debug"; import { mono_wasm_release_cs_owned_object } from "./gc-handles"; import { mono_wasm_load_icu_data, mono_wasm_get_icudt_name } from "./icu"; import { mono_wasm_bind_cs_function } from "./invoke-cs"; @@ -19,6 +19,7 @@ import { mono_wasm_diagnostic_server_on_runtime_server_init, mono_wasm_event_pip import { mono_wasm_diagnostic_server_stream_signal_work_available } from "./diagnostics/server_pthread/stream-queue"; import { mono_wasm_create_cs_owned_object_ref } from "./net6-legacy/cs-to-js"; import { mono_wasm_typed_array_to_array_ref } from "./net6-legacy/js-to-cs"; +import { mono_wasm_trace_logger } from "./logging"; // the methods would be visible to EMCC linker // --- keep in sync with dotnet.cjs.lib.js --- diff --git a/src/mono/wasm/runtime/exports.ts b/src/mono/wasm/runtime/exports.ts index 46eede120336b0..ea46886cfe674d 100644 --- a/src/mono/wasm/runtime/exports.ts +++ b/src/mono/wasm/runtime/exports.ts @@ -2,8 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. import ProductVersion from "consts:productVersion"; -import BuildConfiguration from "consts:configuration"; import MonoWasmThreads from "consts:monoWasmThreads"; +import BuildConfiguration from "consts:configuration"; import { ENVIRONMENT_IS_PTHREAD, set_imports_exports } from "./imports"; import { DotnetModule, is_nullish, EarlyImports, EarlyExports, EarlyReplacements, MonoConfig } from "./types"; diff --git a/src/mono/wasm/runtime/icu.ts b/src/mono/wasm/runtime/icu.ts index 93ea54d9d9fab0..3e6e7105335c48 100644 --- a/src/mono/wasm/runtime/icu.ts +++ b/src/mono/wasm/runtime/icu.ts @@ -3,7 +3,6 @@ import cwraps from "./cwraps"; import { Module, runtimeHelpers } from "./imports"; -import { MonoConfig } from "./types"; import { VoidPtr } from "./types/emscripten"; let num_icu_assets_loaded_successfully = 0; @@ -30,7 +29,7 @@ export function mono_wasm_get_icudt_name(culture: string): string { // "auto" will use "icu" if any ICU data archives have been loaded, // otherwise "invariant". export function mono_wasm_globalization_init(): void { - const config = Module.config as MonoConfig; + const config = runtimeHelpers.config; let invariantMode = false; if (!config.globalizationMode) config.globalizationMode = "auto"; diff --git a/src/mono/wasm/runtime/imports.ts b/src/mono/wasm/runtime/imports.ts index e12bc3857f6a2f..2e4a619238fd7b 100644 --- a/src/mono/wasm/runtime/imports.ts +++ b/src/mono/wasm/runtime/imports.ts @@ -3,6 +3,7 @@ /* eslint-disable @typescript-eslint/triple-slash-reference */ /// +/// import { DotnetModule, EarlyExports, EarlyImports, RuntimeHelpers } from "./types"; import { EmscriptenModule } from "./types/emscripten"; diff --git a/src/mono/wasm/runtime/invoke-js.ts b/src/mono/wasm/runtime/invoke-js.ts index f6a65f43f1d3f8..e078ef8e52ea7f 100644 --- a/src/mono/wasm/runtime/invoke-js.ts +++ b/src/mono/wasm/runtime/invoke-js.ts @@ -11,7 +11,7 @@ import { Int32Ptr } from "./types/emscripten"; import { IMPORTS, INTERNAL, Module, runtimeHelpers } from "./imports"; import { generate_arg_marshal_to_js } from "./marshal-to-js"; import { mono_wasm_new_external_root } from "./roots"; -import { mono_wasm_symbolicate_string } from "./debug"; +import { mono_wasm_symbolicate_string } from "./logging"; export function mono_wasm_bind_js_function(function_name: MonoStringRef, module_name: MonoStringRef, signature: JSFunctionSignature, function_js_handle: Int32Ptr, is_exception: Int32Ptr, result_address: MonoObjectRef): void { const function_name_root = mono_wasm_new_external_root(function_name), diff --git a/src/mono/wasm/runtime/logging.ts b/src/mono/wasm/runtime/logging.ts new file mode 100644 index 00000000000000..34de60da7929d2 --- /dev/null +++ b/src/mono/wasm/runtime/logging.ts @@ -0,0 +1,210 @@ +//! Licensed to the .NET Foundation under one or more agreements. +//! The .NET Foundation licenses this file to you under the MIT license. + +import BuildConfiguration from "consts:configuration"; +import { INTERNAL, Module, runtimeHelpers } from "./imports"; +import { CharPtr, VoidPtr } from "./types/emscripten"; + +const wasm_func_map = new Map(); +const regexes: any[] = []; + +// V8 +// at :wasm-function[1900]:0x83f63 +// at dlfree (:wasm-function[18739]:0x2328ef) +regexes.push(/at (?[^:()]+:wasm-function\[(?\d+)\]:0x[a-fA-F\d]+)((?![^)a-fA-F\d])|$)/); + +//# 5: WASM [009712b2], function #111 (''), pc=0x7c16595c973 (+0x53), pos=38740 (+11) +regexes.push(/(?:WASM \[[\da-zA-Z]+\], (?function #(?[\d]+) \(''\)))/); + +//# chrome +//# at http://127.0.0.1:63817/dotnet.wasm:wasm-function[8963]:0x1e23f4 +regexes.push(/(?[a-z]+:\/\/[^ )]*:wasm-function\[(?\d+)\]:0x[a-fA-F\d]+)/); + +//# .wasm-function[8962] +regexes.push(/(?<[^ >]+>[.:]wasm-function\[(?[0-9]+)\])/); + +export function mono_wasm_symbolicate_string(message: string): string { + try { + if (wasm_func_map.size == 0) + return message; + + const origMessage = message; + + for (let i = 0; i < regexes.length; i++) { + const newRaw = message.replace(new RegExp(regexes[i], "g"), (substring, ...args) => { + const groups = args.find(arg => { + return typeof (arg) == "object" && arg.replaceSection !== undefined; + }); + + if (groups === undefined) + return substring; + + const funcNum = groups.funcNum; + const replaceSection = groups.replaceSection; + const name = wasm_func_map.get(Number(funcNum)); + + if (name === undefined) + return substring; + + return substring.replace(replaceSection, `${name} (${replaceSection})`); + }); + + if (newRaw !== origMessage) + return newRaw; + } + + return origMessage; + } catch (error) { + console.debug(`MONO_WASM: failed to symbolicate: ${error}`); + return message; + } +} + +export function mono_wasm_stringify_as_error_with_stack(err: Error | string): string { + let errObj: any = err; + if (!(err instanceof Error)) + errObj = new Error(err); + + // Error + return mono_wasm_symbolicate_string(errObj.stack); +} + +export function mono_wasm_trace_logger(log_domain_ptr: CharPtr, log_level_ptr: CharPtr, message_ptr: CharPtr, fatal: number, user_data: VoidPtr): void { + const origMessage = Module.UTF8ToString(message_ptr); + const isFatal = !!fatal; + const domain = Module.UTF8ToString(log_domain_ptr); + const dataPtr = user_data; + const log_level = Module.UTF8ToString(log_level_ptr); + + const message = `[MONO] ${origMessage}`; + + if (INTERNAL["logging"] && typeof INTERNAL.logging["trace"] === "function") { + INTERNAL.logging.trace(domain, log_level, message, isFatal, dataPtr); + return; + } + + switch (log_level) { + case "critical": + case "error": + console.error(mono_wasm_stringify_as_error_with_stack(message)); + break; + case "warning": + console.warn(message); + break; + case "message": + console.log(message); + break; + case "info": + console.info(message); + break; + case "debug": + console.debug(message); + break; + default: + console.log(message); + break; + } +} + +export function setup_proxy_console(id: string, console: Console, origin: string): void { + // this need to be copy, in order to keep reference to original methods + const originalConsole = { + log: console.log, + error: console.error + }; + const anyConsole = console as any; + + function proxyConsoleMethod(prefix: string, func: any, asJson: boolean) { + return function (...args: any[]) { + try { + let payload = args[0]; + if (payload === undefined) payload = "undefined"; + else if (payload === null) payload = "null"; + else if (typeof payload === "function") payload = payload.toString(); + else if (typeof payload !== "string") { + try { + payload = JSON.stringify(payload); + } catch (e) { + payload = payload.toString(); + } + } + + if (typeof payload === "string") + payload = `[${id}] ${payload}`; + + if (asJson) { + func(JSON.stringify({ + method: prefix, + payload: payload, + arguments: args + })); + } else { + func([prefix + payload, ...args.slice(1)]); + } + } catch (err) { + originalConsole.error(`proxyConsole failed: ${err}`); + } + }; + } + + const methods = ["debug", "trace", "warn", "info", "error"]; + for (const m of methods) { + if (typeof (anyConsole[m]) !== "function") { + anyConsole[m] = proxyConsoleMethod(`console.${m}: `, console.log, false); + } + } + + const consoleUrl = `${origin}/console`.replace("https://", "wss://").replace("http://", "ws://"); + + const consoleWebSocket = new WebSocket(consoleUrl); + consoleWebSocket.addEventListener("open", () => { + originalConsole.log(`browser: [${id}] Console websocket connected.`); + }); + consoleWebSocket.addEventListener("error", (event) => { + originalConsole.error(`[${id}] websocket error: ${event}`, event); + }); + consoleWebSocket.addEventListener("close", (event) => { + originalConsole.error(`[${id}] websocket closed: ${event}`, event); + }); + + const send = (msg: string) => { + if (consoleWebSocket.readyState === WebSocket.OPEN) { + consoleWebSocket.send(msg); + } + else { + originalConsole.log(msg); + } + }; + + for (const m of ["log", ...methods]) + anyConsole[m] = proxyConsoleMethod(`console.${m}`, send, true); +} + +export function readSymbolMapFile(filename: string): void { + if (runtimeHelpers.mono_wasm_symbols_are_ready) return; + runtimeHelpers.mono_wasm_symbols_are_ready = true; + try { + const res = Module.FS_readFile(filename, { flags: "r", encoding: "utf8" }); + res.split(/[\r\n]/).forEach((line: string) => { + const parts: string[] = line.split(/:/); + if (parts.length < 2) + return; + + parts[1] = parts.splice(1).join(":"); + wasm_func_map.set(Number(parts[0]), parts[1]); + }); + if (BuildConfiguration === "Debug") { + console.debug(`MONO_WASM: Loaded ${wasm_func_map.size} symbols`); + } + } catch (error: any) { + if (error.errno == 44) {// NOENT + if (BuildConfiguration === "Debug") { + console.debug(`MONO_WASM: Could not find symbols file ${filename}. Ignoring.`); + } + } + else { + console.log(`MONO_WASM: Error loading symbol file ${filename}: ${JSON.stringify(error)}`); + } + return; + } +} diff --git a/src/mono/wasm/runtime/net6-legacy/export-types.ts b/src/mono/wasm/runtime/net6-legacy/export-types.ts index 22291a91491802..84c8171cbb67d1 100644 --- a/src/mono/wasm/runtime/net6-legacy/export-types.ts +++ b/src/mono/wasm/runtime/net6-legacy/export-types.ts @@ -1,4 +1,4 @@ -import { MonoArray, MonoObject, MonoObjectRef, MonoString, WasmRoot, WasmRootBuffer, MemOffset, NumberOrPointer } from "../types"; +import { MemOffset, MonoArray, MonoObject, MonoObjectRef, MonoString, NumberOrPointer, WasmRoot, WasmRootBuffer } from "../types"; import { VoidPtr } from "../types/emscripten"; /** @@ -247,4 +247,6 @@ export type MONOType = { * @deprecated Please use getHeapF64 */ getF64: (offset: MemOffset) => number; -}; \ No newline at end of file +}; + +export { MonoArray, MonoObject, MonoString }; \ No newline at end of file diff --git a/src/mono/wasm/runtime/net6-legacy/exports-legacy.ts b/src/mono/wasm/runtime/net6-legacy/exports-legacy.ts index 4202cf7ebc3406..7ead0a1000dd17 100644 --- a/src/mono/wasm/runtime/net6-legacy/exports-legacy.ts +++ b/src/mono/wasm/runtime/net6-legacy/exports-legacy.ts @@ -5,7 +5,7 @@ import { runtimeHelpers } from "../imports"; import { mono_wasm_load_bytes_into_heap, setB32, setI8, setI16, setI32, setI52, setU52, setI64Big, setU8, setU16, setU32, setF32, setF64, getB32, getI8, getI16, getI32, getI52, getU52, getI64Big, getU8, getU16, getU32, getF32, getF64 } from "../memory"; import { mono_wasm_new_root_buffer, mono_wasm_new_root, mono_wasm_new_external_root, mono_wasm_release_roots } from "../roots"; import { mono_run_main, mono_run_main_and_exit } from "../run"; -import { mono_wasm_setenv, mono_wasm_load_data_archive, mono_wasm_load_config, mono_load_runtime_and_bcl_args } from "../startup"; +import { mono_wasm_setenv, mono_wasm_load_config, mono_load_runtime_and_bcl_args } from "../startup"; import { js_string_to_mono_string, conv_string, js_string_to_mono_string_root, conv_string_root } from "../strings"; import { MonoConfig, MonoConfigError } from "../types"; import { mono_array_to_js_array, unbox_mono_obj, unbox_mono_obj_root, mono_array_root_to_js_array } from "./cs-to-js"; @@ -13,6 +13,7 @@ import { js_typed_array_to_array, js_to_mono_obj, js_typed_array_to_array_root, import { mono_bind_static_method, mono_call_assembly_entry_point } from "./method-calls"; import { mono_wasm_load_runtime } from "../startup"; import { BINDINGType, MONOType } from "./export-types"; +import { mono_wasm_load_data_archive } from "../assets"; export function export_mono_api(): MONOType { return { diff --git a/src/mono/wasm/runtime/polyfills.ts b/src/mono/wasm/runtime/polyfills.ts index 1e56ac4e813a5f..afd92c71074896 100644 --- a/src/mono/wasm/runtime/polyfills.ts +++ b/src/mono/wasm/runtime/polyfills.ts @@ -1,4 +1,4 @@ -import Configuration from "consts:configuration"; +import BuildConfiguration from "consts:configuration"; import MonoWasmThreads from "consts:monoWasmThreads"; import { ENVIRONMENT_IS_NODE, ENVIRONMENT_IS_SHELL, ENVIRONMENT_IS_WEB, ENVIRONMENT_IS_WORKER, INTERNAL, Module, runtimeHelpers } from "./imports"; import { afterUpdateGlobalBufferAndViews } from "./memory"; @@ -144,7 +144,7 @@ export function init_polyfills(replacements: EarlyReplacements): void { // script location runtimeHelpers.scriptDirectory = replacements.scriptDirectory = detectScriptDirectory(replacements); anyModule.mainScriptUrlOrBlob = replacements.scriptUrl;// this is needed by worker threads - if (Configuration === "Debug") { + if (BuildConfiguration === "Debug") { console.debug(`MONO_WASM: starting script ${replacements.scriptUrl}`); console.debug(`MONO_WASM: starting in ${runtimeHelpers.scriptDirectory}`); } diff --git a/src/mono/wasm/runtime/startup.ts b/src/mono/wasm/runtime/startup.ts index c26bdf352a0bb3..4e35254d7e456b 100644 --- a/src/mono/wasm/runtime/startup.ts +++ b/src/mono/wasm/runtime/startup.ts @@ -1,49 +1,45 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +import BuildConfiguration from "consts:configuration"; import MonoWasmThreads from "consts:monoWasmThreads"; -import { mono_assert, CharPtrNull, DotnetModule, MonoConfig, MonoConfigError, LoadingResource, AssetEntry, ResourceRequest } from "./types"; +import { CharPtrNull, DotnetModule, MonoConfig, MonoConfigError } from "./types"; import { ENVIRONMENT_IS_NODE, ENVIRONMENT_IS_SHELL, INTERNAL, Module, runtimeHelpers } from "./imports"; import cwraps, { init_c_exports } from "./cwraps"; import { mono_wasm_raise_debug_event, mono_wasm_runtime_ready } from "./debug"; -import { mono_wasm_globalization_init, mono_wasm_load_icu_data } from "./icu"; +import { mono_wasm_globalization_init } from "./icu"; import { toBase64StringImpl } from "./base64"; import { mono_wasm_init_aot_profiler, mono_wasm_init_coverage_profiler } from "./profiler"; -import { VoidPtr, CharPtr } from "./types/emscripten"; import { mono_on_abort, set_exit_code } from "./run"; import { initialize_marshalers_to_cs } from "./marshal-to-cs"; import { initialize_marshalers_to_js } from "./marshal-to-js"; import { init_crypto } from "./crypto-worker"; import { init_polyfills_async } from "./polyfills"; import * as pthreads_worker from "./pthreads/worker"; -import { createPromiseController, PromiseAndController } from "./promise-controller"; +import { createPromiseController } from "./promise-controller"; import { string_decoder } from "./strings"; -import { delay } from "./promise-utils"; import { init_managed_exports } from "./managed-exports"; import { init_legacy_exports } from "./net6-legacy/corebindings"; -import { mono_wasm_load_bytes_into_heap } from "./memory"; import { cwraps_internal } from "./exports-internal"; import { cwraps_binding_api, cwraps_mono_api } from "./net6-legacy/exports-legacy"; -import { DotnetPublicAPI } from "./export-types"; +import { DotnetPublicAPI } from "./exports"; +import { CharPtr, InstantiateWasmCallBack, InstantiateWasmSuccessCallback } from "./types/emscripten"; +import { instantiate_wasm_asset, mono_download_assets, resolve_asset_path, start_asset_download, wait_for_all_assets } from "./assets"; import { BINDING, MONO } from "./net6-legacy/imports"; +import { readSymbolMapFile } from "./logging"; import { mono_wasm_init_diagnostics } from "./diagnostics"; -let all_assets_loaded_in_memory: Promise | null = null; -const loaded_files: { url: string, file: string }[] = []; -const loaded_assets: { [id: string]: [VoidPtr, number] } = Object.create(null); -let instantiated_assets_count = 0; -let downloded_assets_count = 0; -// in order to prevent net::ERR_INSUFFICIENT_RESOURCES if we start downloading too many files at same time -let parallel_count = 0; let config: MonoConfig = undefined as any; - -const afterInstantiateWasm = createPromiseController(); -const beforePreInit = createPromiseController(); -const afterPreInit = createPromiseController(); -const afterPreRun = createPromiseController(); -const beforeOnRuntimeInitialized = createPromiseController(); -const afterOnRuntimeInitialized = createPromiseController(); -const afterPostRun = createPromiseController(); +let configLoaded = false; +let isCustomStartup = false; +export const afterConfigLoaded = createPromiseController(); +export const afterInstantiateWasm = createPromiseController(); +export const beforePreInit = createPromiseController(); +export const afterPreInit = createPromiseController(); +export const afterPreRun = createPromiseController(); +export const beforeOnRuntimeInitialized = createPromiseController(); +export const afterOnRuntimeInitialized = createPromiseController(); +export const afterPostRun = createPromiseController(); // we are making emscripten startup async friendly // emscripten is executing the events without awaiting it and so we need to block progress via PromiseControllers above @@ -56,17 +52,18 @@ export function configure_emscripten_startup(module: DotnetModule, exportedAPI: const userpostRun: (() => void)[] = !module.postRun ? [] : typeof module.postRun === "function" ? [module.postRun] : module.postRun as any; // eslint-disable-next-line @typescript-eslint/no-empty-function const userOnRuntimeInitialized: () => void = module.onRuntimeInitialized ? module.onRuntimeInitialized : () => { }; - const isCustomStartup = !module.configSrc && !module.config; // like blazor + // when assets don't contain DLLs it means this is Blazor or another custom startup + isCustomStartup = !module.configSrc && (!module.config || !module.config.assets || module.config.assets.findIndex(a => a.behavior === "assembly") != -1); // like blazor // execution order == [0] == // - default or user Module.instantiateWasm (will start downloading dotnet.wasm) module.instantiateWasm = (imports, callback) => instantiateWasm(imports, callback, userInstantiateWasm); // execution order == [1] == - module.preInit = [() => preInit(isCustomStartup, userPreInit)]; + module.preInit = [() => preInit(userPreInit)]; // execution order == [2] == module.preRun = [() => preRunAsync(userPreRun)]; // execution order == [4] == - module.onRuntimeInitialized = () => onRuntimeInitializedAsync(isCustomStartup, userOnRuntimeInitialized); + module.onRuntimeInitialized = () => onRuntimeInitializedAsync(userOnRuntimeInitialized); // execution order == [5] == module.postRun = [() => postRunAsync(userpostRun)]; // execution order == [6] == @@ -83,13 +80,11 @@ export function configure_emscripten_startup(module: DotnetModule, exportedAPI: } } -let wasm_module_imports: WebAssembly.Imports | null = null; -let wasm_success_callback: null | ((instance: WebAssembly.Instance, module: WebAssembly.Module) => void) = null; function instantiateWasm( imports: WebAssembly.Imports, - successCallback: (instance: WebAssembly.Instance, module: WebAssembly.Module) => void, - userInstantiateWasm?: (imports: WebAssembly.Imports, successCallback: (instance: WebAssembly.Instance, module: WebAssembly.Module) => void) => any): any[] { + successCallback: InstantiateWasmSuccessCallback, + userInstantiateWasm?: InstantiateWasmCallBack): any[] { // this is called so early that even Module exports like addRunDependency don't exist yet if (!Module.configSrc && !Module.config && !userInstantiateWasm) { @@ -101,9 +96,6 @@ function instantiateWasm( config = runtimeHelpers.config = Module.config = {} as any; } runtimeHelpers.diagnosticTracing = !!config.diagnosticTracing; - if (!config.assets) { - config.assets = []; - } if (userInstantiateWasm) { const exports = userInstantiateWasm(imports, (instance: WebAssembly.Instance, module: WebAssembly.Module) => { @@ -113,13 +105,11 @@ function instantiateWasm( return exports; } - wasm_module_imports = imports; - wasm_success_callback = successCallback; - instantiate_wasm_module(); + instantiate_wasm_module(imports, successCallback); return []; // No exports } -function preInit(isCustomStartup: boolean, userPreInit: (() => void)[]) { +function preInit(userPreInit: (() => void)[]) { Module.addRunDependency("mono_pre_init"); try { mono_wasm_pre_init_essential(); @@ -172,7 +162,7 @@ async function preRunAsync(userPreRun: (() => void)[]) { Module.removeRunDependency("mono_pre_run_async"); } -async function onRuntimeInitializedAsync(isCustomStartup: boolean, userOnRuntimeInitialized: () => void) { +async function onRuntimeInitializedAsync(userOnRuntimeInitialized: () => void) { // wait for previous stage await afterPreRun.promise; if (runtimeHelpers.diagnosticTracing) console.debug("MONO_WASM: onRuntimeInitialized"); @@ -180,13 +170,7 @@ async function onRuntimeInitializedAsync(isCustomStartup: boolean, userOnRuntime beforeOnRuntimeInitialized.promise_control.resolve(); try { if (!isCustomStartup) { - // wait for all assets in memory - await all_assets_loaded_in_memory; - const expected_asset_count = config.assets ? config.assets.length : 0; - mono_assert(downloded_assets_count == expected_asset_count, "Expected assets to be downloaded"); - mono_assert(instantiated_assets_count == expected_asset_count, "Expected assets to be in memory"); - if (runtimeHelpers.diagnosticTracing) console.debug("MONO_WASM: all assets are loaded in wasm memory"); - + await wait_for_all_assets(); // load runtime await mono_wasm_before_user_runtime_initialized(); } @@ -246,7 +230,6 @@ function mono_wasm_pre_init_essential(): void { if (runtimeHelpers.diagnosticTracing) console.debug("MONO_WASM: mono_wasm_pre_init_essential"); // init_polyfills() is already called from export.ts - init_crypto(); init_c_exports(); cwraps_internal(INTERNAL); cwraps_mono_api(MONO); @@ -261,6 +244,8 @@ async function mono_wasm_pre_init_essential_async(): Promise { Module.addRunDependency("mono_wasm_pre_init_essential_async"); await init_polyfills_async(); + await mono_wasm_load_config(Module.configSrc); + init_crypto(); Module.removeRunDependency("mono_wasm_pre_init_essential_async"); } @@ -270,9 +255,6 @@ async function mono_wasm_pre_init_full(): Promise { if (runtimeHelpers.diagnosticTracing) console.debug("MONO_WASM: mono_wasm_pre_init_full"); Module.addRunDependency("mono_wasm_pre_init_full"); - if (Module.configSrc) { - await mono_wasm_load_config(Module.configSrc); - } await mono_download_assets(); Module.removeRunDependency("mono_wasm_pre_init_full"); @@ -282,21 +264,14 @@ async function mono_wasm_pre_init_full(): Promise { async function mono_wasm_before_user_runtime_initialized(): Promise { if (runtimeHelpers.diagnosticTracing) console.debug("MONO_WASM: mono_wasm_before_user_runtime_initialized"); - if (!Module.config) { - return; - } - try { - loaded_files.forEach(value => MONO.loaded_files.push(value.url)); - if (!loaded_files || loaded_files.length == 0) { - Module.print("MONO_WASM: no files were loaded into runtime"); - } - await _apply_configuration_from_args(); mono_wasm_globalization_init(); if (!runtimeHelpers.mono_wasm_load_runtime_done) mono_wasm_load_runtime("unused", config.debugLevel || 0); if (!runtimeHelpers.mono_wasm_runtime_is_ready) mono_wasm_runtime_ready(); + if (!runtimeHelpers.mono_wasm_symbols_are_ready) readSymbolMapFile("dotnet.js.symbols"); + setTimeout(() => { // when there are free CPU cycles string_decoder.init_fields(); @@ -382,137 +357,30 @@ export function mono_wasm_set_runtime_options(options: string[]): void { } -async function instantiate_wasm_module(): Promise { +async function instantiate_wasm_module( + imports: WebAssembly.Imports, + successCallback: InstantiateWasmSuccessCallback, +): Promise { // this is called so early that even Module exports like addRunDependency don't exist yet try { - if (!config.assets && Module.configSrc) { - // when we are starting with mono-config,json, it could have dotnet.wasm location in it, we have to wait for it - await mono_wasm_load_config(Module.configSrc); - } + await mono_wasm_load_config(Module.configSrc); if (runtimeHelpers.diagnosticTracing) console.debug("MONO_WASM: instantiate_wasm_module"); - let assetToLoad: AssetEntry = { - name: "dotnet.wasm", - behavior: "dotnetwasm" - }; - const assetfromConfig = config.assets!.find(a => a.behavior === "dotnetwasm"); - if (assetfromConfig) { - assetToLoad = assetfromConfig; - } else { - config.assets!.push(assetToLoad); - } - + const assetToLoad = resolve_asset_path("dotnetwasm"); const pendingAsset = await start_asset_download(assetToLoad); await beforePreInit.promise; Module.addRunDependency("instantiate_wasm_module"); - mono_assert(pendingAsset && pendingAsset.pending, () => `Can't load ${assetToLoad.name}`); - - const response = await pendingAsset.pending.response; - const contentType = response.headers ? response.headers.get("Content-Type") : undefined; - let compiledInstance: WebAssembly.Instance; - let compiledModule: WebAssembly.Module; - if (typeof WebAssembly.instantiateStreaming === "function" && contentType === "application/wasm") { - if (runtimeHelpers.diagnosticTracing) console.debug("MONO_WASM: instantiate_wasm_module streaming"); - const streamingResult = await WebAssembly.instantiateStreaming(response, wasm_module_imports!); - compiledInstance = streamingResult.instance; - compiledModule = streamingResult.module; - } else { - const arrayBuffer = await response.arrayBuffer(); - if (runtimeHelpers.diagnosticTracing) console.debug("MONO_WASM: instantiate_wasm_module buffered"); - const arrayBufferResult = await WebAssembly.instantiate(arrayBuffer, wasm_module_imports!); - compiledInstance = arrayBufferResult.instance; - compiledModule = arrayBufferResult.module; - } - ++instantiated_assets_count; - wasm_success_callback!(compiledInstance, compiledModule); + instantiate_wasm_asset(pendingAsset!, imports, successCallback); + if (runtimeHelpers.diagnosticTracing) console.debug("MONO_WASM: instantiate_wasm_module done"); afterInstantiateWasm.promise_control.resolve(); - wasm_success_callback = null; - wasm_module_imports = null; } catch (err) { - _print_error("MONO_WASM: _instantiate_wasm_module() failed", err); + _print_error("MONO_WASM: instantiate_wasm_module() failed", err); abort_startup(err, true); throw err; } Module.removeRunDependency("instantiate_wasm_module"); } -// this need to be run only after onRuntimeInitialized event, when the memory is ready -function _instantiate_asset(asset: AssetEntry, url: string, bytes: Uint8Array) { - if (runtimeHelpers.diagnosticTracing) - console.debug(`MONO_WASM: Loaded:${asset.name} as ${asset.behavior} size ${bytes.length} from ${url}`); - - const virtualName: string = typeof (asset.virtualPath) === "string" - ? asset.virtualPath - : asset.name; - let offset: VoidPtr | null = null; - - switch (asset.behavior) { - case "resource": - case "assembly": - case "pdb": - loaded_files.push({ url: url, file: virtualName }); - // falls through - case "heap": - case "icu": - offset = mono_wasm_load_bytes_into_heap(bytes); - loaded_assets[virtualName] = [offset, bytes.length]; - break; - - case "vfs": { - // FIXME - const lastSlash = virtualName.lastIndexOf("/"); - let parentDirectory = (lastSlash > 0) - ? virtualName.substr(0, lastSlash) - : null; - let fileName = (lastSlash > 0) - ? virtualName.substr(lastSlash + 1) - : virtualName; - if (fileName.startsWith("/")) - fileName = fileName.substr(1); - if (parentDirectory) { - if (runtimeHelpers.diagnosticTracing) - console.debug(`MONO_WASM: Creating directory '${parentDirectory}'`); - - Module.FS_createPath( - "/", parentDirectory, true, true // fixme: should canWrite be false? - ); - } else { - parentDirectory = "/"; - } - - if (runtimeHelpers.diagnosticTracing) - console.debug(`MONO_WASM: Creating file '${fileName}' in directory '${parentDirectory}'`); - - if (!mono_wasm_load_data_archive(bytes, parentDirectory)) { - Module.FS_createDataFile( - parentDirectory, fileName, - bytes, true /* canRead */, true /* canWrite */, true /* canOwn */ - ); - } - break; - } - default: - throw new Error(`Unrecognized asset behavior:${asset.behavior}, for asset ${asset.name}`); - } - - if (asset.behavior === "assembly") { - const hasPpdb = cwraps.mono_wasm_add_assembly(virtualName, offset!, bytes.length); - - if (!hasPpdb) { - const index = loaded_files.findIndex(element => element.file == virtualName); - loaded_files.splice(index, 1); - } - } - else if (asset.behavior === "icu") { - if (!mono_wasm_load_icu_data(offset!)) - Module.printErr(`MONO_WASM: Error loading ICU asset ${asset.name}`); - } - else if (asset.behavior === "resource") { - cwraps.mono_wasm_add_satellite_assembly(virtualName, asset.culture!, offset!, bytes.length); - } - ++instantiated_assets_count; -} - // runs just in non-blazor async function _apply_configuration_from_args() { try { @@ -592,251 +460,6 @@ export function bindings_init(): void { } } -function downloadResource(request: ResourceRequest): LoadingResource { - if (typeof Module.downloadResource === "function") { - const loading = Module.downloadResource(request); - if (loading) return loading; - } - const options: any = {}; - if (request.hash) { - options.integrity = request.hash; - } - const response = runtimeHelpers.fetch_like(request.resolvedUrl!, options); - return { - name: request.name, url: request.resolvedUrl!, response - }; -} -async function start_asset_download_sources(asset: AssetEntry): Promise { - if (asset.buffer) { - ++downloded_assets_count; - const buffer = asset.buffer; - asset.buffer = undefined;//GC later - asset.pending = { - url: "undefined://" + asset.name, - name: asset.name, - response: Promise.resolve({ - arrayBuffer: () => buffer, - headers: { - get: () => undefined, - } - }) as any - }; - return Promise.resolve(asset); - } - if (asset.pending) { - ++downloded_assets_count; - return asset; - } - - const sourcesList = asset.loadRemote && config.remoteSources ? config.remoteSources : [""]; - let response: Response | undefined = undefined; - for (let sourcePrefix of sourcesList) { - sourcePrefix = sourcePrefix.trim(); - // HACK: Special-case because MSBuild doesn't allow "" as an attribute - if (sourcePrefix === "./") - sourcePrefix = ""; - - let attemptUrl; - const assemblyRootFolder = config.assemblyRootFolder; - if (!asset.resolvedUrl) { - if (sourcePrefix === "") { - if (asset.behavior === "assembly" || asset.behavior === "pdb") - attemptUrl = assemblyRootFolder + "/" + asset.name; - else if (asset.behavior === "resource") { - const path = asset.culture !== "" ? `${asset.culture}/${asset.name}` : asset.name; - attemptUrl = assemblyRootFolder + "/" + path; - } - else - attemptUrl = asset.name; - } else { - attemptUrl = sourcePrefix + asset.name; - } - attemptUrl = runtimeHelpers.locateFile(attemptUrl); - } - else { - attemptUrl = asset.resolvedUrl; - } - if (asset.name === attemptUrl) { - if (runtimeHelpers.diagnosticTracing) - console.debug(`MONO_WASM: Attempting to download '${attemptUrl}'`); - } else { - if (runtimeHelpers.diagnosticTracing) - console.debug(`MONO_WASM: Attempting to download '${attemptUrl}' for ${asset.name}`); - } - try { - const loadingResource = downloadResource({ - name: asset.name, - resolvedUrl: attemptUrl, - hash: asset.hash, - behavior: asset.behavior - }); - response = await loadingResource.response; - if (!response.ok) { - continue;// next source - } - asset.pending = loadingResource; - ++downloded_assets_count; - return asset; - } - catch (err) { - continue; //next source - } - } - throw response; -} - -let throttling: PromiseAndController | undefined; -async function start_asset_download_throttle(asset: AssetEntry): Promise { - // we don't addRunDependency to allow download in parallel with onRuntimeInitialized event! - while (throttling) { - await throttling.promise; - } - try { - ++parallel_count; - if (parallel_count == runtimeHelpers.maxParallelDownloads) { - if (runtimeHelpers.diagnosticTracing) - console.debug("MONO_WASM: Throttling further parallel downloads"); - throttling = createPromiseController(); - } - return await start_asset_download_sources(asset); - } - catch (response: any) { - const isOkToFail = asset.isOptional || (asset.name.match(/\.pdb$/) && config.ignorePdbLoadErrors); - if (!isOkToFail) { - const err: any = new Error(`MONO_WASM: download '${response.url}' for ${asset.name} failed ${response.status} ${response.statusText}`); - err.status = response.status; - throw err; - } - } - finally { - --parallel_count; - if (throttling && parallel_count == runtimeHelpers.maxParallelDownloads - 1) { - if (runtimeHelpers.diagnosticTracing) - console.debug("MONO_WASM: Resuming more parallel downloads"); - const old_throttling = throttling; - throttling = undefined; - old_throttling.promise_control.resolve(); - } - } -} - -async function start_asset_download(asset: AssetEntry): Promise { - try { - return await start_asset_download_throttle(asset); - } catch (err: any) { - if (err && err.status == 404) { - throw err; - } - // second attempt only after all first attempts are queued - await allDownloadsQueued.promise; - try { - return await start_asset_download_throttle(asset); - } catch (err) { - // third attempt after small delay - await delay(100); - return await start_asset_download_throttle(asset); - } - } -} - -const allDownloadsQueued = createPromiseController(); -async function mono_download_assets(): Promise { - if (runtimeHelpers.diagnosticTracing) console.debug("MONO_WASM: mono_download_assets"); - runtimeHelpers.maxParallelDownloads = runtimeHelpers.config.maxParallelDownloads || runtimeHelpers.maxParallelDownloads; - try { - const download_promises: Promise[] = []; - // start fetching and instantiating all assets in parallel - for (const asset of config.assets || []) { - if (asset.behavior != "dotnetwasm") { - download_promises.push(start_asset_download(asset)); - } - } - allDownloadsQueued.promise_control.resolve(); - - const asset_promises: Promise[] = []; - for (const downloadPromise of download_promises) { - const downloadedAsset = await downloadPromise; - if (downloadedAsset) { - asset_promises.push((async () => { - const url = downloadedAsset.pending!.url; - const response = await downloadedAsset.pending!.response; - downloadedAsset.pending = undefined; //GC - const buffer = await response.arrayBuffer(); - await beforeOnRuntimeInitialized.promise; - // this is after onRuntimeInitialized - _instantiate_asset(downloadedAsset, url, new Uint8Array(buffer)); - })()); - } - } - - // this await will get past the onRuntimeInitialized because we are not blocking via addRunDependency - // and we are not awating it here - all_assets_loaded_in_memory = Promise.all(asset_promises) as any; - // OPTIMIZATION explained: - // we do it this way so that we could allocate memory immediately after asset is downloaded (and after onRuntimeInitialized which happened already) - // spreading in time - // rather than to block all downloads after onRuntimeInitialized or block onRuntimeInitialized after all downloads are done. That would create allocation burst. - } catch (err: any) { - Module.printErr("MONO_WASM: Error in mono_download_assets: " + err); - throw err; - } -} - -// used from Blazor -export function mono_wasm_load_data_archive(data: Uint8Array, prefix: string): boolean { - if (data.length < 8) - return false; - - const dataview = new DataView(data.buffer); - const magic = dataview.getUint32(0, true); - // get magic number - if (magic != 0x626c6174) { - return false; - } - const manifestSize = dataview.getUint32(4, true); - if (manifestSize == 0 || data.length < manifestSize + 8) - return false; - - let manifest; - try { - const manifestContent = Module.UTF8ArrayToString(data, 8, manifestSize); - manifest = JSON.parse(manifestContent); - if (!(manifest instanceof Array)) - return false; - } catch (exc) { - return false; - } - - data = data.slice(manifestSize + 8); - - // Create the folder structure - // /usr/share/zoneinfo - // /usr/share/zoneinfo/Africa - // /usr/share/zoneinfo/Asia - // .. - - const folders = new Set(); - manifest.filter(m => { - const file = m[0]; - const last = file.lastIndexOf("/"); - const directory = file.slice(0, last + 1); - folders.add(directory); - }); - folders.forEach(folder => { - Module["FS_createPath"](prefix, folder, true, true); - }); - - for (const row of manifest) { - const name = row[0]; - const length = row[1]; - const bytes = data.slice(0, length); - Module["FS_createDataFile"](prefix, name, bytes, true, true); - data = data.slice(length); - } - return true; -} - -let configLoaded = false; /** * Loads the mono config file (typically called mono-config.json) asynchroniously * Note: the run dependencies are so emsdk actually awaits it in order. @@ -844,8 +467,15 @@ let configLoaded = false; * @param {string} configFilePath - relative path to the config file * @throws Will throw an error if the config file loading fails */ -export async function mono_wasm_load_config(configFilePath: string): Promise { +export async function mono_wasm_load_config(configFilePath?: string): Promise { if (configLoaded) { + await afterConfigLoaded.promise; + return; + } + configLoaded = true; + if (!configFilePath) { + normalize(); + afterConfigLoaded.promise_control.resolve(); return; } if (runtimeHelpers.diagnosticTracing) console.debug("MONO_WASM: mono_wasm_load_config"); @@ -853,36 +483,48 @@ export async function mono_wasm_load_config(configFilePath: string): PromiseruntimeHelpers.config); + normalize(); } catch (err: any) { _print_error("MONO_WASM: onConfigLoaded() failed", err); throw err; } } - runtimeHelpers.diagnosticTracing = !!runtimeHelpers.config.diagnosticTracing; - configLoaded = true; + afterConfigLoaded.promise_control.resolve(); } catch (err) { const errMessage = `Failed to load config file ${configFilePath} ${err}`; abort_startup(errMessage, true); config = runtimeHelpers.config = Module.config = { message: errMessage, error: err, isError: true }; throw err; } + + function normalize() { + // normalize + config.environmentVariables = config.environmentVariables || {}; + config.assets = config.assets || []; + config.runtimeOptions = config.runtimeOptions || []; + config.globalizationMode = config.globalizationMode || "auto"; + if (config.debugLevel === undefined && BuildConfiguration === "Debug") { + config.debugLevel = -1; + } + if (config.diagnosticTracing === undefined && BuildConfiguration === "Debug") { + config.diagnosticTracing = true; + } + runtimeHelpers.diagnosticTracing = !!runtimeHelpers.config.diagnosticTracing; + } } export function mono_wasm_asm_loaded(assembly_name: CharPtr, assembly_ptr: number, assembly_len: number, pdb_ptr: number, pdb_len: number): void { @@ -946,5 +588,7 @@ export async function mono_wasm_pthread_worker_init(): Promise { export async function mono_load_runtime_and_bcl_args(cfg?: MonoConfig | MonoConfigError | undefined): Promise { config = Module.config = runtimeHelpers.config = Object.assign(runtimeHelpers.config || {}, cfg || {}) as any; await mono_download_assets(); - await all_assets_loaded_in_memory; + if (!isCustomStartup) { + await wait_for_all_assets(); + } } diff --git a/src/mono/wasm/runtime/types.ts b/src/mono/wasm/runtime/types.ts index 271edd7f14595f..d9edc82f9587b6 100644 --- a/src/mono/wasm/runtime/types.ts +++ b/src/mono/wasm/runtime/types.ts @@ -116,7 +116,9 @@ export type AssetBehaviours = | "heap" // store asset into the native heap | "icu" // load asset as an ICU data archive | "vfs" // load asset into the virtual filesystem (for fopen, File.Open, etc) - | "dotnetwasm"; // the binary of the dotnet runtime + | "dotnetwasm" // the binary of the dotnet runtime + | "js-module-crypto" // the javascript module for subtle crypto + | "js-module-threads" // the javascript module for threads export type RuntimeHelpers = { runtime_interop_module: MonoAssembly; @@ -128,6 +130,7 @@ export type RuntimeHelpers = { mono_wasm_load_runtime_done: boolean; mono_wasm_runtime_is_ready: boolean; mono_wasm_bindings_is_ready: boolean; + mono_wasm_symbols_are_ready: boolean; loaded_files: string[]; maxParallelDownloads: number; diff --git a/src/mono/wasm/runtime/types/emscripten.ts b/src/mono/wasm/runtime/types/emscripten.ts index 3c90efaac757a0..5ddd3f6ceb6223 100644 --- a/src/mono/wasm/runtime/types/emscripten.ts +++ b/src/mono/wasm/runtime/types/emscripten.ts @@ -58,7 +58,7 @@ export declare interface EmscriptenModule { ready: Promise; - instantiateWasm?: (imports: WebAssembly.Imports, successCallback: (instance: WebAssembly.Instance, module: WebAssembly.Module) => void) => any; + instantiateWasm?: InstantiateWasmCallBack; preInit?: (() => any)[] | (() => any); preRun?: (() => any)[] | (() => any); onRuntimeInitialized?: () => any; @@ -66,4 +66,7 @@ export declare interface EmscriptenModule { onAbort?: { (error: any): void }; } +export type InstantiateWasmSuccessCallback = (instance: WebAssembly.Instance, module: WebAssembly.Module) => void; +export type InstantiateWasmCallBack = (imports: WebAssembly.Imports, successCallback: InstantiateWasmSuccessCallback) => any; + export declare type TypedArray = Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array; diff --git a/src/mono/wasm/runtime/workers/dotnet-crypto-worker.ts b/src/mono/wasm/runtime/workers/dotnet-crypto-worker.ts index 6b4dcde69c12d1..59866c1c500a7e 100644 --- a/src/mono/wasm/runtime/workers/dotnet-crypto-worker.ts +++ b/src/mono/wasm/runtime/workers/dotnet-crypto-worker.ts @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import { setup_proxy_console } from "../debug"; +import { setup_proxy_console } from "../logging"; import type { InitCryptoMessageData } from "../crypto-worker"; import type { MonoConfig } from "../types"; diff --git a/src/tasks/WasmAppBuilder/WasmAppBuilder.cs b/src/tasks/WasmAppBuilder/WasmAppBuilder.cs index de25059d66d03c..65f1a9a5b0f1ad 100644 --- a/src/tasks/WasmAppBuilder/WasmAppBuilder.cs +++ b/src/tasks/WasmAppBuilder/WasmAppBuilder.cs @@ -74,7 +74,7 @@ public class WasmAppBuilder : Task private sealed class WasmAppConfig { [JsonPropertyName("assemblyRootFolder")] - public string AssemblyRoot { get; set; } = "managed"; + public string AssemblyRootFolder { get; set; } = "managed"; [JsonPropertyName("debugLevel")] public int DebugLevel { get; set; } = 0; [JsonPropertyName("assets")] @@ -96,6 +96,23 @@ protected AssetEntry (string name, string behavior) public string Behavior { get; init; } [JsonPropertyName("name")] public string Name { get; init; } + // TODO [JsonPropertyName("hash")] + // TODO public string? Hash { get; set; } + } + + private sealed class WasmEntry : AssetEntry + { + public WasmEntry(string name) : base(name, "dotnetwasm") { } + } + + private sealed class CryptoWorkerEntry : AssetEntry + { + public CryptoWorkerEntry(string name) : base(name, "js-module-crypto") { } + } + + private sealed class ThreadsWorkerEntry : AssetEntry + { + public ThreadsWorkerEntry(string name) : base(name, "js-module-threads") { } } private sealed class AssemblyEntry : AssetEntry @@ -165,7 +182,7 @@ private bool ExecuteInternal () var config = new WasmAppConfig (); // Create app - var asmRootPath = Path.Combine(AppDir, config.AssemblyRoot); + var asmRootPath = Path.Combine(AppDir, config.AssemblyRootFolder); Directory.CreateDirectory(AppDir!); Directory.CreateDirectory(asmRootPath); foreach (var assembly in _assemblies) @@ -240,7 +257,7 @@ private bool ExecuteInternal () // FIXME: validate the culture? string name = Path.GetFileName(fullPath); - string directory = Path.Combine(AppDir, config.AssemblyRoot, culture); + string directory = Path.Combine(AppDir, config.AssemblyRootFolder, culture); Directory.CreateDirectory(directory); FileCopyChecked(fullPath, Path.Combine(directory, name), "SatelliteAssemblies"); config.Assets.Add(new SatelliteAssemblyEntry(name, culture)); @@ -295,6 +312,8 @@ private bool ExecuteInternal () config.Assets.Add(new IcuData(IcuDataFileName!) { LoadRemote = RemoteSources?.Length > 0 }); config.Assets.Add(new VfsEntry ("dotnet.timezones.blat") { VirtualPath = "/usr/share/zoneinfo/"}); + config.Assets.Add(new WasmEntry ("dotnet.wasm") ); + config.Assets.Add(new CryptoWorkerEntry ("dotnet-crypto-worker.js") ); if (RemoteSources?.Length > 0) { From 7e95181f62b4bab15b32827a91532f3fdf368809 Mon Sep 17 00:00:00 2001 From: Kevin Jones Date: Tue, 9 Aug 2022 13:29:49 -0400 Subject: [PATCH 21/41] Implement initial capacity for AsnWriter --- .../ref/System.Formats.Asn1.cs | 1 + .../src/Resources/Strings.resx | 3 + .../src/System/Formats/Asn1/AsnWriter.cs | 25 +++++ .../tests/Writer/SimpleWriterTests.cs | 102 ++++++++++++++++++ 4 files changed, 131 insertions(+) diff --git a/src/libraries/System.Formats.Asn1/ref/System.Formats.Asn1.cs b/src/libraries/System.Formats.Asn1/ref/System.Formats.Asn1.cs index d649ad56879a8d..7c4003e2978476 100644 --- a/src/libraries/System.Formats.Asn1/ref/System.Formats.Asn1.cs +++ b/src/libraries/System.Formats.Asn1/ref/System.Formats.Asn1.cs @@ -144,6 +144,7 @@ public partial struct AsnReaderOptions public sealed partial class AsnWriter { public AsnWriter(System.Formats.Asn1.AsnEncodingRules ruleSet) { } + public AsnWriter(System.Formats.Asn1.AsnEncodingRules ruleSet, int initialCapacity) { } public System.Formats.Asn1.AsnEncodingRules RuleSet { get { throw null; } } public void CopyTo(System.Formats.Asn1.AsnWriter destination) { } public byte[] Encode() { throw null; } diff --git a/src/libraries/System.Formats.Asn1/src/Resources/Strings.resx b/src/libraries/System.Formats.Asn1/src/Resources/Strings.resx index 10571924b9d2e7..3b028a6f3c90d5 100644 --- a/src/libraries/System.Formats.Asn1/src/Resources/Strings.resx +++ b/src/libraries/System.Formats.Asn1/src/Resources/Strings.resx @@ -99,6 +99,9 @@ The input to WriteEncodedValue must represent a single encoded value with no trailing data. + + Non-negative number required. + Encode cannot be called while a Sequence, Set-Of, or Octet String is still open. diff --git a/src/libraries/System.Formats.Asn1/src/System/Formats/Asn1/AsnWriter.cs b/src/libraries/System.Formats.Asn1/src/System/Formats/Asn1/AsnWriter.cs index 1e5b830c58a25b..efe0423c441e39 100644 --- a/src/libraries/System.Formats.Asn1/src/System/Formats/Asn1/AsnWriter.cs +++ b/src/libraries/System.Formats.Asn1/src/System/Formats/Asn1/AsnWriter.cs @@ -48,6 +48,31 @@ public AsnWriter(AsnEncodingRules ruleSet) RuleSet = ruleSet; } + /// + /// Create a new with a given set of encoding rules and an initial capacity. + /// + /// The encoding constraints for the writer. + /// The minimum capacity with which to initialize the underlying buffer. + /// + /// is not defined. + /// -or- + /// is a negative number. + /// + /// + /// Specifying with a value of zero behaves as if no initial capacity were + /// specified. + /// + public AsnWriter(AsnEncodingRules ruleSet, int initialCapacity) : this(ruleSet) + { + if (initialCapacity < 0) + throw new ArgumentOutOfRangeException(nameof(initialCapacity), SR.ArgumentOutOfRange_NeedNonNegNum); + + if (initialCapacity > 0) + { + _buffer = new byte[initialCapacity]; + } + } + /// /// Reset the writer to have no data, without releasing resources. /// diff --git a/src/libraries/System.Formats.Asn1/tests/Writer/SimpleWriterTests.cs b/src/libraries/System.Formats.Asn1/tests/Writer/SimpleWriterTests.cs index b41f2d0caadbf4..7ef466a40b57ae 100644 --- a/src/libraries/System.Formats.Asn1/tests/Writer/SimpleWriterTests.cs +++ b/src/libraries/System.Formats.Asn1/tests/Writer/SimpleWriterTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Reflection; using Xunit; namespace System.Formats.Asn1.Tests.Writer @@ -17,6 +18,20 @@ public static void ValidateRuleSet(int value) AssertExtensions.Throws( "ruleSet", () => new AsnWriter((AsnEncodingRules)value)); + + AssertExtensions.Throws( + "ruleSet", + () => new AsnWriter((AsnEncodingRules)value, initialCapacity: 1000)); + } + + [Theory] + [InlineData(-1)] + [InlineData(int.MinValue)] + public static void ValidateInitialCapacity(int initialCapacity) + { + AssertExtensions.Throws( + "initialCapacity", + () => new AsnWriter(AsnEncodingRules.DER, initialCapacity)); } [Theory] @@ -138,5 +153,92 @@ public static void CopyTo_Null(AsnEncodingRules ruleSet) "destination", () => writer.CopyTo(null)); } + + [Fact] + public static void InitialCapacity_ExactCapacity() + { + ReadOnlySpan value = new byte[] { 0x04, 0x06, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 }; + AsnWriter writer = new AsnWriter(AsnEncodingRules.DER, initialCapacity: 8); + writer.WriteEncodedValue(value); + + byte[]? buffer = PeekRawBuffer(writer); + Assert.Equal(8, buffer?.Length); + + byte[] encoded = writer.Encode(); + AssertExtensions.SequenceEqual(value, encoded); + + writer.Reset(); + buffer = PeekRawBuffer(writer); + Assert.Equal(8, buffer?.Length); + + writer.WriteEncodedValue(value); + buffer = PeekRawBuffer(writer); + Assert.Equal(8, buffer?.Length); + } + + [Fact] + public static void InitialCapacity_ZeroHasNoInitialCapacity() + { + AsnWriter writer = new AsnWriter(AsnEncodingRules.DER, initialCapacity: 0); + + byte[]? buffer = PeekRawBuffer(writer); + Assert.Null(buffer); + } + + [Fact] + public static void InitialCapacity_UnderCapacity() + { + ReadOnlySpan value = new byte[] { 0x04, 0x01, 0x01 }; + AsnWriter writer = new AsnWriter(AsnEncodingRules.DER, initialCapacity: 8); + writer.WriteEncodedValue(value); + + byte[]? buffer = PeekRawBuffer(writer); + Assert.Equal(8, buffer?.Length); + + byte[] encoded = writer.Encode(); + AssertExtensions.SequenceEqual(value, encoded); + + writer.Reset(); + buffer = PeekRawBuffer(writer); + Assert.Equal(8, buffer?.Length); + } + + [Fact] + public static void InitialCapacity_ExceedCapacity() + { + ReadOnlySpan value = new byte[] { 0x04, 0x07, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07 }; + AsnWriter writer = new AsnWriter(AsnEncodingRules.DER, initialCapacity: 8); + writer.WriteEncodedValue(value); + + byte[]? buffer = PeekRawBuffer(writer); + Assert.Equal(1024, buffer?.Length); + } + + [Fact] + public static void InitialCapacity_ResizeBlockAligns() + { + ReadOnlySpan value = new byte[] { 0x04, 0x06, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 }; + ReadOnlySpan valueLarge = new byte[] { 0x04, 0x07, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07 }; + + AsnWriter writer = new AsnWriter(AsnEncodingRules.DER, initialCapacity: 8); + writer.WriteEncodedValue(value); + + byte[]? buffer = PeekRawBuffer(writer); + Assert.Equal(8, buffer?.Length); + + writer.Reset(); + buffer = PeekRawBuffer(writer); + Assert.Equal(8, buffer?.Length); + + writer.WriteEncodedValue(valueLarge); + buffer = PeekRawBuffer(writer); + Assert.Equal(1024, buffer?.Length); + } + + private static byte[]? PeekRawBuffer(AsnWriter writer) + { + FieldInfo bufField = typeof(AsnWriter).GetField("_buffer", BindingFlags.Instance | BindingFlags.NonPublic); + return (byte[]?)bufField.GetValue(writer); + } } } From d0aa175a0c295747c82fabc260eb188540ed12c7 Mon Sep 17 00:00:00 2001 From: Noah Falk Date: Tue, 9 Aug 2022 10:35:15 -0700 Subject: [PATCH 22/41] Revert "Disable GetGCMemoryInfo on arm (#73477)" (#73595) This reverts commit 9865cc7a89a4a613a47986b8268a10bc37adbd65. We believe the problematic change has been reverted so the test can be re-enabled. --- src/tests/issues.targets | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/tests/issues.targets b/src/tests/issues.targets index 61b04ec3db157c..66b2d741706d88 100644 --- a/src/tests/issues.targets +++ b/src/tests/issues.targets @@ -81,7 +81,6 @@ - needs triage @@ -109,9 +108,6 @@ https://github.com/dotnet/runtime/issues/ - - https://github.com/dotnet/runtime/issues/73247 - times out From 8e407e32bbb13874a7a8b8651d6d3f1d70574754 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marie=20P=C3=ADchov=C3=A1?= <11718369+ManickaP@users.noreply.github.com> Date: Tue, 9 Aug 2022 19:44:06 +0200 Subject: [PATCH 23/41] [QUIC] Copy managed memory instead of pinning (#72746) * Improved server logging to include S.N.Quic traces as well. * MsQuicBuffers copy given memory into native instead of pinning --- .../HttpStress/ClientOperations.cs | 2 +- .../StressTests/HttpStress/StressServer.cs | 54 +++++++++++++++++++ .../System/Net/Quic/Internal/MsQuicBuffers.cs | 39 +++++++------- .../src/System/Net/Quic/QuicStream.cs | 6 --- 4 files changed, 73 insertions(+), 28 deletions(-) diff --git a/src/libraries/System.Net.Http/tests/StressTests/HttpStress/ClientOperations.cs b/src/libraries/System.Net.Http/tests/StressTests/HttpStress/ClientOperations.cs index e557ea40ffb7ec..b8f3bbe3d654d2 100644 --- a/src/libraries/System.Net.Http/tests/StressTests/HttpStress/ClientOperations.cs +++ b/src/libraries/System.Net.Http/tests/StressTests/HttpStress/ClientOperations.cs @@ -85,8 +85,8 @@ public async Task SendAsync(HttpRequestMessage request, Htt await Task.WhenAny(task, Task.Delay(_random.Next(0, 60), cts.Token)); } - cts.Cancel(); IsCancellationRequested = true; + cts.Cancel(); return WithVersionValidation(await task); } else diff --git a/src/libraries/System.Net.Http/tests/StressTests/HttpStress/StressServer.cs b/src/libraries/System.Net.Http/tests/StressTests/HttpStress/StressServer.cs index 503ac939ec3013..8eaa167fef1a8d 100644 --- a/src/libraries/System.Net.Http/tests/StressTests/HttpStress/StressServer.cs +++ b/src/libraries/System.Net.Http/tests/StressTests/HttpStress/StressServer.cs @@ -27,6 +27,7 @@ using Microsoft.Extensions.Primitives; using Microsoft.AspNetCore.Server.Kestrel.Core; using Serilog; +using Microsoft.Extensions.ObjectPool; namespace HttpStress { @@ -36,6 +37,7 @@ public class StressServer : IDisposable public const string ExpectedResponseContentLength = "Expected-Response-Content-Length"; private readonly IWebHost _webHost; + private readonly LogQuicEventListener? _listener; public string ServerUri { get; } @@ -155,6 +157,10 @@ void ConfigureListenOptions(ListenOptions listenOptions) .WriteTo.Console(Serilog.Events.LogEventLevel.Warning); } Log.Logger = loggerConfiguration.CreateLogger(); + if (configuration.Trace) + { + _listener = new LogQuicEventListener(Log.Logger); + } host = host .UseSerilog() @@ -333,6 +339,7 @@ private static void AppendChecksumHeader(IHeaderDictionary headers, ulong checks public void Dispose() { _webHost.Dispose(); + _listener?.Dispose(); } private static (string scheme, string hostname, int port) ParseServerUri(string serverUri) @@ -397,4 +404,51 @@ public static bool IsValidServerContent(string input) return true; } } + + public class LogQuicEventListener : EventListener + { + private DefaultObjectPool _stringBuilderPool = new DefaultObjectPool(new StringBuilderPooledObjectPolicy()); + private readonly Serilog.ILogger _logger; + + public LogQuicEventListener(Serilog.ILogger logger) + { + _logger = logger; + } + + protected override void OnEventSourceCreated(EventSource eventSource) + { + if (eventSource.Name == "Private.InternalDiagnostics.System.Net.Quic") + { + EnableEvents(eventSource, EventLevel.LogAlways); + } + } + + protected override void OnEventWritten(EventWrittenEventArgs eventData) + { + StringBuilder sb = _stringBuilderPool.Get(); + sb.Append($"{eventData.TimeStamp:HH:mm:ss.fffffff}[{eventData.EventName}] "); + for (int i = 0; i < eventData.Payload?.Count; i++) + { + if (i > 0) + { + sb.Append(", "); + } + sb.Append(eventData.PayloadNames?[i]).Append(": ").Append(eventData.Payload[i]); + } + if (eventData.Level > EventLevel.Error) + { + _logger.Debug(sb.ToString()); + } + else + { + _logger.Error(sb.ToString()); + } + _stringBuilderPool.Return(sb); + } + + public override void Dispose() + { + base.Dispose(); + } + } } diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicBuffers.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicBuffers.cs index b50e3c0f5c9a3a..594245a1cb723d 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicBuffers.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicBuffers.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Buffers; using System.Collections.Generic; using System.Runtime.InteropServices; using Microsoft.Quic; @@ -15,8 +14,6 @@ namespace System.Net.Quic; /// internal unsafe struct MsQuicBuffers : IDisposable { - // Handles to pinned memory blocks from the user. - private MemoryHandle[] _handles; // Native memory block which holds the pinned memory pointers from _handles and can be passed to MsQuic as QUIC_BUFFER*. private QUIC_BUFFER* _buffers; // Number of QUIC_BUFFER instance currently allocated in _buffers, so that we can reuse the memory instead of reallocating. @@ -24,7 +21,6 @@ internal unsafe struct MsQuicBuffers : IDisposable public MsQuicBuffers() { - _handles = Array.Empty(); _buffers = null; _count = 0; } @@ -37,44 +33,39 @@ private void FreeNativeMemory() QUIC_BUFFER* buffers = _buffers; _buffers = null; NativeMemory.Free(buffers); + _count = 0; } private void Reserve(int count) { - if (_handles.Length < count) + if (count > _count) { - _handles = new MemoryHandle[count]; FreeNativeMemory(); - _buffers = (QUIC_BUFFER*)NativeMemory.Alloc((nuint)count, (nuint)sizeof(QUIC_BUFFER)); + _buffers = (QUIC_BUFFER*)NativeMemory.AllocZeroed((nuint)count, (nuint)sizeof(QUIC_BUFFER)); + _count = count; } - - _count = count; } private void SetBuffer(int index, ReadOnlyMemory buffer) { - MemoryHandle handle = buffer.Pin(); - - _handles[index] = handle; - _buffers[index].Buffer = (byte*)handle.Pointer; + _buffers[index].Buffer = (byte*)NativeMemory.Alloc((nuint)buffer.Length, (nuint)sizeof(byte)); _buffers[index].Length = (uint)buffer.Length; + buffer.Span.CopyTo(_buffers[index].Span); } /// /// Initializes QUIC_BUFFER* with data from inputs, converted via toBuffer. /// Note that the struct either needs to be freshly created via new or previously cleaned up with Reset. /// - /// Inputs to get their byte array, pin them and pepare them to be passed to MsQuic as QUIC_BUFFER*. + /// Inputs to get their byte array, copy them to be passed to MsQuic as QUIC_BUFFER*. /// Method extracting byte array from the inputs, e.g. applicationProtocol.Protocol. /// The type of the inputs. public void Initialize(IList inputs, Func> toBuffer) { Reserve(inputs.Count); - for (int i = 0; i < inputs.Count; ++i) { - ReadOnlyMemory buffer = toBuffer(inputs[i]); - SetBuffer(i, buffer); + SetBuffer(i, toBuffer(inputs[i])); } } @@ -90,19 +81,25 @@ public void Initialize(ReadOnlyMemory buffer) } /// - /// Unpins the managed memory and allows reuse of this struct. + /// Release the native memory of individual buffers and allows reuse of this struct. /// public void Reset() { for (int i = 0; i < _count; ++i) { - _handles[i].Dispose(); + if (_buffers[i].Buffer is null) + { + break; + } + byte* buffer = _buffers[i].Buffer; + _buffers[i].Buffer = null; + NativeMemory.Free(buffer); + _buffers[i].Length = 0; } } /// - /// Apart from unpinning the managed memory, it returns the shared buffer, - /// but most importantly it releases the unmanaged memory. + /// Releases all the native memory. /// public void Dispose() { diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicStream.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicStream.cs index 217e53f3be103a..2788dc1555f957 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicStream.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicStream.cs @@ -76,10 +76,7 @@ public sealed partial class QuicStream } } }; -// [ActiveIssue("https://github.com/dotnet/roslyn-analyzers/issues/5750")] Structs can have parameterless ctor now and thus the behavior differs from just defaulting the struct to zeros. -#pragma warning disable CA1805 private ReceiveBuffers _receiveBuffers = new ReceiveBuffers(); -#pragma warning restore CA1805 private int _receivedNeedsEnable; private readonly ResettableValueTaskSource _sendTcs = new ResettableValueTaskSource() @@ -92,10 +89,7 @@ public sealed partial class QuicStream } } }; -// [ActiveIssue("https://github.com/dotnet/roslyn-analyzers/issues/5750")] Structs can have parameterless ctor now and thus the behavior differs from just defaulting the struct to zeros. -#pragma warning disable CA1805 private MsQuicBuffers _sendBuffers = new MsQuicBuffers(); -#pragma warning restore CA1805 private readonly long _defaultErrorCode; From 136e29f0cbf0f80c95d08152de352e596ea3d09c Mon Sep 17 00:00:00 2001 From: Steve Pfister Date: Tue, 9 Aug 2022 11:55:48 -0700 Subject: [PATCH 24/41] [Android] Remove BROKEN_CLOCK_SOURCE define on Android (#73489) Fixes https://github.com/dotnet/runtime/issues/58737 --- src/mono/mono/utils/mono-os-mutex.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mono/mono/utils/mono-os-mutex.h b/src/mono/mono/utils/mono-os-mutex.h index 904974b0a08331..30f914b0893a29 100644 --- a/src/mono/mono/utils/mono-os-mutex.h +++ b/src/mono/mono/utils/mono-os-mutex.h @@ -36,7 +36,7 @@ #if !defined(HOST_WIN32) -#if !defined(CLOCK_MONOTONIC) || defined(HOST_DARWIN) || defined(HOST_ANDROID) || defined(HOST_WASM) +#if !defined(CLOCK_MONOTONIC) || defined(HOST_DARWIN) || defined(HOST_WASM) #define BROKEN_CLOCK_SOURCE #endif From 5f80c24a6600e9d52a84f22d7b6088fe63180cee Mon Sep 17 00:00:00 2001 From: Andrew Au Date: Tue, 9 Aug 2022 12:06:56 -0700 Subject: [PATCH 25/41] Reset current_no_gc_region_info after leaving no gc region implicitly (#73602) --- src/coreclr/gc/gc.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/coreclr/gc/gc.cpp b/src/coreclr/gc/gc.cpp index 69465bc59b0a86..77a0979055ab4c 100644 --- a/src/coreclr/gc/gc.cpp +++ b/src/coreclr/gc/gc.cpp @@ -20852,6 +20852,8 @@ BOOL gc_heap::should_proceed_with_gc() // The no_gc mode was already in progress yet we triggered another GC, // this effectively exits the no_gc mode. restore_data_for_no_gc(); + + memset (¤t_no_gc_region_info, 0, sizeof (current_no_gc_region_info)); } else return should_proceed_for_no_gc(); From 4211dc40d06195682d4b0fc9a220bdb75732d720 Mon Sep 17 00:00:00 2001 From: Ankit Jain Date: Tue, 9 Aug 2022 15:15:58 -0400 Subject: [PATCH 26/41] CI: Add support in yml for detecting darc dependency changes in `eng/Version.Details.xml` (#73435) ## Issue Currently, whenever a darc flow PR is opened, we cannot trigger different jobs based on which dependency got updated. This is needed because in some cases, like for ILLinker, Emscripten updates we want to trigger all the wasm jobs. But that might not be needed for some of the other dependencies. - This causes failures to slip through to `main`, which gets discovered after the fact as other PR failures, or rolling build failures, creating extra work for developers. ## Solution - This PR identifies the changed dependencies, and emits one azure variable per dependency, which can then be used in conditions in the yml . - The changed dependency can be checked as `dependencies.evaluate_paths.outputs['DarcDependenciesChanged.System_CommandLine']` - Included this in the `evaluate_paths` job itself, instead of creating another job - Also, use this for wasm jobs --- .../common/evaluate-changed-darc-deps.yml | 15 +++ eng/pipelines/common/evaluate-paths-job.yml | 7 ++ .../common/templates/wasm-build-only.yml | 1 + .../common/templates/wasm-build-tests.yml | 1 + .../common/templates/wasm-debugger-tests.yml | 1 + .../common/templates/wasm-library-tests.yml | 1 + .../common/templates/wasm-runtime-tests.yml | 1 + eng/pipelines/common/xplat-setup.yml | 8 ++ eng/pipelines/evaluate-changed-darc-deps.sh | 98 +++++++++++++++++++ eng/pipelines/get-changed-darc-deps.py | 62 ++++++++++++ 10 files changed, 195 insertions(+) create mode 100644 eng/pipelines/common/evaluate-changed-darc-deps.yml create mode 100755 eng/pipelines/evaluate-changed-darc-deps.sh create mode 100644 eng/pipelines/get-changed-darc-deps.py diff --git a/eng/pipelines/common/evaluate-changed-darc-deps.yml b/eng/pipelines/common/evaluate-changed-darc-deps.yml new file mode 100644 index 00000000000000..1a816f5876b1ae --- /dev/null +++ b/eng/pipelines/common/evaluate-changed-darc-deps.yml @@ -0,0 +1,15 @@ +# This step template evaluates changes in dependencies defined in `eng/Version.Details.xml`. +# For more information on how this works works look at evaluate-changed-darc-deps.sh docs +# at the beginning of that file. + +parameters: + subsetName: '' + # Array containing the arguments that are to be passed down to evaluate-changed-paths.sh + # Note that --azurevariable is always set to the dependency name, no need to pass it down. + arguments: [] + +steps: + - script: eng/pipelines/evaluate-changed-darc-deps.sh + ${{ join(' ', parameters.arguments) }} + displayName: Evaluate eng/Version.Details.xml for dependency changes + name: DarcDependenciesChanged diff --git a/eng/pipelines/common/evaluate-paths-job.yml b/eng/pipelines/common/evaluate-paths-job.yml index 42f8549cbb7f07..71f35bd4f9f4c7 100644 --- a/eng/pipelines/common/evaluate-paths-job.yml +++ b/eng/pipelines/common/evaluate-paths-job.yml @@ -50,3 +50,10 @@ jobs: - --includepaths '${{ join('+', path.include) }}' - ${{ if ne(path.exclude[0], '') }}: - --excludepaths '${{ join('+', path.exclude) }}' + + - template: evaluate-changed-darc-deps.yml + parameters: + arguments: + # The commit that we're building is always a merge commit that is merging into the target branch. + # So the first parent of the commit is on the target branch and the second parent is on the source branch. + - --difftarget HEAD^1 diff --git a/eng/pipelines/common/templates/wasm-build-only.yml b/eng/pipelines/common/templates/wasm-build-only.yml index 0f9dfb22e7720b..cfb6f40f09532e 100644 --- a/eng/pipelines/common/templates/wasm-build-only.yml +++ b/eng/pipelines/common/templates/wasm-build-only.yml @@ -39,6 +39,7 @@ jobs: condition: >- or( eq(variables['alwaysRunVar'], true), + eq(variables['wasmDarcDependenciesChanged'], true), eq(dependencies.evaluate_paths.outputs['SetPathVars_libraries.containsChange'], true), eq(dependencies.evaluate_paths.outputs['SetPathVars_allwasm.containsChange'], true), eq(dependencies.evaluate_paths.outputs['SetPathVars_mono.containsChange'], true)) diff --git a/eng/pipelines/common/templates/wasm-build-tests.yml b/eng/pipelines/common/templates/wasm-build-tests.yml index 369624c0adcb74..c9c8689fa671e3 100644 --- a/eng/pipelines/common/templates/wasm-build-tests.yml +++ b/eng/pipelines/common/templates/wasm-build-tests.yml @@ -34,6 +34,7 @@ jobs: condition: >- or( eq(variables['alwaysRunVar'], true), + eq(variables['wasmDarcDependenciesChanged'], true), eq(dependencies.evaluate_paths.outputs['SetPathVars_allwasm.containsChange'], true), eq(dependencies.evaluate_paths.outputs['SetPathVars_wasmbuildtests.containsChange'], true)) # extra steps, run tests diff --git a/eng/pipelines/common/templates/wasm-debugger-tests.yml b/eng/pipelines/common/templates/wasm-debugger-tests.yml index 3bb39b74b6a17a..5ac7c3ee5dccf8 100644 --- a/eng/pipelines/common/templates/wasm-debugger-tests.yml +++ b/eng/pipelines/common/templates/wasm-debugger-tests.yml @@ -33,6 +33,7 @@ jobs: condition: >- or( eq(variables['alwaysRunVar'], true), + eq(variables['wasmDarcDependenciesChanged'], true), eq(dependencies.evaluate_paths.outputs['SetPathVars_allwasm.containsChange'], true), eq(dependencies.evaluate_paths.outputs['SetPathVars_wasmdebuggertests.containsChange'], true)) # extra steps, run tests diff --git a/eng/pipelines/common/templates/wasm-library-tests.yml b/eng/pipelines/common/templates/wasm-library-tests.yml index 365ea7386d4004..ada2484fedc375 100644 --- a/eng/pipelines/common/templates/wasm-library-tests.yml +++ b/eng/pipelines/common/templates/wasm-library-tests.yml @@ -44,6 +44,7 @@ jobs: condition: >- or( eq(variables['alwaysRunVar'], true), + eq(variables['wasmDarcDependenciesChanged'], true), eq(dependencies.evaluate_paths.outputs['SetPathVars_libraries.containsChange'], true), eq(dependencies.evaluate_paths.outputs['SetPathVars_allwasm.containsChange'], true), eq(dependencies.evaluate_paths.outputs['SetPathVars_mono.containsChange'], true)) diff --git a/eng/pipelines/common/templates/wasm-runtime-tests.yml b/eng/pipelines/common/templates/wasm-runtime-tests.yml index e6bb45d98887ce..37154b3c6e86d4 100644 --- a/eng/pipelines/common/templates/wasm-runtime-tests.yml +++ b/eng/pipelines/common/templates/wasm-runtime-tests.yml @@ -34,6 +34,7 @@ jobs: condition: >- or( eq(variables['alwaysRunVar'], true), + eq(variables['wasmDarcDependenciesChanged'], true), eq(dependencies.evaluate_paths.outputs['SetPathVars_mono.containsChange'], true), eq(dependencies.evaluate_paths.outputs['SetPathVars_allwasm.containsChange'], true), eq(dependencies.evaluate_paths.outputs['SetPathVars_runtimetests.containsChange'], true)) diff --git a/eng/pipelines/common/xplat-setup.yml b/eng/pipelines/common/xplat-setup.yml index 25314635d9b444..9d45d7cddf15d8 100644 --- a/eng/pipelines/common/xplat-setup.yml +++ b/eng/pipelines/common/xplat-setup.yml @@ -108,6 +108,14 @@ jobs: ${{ if eq(parameters.jobParameters.runtimeFlavor, 'coreclr') }}: value: CoreCLR + - name: wasmDarcDependenciesChanged + ${{ if eq(parameters.archType, 'wasm') }}: + value: $[ or( + eq(dependencies.evaluate_paths.outputs['DarcDependenciesChanged.Microsoft_NET_Workload_Emscripten_Manifest-7_0_100'], true), + eq(dependencies.evaluate_paths.outputs['DarcDependenciesChanged.Microsoft_DotNet_Build_Tasks_Workloads'], true), + eq(dependencies.evaluate_paths.outputs['DarcDependenciesChanged.System_Runtime_TimeZoneData'], true), + eq(dependencies.evaluate_paths.outputs['DarcDependenciesChanged.Microsoft_NET_ILLink_Tasks'], true)) ] + - ${{ each variable in parameters.variables }}: - ${{ variable }} diff --git a/eng/pipelines/evaluate-changed-darc-deps.sh b/eng/pipelines/evaluate-changed-darc-deps.sh new file mode 100755 index 00000000000000..b4181a1e503474 --- /dev/null +++ b/eng/pipelines/evaluate-changed-darc-deps.sh @@ -0,0 +1,98 @@ +#!/usr/bin/env bash +: ' + Compares contents of `env/Version.Details.xml` between HEAD and difftarget, and emits variables named for + dependencies that satisfy either of: + 1. version, or sha changed + 2. it is missing from one of the xmls + + The dependency names have `.` replaced with `_`. + + In order to consume these variables in a yaml pipeline, reference them via: $[ dependencies..outputs["."] ] + + Example: + -difftarget ''HEAD^1'' +' + +# Disable globbing in this bash script since we iterate over path patterns +set -f + +# Stop script if unbound variable found (use ${var:-} if intentional) +set -u + +# Stop script if command returns non-zero exit code. +# Prevents hidden errors caused by missing error code propagation. +set -e + +usage() +{ + echo "Script that emits an azure devops variable with all the dependencies that changed in 'eng/Version.Details.xml' contained in the current HEAD against the difftarget" + echo " --difftarget SHA or branch to diff against. (i.e: HEAD^1, origin/main, 0f4hd36, etc.)" + echo " --azurevariableprefix Name of azure devops variable to create if change meets filter criteria" + echo "" + + echo "Arguments can also be passed in with a single hyphen." +} + +source="${BASH_SOURCE[0]}" + +# resolve $source until the file is no longer a symlink +while [[ -h "$source" ]]; do + scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + source="$(readlink "$source")" + # if $source was a relative symlink, we need to resolve it relative to the path where the + # symlink file was located + [[ $source != /* ]] && source="$scriptroot/$source" +done + +scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" +eng_root=`cd -P "$scriptroot/.." && pwd` + +azure_variable_prefix='' +diff_target='' + +while [[ $# > 0 ]]; do + opt="$(echo "${1/#--/-}" | tr "[:upper:]" "[:lower:]")" + case "$opt" in + -help|-h) + usage + exit 0 + ;; + -difftarget) + diff_target=$2 + shift + ;; + -azurevariableprefix) + azure_variable_prefix=$2 + shift + ;; + esac + + shift +done + +if [[ -z "$diff_target" ]]; then + echo "Argument -difftarget is required" + usage + exit 1 +fi + +oldXmlPath=`mktemp` + +ci=true # Needed in order to use pipeline-logging-functions.sh +. "$eng_root/common/pipeline-logging-functions.sh" + +git show $diff_target:eng/Version.Details.xml > $oldXmlPath +# FIXME: errors? +changed_deps=$(python3 "$eng_root/pipelines/get-changed-darc-deps.py" $oldXmlPath eng/Version.Details.xml) +rm -f $oldXmlPath + +if [[ -n "$azure_variable_prefix" ]]; then + azure_variable_prefix="${azure_variable_prefix}_" +fi + +for dep in $changed_deps; do + dep=`echo $dep | tr \. _` + var_name=${azure_variable_prefix}${dep} + echo "Setting pipeline variable $var_name=true" + Write-PipelineSetVariable -name $var_name -value true +done diff --git a/eng/pipelines/get-changed-darc-deps.py b/eng/pipelines/get-changed-darc-deps.py new file mode 100644 index 00000000000000..cae52445eaa0a3 --- /dev/null +++ b/eng/pipelines/get-changed-darc-deps.py @@ -0,0 +1,62 @@ +# +# Emits a comma separated list of dependencies from `eng/Version.Details.xml` +# that changed as compared to another versions file +# +# - we don't really care which is old, and which is new +# - A dependency name is emitted as changed if: +# 1. version, or sha changed +# 2. it is missing from one of the xmls + +import xml.etree.ElementTree as ET +import sys +from os.path import exists + +def getDependencies(xmlfile): + tree = ET.parse(xmlfile) + root = tree.getroot() + deps = {} + for depElement in root.findall('.//Dependency'): + dep = {} + dep['Version'] = depElement.attrib['Version'] + dep['Sha'] = depElement.find('Sha').text + + deps[depElement.attrib['Name']] = dep + + return deps + +def compare(dict1, dict2): + if dict1 is None or dict2 is None: + print('Nones') + return False + + if (not isinstance(dict1, dict)) or (not isinstance(dict2, dict)): + print('Not dict') + return False + + changed_names = [] + all_keys = set(dict1.keys()) | set(dict2.keys()) + for key in all_keys: + if key not in dict1 or key not in dict2: + print(key) + # changed_names.append(key) + elif dict1[key] != dict2[key]: + print(key) + # changed_names.append(key) + + print(','.join(changed_names)) + +if len(sys.argv) != 3: + print(f'Usage: {sys.argv[0]} ') + exit(1) + +if not exists(sys.argv[1]): + print(f'Cannot find {sys.argv[1]}') + exit(1) +if not exists(sys.argv[2]): + print(f'Cannot find {sys.argv[2]}') + exit(1) + +newDeps = getDependencies(sys.argv[1]) +oldDeps = getDependencies(sys.argv[2]) + +compare(oldDeps, newDeps) From 6110ab6266c5934ce38c8181ce3a33d8e1809ba1 Mon Sep 17 00:00:00 2001 From: Alan Hayward Date: Tue, 9 Aug 2022 22:19:55 +0100 Subject: [PATCH 27/41] Add lowering support for conditional nodes (#71705) * Add lowering support for conditional nodes * Contain conditionals * Fix formatting * Use AND and CMP nodes * Fix formatting * Remove LowerNodeCC changes Change-Id: Icdf1828905c4a3f210b06e047ef3c0ac913d71f7 CustomizedGitHooks: yes * Remove asserts & fix variable names * Better contain checks for conditional compares * Simpler contained conditions codegen * Remove Conditional Compare nodes * Minor cleanups * Generate AND compare chains * Fix unsigned compares && reduce chain check recursion * Add compare chain tests * Review fixes * Use GenCondition * Change CompareChainSize to IsValidCompareChain * Move lowering functions to lowerarmarch * Formatting fixes * Fix SELECT issues * Fix test output messages. * Better explanations for AND chains * Compare chains should not contain tst compares Change-Id: I8a1761e1e89f589e1daf0318e120aae5dd3d7241 CustomizedGitHooks: yes * Don't allow tst compares in codegeneration of compare chains * Add tests for chains with tst compares * Don't allow tst compares in lsrabuild of compare chains --- src/coreclr/jit/codegen.h | 9 +- src/coreclr/jit/codegenarm64.cpp | 352 +++++++++++------ src/coreclr/jit/codegenarmarch.cpp | 13 +- src/coreclr/jit/codegenlinear.cpp | 17 +- src/coreclr/jit/compiler.h | 22 ++ src/coreclr/jit/compiler.hpp | 15 + src/coreclr/jit/gentree.cpp | 93 ++++- src/coreclr/jit/gentree.h | 55 +-- src/coreclr/jit/gtlist.h | 6 - src/coreclr/jit/gtstructs.h | 2 +- src/coreclr/jit/instr.cpp | 55 +-- src/coreclr/jit/lower.cpp | 24 +- src/coreclr/jit/lower.h | 8 +- src/coreclr/jit/lowerarmarch.cpp | 216 +++++++++++ src/coreclr/jit/lsraarm64.cpp | 8 + src/coreclr/jit/lsrabuild.cpp | 5 +- .../JIT/opt/Compares/compareAnd2Chains.cs | 367 ++++++++++++++++++ .../JIT/opt/Compares/compareAnd2Chains.csproj | 12 + .../JIT/opt/Compares/compareAnd3Chains.cs | 367 ++++++++++++++++++ .../JIT/opt/Compares/compareAnd3Chains.csproj | 12 + .../JIT/opt/Compares/compareAndTestChains.cs | 367 ++++++++++++++++++ .../opt/Compares/compareAndTestChains.csproj | 12 + 22 files changed, 1790 insertions(+), 247 deletions(-) create mode 100644 src/tests/JIT/opt/Compares/compareAnd2Chains.cs create mode 100644 src/tests/JIT/opt/Compares/compareAnd2Chains.csproj create mode 100644 src/tests/JIT/opt/Compares/compareAnd3Chains.cs create mode 100644 src/tests/JIT/opt/Compares/compareAnd3Chains.csproj create mode 100644 src/tests/JIT/opt/Compares/compareAndTestChains.cs create mode 100644 src/tests/JIT/opt/Compares/compareAndTestChains.csproj diff --git a/src/coreclr/jit/codegen.h b/src/coreclr/jit/codegen.h index 2242b512c14228..973a0ad4422141 100644 --- a/src/coreclr/jit/codegen.h +++ b/src/coreclr/jit/codegen.h @@ -1050,7 +1050,9 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX void genCkfinite(GenTree* treeNode); void genCodeForCompare(GenTreeOp* tree); #ifdef TARGET_ARM64 - void genCodeForConditional(GenTreeConditional* tree); + void genCodeForConditionalCompare(GenTreeOp* tree, GenCondition prevCond); + void genCodeForContainedCompareChain(GenTree* tree, bool* inchain, GenCondition* prevCond); + void genCodeForSelect(GenTreeConditional* tree); #endif void genIntrinsic(GenTree* treeNode); void genPutArgStk(GenTreePutArgStk* treeNode); @@ -1715,9 +1717,8 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX #endif // TARGET_XARCH #ifdef TARGET_ARM64 - static insCond InsCondForCompareOp(GenTree* tree); - static insCond InvertInsCond(insCond cond); - static insCflags InsCflagsForCcmp(insCond cond); + static insCflags InsCflagsForCcmp(GenCondition cond); + static insCond JumpKindToInsCond(emitJumpKind condition); #endif #ifndef TARGET_LOONGARCH64 diff --git a/src/coreclr/jit/codegenarm64.cpp b/src/coreclr/jit/codegenarm64.cpp index 058f8a4b569624..6850014a1d710a 100644 --- a/src/coreclr/jit/codegenarm64.cpp +++ b/src/coreclr/jit/codegenarm64.cpp @@ -2513,6 +2513,9 @@ void CodeGen::genCodeForBinary(GenTreeOp* tree) GenTree* op1 = tree->gtGetOp1(); GenTree* op2 = tree->gtGetOp2(); + // The arithmetic node must be sitting in a register (since it's not contained) + assert(targetReg != REG_NA); + // Handles combined operations: 'madd', 'msub' if (op2->OperIs(GT_MUL) && op2->isContained()) { @@ -2552,6 +2555,35 @@ void CodeGen::genCodeForBinary(GenTreeOp* tree) return; } + if (tree->OperIs(GT_AND) && op2->isContainedAndNotIntOrIImmed()) + { + GenCondition cond; + bool chain = false; + + JITDUMP("Generating compare chain:\n"); + if (op1->isContained()) + { + // Generate Op1 into flags. + genCodeForContainedCompareChain(op1, &chain, &cond); + assert(chain); + } + else + { + // Op1 is not contained, move it from a register into flags. + emit->emitIns_R_I(INS_cmp, emitActualTypeSize(op1), op1->GetRegNum(), 0); + cond = GenCondition::NE; + chain = true; + } + // Gen Op2 into flags. + genCodeForContainedCompareChain(op2, &chain, &cond); + assert(chain); + + // Move the result from flags into a register. + inst_SETCC(cond, tree->TypeGet(), targetReg); + genProduceReg(tree); + return; + } + instruction ins = genGetInsForOper(tree->OperGet(), targetType); if ((tree->gtFlags & GTF_SET_FLAGS) != 0) @@ -2575,9 +2607,6 @@ void CodeGen::genCodeForBinary(GenTreeOp* tree) } } - // The arithmetic node must be sitting in a register (since it's not contained) - assert(targetReg != REG_NA); - regNumber r = emit->emitInsTernary(ins, emitActualTypeSize(tree), tree, op1, op2); assert(r == targetReg); @@ -4361,8 +4390,6 @@ void CodeGen::genCodeForCompare(GenTreeOp* tree) assert(!op1->isUsedFromMemory()); - genConsumeOperands(tree); - emitAttr cmpSize = EA_ATTR(genTypeSize(op1Type)); assert(genTypeSize(op1Type) == genTypeSize(op2Type)); @@ -4412,12 +4439,121 @@ void CodeGen::genCodeForCompare(GenTreeOp* tree) } //------------------------------------------------------------------------ -// genCodeForCompare: Produce code for a GT_CEQ/GT_CNE node. +// genCodeForConditionalCompare: Produce code for a compare that's dependent on a previous compare. +// +// Arguments: +// tree - a compare node (GT_EQ etc) +// cond - the condition of the previous generated compare. +// +void CodeGen::genCodeForConditionalCompare(GenTreeOp* tree, GenCondition prevCond) +{ + emitter* emit = GetEmitter(); + + GenTree* op1 = tree->gtGetOp1(); + GenTree* op2 = tree->gtGetOp2(); + var_types op1Type = genActualType(op1->TypeGet()); + var_types op2Type = genActualType(op2->TypeGet()); + emitAttr cmpSize = EA_ATTR(genTypeSize(op1Type)); + regNumber targetReg = tree->GetRegNum(); + regNumber srcReg1 = op1->GetRegNum(); + + // No float support or swapping op1 and op2 to generate cmp reg, imm. + assert(!varTypeIsFloating(op2Type)); + assert(!op1->isContainedIntOrIImmed()); + + // Should only be called on contained nodes. + assert(targetReg == REG_NA); + + // For the ccmp flags, invert the condition of the compare. + insCflags cflags = InsCflagsForCcmp(GenCondition::FromRelop(tree)); + + // For the condition, use the previous compare. + const GenConditionDesc& prevDesc = GenConditionDesc::Get(prevCond); + insCond prevInsCond = JumpKindToInsCond(prevDesc.jumpKind1); + + if (op2->isContainedIntOrIImmed()) + { + GenTreeIntConCommon* intConst = op2->AsIntConCommon(); + emit->emitIns_R_I_FLAGS_COND(INS_ccmp, cmpSize, srcReg1, (int)intConst->IconValue(), cflags, prevInsCond); + } + else + { + regNumber srcReg2 = op2->GetRegNum(); + emit->emitIns_R_R_FLAGS_COND(INS_ccmp, cmpSize, srcReg1, srcReg2, cflags, prevInsCond); + } +} + +//------------------------------------------------------------------------ +// genCodeForContainedCompareChain: Produce code for a chain of conditional compares. +// +// Only generates for contained nodes. Nodes that are not contained are assumed to be +// generated as part of standard tree generation. +// +// Arguments: +// tree - the node. Either a compare or a tree of compares connected by ANDs. +// inChain - whether a contained chain is in progress. +// prevCond - If a chain is in progress, the condition of the previous compare. +// Return: +// The last compare node generated. +// +void CodeGen::genCodeForContainedCompareChain(GenTree* tree, bool* inChain, GenCondition* prevCond) +{ + assert(tree->isContained()); + + if (tree->OperIs(GT_AND)) + { + GenTree* op1 = tree->gtGetOp1(); + GenTree* op2 = tree->gtGetOp2(); + + assert(op2->isContained()); + + // If Op1 is contained, generate into flags. Otherwise, move the result into flags. + if (op1->isContained()) + { + genCodeForContainedCompareChain(op1, inChain, prevCond); + assert(*inChain); + } + else + { + emitter* emit = GetEmitter(); + emit->emitIns_R_I(INS_cmp, emitActualTypeSize(op1), op1->GetRegNum(), 0); + *prevCond = GenCondition::NE; + *inChain = true; + } + + // Generate Op2 based on Op1. + genCodeForContainedCompareChain(op2, inChain, prevCond); + assert(*inChain); + } + else + { + assert(tree->OperIsCmpCompare()); + + // Generate the compare, putting the result in the flags register. + if (!*inChain) + { + // First item in a chain. Use a standard compare. + genCodeForCompare(tree->AsOp()); + } + else + { + // Within the chain. Use a conditional compare (which is + // dependent on the previous emitted compare). + genCodeForConditionalCompare(tree->AsOp(), *prevCond); + } + + *inChain = true; + *prevCond = GenCondition::FromRelop(tree); + } +} + +//------------------------------------------------------------------------ +// genCodeForSelect: Produce code for a GT_SELECT node. // // Arguments: // tree - the node // -void CodeGen::genCodeForConditional(GenTreeConditional* tree) +void CodeGen::genCodeForSelect(GenTreeConditional* tree) { emitter* emit = GetEmitter(); @@ -4427,42 +4563,34 @@ void CodeGen::genCodeForConditional(GenTreeConditional* tree) var_types op1Type = genActualType(op1->TypeGet()); var_types op2Type = genActualType(op2->TypeGet()); emitAttr cmpSize = EA_ATTR(genTypeSize(op1Type)); - insCond cond = InsCondForCompareOp(opcond); assert(!op1->isUsedFromMemory()); assert(genTypeSize(op1Type) == genTypeSize(op2Type)); - regNumber targetReg = tree->GetRegNum(); - regNumber srcReg1 = genConsumeReg(op1); - - if (tree->OperIs(GT_SELECT)) + GenCondition prevCond; + genConsumeRegs(opcond); + if (opcond->isContained()) { - regNumber srcReg2 = genConsumeReg(op2); - emit->emitIns_R_R_R_COND(INS_csel, cmpSize, targetReg, srcReg1, srcReg2, cond); - regSet.verifyRegUsed(targetReg); + // Generate the contained condition. + bool chain = false; + JITDUMP("Generating compare chain:\n"); + genCodeForContainedCompareChain(opcond, &chain, &prevCond); + assert(chain); } else { - assert(!varTypeIsFloating(op2Type)); - // We don't support swapping op1 and op2 to generate cmp reg, imm. - assert(!op1->isContainedIntOrIImmed()); - // This should not be generating into a register. - assert(targetReg == REG_NA); + // Condition has been generated into a register - move it into flags. + emit->emitIns_R_I(INS_cmp, emitActualTypeSize(opcond), opcond->GetRegNum(), 0); + prevCond = GenCondition::NE; + } - // For the ccmp flags, get the condition of the compare. - insCflags cflags = InsCflagsForCcmp(InsCondForCompareOp(tree)); + regNumber targetReg = tree->GetRegNum(); + regNumber srcReg1 = genConsumeReg(op1); + regNumber srcReg2 = genConsumeReg(op2); + const GenConditionDesc& prevDesc = GenConditionDesc::Get(prevCond); - if (op2->isContainedIntOrIImmed()) - { - GenTreeIntConCommon* intConst = op2->AsIntConCommon(); - emit->emitIns_R_I_FLAGS_COND(INS_ccmp, cmpSize, srcReg1, (int)intConst->IconValue(), cflags, cond); - } - else - { - regNumber srcReg2 = genConsumeReg(op2); - emit->emitIns_R_R_FLAGS_COND(INS_ccmp, cmpSize, srcReg1, srcReg2, cflags, cond); - } - } + emit->emitIns_R_R_R_COND(INS_csel, cmpSize, targetReg, srcReg1, srcReg2, JumpKindToInsCond(prevDesc.jumpKind1)); + regSet.verifyRegUsed(targetReg); } //------------------------------------------------------------------------ @@ -10435,90 +10563,6 @@ void CodeGen::genCodeForCond(GenTreeOp* tree) genProduceReg(tree); } -//------------------------------------------------------------------------ -// InsCondForCompareOp: Map the condition in a Compare/Conditional op to a insCond. -// -// Arguments: -// tree - the node -// -insCond CodeGen::InsCondForCompareOp(GenTree* tree) -{ - assert(tree->OperIsCompare() || tree->OperIsConditionalCompare()); - - if (tree->OperIsCompare()) - { - switch (tree->AsOp()->OperGet()) - { - case GT_EQ: - case GT_TEST_EQ: - return INS_COND_EQ; - case GT_NE: - case GT_TEST_NE: - return INS_COND_NE; - case GT_GE: - return INS_COND_GE; - case GT_GT: - return INS_COND_GT; - case GT_LT: - return INS_COND_LT; - case GT_LE: - return INS_COND_LE; - default: - assert(false && "Invalid condition"); - return INS_COND_EQ; - } - } - else - { - switch (tree->AsConditional()->OperGet()) - { - case GT_CEQ: - return INS_COND_EQ; - case GT_CNE: - return INS_COND_NE; - case GT_CGE: - return INS_COND_GE; - case GT_CGT: - return INS_COND_GT; - case GT_CLT: - return INS_COND_LT; - case GT_CLE: - return INS_COND_LE; - default: - assert(false && "Invalid condition"); - return INS_COND_EQ; - } - } -} - -//------------------------------------------------------------------------ -// InvertInsCond: Invert an insCond -// -// Arguments: -// cond - the insCond. -// -insCond CodeGen::InvertInsCond(insCond cond) -{ - switch (cond) - { - case INS_COND_EQ: - return INS_COND_NE; - case INS_COND_NE: - return INS_COND_EQ; - case INS_COND_GE: - return INS_COND_LT; - case INS_COND_GT: - return INS_COND_LE; - case INS_COND_LT: - return INS_COND_GE; - case INS_COND_LE: - return INS_COND_GT; - default: - assert(false && "Invalid condition"); - return INS_COND_EQ; - } -} - //------------------------------------------------------------------------ // InsCflagsForCcmp: Get the Cflags for a required for a CCMP instruction. // @@ -10530,28 +10574,82 @@ insCond CodeGen::InvertInsCond(insCond cond) // Given COND, this function returns A. // // Arguments: -// cond - the insCond. +// cond - the GenCondition. // -insCflags CodeGen::InsCflagsForCcmp(insCond cond) +insCflags CodeGen::InsCflagsForCcmp(GenCondition cond) { - switch (InvertInsCond(cond)) + GenCondition inverted = GenCondition::Reverse(cond); + switch (inverted.GetCode()) { - case INS_COND_EQ: + case GenCondition::EQ: return INS_FLAGS_Z; - case INS_COND_NE: + case GenCondition::NE: return INS_FLAGS_NONE; - case INS_COND_GE: + case GenCondition::SGE: return INS_FLAGS_Z; - case INS_COND_GT: + case GenCondition::SGT: return INS_FLAGS_NONE; - case INS_COND_LT: + case GenCondition::SLT: return INS_FLAGS_NC; - case INS_COND_LE: + case GenCondition::SLE: return INS_FLAGS_NZC; + case GenCondition::UGE: + return INS_FLAGS_C; + case GenCondition::UGT: + return INS_FLAGS_C; + case GenCondition::ULT: + return INS_FLAGS_NONE; + case GenCondition::ULE: + return INS_FLAGS_Z; default: - assert(false && "Invalid condition"); + NO_WAY("unexpected condition type"); return INS_FLAGS_NONE; } } +//------------------------------------------------------------------------ +// JumpKindToInsCond: Convert a Jump Kind to a condition. +// +// Arguments: +// condition - the emitJumpKind. +// +insCond CodeGen::JumpKindToInsCond(emitJumpKind condition) +{ + /* Convert the condition to an insCond value */ + switch (condition) + { + case EJ_eq: + return INS_COND_EQ; + case EJ_ne: + return INS_COND_NE; + case EJ_hs: + return INS_COND_HS; + case EJ_lo: + return INS_COND_LO; + case EJ_mi: + return INS_COND_MI; + case EJ_pl: + return INS_COND_PL; + case EJ_vs: + return INS_COND_VS; + case EJ_vc: + return INS_COND_VC; + case EJ_hi: + return INS_COND_HI; + case EJ_ls: + return INS_COND_LS; + case EJ_ge: + return INS_COND_GE; + case EJ_lt: + return INS_COND_LT; + case EJ_gt: + return INS_COND_GT; + case EJ_le: + return INS_COND_LE; + default: + NO_WAY("unexpected condition type"); + return INS_COND_EQ; + } +} + #endif // TARGET_ARM64 diff --git a/src/coreclr/jit/codegenarmarch.cpp b/src/coreclr/jit/codegenarmarch.cpp index 69c85482637811..536797eaba9593 100644 --- a/src/coreclr/jit/codegenarmarch.cpp +++ b/src/coreclr/jit/codegenarmarch.cpp @@ -363,19 +363,18 @@ void CodeGen::genCodeForTreeNode(GenTree* treeNode) #ifdef TARGET_ARM64 case GT_TEST_EQ: case GT_TEST_NE: + // On ARM64 genCodeForCompare does not consume its own operands because + // genCodeForBinary also has this behavior and it can end up calling + // genCodeForCompare when generating compare chains for GT_AND. + // Thus, we must do it here. + genConsumeOperands(treeNode->AsOp()); #endif // TARGET_ARM64 genCodeForCompare(treeNode->AsOp()); break; #ifdef TARGET_ARM64 case GT_SELECT: - case GT_CEQ: - case GT_CNE: - case GT_CLT: - case GT_CLE: - case GT_CGE: - case GT_CGT: - genCodeForConditional(treeNode->AsConditional()); + genCodeForSelect(treeNode->AsConditional()); break; #endif diff --git a/src/coreclr/jit/codegenlinear.cpp b/src/coreclr/jit/codegenlinear.cpp index 8045344441020f..e3ac21ba504a27 100644 --- a/src/coreclr/jit/codegenlinear.cpp +++ b/src/coreclr/jit/codegenlinear.cpp @@ -1629,6 +1629,12 @@ void CodeGen::genConsumeRegs(GenTree* tree) assert(cast->isContained()); genConsumeAddress(cast->CastOp()); } + else if (tree->OperIsCmpCompare() || tree->OperIs(GT_AND)) + { + // Compares and ANDs may be contained in a conditional chain. + genConsumeRegs(tree->gtGetOp1()); + genConsumeRegs(tree->gtGetOp2()); + } #endif else if (tree->OperIsLocalRead()) { @@ -2630,16 +2636,7 @@ void CodeGen::genCodeForJumpTrue(GenTreeOp* jtrue) assert(compiler->compCurBB->bbJumpKind == BBJ_COND); assert(jtrue->OperIs(GT_JTRUE)); - GenTreeOp* relop; - if (jtrue->gtGetOp1()->OperIsCompare()) - { - relop = jtrue->gtGetOp1()->AsOp(); - } - else - { - assert(jtrue->gtGetOp1()->OperIsConditionalCompare()); - relop = jtrue->gtGetOp1()->AsConditional(); - } + GenTreeOp* relop = jtrue->gtGetOp1()->AsOp(); GenCondition condition = GenCondition::FromRelop(relop); if (condition.PreferSwap()) diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index e1aef6f7e6f34d..4f0f8e188a2a96 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -10991,6 +10991,28 @@ class GenTreeVisitor break; #endif // defined(FEATURE_SIMD) || defined(FEATURE_HW_INTRINSICS) + case GT_SELECT: + { + GenTreeConditional* const conditional = node->AsConditional(); + + result = WalkTree(&conditional->gtCond, conditional); + if (result == fgWalkResult::WALK_ABORT) + { + return result; + } + result = WalkTree(&conditional->gtOp1, conditional); + if (result == fgWalkResult::WALK_ABORT) + { + return result; + } + result = WalkTree(&conditional->gtOp2, conditional); + if (result == fgWalkResult::WALK_ABORT) + { + return result; + } + break; + } + // Binary nodes default: { diff --git a/src/coreclr/jit/compiler.hpp b/src/coreclr/jit/compiler.hpp index 7f0c5c5686fd06..63b83aa8eb61d1 100644 --- a/src/coreclr/jit/compiler.hpp +++ b/src/coreclr/jit/compiler.hpp @@ -4393,6 +4393,21 @@ void GenTree::VisitOperands(TVisitor visitor) return; } + case GT_SELECT: + { + GenTreeConditional* const cond = this->AsConditional(); + if (visitor(cond->gtCond) == VisitResult::Abort) + { + return; + } + if (visitor(cond->gtOp1) == VisitResult::Abort) + { + return; + } + visitor(cond->gtOp2); + return; + } + // Binary nodes default: assert(this->OperIsBinary()); diff --git a/src/coreclr/jit/gentree.cpp b/src/coreclr/jit/gentree.cpp index c2f4959964b6eb..5338baccb0147a 100644 --- a/src/coreclr/jit/gentree.cpp +++ b/src/coreclr/jit/gentree.cpp @@ -5790,6 +5790,22 @@ unsigned Compiler::gtSetEvalOrder(GenTree* tree) costSz += tree->AsStoreDynBlk()->gtDynamicSize->GetCostSz(); break; + case GT_SELECT: + level = gtSetEvalOrder(tree->AsConditional()->gtCond); + costEx = tree->AsConditional()->gtCond->GetCostEx(); + costSz = tree->AsConditional()->gtCond->GetCostSz(); + + lvl2 = gtSetEvalOrder(tree->AsConditional()->gtOp1); + level = max(level, lvl2); + costEx += tree->AsConditional()->gtOp1->GetCostEx(); + costSz += tree->AsConditional()->gtOp1->GetCostSz(); + + lvl2 = gtSetEvalOrder(tree->AsConditional()->gtOp2); + level = max(level, lvl2); + costEx += tree->AsConditional()->gtOp2->GetCostEx(); + costSz += tree->AsConditional()->gtOp2->GetCostSz(); + break; + default: JITDUMP("unexpected operator in this tree:\n"); DISPTREE(tree); @@ -6182,6 +6198,27 @@ bool GenTree::TryGetUse(GenTree* operand, GenTree*** pUse) return false; } + case GT_SELECT: + { + GenTreeConditional* const conditional = this->AsConditional(); + if (operand == conditional->gtCond) + { + *pUse = &conditional->gtCond; + return true; + } + if (operand == conditional->gtOp1) + { + *pUse = &conditional->gtOp1; + return true; + } + if (operand == conditional->gtOp2) + { + *pUse = &conditional->gtOp2; + return true; + } + return false; + } + // Binary nodes default: assert(this->OperIsBinary()); @@ -8710,6 +8747,13 @@ GenTree* Compiler::gtCloneExpr( gtCloneExpr(tree->AsStoreDynBlk()->gtDynamicSize, addFlags, deepVarNum, deepVarVal)); break; + case GT_SELECT: + copy = new (this, oper) + GenTreeConditional(oper, tree->TypeGet(), + gtCloneExpr(tree->AsConditional()->gtCond, addFlags, deepVarNum, deepVarVal), + gtCloneExpr(tree->AsConditional()->gtOp1, addFlags, deepVarNum, deepVarVal), + gtCloneExpr(tree->AsConditional()->gtOp2, addFlags, deepVarNum, deepVarVal)); + break; default: #ifdef DEBUG gtDispTree(tree); @@ -9369,6 +9413,12 @@ GenTreeUseEdgeIterator::GenTreeUseEdgeIterator(GenTree* node) AdvanceCall(); return; + case GT_SELECT: + m_edge = &m_node->AsConditional()->gtCond; + assert(*m_edge != nullptr); + m_advance = &GenTreeUseEdgeIterator::AdvanceConditional; + return; + // Binary nodes default: assert(m_node->OperIsBinary()); @@ -9501,6 +9551,29 @@ void GenTreeUseEdgeIterator::AdvancePhi() } } +//------------------------------------------------------------------------ +// GenTreeUseEdgeIterator::AdvanceConditional: produces the next operand of a conditional node and advances the state. +// +void GenTreeUseEdgeIterator::AdvanceConditional() +{ + GenTreeConditional* const conditional = m_node->AsConditional(); + switch (m_state) + { + case 0: + m_edge = &conditional->gtOp1; + m_state = 1; + break; + case 1: + m_edge = &conditional->gtOp2; + m_advance = &GenTreeUseEdgeIterator::Terminate; + break; + default: + unreached(); + } + + assert(*m_edge != nullptr); +} + //------------------------------------------------------------------------ // GenTreeUseEdgeIterator::AdvanceBinOp: produces the next operand of a binary node and advances the state. // @@ -10372,6 +10445,7 @@ void Compiler::gtDispNode(GenTree* tree, IndentStack* indentStack, _In_ _In_opt_ case GT_GT: case GT_TEST_EQ: case GT_TEST_NE: + case GT_SELECT: if (tree->gtFlags & GTF_RELOP_NAN_UN) { printf("N"); @@ -12017,6 +12091,17 @@ void Compiler::gtDispTree(GenTree* tree, } break; + case GT_SELECT: + gtDispCommonEndLine(tree); + + if (!topOnly) + { + gtDispChild(tree->AsConditional()->gtCond, indentStack, IIArc, childMsg, topOnly); + gtDispChild(tree->AsConditional()->gtOp1, indentStack, IIArc, childMsg, topOnly); + gtDispChild(tree->AsConditional()->gtOp2, indentStack, IIArcBottom, childMsg, topOnly); + } + break; + default: printf(" :"); printf(""); // null string means flush @@ -16719,14 +16804,6 @@ bool GenTree::isContained() const assert(!isMarkedContained); } - // these actually produce a register (the flags reg, we just don't model it) - // and are a separate instruction from the branch that consumes the result. - // They can only produce a result if the child is a SIMD equality comparison. - else if (OperIsCompare()) - { - assert(isMarkedContained == false); - } - // if it's contained it can't be unused. if (isMarkedContained) { diff --git a/src/coreclr/jit/gentree.h b/src/coreclr/jit/gentree.h index 4b3f1233d4a035..81c0c181bfa3ed 100644 --- a/src/coreclr/jit/gentree.h +++ b/src/coreclr/jit/gentree.h @@ -915,6 +915,12 @@ struct GenTree return isContained() && IsCnsIntOrI() && !isUsedFromSpillTemp(); } + // Node is contained, but it isn't contained due to being a containable int. + bool isContainedAndNotIntOrIImmed() const + { + return isContained() && !isContainedIntOrIImmed(); + } + bool isContainedFltOrDblImmed() const { return isContained() && OperIs(GT_CNS_DBL); @@ -1088,8 +1094,8 @@ struct GenTree if (gtType == TYP_VOID) { // These are the only operators which can produce either VOID or non-VOID results. - assert(OperIs(GT_NOP, GT_CALL, GT_COMMA) || OperIsCompare() || OperIsConditionalCompare() || OperIsLong() || - OperIsSimdOrHWintrinsic() || IsCnsVec()); + assert(OperIs(GT_NOP, GT_CALL, GT_COMMA) || OperIsCompare() || OperIsLong() || OperIsSimdOrHWintrinsic() || + IsCnsVec()); return false; } @@ -1353,10 +1359,21 @@ struct GenTree return OperIsCompare(OperGet()); } + // Oper is a compare that generates a cmp instruction (as opposed to a test instruction). + static bool OperIsCmpCompare(genTreeOps gtOper) + { + static_assert_no_msg(AreContiguous(GT_EQ, GT_NE, GT_LT, GT_LE, GT_GE, GT_GT)); + return (GT_EQ <= gtOper) && (gtOper <= GT_GT); + } + + bool OperIsCmpCompare() const + { + return OperIsCmpCompare(OperGet()); + } + static bool OperIsConditional(genTreeOps gtOper) { - static_assert_no_msg(AreContiguous(GT_SELECT, GT_CEQ, GT_CNE, GT_CLT, GT_CLE, GT_CGE, GT_CGT)); - return (GT_SELECT <= gtOper) && (gtOper <= GT_CGT); + return (GT_SELECT == gtOper); } bool OperIsConditional() const @@ -1364,15 +1381,14 @@ struct GenTree return OperIsConditional(OperGet()); } - static bool OperIsConditionalCompare(genTreeOps gtOper) + static bool OperIsCC(genTreeOps gtOper) { - static_assert_no_msg(AreContiguous(GT_CEQ, GT_CNE, GT_CLT, GT_CLE, GT_CGE, GT_CGT)); - return (GT_CEQ <= gtOper) && (gtOper <= GT_CGT); + return (gtOper == GT_JCC) || (gtOper == GT_SETCC); } - bool OperIsConditionalCompare() const + bool OperIsCC() const { - return OperIsConditionalCompare(OperGet()); + return OperIsCC(OperGet()); } static bool OperIsShift(genTreeOps gtOper) @@ -8222,7 +8238,7 @@ struct GenCondition static GenCondition FromRelop(GenTree* relop) { - assert(relop->OperIsCompare() || relop->OperIsConditionalCompare()); + assert(relop->OperIsCompare()); if (varTypeIsFloating(relop->gtGetOp1())) { @@ -8259,30 +8275,17 @@ struct GenCondition static GenCondition FromIntegralRelop(GenTree* relop) { - if (relop->OperIsConditionalCompare()) - { - assert(!varTypeIsFloating(relop->AsConditional()->gtOp1) && - !varTypeIsFloating(relop->AsConditional()->gtOp2)); - } - else - { - assert(!varTypeIsFloating(relop->gtGetOp1()) && !varTypeIsFloating(relop->gtGetOp2())); - } - + assert(!varTypeIsFloating(relop->gtGetOp1()) && !varTypeIsFloating(relop->gtGetOp2())); return FromIntegralRelop(relop->OperGet(), relop->IsUnsigned()); } static GenCondition FromIntegralRelop(genTreeOps oper, bool isUnsigned) { - assert(GenTree::OperIsCompare(oper) || GenTree::OperIsConditionalCompare(oper)); + assert(GenTree::OperIsCompare(oper)); // GT_TEST_EQ/NE are special, they need to be mapped as GT_EQ/NE unsigned code; - if (oper >= GT_CEQ) - { - code = oper - GT_CEQ; - } - else if (oper >= GT_TEST_EQ) + if (oper >= GT_TEST_EQ) { code = oper - GT_TEST_EQ; } diff --git a/src/coreclr/jit/gtlist.h b/src/coreclr/jit/gtlist.h index fe8b5c89d3ad14..56ea06b2a061bd 100644 --- a/src/coreclr/jit/gtlist.h +++ b/src/coreclr/jit/gtlist.h @@ -147,12 +147,6 @@ GTNODE(TEST_EQ , GenTreeOp ,0,GTK_BINOP|DBK_NOTHIR) GTNODE(TEST_NE , GenTreeOp ,0,GTK_BINOP|DBK_NOTHIR) GTNODE(SELECT , GenTreeConditional ,0,GTK_SPECIAL) -GTNODE(CEQ , GenTreeConditional ,0,GTK_SPECIAL) -GTNODE(CNE , GenTreeConditional ,0,GTK_SPECIAL) -GTNODE(CLT , GenTreeConditional ,0,GTK_SPECIAL) -GTNODE(CLE , GenTreeConditional ,0,GTK_SPECIAL) -GTNODE(CGE , GenTreeConditional ,0,GTK_SPECIAL) -GTNODE(CGT , GenTreeConditional ,0,GTK_SPECIAL) GTNODE(COMMA , GenTreeOp ,0,GTK_BINOP|DBK_NOTLIR) GTNODE(QMARK , GenTreeQmark ,0,GTK_BINOP|GTK_EXOP|DBK_NOTLIR) diff --git a/src/coreclr/jit/gtstructs.h b/src/coreclr/jit/gtstructs.h index f3e89080c390ea..7d50adbca39f97 100644 --- a/src/coreclr/jit/gtstructs.h +++ b/src/coreclr/jit/gtstructs.h @@ -101,7 +101,7 @@ GTSTRUCT_1(PhiArg , GT_PHI_ARG) GTSTRUCT_1(Phi , GT_PHI) GTSTRUCT_1(StoreInd , GT_STOREIND) GTSTRUCT_N(Indir , GT_STOREIND, GT_IND, GT_NULLCHECK, GT_BLK, GT_STORE_BLK, GT_OBJ, GT_STORE_OBJ, GT_STORE_DYN_BLK) -GTSTRUCT_N(Conditional , GT_SELECT, GT_CEQ, GT_CNE, GT_CLT, GT_CLE, GT_CGE, GT_CGT) +GTSTRUCT_N(Conditional , GT_SELECT) #if FEATURE_ARG_SPLIT GTSTRUCT_2_SPECIAL(PutArgStk, GT_PUTARG_STK, GT_PUTARG_SPLIT) GTSTRUCT_1(PutArgSplit , GT_PUTARG_SPLIT) diff --git a/src/coreclr/jit/instr.cpp b/src/coreclr/jit/instr.cpp index 9843482bbef6c6..c9fbb2b27a821d 100644 --- a/src/coreclr/jit/instr.cpp +++ b/src/coreclr/jit/instr.cpp @@ -326,61 +326,8 @@ void CodeGen::inst_SET(emitJumpKind condition, regNumber reg) // These instructions only write the low byte of 'reg' GetEmitter()->emitIns_R(ins, EA_1BYTE, reg); #elif defined(TARGET_ARM64) - insCond cond; - /* Convert the condition to an insCond value */ - switch (condition) - { - case EJ_eq: - cond = INS_COND_EQ; - break; - case EJ_ne: - cond = INS_COND_NE; - break; - case EJ_hs: - cond = INS_COND_HS; - break; - case EJ_lo: - cond = INS_COND_LO; - break; - case EJ_mi: - cond = INS_COND_MI; - break; - case EJ_pl: - cond = INS_COND_PL; - break; - case EJ_vs: - cond = INS_COND_VS; - break; - case EJ_vc: - cond = INS_COND_VC; - break; - - case EJ_hi: - cond = INS_COND_HI; - break; - case EJ_ls: - cond = INS_COND_LS; - break; - case EJ_ge: - cond = INS_COND_GE; - break; - case EJ_lt: - cond = INS_COND_LT; - break; - - case EJ_gt: - cond = INS_COND_GT; - break; - case EJ_le: - cond = INS_COND_LE; - break; - - default: - NO_WAY("unexpected condition type"); - return; - } - GetEmitter()->emitIns_R_COND(INS_cset, EA_8BYTE, reg, cond); + GetEmitter()->emitIns_R_COND(INS_cset, EA_8BYTE, reg, JumpKindToInsCond(condition)); #else NYI("inst_SET"); #endif diff --git a/src/coreclr/jit/lower.cpp b/src/coreclr/jit/lower.cpp index 9753615d62033d..2871a506679acd 100644 --- a/src/coreclr/jit/lower.cpp +++ b/src/coreclr/jit/lower.cpp @@ -238,6 +238,12 @@ GenTree* Lowering::LowerNode(GenTree* node) case GT_JTRUE: return LowerJTrue(node->AsOp()); + case GT_SELECT: +#ifdef TARGET_ARM64 + ContainCheckSelect(node->AsConditional()); +#endif + break; + case GT_JMP: LowerJmpMethod(node); break; @@ -2742,6 +2748,15 @@ GenTree* Lowering::OptimizeConstCompare(GenTree* cmp) GenTreeIntCon* op2 = cmp->gtGetOp2()->AsIntCon(); ssize_t op2Value = op2->IconValue(); +#ifdef TARGET_ARM64 + // Do not optimise further if op1 has a contained chain. + if (op1->OperIs(GT_AND) && + (op1->gtGetOp1()->isContainedAndNotIntOrIImmed() || op1->gtGetOp2()->isContainedAndNotIntOrIImmed())) + { + return cmp; + } +#endif + #ifdef TARGET_XARCH var_types op1Type = op1->TypeGet(); if (IsContainableMemoryOp(op1) && varTypeIsSmall(op1Type) && FitsIn(op1Type, op2Value)) @@ -2836,7 +2851,8 @@ GenTree* Lowering::OptimizeConstCompare(GenTree* cmp) if ((op2Value == 1) && cmp->OperIs(GT_EQ)) { if (andOp2->IsIntegralConst(1) && (genActualType(op1) == cmp->TypeGet()) && - BlockRange().TryGetUse(cmp, &cmpUse) && !cmpUse.User()->OperIs(GT_JTRUE)) + BlockRange().TryGetUse(cmp, &cmpUse) && !cmpUse.User()->OperIs(GT_JTRUE) && + !cmpUse.User()->OperIsConditional()) { GenTree* next = cmp->gtNext; @@ -6814,6 +6830,12 @@ void Lowering::ContainCheckNode(GenTree* node) ContainCheckJTrue(node->AsOp()); break; + case GT_SELECT: +#ifdef TARGET_ARM64 + ContainCheckSelect(node->AsConditional()); +#endif + break; + case GT_ADD: case GT_SUB: #if !defined(TARGET_64BIT) diff --git a/src/coreclr/jit/lower.h b/src/coreclr/jit/lower.h index b8b5686133efc2..b0d880c030227a 100644 --- a/src/coreclr/jit/lower.h +++ b/src/coreclr/jit/lower.h @@ -85,7 +85,13 @@ class Lowering final : public Phase void ContainCheckLclHeap(GenTreeOp* node); void ContainCheckRet(GenTreeUnOp* ret); void ContainCheckJTrue(GenTreeOp* node); - +#ifdef TARGET_ARM64 + bool IsValidCompareChain(GenTree* child, GenTree* parent); + bool ContainCheckCompareChain(GenTree* child, GenTree* parent, GenTree** earliestValid); + void ContainCheckCompareChainForAnd(GenTree* tree); + void ContainCheckConditionalCompare(GenTreeOp* cmp); + void ContainCheckSelect(GenTreeConditional* node); +#endif void ContainCheckBitCast(GenTree* node); void ContainCheckCallOperands(GenTreeCall* call); void ContainCheckIndir(GenTreeIndir* indirNode); diff --git a/src/coreclr/jit/lowerarmarch.cpp b/src/coreclr/jit/lowerarmarch.cpp index 1e2420b37a953b..0ff88151503201 100644 --- a/src/coreclr/jit/lowerarmarch.cpp +++ b/src/coreclr/jit/lowerarmarch.cpp @@ -365,6 +365,12 @@ GenTree* Lowering::LowerBinaryArithmetic(GenTreeOp* binOp) binOp->ChangeOper(GT_AND_NOT); BlockRange().Remove(notNode); } +#ifdef TARGET_ARM64 + else + { + ContainCheckCompareChainForAnd(binOp); + } +#endif } ContainCheckBinary(binOp); @@ -2042,6 +2048,216 @@ void Lowering::ContainCheckCompare(GenTreeOp* cmp) CheckImmedAndMakeContained(cmp, cmp->gtOp2); } +#ifdef TARGET_ARM64 +//------------------------------------------------------------------------ +// IsValidCompareChain : Determine if the node contains a valid chain of ANDs and CMPs. +// +// Arguments: +// child - pointer to the node being checked. +// parent - parent node of the child. +// +// Return value: +// True if a valid chain is found. +// +// Notes: +// A compare chain is a sequence of CMP nodes connected by AND nodes. +// For example: AND (AND (CMP A B) (CMP C D)) (CMP E F) +// The chain can just be a single compare node, however it's parent +// must always be an AND or SELECT node. +// If a CMP or AND node is contained then it and all it's children are +// considered to be in a valid chain. +// Chains are built up during the lowering of each successive parent. +// +bool Lowering::IsValidCompareChain(GenTree* child, GenTree* parent) +{ + assert(parent->OperIs(GT_AND) || parent->OperIs(GT_SELECT)); + + if (child->isContainedAndNotIntOrIImmed()) + { + // Already have a chain. + assert(child->OperIs(GT_AND) || child->OperIsCmpCompare()); + return true; + } + else + { + if (child->OperIs(GT_AND)) + { + // Count both sides. + return IsValidCompareChain(child->AsOp()->gtGetOp2(), child) && + IsValidCompareChain(child->AsOp()->gtGetOp1(), child); + } + else if (child->OperIsCmpCompare()) + { + // Can the child compare be contained. + return IsSafeToContainMem(parent, child); + } + } + + return false; +} + +//------------------------------------------------------------------------ +// ContainCheckCompareChain : Determine if a chain of ANDs and CMPs can be contained. +// +// Arguments: +// child - pointer to the node being checked. +// parent - parent node of the child. +// startOfChain - If found, returns the earliest valid op in the chain. +// +// Return value: +// True if a valid chain is was contained. +// +// Notes: +// Assumes the chain was checked via IsValidCompareChain. +// +bool Lowering::ContainCheckCompareChain(GenTree* child, GenTree* parent, GenTree** startOfChain) +{ + assert(parent->OperIs(GT_AND) || parent->OperIs(GT_SELECT)); + *startOfChain = nullptr; // Nothing found yet. + + if (child->isContainedAndNotIntOrIImmed()) + { + // Already have a chain. + return true; + } + // Can the child be contained. + else if (IsSafeToContainMem(parent, child)) + { + if (child->OperIs(GT_AND)) + { + // If Op2 is not contained, then try to contain it. + if (!child->AsOp()->gtGetOp2()->isContainedAndNotIntOrIImmed()) + { + if (!ContainCheckCompareChain(child->gtGetOp2(), child, startOfChain)) + { + // Op2 must be contained in order to contain Op1 or the AND. + return false; + } + } + + // If Op1 is not contained, then try to contain it. + if (!child->AsOp()->gtGetOp1()->isContainedAndNotIntOrIImmed()) + { + if (!ContainCheckCompareChain(child->gtGetOp1(), child, startOfChain)) + { + return false; + } + } + + // Contain the AND. + child->SetContained(); + return true; + } + else if (child->OperIsCmpCompare()) + { + child->AsOp()->SetContained(); + + // Ensure the children of the compare are contained correctly. + child->AsOp()->gtGetOp1()->ClearContained(); + child->AsOp()->gtGetOp2()->ClearContained(); + ContainCheckConditionalCompare(child->AsOp()); + *startOfChain = child; + return true; + } + } + + return false; +} + +//------------------------------------------------------------------------ +// ContainCheckCompareChainForAnd : Determine if an AND is a containable chain +// +// Arguments: +// node - pointer to the node +// +void Lowering::ContainCheckCompareChainForAnd(GenTree* tree) +{ + assert(tree->OperIs(GT_AND)); + + if (!comp->opts.OptimizationEnabled()) + { + return; + } + + // First check there is a valid chain. + if (IsValidCompareChain(tree->AsOp()->gtGetOp2(), tree) && IsValidCompareChain(tree->AsOp()->gtGetOp1(), tree)) + { + GenTree* startOfChain = nullptr; + + // To ensure ordering at code generation, Op1 and the parent can + // only be contained if Op2 is contained. + if (ContainCheckCompareChain(tree->AsOp()->gtGetOp2(), tree, &startOfChain)) + { + if (ContainCheckCompareChain(tree->AsOp()->gtGetOp1(), tree, &startOfChain)) + { + // If op1 is the start of a chain, then it'll be generated as a standard compare. + if (startOfChain != nullptr) + { + // The earliest node in the chain will be generated as a standard compare. + assert(startOfChain->OperIsCmpCompare()); + startOfChain->AsOp()->gtGetOp1()->ClearContained(); + startOfChain->AsOp()->gtGetOp2()->ClearContained(); + ContainCheckCompare(startOfChain->AsOp()); + } + } + } + + JITDUMP("Lowered `AND` chain:\n"); + DISPTREE(tree); + } +} + +//------------------------------------------------------------------------ +// ContainCheckConditionalCompare: determine whether the source of a compare within a compare chain should be contained. +// +// Arguments: +// node - pointer to the node +// +void Lowering::ContainCheckConditionalCompare(GenTreeOp* cmp) +{ + assert(cmp->OperIsCmpCompare()); + GenTree* op2 = cmp->gtOp2; + + if (op2->IsCnsIntOrI() && !op2->AsIntCon()->ImmedValNeedsReloc(comp)) + { + target_ssize_t immVal = (target_ssize_t)op2->AsIntCon()->gtIconVal; + + if (emitter::emitIns_valid_imm_for_ccmp(immVal)) + { + MakeSrcContained(cmp, op2); + } + } +} + +//------------------------------------------------------------------------ +// ContainCheckSelect : determine whether the source of a select should be contained. +// +// Arguments: +// node - pointer to the node +// +void Lowering::ContainCheckSelect(GenTreeConditional* node) +{ + if (!comp->opts.OptimizationEnabled()) + { + return; + } + + // Check if the compare does not need to be generated into a register. + GenTree* startOfChain = nullptr; + ContainCheckCompareChain(node->gtCond, node, &startOfChain); + + if (startOfChain != nullptr) + { + // The earliest node in the chain will be generated as a standard compare. + assert(startOfChain->OperIsCmpCompare()); + startOfChain->AsOp()->gtGetOp1()->ClearContained(); + startOfChain->AsOp()->gtGetOp2()->ClearContained(); + ContainCheckCompare(startOfChain->AsOp()); + } +} + +#endif // TARGET_ARM64 + //------------------------------------------------------------------------ // ContainCheckBoundsChk: determine whether any source of a bounds check node should be contained. // diff --git a/src/coreclr/jit/lsraarm64.cpp b/src/coreclr/jit/lsraarm64.cpp index 21134d2dab42a5..b2c815bb1e396e 100644 --- a/src/coreclr/jit/lsraarm64.cpp +++ b/src/coreclr/jit/lsraarm64.cpp @@ -792,6 +792,14 @@ int LinearScan::BuildNode(GenTree* tree) BuildDef(tree); break; + case GT_SELECT: + assert(dstCount == 1); + srcCount = BuildOperandUses(tree->AsConditional()->gtCond); + srcCount += BuildOperandUses(tree->AsConditional()->gtOp1); + srcCount += BuildOperandUses(tree->AsConditional()->gtOp2); + BuildDef(tree, dstCandidates); + break; + } // end switch (tree->OperGet()) if (tree->IsUnusedValue() && (dstCount != 0)) diff --git a/src/coreclr/jit/lsrabuild.cpp b/src/coreclr/jit/lsrabuild.cpp index 1d24608ccba6b7..d90320db3b3e08 100644 --- a/src/coreclr/jit/lsrabuild.cpp +++ b/src/coreclr/jit/lsrabuild.cpp @@ -3142,9 +3142,10 @@ int LinearScan::BuildOperandUses(GenTree* node, regMaskTP candidates) } #endif // FEATURE_HW_INTRINSICS #ifdef TARGET_ARM64 - if (node->OperIs(GT_MUL)) + if (node->OperIs(GT_MUL) || node->OperIsCmpCompare() || node->OperIs(GT_AND)) { - // Can be contained for MultiplyAdd on arm64 + // Can be contained for MultiplyAdd on arm64. + // Compare and AND may be contained due to If Conversion. return BuildBinaryUses(node->AsOp(), candidates); } if (node->OperIs(GT_NEG, GT_CAST, GT_LSH)) diff --git a/src/tests/JIT/opt/Compares/compareAnd2Chains.cs b/src/tests/JIT/opt/Compares/compareAnd2Chains.cs new file mode 100644 index 00000000000000..0fd39d96cbcfa6 --- /dev/null +++ b/src/tests/JIT/opt/Compares/compareAnd2Chains.cs @@ -0,0 +1,367 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +// unit test for compare AND chains of length 2. + +using System; +using System.Runtime.CompilerServices; + +public class ComparisonTestAnd2Chains +{ + // Using bitwise AND to ensure compare chains are generated. + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Eq_byte_2(byte a1, byte a2) => a1 == 10 & a2 == 11; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Eq_short_2(short a1, short a2) => a1 == 10 & a2 == 11; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Eq_int_2(int a1, int a2) => a1 == 10 & a2 == 11; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Eq_long_2(long a1, long a2) => a1 == 10 & a2 == 11; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Eq_ushort_2(ushort a1, ushort a2) => a1 == 10 & a2 == 11; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Eq_uint_2(uint a1, uint a2) => a1 == 10 & a2 == 11; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Eq_ulong_2(ulong a1, ulong a2) => a1 == 10 & a2 == 11; + + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ne_byte_2(byte a1, byte a2) => a1 != 5 & a2 != 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ne_short_2(short a1, short a2) => a1 != 5 & a2 != 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ne_int_2(int a1, int a2) => a1 != 5 & a2 != 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ne_long_2(long a1, long a2) => a1 != 5 & a2 != 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ne_ushort_2(ushort a1, ushort a2) => a1 != 5 & a2 != 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ne_uint_2(uint a1, uint a2) => a1 != 5 & a2 != 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ne_ulong_2(ulong a1, ulong a2) => a1 != 5 & a2 != 5; + + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Lt_byte_2(byte a1, byte a2) => a1 < 5 & a2 < 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Lt_short_2(short a1, short a2) => a1 < 5 & a2 < 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Lt_int_2(int a1, int a2) => a1 < 5 & a2 < 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Lt_long_2(long a1, long a2) => a1 < 5 & a2 < 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Lt_ushort_2(ushort a1, ushort a2) => a1 < 5 & a2 < 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Lt_uint_2(uint a1, uint a2) => a1 < 5 & a2 < 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Lt_ulong_2(ulong a1, ulong a2) => a1 < 5 & a2 < 5; + + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Le_byte_2(byte a1, byte a2) => a1 <= 5 & a2 <= 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Le_short_2(short a1, short a2) => a1 <= 5 & a2 <= 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Le_int_2(int a1, int a2) => a1 <= 5 & a2 <= 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Le_long_2(long a1, long a2) => a1 <= 5 & a2 <= 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Le_ushort_2(ushort a1, ushort a2) => a1 <= 5 & a2 <= 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Le_uint_2(uint a1, uint a2) => a1 <= 5 & a2 <= 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Le_ulong_2(ulong a1, ulong a2) => a1 <= 5 & a2 <= 5; + + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Gt_byte_2(byte a1, byte a2) => a1 > 5 & a2 > 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Gt_short_2(short a1, short a2) => a1 > 5 & a2 > 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Gt_int_2(int a1, int a2) => a1 > 5 & a2 > 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Gt_long_2(long a1, long a2) => a1 > 5 & a2 > 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Gt_ushort_2(ushort a1, ushort a2) => a1 > 5 & a2 > 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Gt_uint_2(uint a1, uint a2) => a1 > 5 & a2 > 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Gt_ulong_2(ulong a1, ulong a2) => a1 > 5 & a2 > 5; + + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ge_byte_2(byte a1, byte a2) => a1 >= 5 & a2 >= 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ge_short_2(short a1, short a2) => a1 >= 5 & a2 >= 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ge_int_2(int a1, int a2) => a1 >= 5 & a2 >= 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ge_long_2(long a1, long a2) => a1 >= 5 & a2 >= 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ge_ushort_2(ushort a1, ushort a2) => a1 >= 5 & a2 >= 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ge_uint_2(uint a1, uint a2) => a1 >= 5 & a2 >= 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ge_ulong_2(ulong a1, ulong a2) => a1 >= 5 & a2 >= 5; + + + [MethodImpl(MethodImplOptions.NoInlining)] + public static int Main() + { + if (!Eq_byte_2(10, 11)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Eq_byte_2(10, 11) failed"); + return 101; + } + if (!Eq_short_2(10, 11)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Eq_short_2(10, 11) failed"); + return 101; + } + if (!Eq_int_2(10, 11)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Eq_int_2(10, 11) failed"); + return 101; + } + if (!Eq_long_2(10, 11)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Eq_long_2(10, 11) failed"); + return 101; + } + if (!Eq_ushort_2(10, 11)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Eq_ushort_2(10, 11) failed"); + return 101; + } + if (!Eq_uint_2(10, 11)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Eq_uint_2(10, 11) failed"); + return 101; + } + if (!Eq_ulong_2(10, 11)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Eq_ulong_2(10, 11) failed"); + return 101; + } + + if (!Ne_byte_2(10, 11)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Ne_byte_2(10, 11) failed"); + return 101; + } + if (!Ne_short_2(10, 11)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Ne_short_2(10, 11) failed"); + return 101; + } + if (!Ne_int_2(10, 11)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Ne_int_2(10, 11) failed"); + return 101; + } + if (!Ne_long_2(10, 11)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Ne_long_2(10, 11) failed"); + return 101; + } + if (!Ne_ushort_2(10, 11)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Ne_ushort_2(10, 11) failed"); + return 101; + } + if (!Ne_uint_2(10, 11)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Ne_uint_2(10, 11) failed"); + return 101; + } + if (!Ne_ulong_2(10, 11)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Ne_ulong_2(10, 11) failed"); + return 101; + } + + if (!Lt_byte_2(3, 4)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Lt_byte_2(3, 4) failed"); + return 101; + } + if (!Lt_short_2(3, 4)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Lt_short_2(3, 4) failed"); + return 101; + } + if (!Lt_int_2(3, 4)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Lt_int_2(3, 4) failed"); + return 101; + } + if (!Lt_long_2(3, 4)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Lt_long_2(3, 4) failed"); + return 101; + } + if (!Lt_ushort_2(3, 4)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Lt_ushort_2(3, 4) failed"); + return 101; + } + if (!Lt_uint_2(3, 4)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Lt_uint_2(3, 4) failed"); + return 101; + } + if (!Lt_ulong_2(3, 4)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Lt_ulong_2(3, 4) failed"); + return 101; + } + + if (!Le_byte_2(3, 4)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Le_byte_2(3, 4) failed"); + return 101; + } + if (!Le_short_2(3, 4)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Le_short_2(3, 4) failed"); + return 101; + } + if (!Le_int_2(3, 4)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Le_int_2(3, 4) failed"); + return 101; + } + if (!Le_long_2(3, 4)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Le_long_2(3, 4) failed"); + return 101; + } + if (!Le_ushort_2(3, 4)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Le_ushort_2(3, 4) failed"); + return 101; + } + if (!Le_uint_2(3, 4)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Le_uint_2(3, 4) failed"); + return 101; + } + if (!Le_ulong_2(3, 4)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Le_ulong_2(3, 4) failed"); + return 101; + } + + if (!Gt_byte_2(10, 11)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Gt_byte_2(10, 11) failed"); + return 101; + } + if (!Gt_short_2(10, 11)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Gt_short_2(10, 11) failed"); + return 101; + } + if (!Gt_int_2(10, 11)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Gt_int_2(10, 11) failed"); + return 101; + } + if (!Gt_long_2(10, 11)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Gt_long_2(10, 11) failed"); + return 101; + } + if (!Gt_ushort_2(10, 11)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Gt_ushort_2(10, 11) failed"); + return 101; + } + if (!Gt_uint_2(10, 11)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Gt_uint_2(10, 11) failed"); + return 101; + } + if (!Gt_ulong_2(10, 11)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Gt_ulong_2(10, 11) failed"); + return 101; + } + + if (!Ge_byte_2(10, 11)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Ge_byte_2(10, 11) failed"); + return 101; + } + if (!Ge_short_2(10, 11)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Ge_short_2(10, 11) failed"); + return 101; + } + if (!Ge_int_2(10, 11)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Ge_int_2(10, 11) failed"); + return 101; + } + if (!Ge_long_2(10, 11)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Ge_long_2(10, 11) failed"); + return 101; + } + if (!Ge_ushort_2(10, 11)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Ge_ushort_2(10, 11) failed"); + return 101; + } + if (!Ge_uint_2(10, 11)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Ge_uint_2(10, 11) failed"); + return 101; + } + if (!Ge_ulong_2(10, 11)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Le_ulong_2(10, 11) failed"); + return 101; + } + + Console.WriteLine("PASSED"); + return 100; + } +} diff --git a/src/tests/JIT/opt/Compares/compareAnd2Chains.csproj b/src/tests/JIT/opt/Compares/compareAnd2Chains.csproj new file mode 100644 index 00000000000000..5e5fbae5cb863b --- /dev/null +++ b/src/tests/JIT/opt/Compares/compareAnd2Chains.csproj @@ -0,0 +1,12 @@ + + + Exe + + + PdbOnly + True + + + + + diff --git a/src/tests/JIT/opt/Compares/compareAnd3Chains.cs b/src/tests/JIT/opt/Compares/compareAnd3Chains.cs new file mode 100644 index 00000000000000..c609cdebc2d011 --- /dev/null +++ b/src/tests/JIT/opt/Compares/compareAnd3Chains.cs @@ -0,0 +1,367 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +// unit test for compare AND chains of length 3. + +using System; +using System.Runtime.CompilerServices; + +public class ComparisonTestAnd3Chains +{ + // Using bitwise AND to ensure compare chains are generated. + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Eq_byte_3(byte a1, byte a2, byte a3) => a1 == 10 & a2 == 11 & a3 == 12; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Eq_short_3(short a1, short a2, short a3) => a1 == 10 & a2 == 11 & a3 == 12; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Eq_int_3(int a1, int a2, int a3) => a1 == 10 & a2 == 11 & a3 == 12; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Eq_long_3(long a1, long a2, long a3) => a1 == 10 & a2 == 11 & a3 == 12; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Eq_ushort_3(ushort a1, ushort a2, ushort a3) => a1 == 10 & a2 == 11 & a3 == 12; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Eq_uint_3(uint a1, uint a2, uint a3) => a1 == 10 & a2 == 11 & a3 == 12; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Eq_ulong_3(ulong a1, ulong a2, ulong a3) => a1 == 10 & a2 == 11 & a3 == 12; + + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ne_byte_3(byte a1, byte a2, byte a3) => a1 != 5 & a2 != 5 & a3 != 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ne_short_3(short a1, short a2, short a3) => a1 != 5 & a2 != 5 & a3 != 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ne_int_3(int a1, int a2, int a3) => a1 != 5 & a2 != 5 & a3 != 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ne_long_3(long a1, long a2, long a3) => a1 != 5 & a2 != 5 & a3 != 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ne_ushort_3(ushort a1, ushort a2, ushort a3) => a1 != 5 & a2 != 5 & a3 != 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ne_uint_3(uint a1, uint a2, uint a3) => a1 != 5 & a2 != 5 & a3 != 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ne_ulong_3(ulong a1, ulong a2, ulong a3) => a1 != 5 & a2 != 5 & a3 != 5; + + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Lt_byte_3(byte a1, byte a2, byte a3) => a1 < 5 & a2 < 5 & a3 < 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Lt_short_3(short a1, short a2, short a3) => a1 < 5 & a2 < 5 & a3 < 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Lt_int_3(int a1, int a2, int a3) => a1 < 5 & a2 < 5 & a3 < 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Lt_long_3(long a1, long a2, long a3) => a1 < 5 & a2 < 5 & a3 < 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Lt_ushort_3(ushort a1, ushort a2, ushort a3) => a1 < 5 & a2 < 5 & a3 < 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Lt_uint_3(uint a1, uint a2, uint a3) => a1 < 5 & a2 < 5 & a3 < 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Lt_ulong_3(ulong a1, ulong a2, ulong a3) => a1 < 5 & a2 < 5 & a3 < 5; + + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Le_byte_3(byte a1, byte a2, byte a3) => a1 <= 5 & a2 <= 5 & a3 <= 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Le_short_3(short a1, short a2, short a3) => a1 <= 5 & a2 <= 5 & a3 <= 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Le_int_3(int a1, int a2, int a3) => a1 <= 5 & a2 <= 5 & a3 <= 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Le_long_3(long a1, long a2, long a3) => a1 <= 5 & a2 <= 5 & a3 <= 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Le_ushort_3(ushort a1, ushort a2, ushort a3) => a1 <= 5 & a2 <= 5 & a3 <= 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Le_uint_3(uint a1, uint a2, uint a3) => a1 <= 5 & a2 <= 5 & a3 <= 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Le_ulong_3(ulong a1, ulong a2, ulong a3) => a1 <= 5 & a2 <= 5 & a3 <= 5; + + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Gt_byte_3(byte a1, byte a2, byte a3) => a1 > 5 & a2 > 5 & a3 > 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Gt_short_3(short a1, short a2, short a3) => a1 > 5 & a2 > 5 & a3 > 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Gt_int_3(int a1, int a2, int a3) => a1 > 5 & a2 > 5 & a3 > 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Gt_long_3(long a1, long a2, long a3) => a1 > 5 & a2 > 5 & a3 > 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Gt_ushort_3(ushort a1, ushort a2, ushort a3) => a1 > 5 & a2 > 5 & a3 > 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Gt_uint_3(uint a1, uint a2, uint a3) => a1 > 5 & a2 > 5 & a3 > 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Gt_ulong_3(ulong a1, ulong a2, ulong a3) => a1 > 5 & a2 > 5 & a3 > 5; + + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ge_byte_3(byte a1, byte a2, byte a3) => a1 >= 5 & a2 >= 5 & a3 >= 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ge_short_3(short a1, short a2, short a3) => a1 >= 5 & a2 >= 5 & a3 >= 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ge_int_3(int a1, int a2, int a3) => a1 >= 5 & a2 >= 5 & a3 >= 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ge_long_3(long a1, long a2, long a3) => a1 >= 5 & a2 >= 5 & a3 >= 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ge_ushort_3(ushort a1, ushort a2, ushort a3) => a1 >= 5 & a2 >= 5 & a3 >= 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ge_uint_3(uint a1, uint a2, uint a3) => a1 >= 5 & a2 >= 5 & a3 >= 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ge_ulong_3(ulong a1, ulong a2, ulong a3) => a1 >= 5 & a2 >= 5 & a3 >= 5; + + + [MethodImpl(MethodImplOptions.NoInlining)] + public static int Main() + { + if (!Eq_byte_3(10, 11, 12)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Eq_byte_3(10, 11, 12) failed"); + return 101; + } + if (!Eq_short_3(10, 11, 12)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Eq_short_3(10, 11, 12) failed"); + return 101; + } + if (!Eq_int_3(10, 11, 12)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Eq_int_3(10, 11, 12) failed"); + return 101; + } + if (!Eq_long_3(10, 11, 12)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Eq_long_3(10, 11, 12) failed"); + return 101; + } + if (!Eq_ushort_3(10, 11, 12)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Eq_ushort_3(10, 11, 12) failed"); + return 101; + } + if (!Eq_uint_3(10, 11, 12)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Eq_uint_3(10, 11, 12) failed"); + return 101; + } + if (!Eq_ulong_3(10, 11, 12)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Eq_ulong_3(10, 11, 12) failed"); + return 101; + } + + if (!Ne_byte_3(10, 11, 12)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Ne_byte_3(10, 11, 12) failed"); + return 101; + } + if (!Ne_short_3(10, 11, 12)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Ne_short_3(10, 11, 12) failed"); + return 101; + } + if (!Ne_int_3(10, 11, 12)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Ne_int_3(10, 11, 12) failed"); + return 101; + } + if (!Ne_long_3(10, 11, 12)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Ne_long_3(10, 11, 12) failed"); + return 101; + } + if (!Ne_ushort_3(10, 11, 12)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Ne_ushort_3(10, 11, 12) failed"); + return 101; + } + if (!Ne_uint_3(10, 11, 12)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Ne_uint_3(10, 11, 12) failed"); + return 101; + } + if (!Ne_ulong_3(10, 11, 12)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Ne_ulong_3(10, 11, 12) failed"); + return 101; + } + + if (!Lt_byte_3(2, 3, 4)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Lt_byte_3(2, 3, 4) failed"); + return 101; + } + if (!Lt_short_3(2, 3, 4)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Lt_short_3(2, 3, 4) failed"); + return 101; + } + if (!Lt_int_3(2, 3, 4)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Lt_int_3(2, 3, 4) failed"); + return 101; + } + if (!Lt_long_3(2, 3, 4)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Lt_long_3(2, 3, 4) failed"); + return 101; + } + if (!Lt_ushort_3(2, 3, 4)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Lt_ushort_3(2, 3, 4) failed"); + return 101; + } + if (!Lt_uint_3(2, 3, 4)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Lt_uint_3(2, 3, 4) failed"); + return 101; + } + if (!Lt_ulong_3(2, 3, 4)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Lt_ulong_3(2, 3, 4) failed"); + return 101; + } + + if (!Le_byte_3(2, 3, 4)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Le_byte_3(2, 3, 4) failed"); + return 101; + } + if (!Le_short_3(2, 3, 4)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Le_short_3(2, 3, 4) failed"); + return 101; + } + if (!Le_int_3(2, 3, 4)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Le_int_3(2, 3, 4) failed"); + return 101; + } + if (!Le_long_3(2, 3, 4)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Le_long_3(2, 3, 4) failed"); + return 101; + } + if (!Le_ushort_3(2, 3, 4)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Le_ushort_3(2, 3, 4) failed"); + return 101; + } + if (!Le_uint_3(2, 3, 4)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Le_uint_3(2, 3, 4) failed"); + return 101; + } + if (!Le_ulong_3(2, 3, 4)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Le_ulong_3(2, 3, 4) failed"); + return 101; + } + + if (!Gt_byte_3(10, 11, 12)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Gt_byte_3(10, 11, 12) failed"); + return 101; + } + if (!Gt_short_3(10, 11, 12)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Gt_short_3(10, 11, 12) failed"); + return 101; + } + if (!Gt_int_3(10, 11, 12)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Gt_int_3(10, 11, 12) failed"); + return 101; + } + if (!Gt_long_3(10, 11, 12)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Gt_long_3(10, 11, 12) failed"); + return 101; + } + if (!Gt_ushort_3(10, 11, 12)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Gt_ushort_3(10, 11, 12) failed"); + return 101; + } + if (!Gt_uint_3(10, 11, 12)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Gt_uint_3(10, 11, 12) failed"); + return 101; + } + if (!Gt_ulong_3(10, 11, 12)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Gt_ulong_3(10, 11, 12) failed"); + return 101; + } + + if (!Ge_byte_3(10, 11, 12)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Ge_byte_3(10, 11, 12) failed"); + return 101; + } + if (!Ge_short_3(10, 11, 12)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Ge_short_3(10, 11, 12) failed"); + return 101; + } + if (!Ge_int_3(10, 11, 12)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Ge_int_3(10, 11, 12) failed"); + return 101; + } + if (!Ge_long_3(10, 11, 12)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Ge_long_3(10, 11, 12) failed"); + return 101; + } + if (!Ge_ushort_3(10, 11, 12)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Ge_ushort_3(10, 11, 12) failed"); + return 101; + } + if (!Ge_uint_3(10, 11, 12)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Ge_uint_3(10, 11, 12) failed"); + return 101; + } + if (!Ge_ulong_3(10, 11, 12)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Le_ulong_3(10, 11, 12) failed"); + return 101; + } + + Console.WriteLine("PASSED"); + return 100; + } +} diff --git a/src/tests/JIT/opt/Compares/compareAnd3Chains.csproj b/src/tests/JIT/opt/Compares/compareAnd3Chains.csproj new file mode 100644 index 00000000000000..5e5fbae5cb863b --- /dev/null +++ b/src/tests/JIT/opt/Compares/compareAnd3Chains.csproj @@ -0,0 +1,12 @@ + + + Exe + + + PdbOnly + True + + + + + diff --git a/src/tests/JIT/opt/Compares/compareAndTestChains.cs b/src/tests/JIT/opt/Compares/compareAndTestChains.cs new file mode 100644 index 00000000000000..c89e04f487ecf1 --- /dev/null +++ b/src/tests/JIT/opt/Compares/compareAndTestChains.cs @@ -0,0 +1,367 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +// unit test for compare AND chains that include a binary test. + +using System; +using System.Runtime.CompilerServices; + +public class ComparisonTestAndTestChains +{ + // Using bitwise AND to ensure compare chains are generated. + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Eq_byte_bool(byte a1, bool a2) => (a1 == 10) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Eq_short_bool(short a1, bool a2) => (a1 == 10) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Eq_int_bool(int a1, bool a2) => (a1 == 10) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Eq_long_bool(long a1, bool a2) => (a1 == 10) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Eq_ushort_bool(ushort a1, bool a2) => (a1 == 10) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Eq_uint_bool(uint a1, bool a2) => (a1 == 10) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Eq_ulong_bool(ulong a1, bool a2) => (a1 == 10) & !a2; + + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ne_byte_bool(byte a1, bool a2) => (a1 != 5) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ne_short_bool(short a1, bool a2) => (a1 != 5) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ne_int_bool(int a1, bool a2) => (a1 != 5) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ne_long_bool(long a1, bool a2) => (a1 != 5) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ne_ushort_bool(ushort a1, bool a2) => (a1 != 5) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ne_uint_bool(uint a1, bool a2) => (a1 != 5) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ne_ulong_bool(ulong a1, bool a2) => (a1 != 5) & !a2; + + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Lt_byte_bool(byte a1, bool a2) => (a1 < 5) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Lt_short_bool(short a1, bool a2) => (a1 < 5) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Lt_int_bool(int a1, bool a2) => (a1 < 5) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Lt_long_bool(long a1, bool a2) => (a1 < 5) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Lt_ushort_bool(ushort a1, bool a2) => (a1 < 5) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Lt_uint_bool(uint a1, bool a2) => (a1 < 5) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Lt_ulong_bool(ulong a1, bool a2) => (a1 < 5) & !a2; + + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Le_byte_bool(byte a1, bool a2) => (a1 <= 5) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Le_short_bool(short a1, bool a2) => (a1 <= 5) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Le_int_bool(int a1, bool a2) => (a1 <= 5) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Le_long_bool(long a1, bool a2) => (a1 <= 5) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Le_ushort_bool(ushort a1, bool a2) => (a1 <= 5) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Le_uint_bool(uint a1, bool a2) => (a1 <= 5) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Le_ulong_bool(ulong a1, bool a2) => (a1 <= 5) & !a2; + + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Gt_byte_bool(byte a1, bool a2) => (a1 > 5) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Gt_short_bool(short a1, bool a2) => (a1 > 5) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Gt_int_bool(int a1, bool a2) => (a1 > 5) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Gt_long_bool(long a1, bool a2) => (a1 > 5) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Gt_ushort_bool(ushort a1, bool a2) => (a1 > 5) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Gt_uint_bool(uint a1, bool a2) => (a1 > 5) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Gt_ulong_bool(ulong a1, bool a2) => (a1 > 5) & !a2; + + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ge_byte_bool(byte a1, bool a2) => (a1 >= 5) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ge_short_bool(short a1, bool a2) => (a1 >= 5) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ge_int_bool(int a1, bool a2) => (a1 >= 5) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ge_long_bool(long a1, bool a2) => (a1 >= 5) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ge_ushort_bool(ushort a1, bool a2) => (a1 >= 5) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ge_uint_bool(uint a1, bool a2) => (a1 >= 5) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ge_ulong_bool(ulong a1, bool a2) => (a1 >= 5) & !a2; + + + [MethodImpl(MethodImplOptions.NoInlining)] + public static int Main() + { + if (!Eq_byte_bool(10, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Eq_byte_bool(10, false) failed"); + return 101; + } + if (!Eq_short_bool(10, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Eq_short_bool(10, false) failed"); + return 101; + } + if (!Eq_int_bool(10, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Eq_int_bool(10, false) failed"); + return 101; + } + if (!Eq_long_bool(10, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Eq_long_bool(10, false) failed"); + return 101; + } + if (!Eq_ushort_bool(10, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Eq_ushort_bool(10, false) failed"); + return 101; + } + if (!Eq_uint_bool(10, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Eq_uint_bool(10, false) failed"); + return 101; + } + if (!Eq_ulong_bool(10, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Eq_ulong_bool(10, false) failed"); + return 101; + } + + if (!Ne_byte_bool(10, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Ne_byte_bool(10, false) failed"); + return 101; + } + if (!Ne_short_bool(10, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Ne_short_bool(10, false) failed"); + return 101; + } + if (!Ne_int_bool(10, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Ne_int_bool(10, false) failed"); + return 101; + } + if (!Ne_long_bool(10, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Ne_long_bool(10, false) failed"); + return 101; + } + if (!Ne_ushort_bool(10, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Ne_ushort_bool(10, false) failed"); + return 101; + } + if (!Ne_uint_bool(10, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Ne_uint_bool(10, false) failed"); + return 101; + } + if (!Ne_ulong_bool(10, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Ne_ulong_bool(10, false) failed"); + return 101; + } + + if (!Lt_byte_bool(3, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Lt_byte_bool(3, false) failed"); + return 101; + } + if (!Lt_short_bool(3, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Lt_short_bool(3, false) failed"); + return 101; + } + if (!Lt_int_bool(3, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Lt_int_bool(3, false) failed"); + return 101; + } + if (!Lt_long_bool(3, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Lt_long_bool(3, false) failed"); + return 101; + } + if (!Lt_ushort_bool(3, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Lt_ushort_bool(3, false) failed"); + return 101; + } + if (!Lt_uint_bool(3, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Lt_uint_bool(3, false) failed"); + return 101; + } + if (!Lt_ulong_bool(3, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Lt_ulong_bool(3, false) failed"); + return 101; + } + + if (!Le_byte_bool(3, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Le_byte_bool(3, false) failed"); + return 101; + } + if (!Le_short_bool(3, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Le_short_bool(3, false) failed"); + return 101; + } + if (!Le_int_bool(3, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Le_int_bool(3, false) failed"); + return 101; + } + if (!Le_long_bool(3, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Le_long_bool(3, false) failed"); + return 101; + } + if (!Le_ushort_bool(3, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Le_ushort_bool(3, false) failed"); + return 101; + } + if (!Le_uint_bool(3, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Le_uint_bool(3, false) failed"); + return 101; + } + if (!Le_ulong_bool(3, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Le_ulong_bool(3, false) failed"); + return 101; + } + + if (!Gt_byte_bool(10, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Gt_byte_bool(10, false) failed"); + return 101; + } + if (!Gt_short_bool(10, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Gt_short_bool(10, false) failed"); + return 101; + } + if (!Gt_int_bool(10, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Gt_int_bool(10, false) failed"); + return 101; + } + if (!Gt_long_bool(10, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Gt_long_bool(10, false) failed"); + return 101; + } + if (!Gt_ushort_bool(10, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Gt_ushort_bool(10, false) failed"); + return 101; + } + if (!Gt_uint_bool(10, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Gt_uint_bool(10, false) failed"); + return 101; + } + if (!Gt_ulong_bool(10, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Gt_ulong_bool(10, false) failed"); + return 101; + } + + if (!Ge_byte_bool(10, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Ge_byte_bool(10, false) failed"); + return 101; + } + if (!Ge_short_bool(10, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Ge_short_bool(10, false) failed"); + return 101; + } + if (!Ge_int_bool(10, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Ge_int_bool(10, false) failed"); + return 101; + } + if (!Ge_long_bool(10, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Ge_long_bool(10, false) failed"); + return 101; + } + if (!Ge_ushort_bool(10, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Ge_ushort_bool(10, false) failed"); + return 101; + } + if (!Ge_uint_bool(10, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Ge_uint_bool(10, false) failed"); + return 101; + } + if (!Ge_ulong_bool(10, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Le_ulong_bool(10, false) failed"); + return 101; + } + + Console.WriteLine("PASSED"); + return 100; + } +} diff --git a/src/tests/JIT/opt/Compares/compareAndTestChains.csproj b/src/tests/JIT/opt/Compares/compareAndTestChains.csproj new file mode 100644 index 00000000000000..5e5fbae5cb863b --- /dev/null +++ b/src/tests/JIT/opt/Compares/compareAndTestChains.csproj @@ -0,0 +1,12 @@ + + + Exe + + + PdbOnly + True + + + + + From 9687016b1652735b143845bc4b6b35ec0b72a680 Mon Sep 17 00:00:00 2001 From: Jo Shields Date: Tue, 9 Aug 2022 17:33:37 -0400 Subject: [PATCH 28/41] PlatformNotSupportedException for UNIX domain sockets on iOS/tvOS (#73374) * PNSE for domain sockets on iOS/tvOS * Re-enable some tests which SHOULD now throw PNSE --- .../Net/SocketProtocolSupportPal.Unix.cs | 6 ++ .../System/IO/StreamConformanceTests.cs | 100 +++++++++--------- .../tests/Base/FileGetSetAttributes.cs | 5 +- .../tests/Directory/CreateDirectory.cs | 2 +- .../tests/Directory/Delete.cs | 4 +- .../tests/Directory/Exists.cs | 2 +- .../Directory/GetFileSystemEntries_str_str.cs | 1 - .../tests/Directory/GetLogicalDrives.cs | 1 - .../tests/DirectoryInfo/Exists.cs | 2 +- .../System.IO.FileSystem/tests/File/Exists.cs | 2 +- .../tests/File/ReadWriteAllBytes.cs | 2 +- .../tests/File/ReadWriteAllBytesAsync.cs | 2 +- .../tests/FileInfo/Exists.cs | 2 +- .../FileStream/DevicesPipesAndSockets.cs | 8 +- .../tests/Path/Exists_File.cs | 2 +- .../NamedPipeTest.CreateServer.cs | 9 +- .../NamedPipeTest.CrossProcess.cs | 6 +- .../NamedPipeTests/NamedPipeTest.Specific.cs | 31 ++++-- .../NamedPipeTest.UnixDomainSockets.cs | 2 + .../tests/PipeStreamConformanceTests.cs | 51 ++++++--- .../tests/StreamReader/StreamReaderTests.cs | 4 +- ...icStreamConnectedStreamConformanceTests.cs | 2 +- .../tests/FunctionalTests/Accept.cs | 1 + .../tests/FunctionalTests/SendFile.cs | 1 + .../FunctionalTests/UnixDomainSocketTest.cs | 82 ++++---------- src/libraries/tests.proj | 5 - 26 files changed, 167 insertions(+), 168 deletions(-) diff --git a/src/libraries/Common/src/System/Net/SocketProtocolSupportPal.Unix.cs b/src/libraries/Common/src/System/Net/SocketProtocolSupportPal.Unix.cs index c755c16517c77a..973254788e5427 100644 --- a/src/libraries/Common/src/System/Net/SocketProtocolSupportPal.Unix.cs +++ b/src/libraries/Common/src/System/Net/SocketProtocolSupportPal.Unix.cs @@ -11,6 +11,12 @@ internal static partial class SocketProtocolSupportPal { private static unsafe bool IsSupported(AddressFamily af) { + // Check for AF_UNIX on iOS/tvOS. The OS claims to support this, but returns EPERM on bind. + // We should explicitly set the return here to false, to avoid giving a false impression. + if (af == AddressFamily.Unix && (OperatingSystem.IsTvOS() || (OperatingSystem.IsIOS() && !OperatingSystem.IsMacCatalyst()))) + { + return false; + } IntPtr invalid = (IntPtr)(-1); IntPtr socket = invalid; try diff --git a/src/libraries/Common/tests/StreamConformanceTests/System/IO/StreamConformanceTests.cs b/src/libraries/Common/tests/StreamConformanceTests/System/IO/StreamConformanceTests.cs index 1c21996128bf6b..bd702c278c6a68 100644 --- a/src/libraries/Common/tests/StreamConformanceTests/System/IO/StreamConformanceTests.cs +++ b/src/libraries/Common/tests/StreamConformanceTests/System/IO/StreamConformanceTests.cs @@ -722,7 +722,6 @@ public abstract class StandaloneStreamConformanceTests : StreamConformanceTests } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/67853", TestPlatforms.tvOS)] public virtual async Task ArgumentValidation_ThrowsExpectedException() { await foreach (Stream? stream in GetStreamsForValidation()) @@ -736,7 +735,6 @@ public virtual async Task ArgumentValidation_ThrowsExpectedException() } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/67853", TestPlatforms.tvOS)] public virtual async Task Disposed_ThrowsObjectDisposedException() { await foreach (Stream? stream in GetStreamsForValidation()) @@ -801,7 +799,6 @@ public virtual async Task Write_Nop_Success(ReadWriteMode mode) [InlineData(ReadWriteMode.SyncArray)] [InlineData(ReadWriteMode.AsyncArray)] [InlineData(ReadWriteMode.AsyncAPM)] - [ActiveIssue("https://github.com/dotnet/runtime/issues/67853", TestPlatforms.tvOS)] public virtual async Task Read_DataStoredAtDesiredOffset(ReadWriteMode mode) { const byte Expected = 42; @@ -1635,8 +1632,8 @@ protected static bool Bidirectional(StreamPair streams) => streams.Stream2.CanRead && streams.Stream2.CanWrite; [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/67853", TestPlatforms.tvOS)] - [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets")] + [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")] + [SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.tvOS, "iOS/tvOS blocks binding to UNIX sockets")] public virtual async Task ArgumentValidation_ThrowsExpectedException() { using StreamPair streams = await CreateConnectedStreamsAsync(); @@ -1648,8 +1645,8 @@ public virtual async Task ArgumentValidation_ThrowsExpectedException() } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/67853", TestPlatforms.tvOS)] - [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets")] + [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")] + [SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.tvOS, "iOS/tvOS blocks binding to UNIX sockets")] public virtual async Task Disposed_ThrowsObjectDisposedException() { StreamPair streams = await CreateConnectedStreamsAsync(); @@ -1662,8 +1659,8 @@ public virtual async Task Disposed_ThrowsObjectDisposedException() } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/67853", TestPlatforms.tvOS)] - [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets")] + [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")] + [SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.tvOS, "iOS/tvOS blocks binding to UNIX sockets")] public virtual async Task ReadWriteAsync_PrecanceledOperations_ThrowsCancellationException() { using StreamPair streams = await CreateConnectedStreamsAsync(); @@ -1677,8 +1674,8 @@ public virtual async Task ReadWriteAsync_PrecanceledOperations_ThrowsCancellatio [Theory] [InlineData(0)] [InlineData(100)] - [ActiveIssue("https://github.com/dotnet/runtime/issues/67853", TestPlatforms.tvOS)] - [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets")] + [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")] + [SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.tvOS, "iOS/tvOS blocks binding to UNIX sockets")] public virtual async Task ReadAsync_CancelPendingTask_ThrowsCancellationException(int cancellationDelay) { using StreamPair streams = await CreateConnectedStreamsAsync(); @@ -1690,8 +1687,8 @@ public virtual async Task ReadAsync_CancelPendingTask_ThrowsCancellationExceptio [Theory] [InlineData(0)] [InlineData(100)] - [ActiveIssue("https://github.com/dotnet/runtime/issues/67853", TestPlatforms.tvOS)] - [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets")] + [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")] + [SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.tvOS, "iOS/tvOS blocks binding to UNIX sockets")] public virtual async Task ReadAsync_CancelPendingValueTask_ThrowsCancellationException(int cancellationDelay) { using StreamPair streams = await CreateConnectedStreamsAsync(); @@ -1701,8 +1698,8 @@ public virtual async Task ReadAsync_CancelPendingValueTask_ThrowsCancellationExc } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/67853", TestPlatforms.tvOS)] - [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets")] + [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")] + [SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.tvOS, "iOS/tvOS blocks binding to UNIX sockets")] public virtual async Task ReadWriteByte_Success() { using StreamPair streams = await CreateConnectedStreamsAsync(); @@ -1775,8 +1772,8 @@ public virtual async Task ReadWrite_Success_Large(ReadWriteMode mode, int writeS [Theory] [MemberData(nameof(ReadWrite_Success_MemberData))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/67853", TestPlatforms.tvOS)] - [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets")] + [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")] + [SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.tvOS, "iOS/tvOS blocks binding to UNIX sockets")] public virtual async Task ReadWrite_Success(ReadWriteMode mode, int writeSize, bool startWithFlush) { foreach (CancellationToken nonCanceledToken in new[] { CancellationToken.None, new CancellationTokenSource().Token }) @@ -1833,8 +1830,8 @@ public virtual async Task ReadWrite_Success(ReadWriteMode mode, int writeSize, b [Theory] [MemberData(nameof(ReadWrite_Modes))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/67853", TestPlatforms.tvOS)] - [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets")] + [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")] + [SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.tvOS, "iOS/tvOS blocks binding to UNIX sockets")] public virtual async Task ReadWrite_MessagesSmallerThanReadBuffer_Success(ReadWriteMode mode) { if (!FlushGuaranteesAllDataWritten) @@ -1883,8 +1880,8 @@ public virtual async Task ReadWrite_MessagesSmallerThanReadBuffer_Success(ReadWr [Theory] [MemberData(nameof(AllReadWriteModesAndValue), false)] [MemberData(nameof(AllReadWriteModesAndValue), true)] - [ActiveIssue("https://github.com/dotnet/runtime/issues/67853", TestPlatforms.tvOS)] - [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets")] + [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")] + [SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.tvOS, "iOS/tvOS blocks binding to UNIX sockets")] public virtual async Task Read_Eof_Returns0(ReadWriteMode mode, bool dataAvailableFirst) { using StreamPair streams = await CreateConnectedStreamsAsync(); @@ -1923,8 +1920,8 @@ public virtual async Task Read_Eof_Returns0(ReadWriteMode mode, bool dataAvailab [InlineData(ReadWriteMode.SyncArray)] [InlineData(ReadWriteMode.AsyncArray)] [InlineData(ReadWriteMode.AsyncAPM)] - [ActiveIssue("https://github.com/dotnet/runtime/issues/67853", TestPlatforms.tvOS)] - [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets")] + [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")] + [SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.tvOS, "iOS/tvOS blocks binding to UNIX sockets")] public virtual async Task Read_DataStoredAtDesiredOffset(ReadWriteMode mode) { using StreamPair streams = await CreateConnectedStreamsAsync(); @@ -1954,8 +1951,8 @@ public virtual async Task Read_DataStoredAtDesiredOffset(ReadWriteMode mode) [InlineData(ReadWriteMode.SyncArray)] [InlineData(ReadWriteMode.AsyncArray)] [InlineData(ReadWriteMode.AsyncAPM)] - [ActiveIssue("https://github.com/dotnet/runtime/issues/67853", TestPlatforms.tvOS)] - [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets")] + [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")] + [SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.tvOS, "iOS/tvOS blocks binding to UNIX sockets")] public virtual async Task Write_DataReadFromDesiredOffset(ReadWriteMode mode) { using StreamPair streams = await CreateConnectedStreamsAsync(); @@ -2054,8 +2051,8 @@ public static IEnumerable ReadAsync_ContinuesOnCurrentContextIfDesired [Theory] [MemberData(nameof(ReadAsync_ContinuesOnCurrentContextIfDesired_MemberData))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/67853", TestPlatforms.tvOS)] - [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets")] + [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")] + [SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.tvOS, "iOS/tvOS blocks binding to UNIX sockets")] public virtual async Task ReadAsync_ContinuesOnCurrentSynchronizationContextIfDesired(bool flowExecutionContext, bool? continueOnCapturedContext) { await default(JumpToThreadPoolAwaiter); // escape xunit sync ctx @@ -2138,8 +2135,8 @@ public virtual async Task ReadAsync_ContinuesOnCurrentSynchronizationContextIfDe [Theory] [MemberData(nameof(ReadAsync_ContinuesOnCurrentContextIfDesired_MemberData))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/67853", TestPlatforms.tvOS)] - [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets")] + [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")] + [SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.tvOS, "iOS/tvOS blocks binding to UNIX sockets")] public virtual async Task ReadAsync_ContinuesOnCurrentTaskSchedulerIfDesired(bool flowExecutionContext, bool? continueOnCapturedContext) { await default(JumpToThreadPoolAwaiter); // escape xunit sync ctx @@ -2229,8 +2226,8 @@ await Task.Factory.StartNew(() => [InlineData(ReadWriteMode.AsyncMemory)] [InlineData(ReadWriteMode.SyncAPM)] [InlineData(ReadWriteMode.AsyncAPM)] - [ActiveIssue("https://github.com/dotnet/runtime/issues/67853", TestPlatforms.tvOS)] - [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets")] + [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")] + [SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.tvOS, "iOS/tvOS blocks binding to UNIX sockets")] public virtual async Task ZeroByteRead_BlocksUntilDataAvailableOrNops(ReadWriteMode mode) { using StreamPair streams = await CreateConnectedStreamsAsync(); @@ -2297,8 +2294,8 @@ public virtual async Task ZeroByteRead_BlocksUntilDataAvailableOrNops(ReadWriteM [InlineData(ReadWriteMode.AsyncMemory)] [InlineData(ReadWriteMode.SyncAPM)] [InlineData(ReadWriteMode.AsyncAPM)] - [ActiveIssue("https://github.com/dotnet/runtime/issues/67853", TestPlatforms.tvOS)] - [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets")] + [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")] + [SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.tvOS, "iOS/tvOS blocks binding to UNIX sockets")] public virtual async Task ZeroByteWrite_OtherDataReceivedSuccessfully(ReadWriteMode mode) { byte[][] buffers = new[] { Array.Empty(), "hello"u8.ToArray(), Array.Empty(), "world"u8.ToArray() }; @@ -2352,8 +2349,8 @@ public virtual async Task ZeroByteWrite_OtherDataReceivedSuccessfully(ReadWriteM [Theory] [InlineData(false)] [InlineData(true)] - [ActiveIssue("https://github.com/dotnet/runtime/issues/67853", TestPlatforms.tvOS)] - [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets")] + [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")] + [SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.tvOS, "iOS/tvOS blocks binding to UNIX sockets")] public virtual async Task ReadWrite_CustomMemoryManager_Success(bool useAsync) { using StreamPair streams = await CreateConnectedStreamsAsync(); @@ -2418,7 +2415,8 @@ await readable.ReadAsync(readBuffer.Memory.Slice(bytesRead)) : } [Fact] - [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets")] + [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")] + [SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.tvOS, "iOS/tvOS blocks binding to UNIX sockets")] public virtual async Task ConcurrentBidirectionalReadsWrites_Success() { if (!SupportsConcurrentBidirectionalUse) @@ -2475,8 +2473,8 @@ public virtual async Task CopyToAsync_AllDataCopied_Large(bool useAsync) => [Theory] [MemberData(nameof(CopyToAsync_AllDataCopied_MemberData))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/67853", TestPlatforms.tvOS)] - [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets")] + [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")] + [SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.tvOS, "iOS/tvOS blocks binding to UNIX sockets")] public virtual async Task CopyToAsync_AllDataCopied(int byteCount, bool useAsync) { using StreamPair streams = await CreateConnectedStreamsAsync(); @@ -2505,7 +2503,8 @@ public virtual async Task CopyToAsync_AllDataCopied(int byteCount, bool useAsync [OuterLoop("May take several seconds")] [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] - [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets")] + [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")] + [SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.tvOS, "iOS/tvOS blocks binding to UNIX sockets")] public virtual async Task Parallel_ReadWriteMultipleStreamsConcurrently() { await Task.WhenAll(Enumerable.Range(0, 20).Select(_ => Task.Run(async () => @@ -2515,8 +2514,8 @@ await Task.WhenAll(Enumerable.Range(0, 20).Select(_ => Task.Run(async () => } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/67853", TestPlatforms.tvOS)] - [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets")] + [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")] + [SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.tvOS, "iOS/tvOS blocks binding to UNIX sockets")] public virtual async Task Timeout_Roundtrips() { using StreamPair streams = await CreateConnectedStreamsAsync(); @@ -2551,8 +2550,8 @@ public virtual async Task Timeout_Roundtrips() } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/67853", TestPlatforms.tvOS)] - [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets")] + [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")] + [SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.tvOS, "iOS/tvOS blocks binding to UNIX sockets")] public virtual async Task ReadTimeout_Expires_Throws() { using StreamPair streams = await CreateConnectedStreamsAsync(); @@ -2667,7 +2666,8 @@ public virtual async Task ClosedConnection_WritesFailImmediately_ThrowException( } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] - [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets")] + [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")] + [SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.tvOS, "iOS/tvOS blocks binding to UNIX sockets")] public virtual async Task ReadAsync_DuringReadAsync_ThrowsIfUnsupported() { if (UnsupportedConcurrentExceptionType is null) @@ -2688,8 +2688,8 @@ public virtual async Task ReadAsync_DuringReadAsync_ThrowsIfUnsupported() } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/67853", TestPlatforms.tvOS)] - [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets")] + [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")] + [SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.tvOS, "iOS/tvOS blocks binding to UNIX sockets")] public virtual async Task Flush_ValidOnWriteableStreamWithNoData_Success() { using StreamPair streams = await CreateConnectedStreamsAsync(); @@ -2704,8 +2704,8 @@ public virtual async Task Flush_ValidOnWriteableStreamWithNoData_Success() } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/67853", TestPlatforms.tvOS)] - [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets")] + [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")] + [SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.tvOS, "iOS/tvOS blocks binding to UNIX sockets")] public virtual async Task Flush_ValidOnReadableStream_Success() { using StreamPair streams = await CreateConnectedStreamsAsync(); @@ -2723,8 +2723,8 @@ public virtual async Task Flush_ValidOnReadableStream_Success() [InlineData(0)] [InlineData(1)] [InlineData(2)] - [ActiveIssue("https://github.com/dotnet/runtime/issues/67853", TestPlatforms.tvOS)] - [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets")] + [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")] + [SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.tvOS, "iOS/tvOS blocks binding to UNIX sockets")] public virtual async Task Dispose_ClosesStream(int disposeMode) { if (!CansReturnFalseAfterDispose) diff --git a/src/libraries/System.IO.FileSystem/tests/Base/FileGetSetAttributes.cs b/src/libraries/System.IO.FileSystem/tests/Base/FileGetSetAttributes.cs index cbf6e8eadeea6c..79102175659061 100644 --- a/src/libraries/System.IO.FileSystem/tests/Base/FileGetSetAttributes.cs +++ b/src/libraries/System.IO.FileSystem/tests/Base/FileGetSetAttributes.cs @@ -27,7 +27,7 @@ public void SettingAttributes_Unix_ReadOnly() [Theory] [InlineData(FileAttributes.Hidden)] - [PlatformSpecific(TestPlatforms.OSX | TestPlatforms.FreeBSD)] + [PlatformSpecific(TestPlatforms.OSX | TestPlatforms.FreeBSD | TestPlatforms.iOS | TestPlatforms.tvOS | TestPlatforms.MacCatalyst)] public void SettingAttributes_OSXAndFreeBSD(FileAttributes attributes) { string path = CreateItem(); @@ -80,8 +80,7 @@ public void SettingInvalidAttributes_Unix(FileAttributes attributes) [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.FileCreateCaseSensitive))] [InlineData(FileAttributes.Hidden)] - [PlatformSpecific(TestPlatforms.AnyUnix & ~(TestPlatforms.OSX | TestPlatforms.FreeBSD))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/67853", TestPlatforms.tvOS)] + [PlatformSpecific(TestPlatforms.AnyUnix & ~(TestPlatforms.OSX | TestPlatforms.FreeBSD | TestPlatforms.iOS | TestPlatforms.tvOS | TestPlatforms.MacCatalyst))] public void SettingInvalidAttributes_UnixExceptOSXAndFreeBSD(FileAttributes attributes) { string path = CreateItem(); diff --git a/src/libraries/System.IO.FileSystem/tests/Directory/CreateDirectory.cs b/src/libraries/System.IO.FileSystem/tests/Directory/CreateDirectory.cs index 988914e56f00d1..4cf06cc34b99f6 100644 --- a/src/libraries/System.IO.FileSystem/tests/Directory/CreateDirectory.cs +++ b/src/libraries/System.IO.FileSystem/tests/Directory/CreateDirectory.cs @@ -468,7 +468,7 @@ public void DriveLetter_Windows() [Fact] [PlatformSpecific(TestPlatforms.AnyUnix)] // drive letters casing - [ActiveIssue("https://github.com/dotnet/runtime/issues/67853", TestPlatforms.tvOS)] + [ActiveIssue("https://github.com/dotnet/runtime/issues/67853", TestPlatforms.iOS | TestPlatforms.tvOS)] public void DriveLetter_Unix() { // On Unix, there's no special casing for drive letters. These may or may not be valid names, depending diff --git a/src/libraries/System.IO.FileSystem/tests/Directory/Delete.cs b/src/libraries/System.IO.FileSystem/tests/Directory/Delete.cs index fa6cc76cda47b9..feef272704f484 100644 --- a/src/libraries/System.IO.FileSystem/tests/Directory/Delete.cs +++ b/src/libraries/System.IO.FileSystem/tests/Directory/Delete.cs @@ -122,7 +122,7 @@ public void DeletingSymLinkDoesntDeleteTarget() } [ConditionalFact(nameof(UsingNewNormalization))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/67853", TestPlatforms.tvOS)] + [ActiveIssue("https://github.com/dotnet/runtime/issues/67853", TestPlatforms.iOS | TestPlatforms.tvOS)] public void ExtendedDirectoryWithSubdirectories() { DirectoryInfo testDir = Directory.CreateDirectory(IOInputs.ExtendedPrefix + GetTestFilePath()); @@ -132,7 +132,7 @@ public void ExtendedDirectoryWithSubdirectories() } [ConditionalFact(nameof(LongPathsAreNotBlocked), nameof(UsingNewNormalization))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/67853", TestPlatforms.tvOS)] + [ActiveIssue("https://github.com/dotnet/runtime/issues/67853", TestPlatforms.iOS | TestPlatforms.tvOS)] public void LongPathExtendedDirectory() { DirectoryInfo testDir = Directory.CreateDirectory(IOServices.GetPath(IOInputs.ExtendedPrefix + TestDirectory, characterCount: 500)); diff --git a/src/libraries/System.IO.FileSystem/tests/Directory/Exists.cs b/src/libraries/System.IO.FileSystem/tests/Directory/Exists.cs index b5daf1323139b5..74b77fd2f3bcbe 100644 --- a/src/libraries/System.IO.FileSystem/tests/Directory/Exists.cs +++ b/src/libraries/System.IO.FileSystem/tests/Directory/Exists.cs @@ -394,7 +394,7 @@ public void ExtendedPathAlreadyExistsAsFile() [Fact] [PlatformSpecific(TestPlatforms.AnyUnix & ~TestPlatforms.Browser)] // Makes call to native code (libc) - [ActiveIssue("https://github.com/dotnet/runtime/issues/67853", TestPlatforms.tvOS)] + [ActiveIssue("https://github.com/dotnet/runtime/issues/67853", TestPlatforms.iOS | TestPlatforms.tvOS)] public void FalseForNonRegularFile() { string fileName = GetTestFilePath(); diff --git a/src/libraries/System.IO.FileSystem/tests/Directory/GetFileSystemEntries_str_str.cs b/src/libraries/System.IO.FileSystem/tests/Directory/GetFileSystemEntries_str_str.cs index 7ccdc8bcb3f930..8ae44e8899272f 100644 --- a/src/libraries/System.IO.FileSystem/tests/Directory/GetFileSystemEntries_str_str.cs +++ b/src/libraries/System.IO.FileSystem/tests/Directory/GetFileSystemEntries_str_str.cs @@ -698,7 +698,6 @@ public void WindowsSearchPatternWhitespace() } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.FileCreateCaseSensitive))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/67853", TestPlatforms.tvOS)] public void SearchPatternCaseSensitive() { DirectoryInfo testDir = Directory.CreateDirectory(GetTestFilePath()); diff --git a/src/libraries/System.IO.FileSystem/tests/Directory/GetLogicalDrives.cs b/src/libraries/System.IO.FileSystem/tests/Directory/GetLogicalDrives.cs index 3f0dc42e96e99d..0061ab46b077a9 100644 --- a/src/libraries/System.IO.FileSystem/tests/Directory/GetLogicalDrives.cs +++ b/src/libraries/System.IO.FileSystem/tests/Directory/GetLogicalDrives.cs @@ -11,7 +11,6 @@ public class Directory_GetLogicalDrives { [Fact] [PlatformSpecific(TestPlatforms.AnyUnix)] // Valid drive strings on Unix - [ActiveIssue("https://github.com/dotnet/runtime/issues/67853", TestPlatforms.tvOS)] public void GetsValidDriveStrings_Unix() { string[] drives = Directory.GetLogicalDrives(); diff --git a/src/libraries/System.IO.FileSystem/tests/DirectoryInfo/Exists.cs b/src/libraries/System.IO.FileSystem/tests/DirectoryInfo/Exists.cs index d0f3d095d2da0f..05b41aa991adc5 100644 --- a/src/libraries/System.IO.FileSystem/tests/DirectoryInfo/Exists.cs +++ b/src/libraries/System.IO.FileSystem/tests/DirectoryInfo/Exists.cs @@ -109,7 +109,7 @@ public void FalseForFile() [Fact] [PlatformSpecific(TestPlatforms.AnyUnix & ~TestPlatforms.Browser)] // Uses P/Invokes - [ActiveIssue("https://github.com/dotnet/runtime/issues/67853", TestPlatforms.tvOS)] + [ActiveIssue("https://github.com/dotnet/runtime/issues/67853", TestPlatforms.iOS | TestPlatforms.tvOS)] public void FalseForNonRegularFile() { string fileName = GetTestFilePath(); diff --git a/src/libraries/System.IO.FileSystem/tests/File/Exists.cs b/src/libraries/System.IO.FileSystem/tests/File/Exists.cs index 48d87fc2b79466..2716c13b290d4a 100644 --- a/src/libraries/System.IO.FileSystem/tests/File/Exists.cs +++ b/src/libraries/System.IO.FileSystem/tests/File/Exists.cs @@ -255,7 +255,7 @@ public void PathAlreadyExistsAsDirectory() [Fact] [PlatformSpecific(TestPlatforms.AnyUnix & ~TestPlatforms.Browser)] // Uses P/Invokes - [ActiveIssue("https://github.com/dotnet/runtime/issues/67853", TestPlatforms.tvOS)] + [ActiveIssue("https://github.com/dotnet/runtime/issues/67853", TestPlatforms.iOS | TestPlatforms.tvOS)] public void FalseForNonRegularFile() { string fileName = GetTestFilePath(); diff --git a/src/libraries/System.IO.FileSystem/tests/File/ReadWriteAllBytes.cs b/src/libraries/System.IO.FileSystem/tests/File/ReadWriteAllBytes.cs index a3abe2311868ec..ce2699fcdf1f51 100644 --- a/src/libraries/System.IO.FileSystem/tests/File/ReadWriteAllBytes.cs +++ b/src/libraries/System.IO.FileSystem/tests/File/ReadWriteAllBytes.cs @@ -194,7 +194,7 @@ static async Task WaitConnectionAndWritePipeStreamAsync(NamedPipeServerStream na [Fact] [PlatformSpecific(TestPlatforms.AnyUnix & ~TestPlatforms.Browser)] - [ActiveIssue("https://github.com/dotnet/runtime/issues/67853", TestPlatforms.tvOS)] + [ActiveIssue("https://github.com/dotnet/runtime/issues/67853", TestPlatforms.iOS | TestPlatforms.tvOS)] public async Task ReadAllBytes_NonSeekableFileStream_InUnix() { string fifoPath = GetTestFilePath(); diff --git a/src/libraries/System.IO.FileSystem/tests/File/ReadWriteAllBytesAsync.cs b/src/libraries/System.IO.FileSystem/tests/File/ReadWriteAllBytesAsync.cs index 8ae63fe3c9e0a5..47b8324f66c343 100644 --- a/src/libraries/System.IO.FileSystem/tests/File/ReadWriteAllBytesAsync.cs +++ b/src/libraries/System.IO.FileSystem/tests/File/ReadWriteAllBytesAsync.cs @@ -204,7 +204,7 @@ static async Task WaitConnectionAndWritePipeStreamAsync(NamedPipeServerStream na [Fact] [PlatformSpecific(TestPlatforms.AnyUnix & ~TestPlatforms.Browser)] - [ActiveIssue("https://github.com/dotnet/runtime/issues/67853", TestPlatforms.tvOS)] + [ActiveIssue("https://github.com/dotnet/runtime/issues/67853", TestPlatforms.iOS | TestPlatforms.tvOS)] public async Task ReadAllBytesAsync_NonSeekableFileStream_InUnix() { string fifoPath = GetTestFilePath(); diff --git a/src/libraries/System.IO.FileSystem/tests/FileInfo/Exists.cs b/src/libraries/System.IO.FileSystem/tests/FileInfo/Exists.cs index 04e13706db6564..782f0cc3445629 100644 --- a/src/libraries/System.IO.FileSystem/tests/FileInfo/Exists.cs +++ b/src/libraries/System.IO.FileSystem/tests/FileInfo/Exists.cs @@ -90,7 +90,7 @@ public void FalseForDirectory() [Fact] [PlatformSpecific(TestPlatforms.AnyUnix & ~TestPlatforms.Browser)] // Uses P/Invokes - [ActiveIssue("https://github.com/dotnet/runtime/issues/67853", TestPlatforms.tvOS)] + [ActiveIssue("https://github.com/dotnet/runtime/issues/67853", TestPlatforms.iOS | TestPlatforms.tvOS)] public void TrueForNonRegularFile() { string fileName = GetTestFilePath(); diff --git a/src/libraries/System.IO.FileSystem/tests/FileStream/DevicesPipesAndSockets.cs b/src/libraries/System.IO.FileSystem/tests/FileStream/DevicesPipesAndSockets.cs index 86a4901b344ce5..2a792a8f4e4fb3 100644 --- a/src/libraries/System.IO.FileSystem/tests/FileStream/DevicesPipesAndSockets.cs +++ b/src/libraries/System.IO.FileSystem/tests/FileStream/DevicesPipesAndSockets.cs @@ -63,8 +63,8 @@ public async Task CharacterDevice_WriteAllTextAsync(string devicePath) [Fact] [PlatformSpecific(TestPlatforms.AnyUnix & ~TestPlatforms.Browser)] - [ActiveIssue("https://github.com/dotnet/runtime/issues/67853", TestPlatforms.tvOS)] - [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets")] + [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")] + [SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.tvOS, "iOS/tvOS blocks binding to UNIX sockets")] public async Task NamedPipe_ReadWrite() { string fifoPath = GetTestFilePath(); @@ -85,8 +85,8 @@ await Task.WhenAll( [Fact] [PlatformSpecific(TestPlatforms.AnyUnix & ~TestPlatforms.Browser)] - [ActiveIssue("https://github.com/dotnet/runtime/issues/67853", TestPlatforms.tvOS)] - [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets")] + [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")] + [SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.tvOS, "iOS/tvOS blocks binding to UNIX sockets")] public async Task NamedPipe_ReadWrite_Async() { string fifoPath = GetTestFilePath(); diff --git a/src/libraries/System.IO.FileSystem/tests/Path/Exists_File.cs b/src/libraries/System.IO.FileSystem/tests/Path/Exists_File.cs index 652d348b5c3f3e..80f1dc52c6fbb9 100644 --- a/src/libraries/System.IO.FileSystem/tests/Path/Exists_File.cs +++ b/src/libraries/System.IO.FileSystem/tests/Path/Exists_File.cs @@ -22,7 +22,7 @@ public void PathAlreadyExistsAsDirectory() [Fact] [PlatformSpecific(TestPlatforms.AnyUnix & ~TestPlatforms.Browser)] // Uses P/Invokes - [ActiveIssue("https://github.com/dotnet/runtime/issues/67853", TestPlatforms.tvOS)] + [ActiveIssue("https://github.com/dotnet/runtime/issues/67853", TestPlatforms.iOS | TestPlatforms.tvOS)] public void TrueForNonRegularFile() { string fileName = GetTestFilePath(); diff --git a/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.CreateServer.cs b/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.CreateServer.cs index 412010a0c9953b..d8afc8094a3de3 100644 --- a/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.CreateServer.cs +++ b/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.CreateServer.cs @@ -55,14 +55,16 @@ public static void ReservedPipeName_Throws_ArgumentOutOfRangeException(PipeDirec AssertExtensions.Throws("pipeName", () => new NamedPipeServerStream(reservedName, direction, 1, PipeTransmissionMode.Byte, PipeOptions.None, 0, 0));} [Fact] - [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets")] + [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")] + [SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.tvOS, "iOS/tvOS blocks binding to UNIX sockets")] public static void Create_PipeName() { new NamedPipeServerStream(PipeStreamConformanceTests.GetUniquePipeName()).Dispose(); } [Fact] - [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets")] + [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")] + [SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.tvOS, "iOS/tvOS blocks binding to UNIX sockets")] public static void Create_PipeName_Direction_MaxInstances() { new NamedPipeServerStream(PipeStreamConformanceTests.GetUniquePipeName(), PipeDirection.Out, 1).Dispose(); @@ -207,7 +209,8 @@ public static void Windows_CreateFromDisposedServerHandle_Throws_ObjectDisposedE [Fact] [PlatformSpecific(TestPlatforms.AnyUnix)] // accessing SafePipeHandle on Unix fails for a non-connected stream - [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets")] + [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")] + [SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.tvOS, "iOS/tvOS blocks binding to UNIX sockets")] public static void Unix_GetHandleOfNewServerStream_Throws_InvalidOperationException() { using (var pipe = new NamedPipeServerStream(PipeStreamConformanceTests.GetUniquePipeName(), PipeDirection.Out, 1, PipeTransmissionMode.Byte)) diff --git a/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.CrossProcess.cs b/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.CrossProcess.cs index 858c7a77fff6f7..b94f6771f07d41 100644 --- a/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.CrossProcess.cs +++ b/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.CrossProcess.cs @@ -14,7 +14,7 @@ namespace System.IO.Pipes.Tests public sealed class NamedPipeTest_CrossProcess { [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] - [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets")] + [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")] public void InheritHandles_AvailableInChildProcess() { string pipeName = PipeStreamConformanceTests.GetUniquePipeName(); @@ -46,7 +46,7 @@ void ChildFunc(string handle) } [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] - [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets")] + [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")] public void PingPong_Sync() { // Create names for two pipes @@ -73,7 +73,7 @@ public void PingPong_Sync() } [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] - [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets")] + [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")] public async Task PingPong_Async() { // Create names for two pipes diff --git a/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.Specific.cs b/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.Specific.cs index 6f653c836a8809..50398fd55501f6 100644 --- a/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.Specific.cs +++ b/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.Specific.cs @@ -31,6 +31,7 @@ public void InvalidConnectTimeout_Throws_ArgumentOutOfRangeException() } [Fact] + [SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.tvOS, "iOS/tvOS blocks binding to UNIX sockets")] public async Task ConnectToNonExistentServer_Throws_TimeoutException() { using (NamedPipeClientStream client = new NamedPipeClientStream(".", "notthere")) @@ -46,6 +47,7 @@ await Assert.ThrowsAsync(() => } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.tvOS, "iOS/tvOS blocks binding to UNIX sockets")] public async Task CancelConnectToNonExistentServer_Throws_OperationCanceledException() { using (NamedPipeClientStream client = new NamedPipeClientStream(".", "notthere")) @@ -85,7 +87,8 @@ public void ConnectWithConflictingDirections_Throws_UnauthorizedAccessException( [Theory] [InlineData(1)] [InlineData(3)] - [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets")] + [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")] + [SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.tvOS, "iOS/tvOS blocks binding to UNIX sockets")] public async Task MultipleWaitingClients_ServerServesOneAtATime(int numClients) { string name = PipeStreamConformanceTests.GetUniquePipeName(); @@ -121,7 +124,8 @@ async Task ConnectClientAndReadAsync() } [Fact] - [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets")] + [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")] + [SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.tvOS, "iOS/tvOS blocks binding to UNIX sockets")] public void MaxNumberOfServerInstances_TooManyServers_Throws() { string name = PipeStreamConformanceTests.GetUniquePipeName(); @@ -159,7 +163,8 @@ public void MaxNumberOfServerInstances_TooManyServers_Throws() [Theory] [InlineData(1)] [InlineData(4)] - [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets")] + [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")] + [SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.tvOS, "iOS/tvOS blocks binding to UNIX sockets")] public async Task MultipleServers_ServeMultipleClientsConcurrently(int numServers) { string name = PipeStreamConformanceTests.GetUniquePipeName(); @@ -356,7 +361,8 @@ public async Task Windows_GetImpersonationUserName_Succeed(TokenImpersonationLev [Fact] [PlatformSpecific(TestPlatforms.AnyUnix)] // Uses P/Invoke to verify the user name - [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets")] + [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")] + [SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.tvOS, "iOS/tvOS blocks binding to UNIX sockets")] public async Task Unix_GetImpersonationUserName_Succeed() { string pipeName = PipeStreamConformanceTests.GetUniquePipeName(); @@ -387,7 +393,8 @@ public void Unix_MessagePipeTransmissionMode() [InlineData(PipeDirection.Out)] [InlineData(PipeDirection.InOut)] [PlatformSpecific(TestPlatforms.AnyUnix)] // Unix implementation uses bidirectional sockets - [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets")] + [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")] + [SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.tvOS, "iOS/tvOS blocks binding to UNIX sockets")] public static void Unix_BufferSizeRoundtripping(PipeDirection direction) { int desiredBufferSize = 0; @@ -451,7 +458,8 @@ public static void Windows_BufferSizeRoundtripping() } [Fact] - [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets")] + [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")] + [SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.tvOS, "iOS/tvOS blocks binding to UNIX sockets")] public async Task PipeTransmissionMode_Returns_Byte() { string pipeName = PipeStreamConformanceTests.GetUniquePipeName(); @@ -507,7 +515,8 @@ public void Windows_SetReadModeTo__PipeTransmissionModeByte() [Fact] [PlatformSpecific(TestPlatforms.AnyUnix)] // Unix doesn't currently support message mode - [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets")] + [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")] + [SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.tvOS, "iOS/tvOS blocks binding to UNIX sockets")] public void Unix_SetReadModeTo__PipeTransmissionModeByte() { string pipeName = PipeStreamConformanceTests.GetUniquePipeName(); @@ -548,7 +557,8 @@ public void Unix_SetReadModeTo__PipeTransmissionModeByte() [Theory] [InlineData(PipeDirection.Out, PipeDirection.In)] [InlineData(PipeDirection.In, PipeDirection.Out)] - [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets")] + [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")] + [SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.tvOS, "iOS/tvOS blocks binding to UNIX sockets")] public void InvalidReadMode_Throws_ArgumentOutOfRangeException(PipeDirection serverDirection, PipeDirection clientDirection) { string pipeName = PipeStreamConformanceTests.GetUniquePipeName(); @@ -566,7 +576,8 @@ public void InvalidReadMode_Throws_ArgumentOutOfRangeException(PipeDirection ser [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [PlatformSpecific(TestPlatforms.AnyUnix)] // Checks MaxLength for PipeName on Unix - [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets")] + [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")] + [SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.tvOS, "iOS/tvOS blocks binding to UNIX sockets")] public void NameTooLong_MaxLengthPerPlatform() { // Increase a name's length until it fails @@ -611,6 +622,7 @@ public void NameTooLong_MaxLengthPerPlatform() } [Fact] + [SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.tvOS, "iOS/tvOS blocks binding to UNIX sockets")] public void ClientConnect_Throws_Timeout_When_Pipe_Not_Found() { string pipeName = PipeStreamConformanceTests.GetUniquePipeName(); @@ -622,6 +634,7 @@ public void ClientConnect_Throws_Timeout_When_Pipe_Not_Found() [Theory] [MemberData(nameof(GetCancellationTokens))] + [SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.tvOS, "iOS/tvOS blocks binding to UNIX sockets")] public async Task ClientConnectAsync_Throws_Timeout_When_Pipe_Not_Found(CancellationToken cancellationToken) { string pipeName = PipeStreamConformanceTests.GetUniquePipeName(); diff --git a/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.UnixDomainSockets.cs b/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.UnixDomainSockets.cs index 6a473be5aeefd2..67ec9825de3063 100644 --- a/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.UnixDomainSockets.cs +++ b/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.UnixDomainSockets.cs @@ -11,6 +11,7 @@ public class NamedPipeTest_UnixDomainSockets { [Fact] [PlatformSpecific(TestPlatforms.AnyUnix)] + [SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.tvOS, "iOS/tvOS blocks binding to UNIX sockets")] public void NamedPipeServer_Connects_With_UnixDomainSocketEndPointClient() { string pipeName = Path.Combine(Path.GetTempPath(), "pipe-tests-corefx-" + Path.GetRandomFileName()); @@ -28,6 +29,7 @@ public void NamedPipeServer_Connects_With_UnixDomainSocketEndPointClient() [Fact] [PlatformSpecific(TestPlatforms.AnyUnix)] + [SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.tvOS, "iOS/tvOS blocks binding to UNIX sockets")] public async Task NamedPipeClient_Connects_With_UnixDomainSocketEndPointServer() { string pipeName = Path.Combine(Path.GetTempPath(), "pipe-tests-corefx-" + Path.GetRandomFileName()); diff --git a/src/libraries/System.IO.Pipes/tests/PipeStreamConformanceTests.cs b/src/libraries/System.IO.Pipes/tests/PipeStreamConformanceTests.cs index 3d03bdc1b2e066..918303b019a0a0 100644 --- a/src/libraries/System.IO.Pipes/tests/PipeStreamConformanceTests.cs +++ b/src/libraries/System.IO.Pipes/tests/PipeStreamConformanceTests.cs @@ -119,7 +119,8 @@ public static IEnumerable OneWayReadWritesMemberData() => select new object[] { serverOption, clientOption, asyncServerOps, asyncClientOps }; [Fact] - [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets")] + [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")] + [SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.tvOS, "iOS/tvOS blocks binding to UNIX sockets")] public async Task ClonedServer_ActsAsOriginalServer() { byte[] msg1 = new byte[] { 5, 7, 9, 10 }; @@ -158,7 +159,8 @@ public async Task ClonedServer_ActsAsOriginalServer() } [Fact] - [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets")] + [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")] + [SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.tvOS, "iOS/tvOS blocks binding to UNIX sockets")] public async Task ClonedClient_ActsAsOriginalClient() { byte[] msg1 = new byte[] { 5, 7, 9, 10 }; @@ -196,7 +198,8 @@ public async Task ClonedClient_ActsAsOriginalClient() } [Fact] - [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets")] + [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")] + [SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.tvOS, "iOS/tvOS blocks binding to UNIX sockets")] public async Task ConnectOnAlreadyConnectedClient_Throws_InvalidOperationException() { using StreamPair streams = await CreateConnectedStreamsAsync(); @@ -207,7 +210,8 @@ public async Task ConnectOnAlreadyConnectedClient_Throws_InvalidOperationExcepti } [Fact] - [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets")] + [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")] + [SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.tvOS, "iOS/tvOS blocks binding to UNIX sockets")] public async Task WaitForConnectionOnAlreadyConnectedServer_Throws_InvalidOperationException() { using StreamPair streams = await CreateConnectedStreamsAsync(); @@ -220,7 +224,8 @@ public async Task WaitForConnectionOnAlreadyConnectedServer_Throws_InvalidOperat [Theory] [InlineData(0)] [InlineData(100)] - [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets")] + [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")] + [SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.tvOS, "iOS/tvOS blocks binding to UNIX sockets")] public async Task CancelTokenOn_ServerWaitForConnectionAsync_Throws_OperationCanceledException(int cancellationDelay) { (NamedPipeServerStream server, NamedPipeClientStream client) = CreateServerAndClientStreams(); @@ -277,7 +282,8 @@ public async Task CancelTokenOn_ServerWaitForConnectionAsyncWithOuterCancellatio } [Fact] - [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets")] + [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")] + [SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.tvOS, "iOS/tvOS blocks binding to UNIX sockets")] public async Task OperationsOnDisconnectedServer() { using StreamPair streams = await CreateConnectedStreamsAsync(); @@ -314,7 +320,8 @@ public async Task OperationsOnDisconnectedServer() } [Fact] - [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets")] + [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")] + [SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.tvOS, "iOS/tvOS blocks binding to UNIX sockets")] public virtual async Task OperationsOnDisconnectedClient() { using StreamPair streams = await CreateConnectedStreamsAsync(); @@ -359,7 +366,8 @@ public virtual async Task OperationsOnDisconnectedClient() } [Fact] - [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets")] + [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")] + [SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.tvOS, "iOS/tvOS blocks binding to UNIX sockets")] public async Task Windows_OperationsOnNamedServerWithDisposedClient() { using StreamPair streams = await CreateConnectedStreamsAsync(); @@ -383,7 +391,8 @@ public async Task Windows_OperationsOnNamedServerWithDisposedClient() } [Fact] - [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets")] + [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")] + [SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.tvOS, "iOS/tvOS blocks binding to UNIX sockets")] public void OperationsOnUnconnectedServer() { (NamedPipeServerStream server, NamedPipeClientStream client) = CreateServerAndClientStreams(); @@ -419,7 +428,8 @@ public void OperationsOnUnconnectedServer() } [Fact] - [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets")] + [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")] + [SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.tvOS, "iOS/tvOS blocks binding to UNIX sockets")] public void OperationsOnUnconnectedClient() { (NamedPipeServerStream server, NamedPipeClientStream client) = CreateServerAndClientStreams(); @@ -451,7 +461,8 @@ public void OperationsOnUnconnectedClient() } [Fact] - [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets")] + [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")] + [SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.tvOS, "iOS/tvOS blocks binding to UNIX sockets")] public async Task DisposedServerPipe_Throws_ObjectDisposedException() { (NamedPipeServerStream server, NamedPipeClientStream client) = CreateServerAndClientStreams(); @@ -464,7 +475,8 @@ public async Task DisposedServerPipe_Throws_ObjectDisposedException() } [Fact] - [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets")] + [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")] + [SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.tvOS, "iOS/tvOS blocks binding to UNIX sockets")] public async Task DisposedClientPipe_Throws_ObjectDisposedException() { using StreamPair streams = await CreateConnectedStreamsAsync(); @@ -477,7 +489,8 @@ public async Task DisposedClientPipe_Throws_ObjectDisposedException() } [Fact] - [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets")] + [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")] + [SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.tvOS, "iOS/tvOS blocks binding to UNIX sockets")] public async Task ReadAsync_DisconnectDuringRead_Returns0() { using StreamPair streams = await CreateConnectedStreamsAsync(); @@ -489,7 +502,8 @@ public async Task ReadAsync_DisconnectDuringRead_Returns0() } [PlatformSpecific(TestPlatforms.Windows)] // Unix named pipes are on sockets, where small writes with an empty buffer will succeed immediately - [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets")] + [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")] + [SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.tvOS, "iOS/tvOS blocks binding to UNIX sockets")] [Fact] public async Task WriteAsync_DisconnectDuringWrite_Throws() { @@ -502,7 +516,8 @@ public async Task WriteAsync_DisconnectDuringWrite_Throws() } [Fact] - [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets")] + [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")] + [SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.tvOS, "iOS/tvOS blocks binding to UNIX sockets")] public async Task Server_ReadWriteCancelledToken_Throws_OperationCanceledException() { using StreamPair streams = await CreateConnectedStreamsAsync(); @@ -604,7 +619,8 @@ public async Task CancelTokenOn_Server_ReadWriteCancelledToken_Throws_OperationC } [Fact] - [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets")] + [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")] + [SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.tvOS, "iOS/tvOS blocks binding to UNIX sockets")] public async Task Client_ReadWriteCancelledToken_Throws_OperationCanceledException() { using StreamPair streams = await CreateConnectedStreamsAsync(); @@ -706,7 +722,8 @@ public async Task CancelTokenOn_Client_ReadWriteCancelledToken_Throws_OperationC } [Fact] - [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets")] + [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")] + [SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.tvOS, "iOS/tvOS blocks binding to UNIX sockets")] public async Task TwoServerInstances_OnceDisposed_Throws() { if ((Options & PipeOptions.Asynchronous) == 0) diff --git a/src/libraries/System.IO/tests/StreamReader/StreamReaderTests.cs b/src/libraries/System.IO/tests/StreamReader/StreamReaderTests.cs index 24cf85352ff2d9..b8098fdd3ac0af 100644 --- a/src/libraries/System.IO/tests/StreamReader/StreamReaderTests.cs +++ b/src/libraries/System.IO/tests/StreamReader/StreamReaderTests.cs @@ -555,8 +555,8 @@ public async Task ReadBlockAsync_RepeatsReadsUntilReadDesiredAmount() [InlineData(1, false)] [InlineData(1, true)] [SkipOnPlatform(TestPlatforms.Browser, "Not supported on Browser.")] - [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets")] - [ActiveIssue("https://github.com/dotnet/runtime/issues/51390", TestPlatforms.iOS | TestPlatforms.tvOS | TestPlatforms.MacCatalyst)] + [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")] + [SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.tvOS, "iOS/tvOS blocks binding to UNIX sockets")] public async Task ReadAsync_Canceled_ThrowsException(int method, bool precanceled) { Func> func = method switch diff --git a/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamConnectedStreamConformanceTests.cs b/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamConnectedStreamConformanceTests.cs index cf19c41fb4dcac..5dafe8f6266a32 100644 --- a/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamConnectedStreamConformanceTests.cs +++ b/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamConnectedStreamConformanceTests.cs @@ -156,7 +156,7 @@ public override void Dispose() [OuterLoop("May take several seconds")] [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] - [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets")] + [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")] [ActiveIssue("https://github.com/dotnet/runtime/issues/73377")] public override Task Parallel_ReadWriteMultipleStreamsConcurrently() { diff --git a/src/libraries/System.Net.Sockets/tests/FunctionalTests/Accept.cs b/src/libraries/System.Net.Sockets/tests/FunctionalTests/Accept.cs index 7098e93a1b2a21..f0fbcde1e82f54 100644 --- a/src/libraries/System.Net.Sockets/tests/FunctionalTests/Accept.cs +++ b/src/libraries/System.Net.Sockets/tests/FunctionalTests/Accept.cs @@ -303,6 +303,7 @@ public async Task AcceptAsync_MultipleAcceptsThenDispose_AcceptsThrowAfterDispos [Theory] [MemberData(nameof(AcceptGetsCanceledByDispose_Data))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/73536", TestPlatforms.iOS | TestPlatforms.tvOS)] public async Task AcceptGetsCanceledByDispose(IPAddress loopback, bool owning) { // Aborting sync operations for non-owning handles is not supported on Unix. diff --git a/src/libraries/System.Net.Sockets/tests/FunctionalTests/SendFile.cs b/src/libraries/System.Net.Sockets/tests/FunctionalTests/SendFile.cs index de68b83498cfdc..4fe44d9a2d7b7b 100644 --- a/src/libraries/System.Net.Sockets/tests/FunctionalTests/SendFile.cs +++ b/src/libraries/System.Net.Sockets/tests/FunctionalTests/SendFile.cs @@ -238,6 +238,7 @@ public async Task SliceBuffers_Success() [Theory] [InlineData(true)] [InlineData(false)] + [ActiveIssue("https://github.com/dotnet/runtime/issues/73536", TestPlatforms.iOS | TestPlatforms.tvOS)] public async Task SendFileGetsCanceledByDispose(bool owning) { // Aborting sync operations for non-owning handles is not supported on Unix. diff --git a/src/libraries/System.Net.Sockets/tests/FunctionalTests/UnixDomainSocketTest.cs b/src/libraries/System.Net.Sockets/tests/FunctionalTests/UnixDomainSocketTest.cs index 28432569547fea..ba0fbd7c13edb0 100644 --- a/src/libraries/System.Net.Sockets/tests/FunctionalTests/UnixDomainSocketTest.cs +++ b/src/libraries/System.Net.Sockets/tests/FunctionalTests/UnixDomainSocketTest.cs @@ -23,15 +23,8 @@ public UnixDomainSocketTest(ITestOutputHelper output) _log = output; } - [Fact] - public void OSSupportsUnixDomainSockets_ReturnsCorrectValue() - { - Assert.Equal(PlatformSupportsUnixDomainSockets, Socket.OSSupportsUnixDomainSockets); - } - - [ConditionalFact(nameof(PlatformSupportsUnixDomainSockets))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/51392", TestPlatforms.iOS | TestPlatforms.tvOS | TestPlatforms.MacCatalyst)] - [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets")] + [ConditionalFact(typeof(Socket), nameof(Socket.OSSupportsUnixDomainSockets))] + [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")] public async Task Socket_ConnectAsyncUnixDomainSocketEndPoint_Success() { string path = null; @@ -85,8 +78,7 @@ public async Task Socket_ConnectAsyncUnixDomainSocketEndPoint_Success() } } - [ConditionalFact(nameof(PlatformSupportsUnixDomainSockets))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/51392", TestPlatforms.iOS | TestPlatforms.tvOS | TestPlatforms.MacCatalyst)] + [ConditionalFact(typeof(Socket), nameof(Socket.OSSupportsUnixDomainSockets))] public async Task Socket_ConnectAsyncUnixDomainSocketEndPoint_NotServer() { string path = GetRandomNonExistingFilePath(); @@ -124,9 +116,8 @@ public async Task Socket_ConnectAsyncUnixDomainSocketEndPoint_NotServer() } } - [ConditionalFact(nameof(PlatformSupportsUnixDomainSockets))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/51392", TestPlatforms.iOS | TestPlatforms.tvOS | TestPlatforms.MacCatalyst)] - [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets")] + [ConditionalFact(typeof(Socket), nameof(Socket.OSSupportsUnixDomainSockets))] + [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")] public void Socket_SendReceive_Success() { string path = GetRandomNonExistingFilePath(); @@ -157,9 +148,8 @@ public void Socket_SendReceive_Success() Assert.False(File.Exists(path)); } - [ConditionalFact(nameof(PlatformSupportsUnixDomainSockets))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/51392", TestPlatforms.iOS | TestPlatforms.tvOS | TestPlatforms.MacCatalyst)] - [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets")] + [ConditionalFact(typeof(Socket), nameof(Socket.OSSupportsUnixDomainSockets))] + [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")] public void Socket_SendReceive_Clone_Success() { string path = GetRandomNonExistingFilePath(); @@ -203,9 +193,8 @@ public void Socket_SendReceive_Clone_Success() Assert.False(File.Exists(path)); } - [ConditionalFact(nameof(PlatformSupportsUnixDomainSockets))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/51392", TestPlatforms.iOS | TestPlatforms.tvOS | TestPlatforms.MacCatalyst)] - [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets")] + [ConditionalFact(typeof(Socket), nameof(Socket.OSSupportsUnixDomainSockets))] + [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")] public async Task Socket_SendReceiveAsync_Success() { string path = GetRandomNonExistingFilePath(); @@ -237,13 +226,12 @@ public async Task Socket_SendReceiveAsync_Success() } [ActiveIssue("https://github.com/dotnet/runtime/issues/26189", TestPlatforms.Windows)] - [ConditionalTheory(nameof(PlatformSupportsUnixDomainSockets))] + [ConditionalTheory(typeof(Socket), nameof(Socket.OSSupportsUnixDomainSockets))] [InlineData(5000, 1, 1)] [InlineData(500, 18, 21)] [InlineData(500, 21, 18)] [InlineData(5, 128000, 64000)] - [ActiveIssue("https://github.com/dotnet/runtime/issues/51392", TestPlatforms.iOS | TestPlatforms.tvOS | TestPlatforms.MacCatalyst)] - [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets")] + [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")] public async Task Socket_SendReceiveAsync_PropagateToStream_Success(int iterations, int writeBufferSize, int readBufferSize) { var writeBuffer = new byte[writeBufferSize * iterations]; @@ -295,12 +283,11 @@ public async Task Socket_SendReceiveAsync_PropagateToStream_Success(int iteratio Assert.False(File.Exists(path)); } - [ConditionalTheory(nameof(PlatformSupportsUnixDomainSockets))] + [ConditionalTheory(typeof(Socket), nameof(Socket.OSSupportsUnixDomainSockets))] [ActiveIssue("https://github.com/dotnet/runtime/issues/26189", TestPlatforms.Windows)] [InlineData(false)] [InlineData(true)] - [ActiveIssue("https://github.com/dotnet/runtime/issues/51392", TestPlatforms.iOS | TestPlatforms.tvOS | TestPlatforms.MacCatalyst)] - [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets")] + [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")] public async Task ConcurrentSendReceive(bool forceNonBlocking) { using (Socket server = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified)) @@ -342,9 +329,8 @@ public async Task ConcurrentSendReceive(bool forceNonBlocking) } } - [ConditionalFact(nameof(PlatformSupportsUnixDomainSockets))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/51392", TestPlatforms.iOS | TestPlatforms.tvOS | TestPlatforms.MacCatalyst)] - [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets")] + [ConditionalFact(typeof(Socket), nameof(Socket.OSSupportsUnixDomainSockets))] + [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")] public async Task ConcurrentSendReceiveAsync() { using (Socket server = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified)) @@ -382,7 +368,7 @@ public async Task ConcurrentSendReceiveAsync() } } - [ConditionalFact(nameof(PlatformSupportsUnixDomainSockets))] + [ConditionalFact(typeof(Socket), nameof(Socket.OSSupportsUnixDomainSockets))] public void UnixDomainSocketEndPoint_InvalidPaths_Throws() { Assert.Throws(() => new UnixDomainSocketEndPoint(null)); @@ -396,11 +382,10 @@ public void UnixDomainSocketEndPoint_InvalidPaths_Throws() Assert.Throws(() => new UnixDomainSocketEndPoint(invalidLengthString)); } - [ConditionalTheory(nameof(PlatformSupportsUnixDomainSockets))] + [ConditionalTheory(typeof(Socket), nameof(Socket.OSSupportsUnixDomainSockets))] [InlineData(false)] [InlineData(true)] - [ActiveIssue("https://github.com/dotnet/runtime/issues/51392", TestPlatforms.iOS | TestPlatforms.tvOS | TestPlatforms.MacCatalyst)] - [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets")] + [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")] public void UnixDomainSocketEndPoint_RemoteEndPointEqualsBindAddress(bool abstractAddress) { string serverAddress; @@ -448,7 +433,7 @@ public void UnixDomainSocketEndPoint_RemoteEndPointEqualsBindAddress(bool abstra Assert.False(File.Exists(clientAddress)); } - [ConditionalFact(nameof(PlatformSupportsUnixDomainSockets))] + [ConditionalFact(typeof(Socket), nameof(Socket.OSSupportsUnixDomainSockets))] [PlatformSpecific(TestPlatforms.AnyUnix & ~TestPlatforms.Linux)] // Don't support abstract socket addresses. [ActiveIssue("https://github.com/dotnet/runtime/issues/50568", TestPlatforms.Android | TestPlatforms.LinuxBionic)] public void UnixDomainSocketEndPoint_UsingAbstractSocketAddressOnUnsupported_Throws() @@ -480,10 +465,10 @@ public void Socket_CreateUnixDomainSocket_Throws_OnWindows() } [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] - [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets")] + [SkipOnPlatform(TestPlatforms.LinuxBionic, "SElinux blocks UNIX sockets in our CI environment")] public void UnixDomainSocketEndPoint_RelativePathDeletesFile() { - if (!PlatformSupportsUnixDomainSockets) + if (!Socket.OSSupportsUnixDomainSockets) { return; } @@ -518,7 +503,7 @@ public void UnixDomainSocketEndPoint_RelativePathDeletesFile() }).Dispose(); } - [ConditionalFact(nameof(PlatformSupportsUnixDomainSockets))] + [ConditionalFact(typeof(Socket), nameof(Socket.OSSupportsUnixDomainSockets))] public void AbstractPathEquality() { string abstractPath = '\0' + Guid.NewGuid().ToString(); @@ -533,8 +518,7 @@ public void AbstractPathEquality() Assert.NotEqual(endPoint2, endPoint3); } - [ConditionalFact(nameof(PlatformSupportsUnixDomainSockets))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/51392", TestPlatforms.iOS | TestPlatforms.tvOS | TestPlatforms.MacCatalyst)] + [ConditionalFact(typeof(Socket), nameof(Socket.OSSupportsUnixDomainSockets))] public void FilePathEquality() { string path1 = "relative" + Path.DirectorySeparatorChar + "path"; @@ -565,26 +549,6 @@ private static string GetRandomNonExistingFilePath() return result; } - private static bool PlatformSupportsUnixDomainSockets - { - get - { - if (OperatingSystem.IsWindows()) - { - try - { - using var socket = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.Tcp); - } - catch (SocketException se) - { - return se.SocketErrorCode != SocketError.AddressFamilyNotSupported; - } - } - - return true; - } - } - private static bool IsSubWindows10 => PlatformDetection.IsWindows && PlatformDetection.WindowsVersion < 10; } } diff --git a/src/libraries/tests.proj b/src/libraries/tests.proj index 6c222ac05cbd12..c153884d978452 100644 --- a/src/libraries/tests.proj +++ b/src/libraries/tests.proj @@ -339,11 +339,6 @@ - - - - - From 1dc5eba56ec882fa13545975f49c05134ceab680 Mon Sep 17 00:00:00 2001 From: Jan Vorlicek Date: Wed, 10 Aug 2022 00:00:02 +0200 Subject: [PATCH 29/41] Fix interleaved heap RW commit failure handling (#73566) We were not checking the result of the ExecutableAllocator::Commit call for mapping the RW page in the case of interleaved heaps. I've seen a failure in the CI that seems to be caused by this - when we succeeded committing the RX page, but failed to commit the related RW page, we have then crashed when trying to initialize the precode stubs data. This change adds the check to fix the problem. This causes the LoaderHeap allocation to fail as expected in such case. --- src/coreclr/utilcode/loaderheap.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/coreclr/utilcode/loaderheap.cpp b/src/coreclr/utilcode/loaderheap.cpp index 74a2f42fa85271..5c55e6283c16b4 100644 --- a/src/coreclr/utilcode/loaderheap.cpp +++ b/src/coreclr/utilcode/loaderheap.cpp @@ -1304,14 +1304,17 @@ BOOL UnlockedLoaderHeap::GetMoreCommittedPages(size_t dwMinSize) void *pData = ExecutableAllocator::Instance()->Commit(m_pPtrToEndOfCommittedRegion, dwSizeToCommitPart, IsExecutable()); if (pData == NULL) { - _ASSERTE(!"Unable to commit a loaderheap page"); return FALSE; } if (IsInterleaved()) { // Commit a data page after the code page - ExecutableAllocator::Instance()->Commit(m_pPtrToEndOfCommittedRegion + dwSizeToCommitPart, dwSizeToCommitPart, FALSE); + void* pDataRW = ExecutableAllocator::Instance()->Commit(m_pPtrToEndOfCommittedRegion + dwSizeToCommitPart, dwSizeToCommitPart, FALSE); + if (pDataRW == NULL) + { + return FALSE; + } ExecutableWriterHolder codePageWriterHolder((BYTE*)pData, GetOsPageSize()); m_codePageGenerator(codePageWriterHolder.GetRW(), (BYTE*)pData); From 22ba36d5b0e78c984d8e0b24ae389cabd345ec30 Mon Sep 17 00:00:00 2001 From: Drew Kersnar <18474647+dakersnar@users.noreply.github.com> Date: Tue, 9 Aug 2022 17:13:00 -0500 Subject: [PATCH 30/41] Fix overflow check for parsing format strings (#72647) * WIP: fix overflow checking * Change limit to 9 digits * Fixed the same bug in other places * Remove mistake include * Move expensive tests to OuterLoop --- .../Globalization/FormatProvider.Number.cs | 10 ++--- .../src/System/Number.Formatting.cs | 10 ++--- .../src/System/Numerics/BigNumber.cs | 24 +++++------ .../BigInteger/BigIntegerToStringTests.cs | 40 +++++++++++++++++++ .../tests/System/DoubleTests.cs | 18 +++++++++ 5 files changed, 80 insertions(+), 22 deletions(-) diff --git a/src/libraries/Common/src/System/Globalization/FormatProvider.Number.cs b/src/libraries/Common/src/System/Globalization/FormatProvider.Number.cs index 8636878c895025..0e11b9b186d42a 100644 --- a/src/libraries/Common/src/System/Globalization/FormatProvider.Number.cs +++ b/src/libraries/Common/src/System/Globalization/FormatProvider.Number.cs @@ -700,19 +700,19 @@ internal static char ParseFormatSpecifier(ReadOnlySpan format, out int dig } } - // Fallback for symbol and any length digits. The digits value must be >= 0 && <= 99, - // but it can begin with any number of 0s, and thus we may need to check more than two + // Fallback for symbol and any length digits. The digits value must be >= 0 && <= 999_999_999, + // but it can begin with any number of 0s, and thus we may need to check more than 9 // digits. Further, for compat, we need to stop when we hit a null char. int n = 0; int i = 1; while ((uint)i < (uint)format.Length && char.IsAsciiDigit(format[i])) { - int temp = (n * 10) + format[i++] - '0'; - if (temp < n) + // Check if we are about to overflow past our limit of 9 digits + if (n >= 100_000_000) { throw new FormatException(SR.Argument_BadFormatSpecifier); } - n = temp; + n = ((n * 10) + format[i++] - '0'); } // If we're at the end of the digits rather than having stopped because we hit something diff --git a/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs b/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs index d84220ef8889bc..f81c6ac437aba2 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs @@ -2318,19 +2318,19 @@ internal static unsafe char ParseFormatSpecifier(ReadOnlySpan format, out } } - // Fallback for symbol and any length digits. The digits value must be >= 0 && <= 99, - // but it can begin with any number of 0s, and thus we may need to check more than two + // Fallback for symbol and any length digits. The digits value must be >= 0 && <= 999_999_999, + // but it can begin with any number of 0s, and thus we may need to check more than 9 // digits. Further, for compat, we need to stop when we hit a null char. int n = 0; int i = 1; while ((uint)i < (uint)format.Length && char.IsAsciiDigit(format[i])) { - int temp = ((n * 10) + format[i++] - '0'); - if (temp < n) + // Check if we are about to overflow past our limit of 9 digits + if (n >= 100_000_000) { throw new FormatException(SR.Argument_BadFormatSpecifier); } - n = temp; + n = ((n * 10) + format[i++] - '0'); } // If we're at the end of the digits rather than having stopped because we hit something diff --git a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs index 6cad9fe0ffa70b..ff6fbca60184d9 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs @@ -854,7 +854,6 @@ void MultiplyAdd(ref Span currentBuffer, uint multiplier, uint addValue) } } - // This function is consistent with VM\COMNumber.cpp!COMNumber::ParseFormatSpecifier internal static char ParseFormatSpecifier(ReadOnlySpan format, out int digits) { digits = -1; @@ -867,22 +866,23 @@ internal static char ParseFormatSpecifier(ReadOnlySpan format, out int dig char ch = format[i]; if (char.IsAsciiLetter(ch)) { + // The digits value must be >= 0 && <= 999_999_999, + // but it can begin with any number of 0s, and thus we may need to check more than 9 + // digits. Further, for compat, we need to stop when we hit a null char. i++; - int n = -1; - - if (i < format.Length && char.IsAsciiDigit(format[i])) + int n = 0; + while ((uint)i < (uint)format.Length && char.IsAsciiDigit(format[i])) { - n = format[i++] - '0'; - while (i < format.Length && char.IsAsciiDigit(format[i])) + // Check if we are about to overflow past our limit of 9 digits + if (n >= 100_000_000) { - int temp = n * 10 + (format[i++] - '0'); - if (temp < n) - { - throw new FormatException(SR.Argument_BadFormatSpecifier); - } - n = temp; + throw new FormatException(SR.Argument_BadFormatSpecifier); } + n = ((n * 10) + format[i++] - '0'); } + + // If we're at the end of the digits rather than having stopped because we hit something + // other than a digit or overflowed, return the standard format info. if (i >= format.Length || format[i] == '\0') { digits = n; diff --git a/src/libraries/System.Runtime.Numerics/tests/BigInteger/BigIntegerToStringTests.cs b/src/libraries/System.Runtime.Numerics/tests/BigInteger/BigIntegerToStringTests.cs index 35949f76935fd5..78b47e967784c0 100644 --- a/src/libraries/System.Runtime.Numerics/tests/BigInteger/BigIntegerToStringTests.cs +++ b/src/libraries/System.Runtime.Numerics/tests/BigInteger/BigIntegerToStringTests.cs @@ -439,6 +439,46 @@ public static void CustomFormatPerMille() RunCustomFormatToStringTests(s_random, "#\u2030000000", CultureInfo.CurrentCulture.NumberFormat.NegativeSign, 6, PerMilleSymbolFormatter); } + [Fact] + public static void ToString_InvalidFormat_ThrowsFormatException() + { + BigInteger b = new BigInteger(123456789000m); + + // Format precision limit is 999_999_999 (9 digits). Anything larger should throw. + // Check ParseFormatSpecifier in FormatProvider.Number.cs with `E` format + Assert.Throws(() => b.ToString("E" + int.MaxValue.ToString())); + long intMaxPlus1 = (long)int.MaxValue + 1; + string intMaxPlus1String = intMaxPlus1.ToString(); + Assert.Throws(() => b.ToString("E" + intMaxPlus1String)); + Assert.Throws(() => b.ToString("E4772185890")); + Assert.Throws(() => b.ToString("E1000000000")); + Assert.Throws(() => b.ToString("E000001000000000")); + + // Check ParseFormatSpecifier in BigNumber.cs with `G` format + Assert.Throws(() => b.ToString("G" + int.MaxValue.ToString())); + Assert.Throws(() => b.ToString("G" + intMaxPlus1String)); + Assert.Throws(() => b.ToString("G4772185890")); + Assert.Throws(() => b.ToString("G1000000000")); + Assert.Throws(() => b.ToString("G000001000000000")); + } + + [Fact] + [OuterLoop("Takes a long time, allocates a lot of memory")] + public static void ToString_ValidLargeFormat() + { + BigInteger b = new BigInteger(123456789000m); + + // Format precision limit is 999_999_999 (9 digits). Anything larger should throw. + + // Check ParseFormatSpecifier in FormatProvider.Number.cs with `E` format + b.ToString("E999999999"); // Should not throw + b.ToString("E00000999999999"); // Should not throw + + // Check ParseFormatSpecifier in BigNumber.cs with `G` format + b.ToString("G999999999"); // Should not throw + b.ToString("G00000999999999"); // Should not throw + } + private static void RunSimpleProviderToStringTests(Random random, string format, NumberFormatInfo provider, int precision, StringFormatter formatter) { string test; diff --git a/src/libraries/System.Runtime/tests/System/DoubleTests.cs b/src/libraries/System.Runtime/tests/System/DoubleTests.cs index 63232f8f505457..660fd463f32174 100644 --- a/src/libraries/System.Runtime/tests/System/DoubleTests.cs +++ b/src/libraries/System.Runtime/tests/System/DoubleTests.cs @@ -827,9 +827,27 @@ public static void ToString_InvalidFormat_ThrowsFormatException() double d = 123.0; Assert.Throws(() => d.ToString("Y")); // Invalid format Assert.Throws(() => d.ToString("Y", null)); // Invalid format + + // Format precision limit is 999_999_999 (9 digits). Anything larger should throw. + Assert.Throws(() => d.ToString("E" + int.MaxValue.ToString())); long intMaxPlus1 = (long)int.MaxValue + 1; string intMaxPlus1String = intMaxPlus1.ToString(); Assert.Throws(() => d.ToString("E" + intMaxPlus1String)); + Assert.Throws(() => d.ToString("E4772185890")); + Assert.Throws(() => d.ToString("E1000000000")); + Assert.Throws(() => d.ToString("E000001000000000")); + } + + [Fact] + [OuterLoop("Takes a long time, allocates a lot of memory")] + public static void ToString_ValidLargeFormat() + { + double d = 123.0; + + // Format precision limit is 999_999_999 (9 digits). Anything larger should throw. + d.ToString("E999999999"); // Should not throw + d.ToString("E00000999999999"); // Should not throw + } [Theory] From 88580018305c28d74407afd0b967c1df3bf7fbbe Mon Sep 17 00:00:00 2001 From: Ankit Jain Date: Tue, 9 Aug 2022 19:14:18 -0400 Subject: [PATCH 31/41] [wasm][debugger] Fix some racy tests (#73524) ## 1. Fix random HotReload test failures Some of the hot reload tests fail randomly because they call the updated methods too early. The tests used `Thread.Sleep(3000)` to wait for the method to get updated, and the proxy to respond to that. And that's essentially racy. Instead, wait for the `breakpointResolved` event, or the `scriptParsed` events for the methods, as appropriate. Fixes https://github.com/dotnet/runtime/issues/66024 Fixes https://github.com/dotnet/runtime/issues/72946 ## 2. Fix race in adding/removing event handlers Use ConcurrentDictionary for event listeners, and notification handlers, since they can be modified from different threads. Fixes https://github.com/dotnet/runtime/issues/69144 . ## 3. Fix a race condition where the tests start calling methods before the app is ready Fixes https://github.com/dotnet/runtime/issues/73528 --- src/mono/wasm/Makefile | 2 +- .../debugger/BrowserDebugProxy/MonoProxy.cs | 6 +- .../DebuggerTestSuite/BreakpointTests.cs | 25 ++-- .../DebuggerTestSuite/ChromeProvider.cs | 18 ++- .../DebuggerTestSuite/DebuggerTestBase.cs | 113 +++++++++++++----- .../DebuggerTestSuite/DebuggerTestFirefox.cs | 4 +- .../DebuggerTestSuite/HotReloadTests.cs | 34 +++--- .../debugger/DebuggerTestSuite/Inspector.cs | 56 +++++++-- .../debugger/DebuggerTestSuite/MiscTests.cs | 8 +- .../debugger/DebuggerTestSuite/MonoJsTests.cs | 9 +- .../DebuggerTestSuite/TestExtensions.cs | 16 +++ .../DebuggerTestSuite/WasmHostProvider.cs | 5 + .../tests/debugger-test/debugger-driver.html | 3 +- .../tests/debugger-test/debugger-test.cs | 2 + 14 files changed, 210 insertions(+), 91 deletions(-) create mode 100644 src/mono/wasm/debugger/DebuggerTestSuite/TestExtensions.cs diff --git a/src/mono/wasm/Makefile b/src/mono/wasm/Makefile index 425ff8dd46627f..6ee499422d5910 100644 --- a/src/mono/wasm/Makefile +++ b/src/mono/wasm/Makefile @@ -130,7 +130,7 @@ submit-tests-helix: run-debugger-tests: rm -f $(TOP)/artifacts/bin/DebuggerTestSuite/x64/Debug/*log; \ if [ ! -z "$(TEST_FILTER)" ]; then \ - $(DOTNET) test $(TOP)/src/mono/wasm/debugger/DebuggerTestSuite $(MSBUILD_ARGS) "-l:trx;LogFileName=DebuggerTestsResults.xml" --results-directory $(TOP)/artifacts/log/$(CONFIG) --filter "Category!=failing&FullyQualifiedName~$(TEST_FILTER)" $(TEST_ARGS); \ + $(DOTNET) test $(TOP)/src/mono/wasm/debugger/DebuggerTestSuite $(MSBUILD_ARGS) "-l:trx;LogFileName=DebuggerTestsResults.xml" --results-directory $(TOP)/artifacts/log/$(CONFIG) --filter "Category!=failing&FullyQualifiedName$(TEST_FILTER)" $(TEST_ARGS); \ else \ $(DOTNET) test $(TOP)/src/mono/wasm/debugger/DebuggerTestSuite $(MSBUILD_ARGS) "-l:trx;LogFileName=DebuggerTestsResults.xml" --results-directory $(TOP)/artifacts/log/$(CONFIG) --filter "Category!=failing" $(TEST_ARGS); \ fi diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs index 4f764d207daf7c..3b15acccedf796 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs @@ -1235,7 +1235,11 @@ private async Task OnJSEventRaised(SessionId sessionId, JObject eventArgs, return false; } - logger.LogDebug($"OnJsEventRaised: args: {eventArgs}"); + string eventStr = eventArgs.ToString(); + string newStr = eventStr.Substring(0, Math.Min(1024, eventStr.Length)); + if (newStr.Length != eventStr.Length) + newStr += " ... truncated }"; + logger.LogDebug($"OnJsEventRaised: args: {newStr}"); switch (eventName) { diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/BreakpointTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/BreakpointTests.cs index 366a25b0074c80..3c4173faea7ec7 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/BreakpointTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/BreakpointTests.cs @@ -510,7 +510,7 @@ public async Task CreateGoodBreakpointAndHitGoToNonWasmPageComeBackAndHitAgain() expression = "window.setTimeout(function() { reload_wasm_page(); }, 1);" }); await cli.SendCommand("Runtime.evaluate", run_method, token); - await insp.WaitFor(Inspector.READY); + await insp.WaitFor(Inspector.APP_READY); await EvaluateAndCheck( "window.setTimeout(function() { invoke_add(); }, 1);", "dotnet://debugger-test.dll/debugger-test.cs", 10, 8, @@ -592,10 +592,10 @@ await SendCommandAndCheck(null, "Debugger.resume", [InlineData(true, "RunNonUserCode", "DebuggerAttribute", 1, 867, 8, "DebuggerAttribute.RunNonUserCode")] [InlineData(false, "RunStepThroughWithNonUserCode", "DebuggerAttribute", 1, 933, 8, "DebuggerAttribute.RunStepThroughWithNonUserCode")] [InlineData(true, "RunStepThroughWithNonUserCode", "DebuggerAttribute", 1, 933, 8, "DebuggerAttribute.RunStepThroughWithNonUserCode")] - [InlineData(false, "EvaluateStepThroughAttr", "DefaultInterfaceMethod", 2, 1108, 4, "DefaultInterfaceMethod.EvaluateStepThroughAttr")] - [InlineData(true, "EvaluateStepThroughAttr", "DefaultInterfaceMethod", 2, 1108, 4, "DefaultInterfaceMethod.EvaluateStepThroughAttr")] - [InlineData(false, "EvaluateNonUserCodeAttr", "DefaultInterfaceMethod", 2, 1065, 4, "IExtendIDefaultInterface.NonUserCodeDefaultMethod")] - [InlineData(true, "EvaluateNonUserCodeAttr", "DefaultInterfaceMethod", 2, 1114, 4, "DefaultInterfaceMethod.EvaluateNonUserCodeAttr")] + [InlineData(false, "EvaluateStepThroughAttr", "DefaultInterfaceMethod", 2, 1110, 4, "DefaultInterfaceMethod.EvaluateStepThroughAttr")] + [InlineData(true, "EvaluateStepThroughAttr", "DefaultInterfaceMethod", 2, 1110, 4, "DefaultInterfaceMethod.EvaluateStepThroughAttr")] + [InlineData(false, "EvaluateNonUserCodeAttr", "DefaultInterfaceMethod", 2, 1067, 4, "IExtendIDefaultInterface.NonUserCodeDefaultMethod")] + [InlineData(true, "EvaluateNonUserCodeAttr", "DefaultInterfaceMethod", 2, 1116, 4, "DefaultInterfaceMethod.EvaluateNonUserCodeAttr")] public async Task StepThroughOrNonUserCodeAttributeStepInNoBp2(bool justMyCodeEnabled, string evalFunName, string evalClassName, int bpLine, int line, int col, string funcName="") { var bp_init = await SetBreakpointInMethod("debugger-test.dll", evalClassName, evalFunName, bpLine); @@ -785,14 +785,14 @@ public async Task CreateGoodBreakpointAndHitGoToWasmPageWithoutAssetsComeBackAnd expression = "window.setTimeout(function() { load_wasm_page_without_assets(); }, 1);" }); await cli.SendCommand("Runtime.evaluate", run_method, token); - await insp.WaitFor(Inspector.READY); + await insp.WaitFor(Inspector.APP_READY); run_method = JObject.FromObject(new { expression = "window.setTimeout(function() { reload_wasm_page(); }, 1);" }); await cli.SendCommand("Runtime.evaluate", run_method, token); - await insp.WaitFor(Inspector.READY); + await insp.WaitFor(Inspector.APP_READY); await EvaluateAndCheck( "window.setTimeout(function() { invoke_add(); }, 1);", @@ -823,11 +823,11 @@ await EvaluateAndCheck( } [Theory] - [InlineData("IDefaultInterface", "DefaultMethod", "Evaluate", "DefaultInterfaceMethod.Evaluate", 1087, 1003, 1001, 1005)] - [InlineData("IExtendIDefaultInterface", "IDefaultInterface.DefaultMethodToOverride", "Evaluate", "DefaultInterfaceMethod.Evaluate", 1088, 1047, 1045, 1049)] - [InlineData("IDefaultInterface", "DefaultMethodAsync", "EvaluateAsync", "System.Runtime.CompilerServices.AsyncMethodBuilderCore.Startd__3>", 37, 1016, 1014, 1018)] - [InlineData("IDefaultInterface", "DefaultMethodStatic", "EvaluateStatic", "DefaultInterfaceMethod.EvaluateStatic", 1124, 1022, 1020, 1024)] - [InlineData("IDefaultInterface", "DefaultMethodAsyncStatic", "EvaluateAsyncStatic", "System.Runtime.CompilerServices.AsyncMethodBuilderCore.Startd__5>", 37, 1031, 1029, 1033)] + [InlineData("IDefaultInterface", "DefaultMethod", "Evaluate", "DefaultInterfaceMethod.Evaluate", 1089, 1005, 1003, 1007)] + [InlineData("IExtendIDefaultInterface", "IDefaultInterface.DefaultMethodToOverride", "Evaluate", "DefaultInterfaceMethod.Evaluate", 1090, 1049, 1047, 1051)] + [InlineData("IDefaultInterface", "DefaultMethodAsync", "EvaluateAsync", "System.Runtime.CompilerServices.AsyncMethodBuilderCore.Startd__3>", 37, 1018, 1016, 1020)] + [InlineData("IDefaultInterface", "DefaultMethodStatic", "EvaluateStatic", "DefaultInterfaceMethod.EvaluateStatic", 1126, 1024, 1022, 1026)] + [InlineData("IDefaultInterface", "DefaultMethodAsyncStatic", "EvaluateAsyncStatic", "System.Runtime.CompilerServices.AsyncMethodBuilderCore.Startd__5>", 37, 1033, 1031, 1035)] public async Task BreakInDefaultInterfaceMethod( string dimClassName, string dimName, string entryMethod, string prevFrameInDim, int evaluateAsPrevFrameLine, int dimAsPrevFrameLine, int functionLocationLine, int functionEndLine) { @@ -860,7 +860,6 @@ public async Task BreakInDefaultInterfaceMethod( Assert.Equal(pauseInFunCalledFromDim["callFrames"][1]["functionName"].Value(), dimClassName + "." + prevFrameFromDim); CheckLocationLine(pauseInFunCalledFromDim["callFrames"][1]["location"], dimAsPrevFrameLine); - async Task CheckDefaultMethod(JObject pause_location, string methodName) { Assert.Equal("other", pause_location["reason"]?.Value()); diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/ChromeProvider.cs b/src/mono/wasm/debugger/DebuggerTestSuite/ChromeProvider.cs index c704d8699bd296..d13dc964348823 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/ChromeProvider.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/ChromeProvider.cs @@ -15,6 +15,7 @@ using System.Threading; using System.Collections.Generic; using Wasm.Tests.Internal; +using System.Linq; #nullable enable @@ -30,6 +31,11 @@ internal class ChromeProvider : WasmHostProvider string artifactsBinDir = Path.Combine(Path.GetDirectoryName(typeof(ChromeProvider).Assembly.Location)!, "..", "..", ".."); return BrowserLocator.FindChrome(artifactsBinDir, "BROWSER_PATH_FOR_TESTS"); }); + private static readonly string[] s_messagesToFilterOut = new[] + { + "Received unexpected number of handles", + "Failed to connect to the bus:", + }; public ChromeProvider(string id, ILogger logger) : base(id, logger) { @@ -104,6 +110,14 @@ public override void Dispose() _isDisposing = false; } + protected override bool ShouldMessageBeLogged(string prefix, string? msg) + { + if (msg is null || !prefix.Contains("browser-stderr")) + return true; + + return !s_messagesToFilterOut.Any(f => msg.Contains(f)); + } + private async Task ExtractConnUrl (string str, ILogger logger) { var client = new HttpClient(); @@ -119,7 +133,7 @@ private async Task ExtractConnUrl (string str, ILogger logger) await Task.Delay(100); var res = await client.GetStringAsync(new Uri(new Uri(str), "/json/list")); - logger.LogInformation("res is {0}", res); + logger.LogTrace("res is {0}", res); if (!string.IsNullOrEmpty(res)) { @@ -157,6 +171,4 @@ private static string GetInitParms(int port) } return str; } - - } diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs b/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs index 7349d07dcbb0df..937c03327dbd85 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs @@ -159,30 +159,32 @@ internal virtual Dictionary SubscribeToScripts(Inspector insp) { dicScriptsIdToUrl = new Dictionary(); dicFileToUrl = new Dictionary(); - insp.On("Debugger.scriptParsed", async (args, c) => - { - var script_id = args?["scriptId"]?.Value(); - var url = args["url"]?.Value(); - if (script_id.StartsWith("dotnet://")) - { - var dbgUrl = args["dotNetUrl"]?.Value(); - var arrStr = dbgUrl.Split("/"); - dbgUrl = arrStr[0] + "/" + arrStr[1] + "/" + arrStr[2] + "/" + arrStr[arrStr.Length - 1]; - dicScriptsIdToUrl[script_id] = dbgUrl; - dicFileToUrl[dbgUrl] = args["url"]?.Value(); - } - else if (!String.IsNullOrEmpty(url)) - { - var dbgUrl = args["url"]?.Value(); - var arrStr = dbgUrl.Split("/"); - dicScriptsIdToUrl[script_id] = arrStr[arrStr.Length - 1]; - dicFileToUrl[new Uri(url).AbsolutePath] = url; - } - await Task.FromResult(0); - }); + insp.On("Debugger.scriptParsed", DefaultScriptParsedHandler); return dicScriptsIdToUrl; } + protected Task DefaultScriptParsedHandler(JObject args, CancellationToken token) + { + var script_id = args?["scriptId"]?.Value(); + var url = args["url"]?.Value(); + if (script_id.StartsWith("dotnet://")) + { + var dbgUrl = args["dotNetUrl"]?.Value(); + var arrStr = dbgUrl.Split("/"); + dbgUrl = arrStr[0] + "/" + arrStr[1] + "/" + arrStr[2] + "/" + arrStr[arrStr.Length - 1]; + dicScriptsIdToUrl[script_id] = dbgUrl; + dicFileToUrl[dbgUrl] = args["url"]?.Value(); + } + else if (!String.IsNullOrEmpty(url)) + { + var dbgUrl = args["url"]?.Value(); + var arrStr = dbgUrl.Split("/"); + dicScriptsIdToUrl[script_id] = arrStr[arrStr.Length - 1]; + dicFileToUrl[new Uri(url).AbsolutePath] = url; + } + return Task.FromResult(ProtocolEventHandlerReturn.KeepHandler); + } + internal async Task CheckInspectLocalsAtBreakpointSite(string url_key, int line, int column, string function_name, string eval_expression, Func test_fn = null, Func wait_for_event_fn = null, bool use_cfo = false) { @@ -228,6 +230,47 @@ internal virtual async Task WaitFor(string what) return await insp.WaitFor(what); } + public async Task WaitForScriptParsedEventsAsync(params string[] paths) + { + object llock = new(); + List pathsList = new(paths); + var tcs = new TaskCompletionSource(); + insp.On("Debugger.scriptParsed", async (args, c) => + { + await DefaultScriptParsedHandler(args, c); + + string url = args["url"]?.Value(); + if (string.IsNullOrEmpty(url)) + return await Task.FromResult(ProtocolEventHandlerReturn.KeepHandler); + + lock (llock) + { + try + { + int idx = pathsList.FindIndex(p => url?.EndsWith(p) == true); + if (idx >= 0) + { + pathsList.RemoveAt(idx); + if (pathsList.Count == 0) + { + tcs.SetResult(); + } + } + } + catch (Exception ex) + { + tcs.SetException(ex); + } + } + + return tcs.Task.IsCompleted + ? await Task.FromResult(ProtocolEventHandlerReturn.RemoveHandler) + : await Task.FromResult(ProtocolEventHandlerReturn.KeepHandler); + }); + + await tcs.Task; + } + // sets breakpoint by method name and line offset internal async Task CheckInspectLocalsAtBreakpointSite(string type, string method, int line_offset, string bp_function_name, string eval_expression, Func locals_fn = null, Func wait_for_event_fn = null, bool use_cfo = false, string assembly = "debugger-test.dll", int col = 0) @@ -1244,7 +1287,7 @@ internal async Task LoadAssemblyDynamically(string asm_file, string pdb_file) }); Result load_assemblies_res = await cli.SendCommand("Runtime.evaluate", load_assemblies, token); - Assert.True(load_assemblies_res.IsOk); + load_assemblies_res.AssertOk(); } internal async Task LoadAssemblyDynamicallyALCAndRunMethod(string asm_file, string pdb_file, string type_name, string method_name) @@ -1267,8 +1310,7 @@ internal async Task LoadAssemblyDynamicallyALCAndRunMethod(string asm_f }); Result load_assemblies_res = await cli.SendCommand("Runtime.evaluate", load_assemblies, token); - - Assert.True(load_assemblies_res.IsOk); + load_assemblies_res.AssertOk(); await bpResolved; var run_method = JObject.FromObject(new @@ -1280,7 +1322,7 @@ internal async Task LoadAssemblyDynamicallyALCAndRunMethod(string asm_f return await WaitFor(Inspector.PAUSE); } - internal async Task LoadAssemblyAndTestHotReloadUsingSDBWithoutChanges(string asm_file, string pdb_file, string class_name, string method_name) + internal async Task LoadAssemblyAndTestHotReloadUsingSDBWithoutChanges(string asm_file, string pdb_file, string class_name, string method_name, bool expectBpResolvedEvent) { byte[] bytes = File.ReadAllBytes(asm_file); string asm_base64 = Convert.ToBase64String(bytes); @@ -1294,15 +1336,18 @@ internal async Task LoadAssemblyAndTestHotReloadUsingSDBWithoutChanges( expression }); - Result load_assemblies_res = await cli.SendCommand("Runtime.evaluate", load_assemblies, token); + Task eventTask = expectBpResolvedEvent + ? WaitForBreakpointResolvedEvent() + : WaitForScriptParsedEventsAsync("MethodBody0.cs", "MethodBody1.cs"); + (await cli.SendCommand("Runtime.evaluate", load_assemblies, token)).AssertOk(); + await eventTask; - Thread.Sleep(1000); var run_method = JObject.FromObject(new { expression = "window.setTimeout(function() { invoke_static_method('[debugger-test] TestHotReloadUsingSDB:RunMethod', '" + class_name + "', '" + method_name + "'); }, 1);" }); - await cli.SendCommand("Runtime.evaluate", run_method, token); + (await cli.SendCommand("Runtime.evaluate", run_method, token)).AssertOk(); return await WaitFor(Inspector.PAUSE); } @@ -1352,7 +1397,7 @@ internal async Task LoadAssemblyAndTestHotReloadUsingSDB(string asm_fil return await WaitFor(Inspector.PAUSE); } - internal async Task LoadAssemblyAndTestHotReload(string asm_file, string pdb_file, string asm_file_hot_reload, string class_name, string method_name) + internal async Task LoadAssemblyAndTestHotReload(string asm_file, string pdb_file, string asm_file_hot_reload, string class_name, string method_name, bool expectBpResolvedEvent) { byte[] bytes = File.ReadAllBytes(asm_file); string asm_base64 = Convert.ToBase64String(bytes); @@ -1387,10 +1432,12 @@ internal async Task LoadAssemblyAndTestHotReload(string asm_file, strin expression }); - Result load_assemblies_res = await cli.SendCommand("Runtime.evaluate", load_assemblies, token); + Task eventTask = expectBpResolvedEvent + ? WaitForBreakpointResolvedEvent() + : WaitForScriptParsedEventsAsync("MethodBody0.cs", "MethodBody1.cs"); + (await cli.SendCommand("Runtime.evaluate", load_assemblies, token)).AssertOk(); + await eventTask; - Assert.True(load_assemblies_res.IsOk); - Thread.Sleep(1000); var run_method = JObject.FromObject(new { expression = "window.setTimeout(function() { invoke_static_method('[debugger-test] TestHotReload:RunMethod', '" + class_name + "', '" + method_name + "'); }, 1);" @@ -1416,7 +1463,7 @@ public async Task WaitForBreakpointResolvedEvent() internal async Task SetJustMyCode(bool enabled) { - var req = JObject.FromObject(new { enabled = enabled }); + var req = JObject.FromObject(new { enabled }); var res = await cli.SendCommand("DotnetDebugger.justMyCode", req, token); Assert.True(res.IsOk); Assert.Equal(res.Value["justMyCodeEnabled"], enabled); diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestFirefox.cs b/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestFirefox.cs index 43e9d986d89df5..eaafe3ffcbbc09 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestFirefox.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestFirefox.cs @@ -65,7 +65,7 @@ internal override Dictionary SubscribeToScripts(Inspector insp) dicScriptsIdToUrl[script_id] = arrStr[arrStr.Length - 1]; dicFileToUrl[new Uri(url).AbsolutePath] = url; } - await Task.FromResult(0); + return await Task.FromResult(ProtocolEventHandlerReturn.KeepHandler); }); insp.On("resource-available-form", async (args, c) => { @@ -86,7 +86,7 @@ internal override Dictionary SubscribeToScripts(Inspector insp) dicScriptsIdToUrl[script_id] = arrStr[arrStr.Length - 1]; dicFileToUrl[new Uri(url).AbsolutePath] = url; } - await Task.FromResult(0); + return await Task.FromResult(ProtocolEventHandlerReturn.KeepHandler); }); return dicScriptsIdToUrl; } diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/HotReloadTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/HotReloadTests.cs index f0a6a58a61e679..ec5cdbc2452cba 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/HotReloadTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/HotReloadTests.cs @@ -22,7 +22,7 @@ public async Task DebugHotReloadMethodChangedUserBreak() Path.Combine(DebuggerTestAppPath, "ApplyUpdateReferencedAssembly.dll"), Path.Combine(DebuggerTestAppPath, "ApplyUpdateReferencedAssembly.pdb"), Path.Combine(DebuggerTestAppPath, "../wasm/ApplyUpdateReferencedAssembly.dll"), - "MethodBody1", "StaticMethod1"); + "MethodBody1", "StaticMethod1", expectBpResolvedEvent: false); var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); CheckNumber(locals, "a", 10); pause_location = await SendCommandAndCheck(JObject.FromObject(new { }), "Debugger.resume", "dotnet://ApplyUpdateReferencedAssembly.dll/MethodBody1.cs", 12, 16, "ApplyUpdateReferencedAssembly.MethodBody1.StaticMethod1"); @@ -40,7 +40,7 @@ public async Task DebugHotReloadMethodUnchanged() Path.Combine(DebuggerTestAppPath, "ApplyUpdateReferencedAssembly.dll"), Path.Combine(DebuggerTestAppPath, "ApplyUpdateReferencedAssembly.pdb"), Path.Combine(DebuggerTestAppPath, "../wasm/ApplyUpdateReferencedAssembly.dll"), - "MethodBody2", "StaticMethod1"); + "MethodBody2", "StaticMethod1", expectBpResolvedEvent: false); var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); CheckNumber(locals, "a", 10); pause_location = await SendCommandAndCheck(JObject.FromObject(new { }), "Debugger.resume", "dotnet://ApplyUpdateReferencedAssembly.dll/MethodBody1.cs", 21, 12, "ApplyUpdateReferencedAssembly.MethodBody2.StaticMethod1"); @@ -60,7 +60,7 @@ public async Task DebugHotReloadMethodAddBreakpoint() Path.Combine(DebuggerTestAppPath, "ApplyUpdateReferencedAssembly.dll"), Path.Combine(DebuggerTestAppPath, "ApplyUpdateReferencedAssembly.pdb"), Path.Combine(DebuggerTestAppPath, "../wasm/ApplyUpdateReferencedAssembly.dll"), - "MethodBody3", "StaticMethod3"); + "MethodBody3", "StaticMethod3", expectBpResolvedEvent: false); var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); CheckNumber(locals, "a", 10); @@ -108,7 +108,7 @@ public async Task DebugHotReloadMethodEmpty() Path.Combine(DebuggerTestAppPath, "ApplyUpdateReferencedAssembly.dll"), Path.Combine(DebuggerTestAppPath, "ApplyUpdateReferencedAssembly.pdb"), Path.Combine(DebuggerTestAppPath, "../wasm/ApplyUpdateReferencedAssembly.dll"), - "MethodBody4", "StaticMethod4"); + "MethodBody4", "StaticMethod4", expectBpResolvedEvent: true); var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); pause_location = await SendCommandAndCheck(JObject.FromObject(new { }), "Debugger.resume", "dotnet://ApplyUpdateReferencedAssembly.dll/MethodBody1.cs", 38, 12, "ApplyUpdateReferencedAssembly.MethodBody4.StaticMethod4"); @@ -164,7 +164,7 @@ public async Task DebugHotReloadMethodChangedUserBreakUsingSDB() string asm_file_hot_reload = Path.Combine(DebuggerTestAppPath, "../wasm/ApplyUpdateReferencedAssembly.dll"); var pause_location = await LoadAssemblyAndTestHotReloadUsingSDBWithoutChanges( - asm_file, pdb_file, "MethodBody1", "StaticMethod1"); + asm_file, pdb_file, "MethodBody1", "StaticMethod1", expectBpResolvedEvent: false); var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); CheckNumber(locals, "a", 10); @@ -196,7 +196,7 @@ public async Task DebugHotReloadMethodUnchangedUsingSDB() string asm_file_hot_reload = Path.Combine(DebuggerTestAppPath, "../wasm/ApplyUpdateReferencedAssembly.dll"); var pause_location = await LoadAssemblyAndTestHotReloadUsingSDBWithoutChanges( - asm_file, pdb_file, "MethodBody2", "StaticMethod1"); + asm_file, pdb_file, "MethodBody2", "StaticMethod1", expectBpResolvedEvent: false); var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); CheckNumber(locals, "a", 10); @@ -227,7 +227,7 @@ public async Task DebugHotReloadMethodAddBreakpointUsingSDB() int line = 30; await SetBreakpoint(".*/MethodBody1.cs$", line, 12, use_regex: true); var pause_location = await LoadAssemblyAndTestHotReloadUsingSDBWithoutChanges( - asm_file, pdb_file, "MethodBody3", "StaticMethod3"); + asm_file, pdb_file, "MethodBody3", "StaticMethod3", expectBpResolvedEvent: true); var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); CheckNumber(locals, "a", 10); @@ -290,7 +290,7 @@ public async Task DebugHotReloadMethodEmptyUsingSDB() int line = 38; await SetBreakpoint(".*/MethodBody1.cs$", line, 0, use_regex: true); var pause_location = await LoadAssemblyAndTestHotReloadUsingSDBWithoutChanges( - asm_file, pdb_file, "MethodBody4", "StaticMethod4"); + asm_file, pdb_file, "MethodBody4", "StaticMethod4", expectBpResolvedEvent: true); //apply first update pause_location = await LoadAssemblyAndTestHotReloadUsingSDB( @@ -347,11 +347,11 @@ public async Task DebugHotReloadMethod_CheckBreakpointLineUpdated_ByVS_Simulated var bp = await SetBreakpoint(".*/MethodBody1.cs$", 48, 12, use_regex: true); var pause_location = await LoadAssemblyAndTestHotReloadUsingSDBWithoutChanges( - asm_file, pdb_file, "MethodBody5", "StaticMethod1"); + asm_file, pdb_file, "MethodBody5", "StaticMethod1", expectBpResolvedEvent: true); //apply first update pause_location = await LoadAssemblyAndTestHotReloadUsingSDB( - asm_file_hot_reload, "MethodBody5", "StaticMethod1", 1, + asm_file_hot_reload, "MethodBody5", "StaticMethod1", 1, rebindBreakpoint : async () => { await RemoveBreakpoint(bp.Value["breakpointId"].Value()); @@ -372,11 +372,11 @@ public async Task DebugHotReloadMethod_CheckBreakpointLineUpdated_ByVS_Simulated var bp = await SetBreakpoint(".*/MethodBody1.cs$", 49, 12, use_regex: true); var pause_location = await LoadAssemblyAndTestHotReloadUsingSDBWithoutChanges( - asm_file, pdb_file, "MethodBody5", "StaticMethod1"); + asm_file, pdb_file, "MethodBody5", "StaticMethod1", expectBpResolvedEvent: true); //apply first update pause_location = await LoadAssemblyAndTestHotReloadUsingSDB( - asm_file_hot_reload, "MethodBody5", "StaticMethod1", 1, + asm_file_hot_reload, "MethodBody5", "StaticMethod1", 1, rebindBreakpoint : async () => { await RemoveBreakpoint(bp.Value["breakpointId"].Value()); @@ -399,7 +399,7 @@ public async Task DebugHotReloadMethod_CheckBreakpointLineUpdated_ByVS_Simulated var bp_notchanged = await SetBreakpoint(".*/MethodBody1.cs$", 48, 12, use_regex: true); var pause_location = await LoadAssemblyAndTestHotReloadUsingSDBWithoutChanges( - asm_file, pdb_file, "MethodBody5", "StaticMethod1"); + asm_file, pdb_file, "MethodBody5", "StaticMethod1", expectBpResolvedEvent: true); CheckLocation("dotnet://ApplyUpdateReferencedAssembly.dll/MethodBody1.cs", 48, 12, scripts, pause_location["callFrames"]?[0]["location"]); //apply first update @@ -422,7 +422,7 @@ public async Task DebugHotReloadMethod_AddingNewMethod() var bp_invalid = await SetBreakpoint(".*/MethodBody1.cs$", 59, 12, use_regex: true); var pause_location = await LoadAssemblyAndTestHotReloadUsingSDBWithoutChanges( - asm_file, pdb_file, "MethodBody6", "StaticMethod1"); + asm_file, pdb_file, "MethodBody6", "StaticMethod1", expectBpResolvedEvent: true); CheckLocation("dotnet://ApplyUpdateReferencedAssembly.dll/MethodBody1.cs", 55, 12, scripts, pause_location["callFrames"]?[0]["location"]); //apply first update @@ -452,7 +452,7 @@ public async Task DebugHotReloadMethod_AddingNewStaticField() var bp_invalid = await SetBreakpoint(".*/MethodBody1.cs$", 59, 12, use_regex: true); var pause_location = await LoadAssemblyAndTestHotReloadUsingSDBWithoutChanges( - asm_file, pdb_file, "MethodBody6", "StaticMethod1"); + asm_file, pdb_file, "MethodBody6", "StaticMethod1", expectBpResolvedEvent: true); CheckLocation("dotnet://ApplyUpdateReferencedAssembly.dll/MethodBody1.cs", 55, 12, scripts, pause_location["callFrames"]?[0]["location"]); //apply first update @@ -488,7 +488,7 @@ public async Task DebugHotReloadMethod_AddingNewClass() var bp_invalid2 = await SetBreakpoint(".*/MethodBody1.cs$", 102, 12, use_regex: true); var pause_location = await LoadAssemblyAndTestHotReloadUsingSDBWithoutChanges( - asm_file, pdb_file, "MethodBody6", "StaticMethod1"); + asm_file, pdb_file, "MethodBody6", "StaticMethod1", expectBpResolvedEvent: true); CheckLocation("dotnet://ApplyUpdateReferencedAssembly.dll/MethodBody1.cs", 55, 12, scripts, pause_location["callFrames"]?[0]["location"]); //apply first update @@ -511,7 +511,7 @@ public async Task DebugHotReloadMethod_AddingNewClass() Assert.Equal(3, props.Count()); CheckNumber(props, "attr1", 15); await CheckString(props, "attr2", "20"); - + await EvaluateOnCallFrameAndCheck(pause_location["callFrames"]?[0]["callFrameId"].Value(), ("ApplyUpdateReferencedAssembly.MethodBody7.staticField", TNumber(80))); diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/Inspector.cs b/src/mono/wasm/debugger/DebuggerTestSuite/Inspector.cs index 49c4efe53d1d11..42e880f6dd095e 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/Inspector.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/Inspector.cs @@ -13,6 +13,7 @@ using Newtonsoft.Json.Linq; using System.Runtime.ExceptionServices; using Xunit.Abstractions; +using System.Collections.Concurrent; #nullable enable @@ -23,11 +24,11 @@ class Inspector // https://console.spec.whatwg.org/#formatting-specifiers private static Regex _consoleArgsRegex = new(@"(%[sdifoOc])", RegexOptions.Compiled); - Dictionary> notifications = new Dictionary>(); - Dictionary> eventListeners = new Dictionary>(); + ConcurrentDictionary> notifications = new (); + ConcurrentDictionary>> eventListeners = new (); public const string PAUSE = "pause"; - public const string READY = "ready"; + public const string APP_READY = "app-ready"; public CancellationToken Token { get; } public InspectorClient Client { get; } public DebuggerProxyBase? Proxy { get; } @@ -35,6 +36,8 @@ class Inspector private CancellationTokenSource _cancellationTokenSource; private Exception? _isFailingWithException; + private bool _gotRuntimeReady = false; + private bool _gotAppReady = false; protected static Lazy s_loggerFactory = new(() => LoggerFactory.Create(builder => @@ -78,7 +81,7 @@ public Task WaitFor(string what) { if (tcs.Task.IsCompleted) { - notifications.Remove(what); + notifications.Remove(what, out _); return tcs.Task; } @@ -95,7 +98,7 @@ public Task WaitFor(string what) public void ClearWaiterFor(string what) { if (notifications.ContainsKey(what)) - notifications.Remove(what); + notifications.Remove(what, out _); } void NotifyOf(string what, JObject args) @@ -106,7 +109,7 @@ void NotifyOf(string what, JObject args) throw new Exception($"Invalid internal state. Notifying for {what} again, but the previous one hasn't been read."); notifications[what].SetResult(args); - notifications.Remove(what); + notifications.Remove(what, out _); } else { @@ -116,7 +119,7 @@ void NotifyOf(string what, JObject args) } } - public void On(string evtName, Func cb) + public void On(string evtName, Func> cb) { eventListeners[evtName] = cb; } @@ -127,7 +130,7 @@ public Task WaitForEvent(string evtName) On(evtName, async (args, token) => { eventReceived.SetResult(args); - await Task.CompletedTask; + return await Task.FromResult(ProtocolEventHandlerReturn.RemoveHandler); }); return eventReceived.Task.WaitAsync(Token); @@ -200,8 +203,15 @@ async Task OnMessage(string method, JObject args, CancellationToken token) NotifyOf(PAUSE, args); break; case "Mono.runtimeReady": - NotifyOf(READY, args); + { + _gotRuntimeReady = true; + if (_gotAppReady || !DebuggerTestBase.RunningOnChrome) + { + // got both the events + NotifyOf(APP_READY, args); + } break; + } case "Runtime.consoleAPICalled": { (string line, string type) = FormatConsoleAPICalled(args); @@ -215,6 +225,17 @@ async Task OnMessage(string method, JObject args, CancellationToken token) default: _logger.LogInformation(line); break; } + if (!_gotAppReady && line == "console.debug: #debugger-app-ready#") + { + _gotAppReady = true; + + if (_gotRuntimeReady) + { + // got both the events + NotifyOf(APP_READY, args); + } + } + if (DetectAndFailOnAssertions && (line.Contains("console.error: [MONO]") || line.Contains("console.warning: [MONO]"))) { @@ -236,9 +257,12 @@ async Task OnMessage(string method, JObject args, CancellationToken token) fail = true; break; } - if (eventListeners.TryGetValue(method, out var listener)) + if (eventListeners.TryGetValue(method, out Func>? listener) + && listener != null) { - await listener(args, token).ConfigureAwait(false); + ProtocolEventHandlerReturn result = await listener(args, token).ConfigureAwait(false); + if (result is ProtocolEventHandlerReturn.RemoveHandler) + eventListeners.Remove(method, out _); } else if (fail) { @@ -303,8 +327,8 @@ public async Task OpenSessionAsync(Func readyTask = Task.Run(async () => Result.FromJson(await WaitFor(READY))); - init_cmds.Add((READY, readyTask)); + Task readyTask = Task.Run(async () => Result.FromJson(await WaitFor(APP_READY))); + init_cmds.Add((APP_READY, readyTask)); _logger.LogInformation("waiting for the runtime to be ready"); while (!_cancellationTokenSource.IsCancellationRequested && init_cmds.Count > 0) @@ -397,4 +421,10 @@ public async Task ShutdownAsync() } } } + + public enum ProtocolEventHandlerReturn + { + KeepHandler, + RemoveHandler + } } diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/MiscTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/MiscTests.cs index 02f73ff20f9366..5e0f0d6802c5a9 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/MiscTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/MiscTests.cs @@ -845,7 +845,7 @@ public async Task GetSourceEmbeddedSource() var bp = await SetBreakpoint(".*/MethodBody1.cs$", 48, 12, use_regex: true); var pause_location = await LoadAssemblyAndTestHotReloadUsingSDBWithoutChanges( - asm_file, pdb_file, "MethodBody5", "StaticMethod1"); + asm_file, pdb_file, "MethodBody5", "StaticMethod1", expectBpResolvedEvent: true); var sourceToGet = JObject.FromObject(new { @@ -978,10 +978,10 @@ await EvaluateAndCheck( [Theory] [InlineData( "DebugWithDeletedPdb", - 1146)] + 1148)] [InlineData( "DebugWithoutDebugSymbols", - 1158)] + 1160)] public async Task InspectPropertiesOfObjectFromExternalLibrary(string className, int line) { var expression = $"{{ invoke_static_method('[debugger-test] {className}:Run'); }}"; @@ -1030,7 +1030,7 @@ public async Task InspectLocalRecursiveFieldValue() await EvaluateAndCheck( "window.setTimeout(function() {" + expression + "; }, 1);", - "dotnet://debugger-test.dll/debugger-test.cs", 1256, 8, + "dotnet://debugger-test.dll/debugger-test.cs", 1258, 8, $"InspectIntPtr.Run", locals_fn: async (locals) => { diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/MonoJsTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/MonoJsTests.cs index e7cc513ee0c265..93d7221c51f7ee 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/MonoJsTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/MonoJsTests.cs @@ -58,7 +58,9 @@ public async Task RaiseDebugEventTraceTest(bool? trace) tcs.SetResult(true); } - await Task.CompletedTask; + return tcs.Task.IsCompleted + ? await Task.FromResult(ProtocolEventHandlerReturn.RemoveHandler) + : await Task.FromResult(ProtocolEventHandlerReturn.KeepHandler); }); var trace_str = trace.HasValue ? $"trace: {trace.ToString().ToLower()}" : String.Empty; @@ -110,7 +112,6 @@ public async Task DuplicateAssemblyLoadedEventWithEmbeddedPdbNotLoadedFromBundle async Task AssemblyLoadedEventTest(string asm_name, string asm_path, string pdb_path, string source_file, int expected_count) { - int event_count = 0; var tcs = new TaskCompletionSource(); insp.On("Debugger.scriptParsed", async (args, c) => @@ -130,7 +131,9 @@ async Task AssemblyLoadedEventTest(string asm_name, string asm_path, string pdb_ tcs.SetException(ex); } - await Task.CompletedTask; + return tcs.Task.IsCompleted + ? await Task.FromResult(ProtocolEventHandlerReturn.RemoveHandler) + : await Task.FromResult(ProtocolEventHandlerReturn.KeepHandler); }); byte[] bytes = File.ReadAllBytes(asm_path); diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/TestExtensions.cs b/src/mono/wasm/debugger/DebuggerTestSuite/TestExtensions.cs new file mode 100644 index 00000000000000..e23554afadec77 --- /dev/null +++ b/src/mono/wasm/debugger/DebuggerTestSuite/TestExtensions.cs @@ -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. + +using Microsoft.WebAssembly.Diagnostics; +using Xunit; + +namespace DebuggerTests; + +internal static class TestExtensions +{ + public static void AssertOk(this Result res, string prefix = "") + => Assert.True(res.IsOk, $"{prefix}: Expected Ok result but got {res}"); + + public static void AssertErr(this Result res, string prefix = "") + => Assert.False(res.IsOk, $"{prefix}: Expected error but got {res}"); +} diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/WasmHostProvider.cs b/src/mono/wasm/debugger/DebuggerTestSuite/WasmHostProvider.cs index 8f219610fe601f..2694c8868136b4 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/WasmHostProvider.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/WasmHostProvider.cs @@ -92,6 +92,9 @@ protected ProcessStartInfo GetProcessStartInfo(string browserPath, string argume void ProcessOutput(string prefix, string? msg) { + if (!ShouldMessageBeLogged(prefix, msg)) + return; + _logger.LogDebug($"{prefix}{msg}"); if (string.IsNullOrEmpty(msg) || browserReadyTCS.Task.IsCompleted) @@ -103,6 +106,8 @@ void ProcessOutput(string prefix, string? msg) } } + protected virtual bool ShouldMessageBeLogged(string prefix, string? msg) => true; + public virtual void Dispose() { if (_process is not null && !_process.HasExited) diff --git a/src/mono/wasm/debugger/tests/debugger-test/debugger-driver.html b/src/mono/wasm/debugger/tests/debugger-test/debugger-driver.html index 33c6f7e01db24a..a5011e95168028 100644 --- a/src/mono/wasm/debugger/tests/debugger-test/debugger-driver.html +++ b/src/mono/wasm/debugger/tests/debugger-test/debugger-driver.html @@ -16,7 +16,8 @@ this.method_with_structs = App.runtime.BINDING.bind_static_method("[debugger-test] DebuggerTests.ValueTypesTest:MethodWithLocalStructs"); this.run_all = App.runtime.BINDING.bind_static_method("[debugger-test] DebuggerTest:run_all"); this.static_method_table = {}; - console.log ("ready"); + console.log ("ready"); // HACK: firefox tests are looking for this + console.debug ("#debugger-app-ready#"); }, }; function invoke_static_method (method_name, ...args) { diff --git a/src/mono/wasm/debugger/tests/debugger-test/debugger-test.cs b/src/mono/wasm/debugger/tests/debugger-test/debugger-test.cs index 9f55948cbd3d09..edbefe1ea41e17 100644 --- a/src/mono/wasm/debugger/tests/debugger-test/debugger-test.cs +++ b/src/mono/wasm/debugger/tests/debugger-test/debugger-test.cs @@ -987,6 +987,8 @@ public static string GetModuleGUID() public static void RunMethod(string className, string methodName) { + if (loadedAssembly is null) + throw new InvalidOperationException($"{nameof(loadedAssembly)} is null!"); var myType = loadedAssembly.GetType($"ApplyUpdateReferencedAssembly.{className}"); var myMethod = myType.GetMethod(methodName); myMethod.Invoke(null, null); From 77b39a505403751e7d1ba617e01f3a97785ded19 Mon Sep 17 00:00:00 2001 From: Tomas Weinfurt Date: Tue, 9 Aug 2022 16:21:51 -0700 Subject: [PATCH 32/41] fix credential caching on macOS (#73577) --- .../Net/Security/Pal.OSX/SafeDeleteSslContext.cs | 15 +++++++-------- .../FunctionalTests/SslStreamNetworkStreamTest.cs | 5 +---- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/Pal.OSX/SafeDeleteSslContext.cs b/src/libraries/System.Net.Security/src/System/Net/Security/Pal.OSX/SafeDeleteSslContext.cs index 8db7686811751e..bb4f84ce8de8ff 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/Pal.OSX/SafeDeleteSslContext.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/Pal.OSX/SafeDeleteSslContext.cs @@ -34,7 +34,7 @@ public SafeDeleteSslContext(SafeFreeSslCredentials credential, SslAuthentication { int osStatus; - _sslContext = CreateSslContext(credential, sslAuthenticationOptions.IsServer); + _sslContext = CreateSslContext(credential, sslAuthenticationOptions); // Make sure the class instance is associated to the session and is provided // in the Read/Write callback connection parameter @@ -129,7 +129,7 @@ public SafeDeleteSslContext(SafeFreeSslCredentials credential, SslAuthentication } } - private static SafeSslHandle CreateSslContext(SafeFreeSslCredentials credential, bool isServer) + private static SafeSslHandle CreateSslContext(SafeFreeSslCredentials credential, SslAuthenticationOptions sslAuthenticationOptions) { switch (credential.Policy) { @@ -145,7 +145,7 @@ private static SafeSslHandle CreateSslContext(SafeFreeSslCredentials credential, throw new PlatformNotSupportedException(SR.Format(SR.net_encryptionpolicy_notsupported, credential.Policy)); } - SafeSslHandle sslContext = Interop.AppleCrypto.SslCreateContext(isServer ? 1 : 0); + SafeSslHandle sslContext = Interop.AppleCrypto.SslCreateContext(sslAuthenticationOptions.IsServer ? 1 : 0); try { @@ -157,14 +157,14 @@ private static SafeSslHandle CreateSslContext(SafeFreeSslCredentials credential, } // Let None mean "system default" - if (credential.Protocols != SslProtocols.None) + if (sslAuthenticationOptions.EnabledSslProtocols != SslProtocols.None) { - SetProtocols(sslContext, credential.Protocols); + SetProtocols(sslContext, sslAuthenticationOptions.EnabledSslProtocols); } - if (credential.CertificateContext != null) + if (sslAuthenticationOptions.CertificateContext != null) { - SetCertificate(sslContext, credential.CertificateContext); + SetCertificate(sslContext, sslAuthenticationOptions.CertificateContext); } Interop.AppleCrypto.SslBreakOnCertRequested(sslContext, true); @@ -360,7 +360,6 @@ internal static void SetCertificate(SafeSslHandle sslContext, SslStreamCertifica { Debug.Assert(sslContext != null, "sslContext != null"); - IntPtr[] ptrs = new IntPtr[context!.IntermediateCertificates!.Length + 1]; for (int i = 0; i < context.IntermediateCertificates.Length; i++) diff --git a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamNetworkStreamTest.cs b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamNetworkStreamTest.cs index 5528baeb02abbd..e27f6f635ac905 100644 --- a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamNetworkStreamTest.cs +++ b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamNetworkStreamTest.cs @@ -791,10 +791,7 @@ public async Task SslStream_UntrustedCaWithCustomTrust_OK(bool usePartialChain) serverChain = _certificates.serverChain; } - // TODO: line below is wrong, but it breaks on Mac, it should be - // serverOptions.ServerCertificateContext = SslStreamCertificateContext.Create(_certificates.serverCert, serverChain); - // [ActiveIssue("https://github.com/dotnet/runtime/issues/73295")] - serverOptions.ServerCertificateContext = SslStreamCertificateContext.Create(_certificates.serverCert, _certificates.serverChain); + serverOptions.ServerCertificateContext = SslStreamCertificateContext.Create(_certificates.serverCert, serverChain); (Stream clientStream, Stream serverStream) = TestHelper.GetConnectedStreams(); using (clientStream) From aee90131778280f037cfdbba76c3f3429c06d7a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aleksey=20Kliger=20=28=CE=BBgeek=29?= Date: Tue, 9 Aug 2022 19:49:37 -0400 Subject: [PATCH 33/41] [host] Copy hostfxr and hostpolicy for Mono also, if !mobile (#73633) Fixes the mono "HelloWorld" sample (src/mono/samples/HelloWorld) on desktop platforms --- src/libraries/externals.csproj | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/libraries/externals.csproj b/src/libraries/externals.csproj index a2fbff29ec0609..00effdc6e8edc6 100644 --- a/src/libraries/externals.csproj +++ b/src/libraries/externals.csproj @@ -95,6 +95,9 @@ AfterTargets="AfterResolveReferences" Condition="'$(RuntimeFlavor)' == 'Mono'"> + + + Date: Tue, 9 Aug 2022 19:50:25 -0400 Subject: [PATCH 34/41] [interp] bounds check fallthru in conditional branches (#73580) Throw if we ever take the fall thru on a conditional branch at the end of a method. Related to https://github.com/dotnet/runtime/issues/73474 but doesn't fix it: this just makes the interpreter throw instead of falling off the end of the transformed CFG --- src/mono/mono/mini/interp/transform.c | 43 +++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/src/mono/mono/mini/interp/transform.c b/src/mono/mono/mini/interp/transform.c index 4ebc7f02c1b21c..d8679166af3fd6 100644 --- a/src/mono/mono/mini/interp/transform.c +++ b/src/mono/mono/mini/interp/transform.c @@ -295,6 +295,13 @@ interp_prev_ins (InterpInst *ins) } \ } while (0) +#define CHECK_FALLTHRU() \ + do { \ + if (G_UNLIKELY (td->ip >= end)) { \ + interp_generate_ipe_bad_fallthru (td); \ + } \ + } while (0) + #if NO_UNALIGNED_ACCESS #define WRITE32(ip, v) \ do { \ @@ -1305,6 +1312,18 @@ interp_generate_ipe_throw_with_msg (TransformData *td, MonoError *error_msg) } } +static void +interp_generate_ipe_bad_fallthru (TransformData *td) +{ + ERROR_DECL (bad_fallthru_error); + char *method_code = mono_disasm_code_one (NULL, td->method, td->ip, NULL); + mono_error_set_invalid_program (bad_fallthru_error, "Invalid IL (conditional fallthru past end of method) due to: %s", method_code); + interp_generate_ipe_throw_with_msg (td, bad_fallthru_error); + g_free (method_code); + mono_error_cleanup (bad_fallthru_error); +} + + static int create_interp_local (TransformData *td, MonoType *type) { @@ -4939,98 +4958,122 @@ generate_code (TransformData *td, MonoMethod *method, MonoMethodHeader *header, case CEE_BRFALSE: one_arg_branch (td, MINT_BRFALSE_I4, read32 (td->ip + 1), 5); td->ip += 5; + CHECK_FALLTHRU (); break; case CEE_BRFALSE_S: one_arg_branch (td, MINT_BRFALSE_I4, (gint8)td->ip [1], 2); td->ip += 2; + CHECK_FALLTHRU (); break; case CEE_BRTRUE: one_arg_branch (td, MINT_BRTRUE_I4, read32 (td->ip + 1), 5); td->ip += 5; + CHECK_FALLTHRU (); break; case CEE_BRTRUE_S: one_arg_branch (td, MINT_BRTRUE_I4, (gint8)td->ip [1], 2); td->ip += 2; + CHECK_FALLTHRU (); break; case CEE_BEQ: two_arg_branch (td, MINT_BEQ_I4, read32 (td->ip + 1), 5); td->ip += 5; + CHECK_FALLTHRU (); break; case CEE_BEQ_S: two_arg_branch (td, MINT_BEQ_I4, (gint8) td->ip [1], 2); td->ip += 2; + CHECK_FALLTHRU (); break; case CEE_BGE: two_arg_branch (td, MINT_BGE_I4, read32 (td->ip + 1), 5); td->ip += 5; + CHECK_FALLTHRU (); break; case CEE_BGE_S: two_arg_branch (td, MINT_BGE_I4, (gint8) td->ip [1], 2); td->ip += 2; + CHECK_FALLTHRU (); break; case CEE_BGT: two_arg_branch (td, MINT_BGT_I4, read32 (td->ip + 1), 5); td->ip += 5; + CHECK_FALLTHRU (); break; case CEE_BGT_S: two_arg_branch (td, MINT_BGT_I4, (gint8) td->ip [1], 2); td->ip += 2; + CHECK_FALLTHRU (); break; case CEE_BLT: two_arg_branch (td, MINT_BLT_I4, read32 (td->ip + 1), 5); td->ip += 5; + CHECK_FALLTHRU (); break; case CEE_BLT_S: two_arg_branch (td, MINT_BLT_I4, (gint8) td->ip [1], 2); td->ip += 2; + CHECK_FALLTHRU (); break; case CEE_BLE: two_arg_branch (td, MINT_BLE_I4, read32 (td->ip + 1), 5); td->ip += 5; + CHECK_FALLTHRU (); break; case CEE_BLE_S: two_arg_branch (td, MINT_BLE_I4, (gint8) td->ip [1], 2); td->ip += 2; + CHECK_FALLTHRU (); break; case CEE_BNE_UN: two_arg_branch (td, MINT_BNE_UN_I4, read32 (td->ip + 1), 5); td->ip += 5; + CHECK_FALLTHRU (); break; case CEE_BNE_UN_S: two_arg_branch (td, MINT_BNE_UN_I4, (gint8) td->ip [1], 2); td->ip += 2; + CHECK_FALLTHRU (); break; case CEE_BGE_UN: two_arg_branch (td, MINT_BGE_UN_I4, read32 (td->ip + 1), 5); td->ip += 5; + CHECK_FALLTHRU (); break; case CEE_BGE_UN_S: two_arg_branch (td, MINT_BGE_UN_I4, (gint8) td->ip [1], 2); td->ip += 2; + CHECK_FALLTHRU (); break; case CEE_BGT_UN: two_arg_branch (td, MINT_BGT_UN_I4, read32 (td->ip + 1), 5); td->ip += 5; + CHECK_FALLTHRU (); break; case CEE_BGT_UN_S: two_arg_branch (td, MINT_BGT_UN_I4, (gint8) td->ip [1], 2); td->ip += 2; + CHECK_FALLTHRU (); break; case CEE_BLE_UN: two_arg_branch (td, MINT_BLE_UN_I4, read32 (td->ip + 1), 5); td->ip += 5; + CHECK_FALLTHRU (); break; case CEE_BLE_UN_S: two_arg_branch (td, MINT_BLE_UN_I4, (gint8) td->ip [1], 2); td->ip += 2; + CHECK_FALLTHRU (); break; case CEE_BLT_UN: two_arg_branch (td, MINT_BLT_UN_I4, read32 (td->ip + 1), 5); td->ip += 5; + CHECK_FALLTHRU (); break; case CEE_BLT_UN_S: two_arg_branch (td, MINT_BLT_UN_I4, (gint8) td->ip [1], 2); td->ip += 2; + CHECK_FALLTHRU (); break; case CEE_SWITCH: { guint32 n; From 1cae013cf341e681e94d31f5d8d297eafb2bf2c1 Mon Sep 17 00:00:00 2001 From: Kunal Pathak Date: Tue, 9 Aug 2022 17:46:18 -0700 Subject: [PATCH 35/41] Use JitStdOutFile for .dasm file creation (#73638) * Use JitStdOutFile for .dasm creation * add back logging.debug --- src/coreclr/scripts/superpmi.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/coreclr/scripts/superpmi.py b/src/coreclr/scripts/superpmi.py index 3537933920139b..48d564d1cde32f 100644 --- a/src/coreclr/scripts/superpmi.py +++ b/src/coreclr/scripts/superpmi.py @@ -1634,11 +1634,12 @@ async def create_replay_artifacts(print_prefix, item, self, mch_file, env, jit_d async def create_one_artifact(jit_path: str, location: str, flags) -> str: command = [self.superpmi_path] + flags + [jit_path, mch_file] item_path = os.path.join(location, "{}{}".format(item, extension)) - with open(item_path, 'w') as file_handle: - logging.debug("%sGenerating %s", print_prefix, item_path) - logging.debug("%sInvoking: %s", print_prefix, " ".join(command)) - proc = await asyncio.create_subprocess_shell(" ".join(command), stdout=file_handle, stderr=asyncio.subprocess.PIPE, env=env) - await proc.communicate() + modified_env = env.copy() + modified_env['COMPlus_JitStdOutFile'] = item_path + logging.debug("%sGenerating %s", print_prefix, item_path) + logging.debug("%sInvoking: %s", print_prefix, " ".join(command)) + proc = await asyncio.create_subprocess_shell(" ".join(command), stderr=asyncio.subprocess.PIPE, env=modified_env) + await proc.communicate() with open(item_path, 'r') as file_handle: generated_txt = file_handle.read() return generated_txt @@ -3088,7 +3089,7 @@ def process_base_jit_path_arg(coreclr_args): def get_pintools_path(coreclr_args): """ Get the local path where we expect pintools for this OS to be located - + Returns: A path to the folder. """ @@ -3096,7 +3097,7 @@ def get_pintools_path(coreclr_args): def get_pin_exe_path(coreclr_args): """ Get the local path where we expect the pin executable to be located - + Returns: A path to the executable. """ @@ -3106,7 +3107,7 @@ def get_pin_exe_path(coreclr_args): def get_inscount_pintool_path(coreclr_args): """ Get the local path where we expect the clrjit inscount pintool to be located - + Returns: A path to the pintool library. """ @@ -3974,7 +3975,7 @@ def main(args): logging.info("SuperPMI throughput diff") logging.debug("------------------------------------------------------------") logging.debug("Start time: %s", begin_time.strftime("%H:%M:%S")) - + base_jit_path = coreclr_args.base_jit_path diff_jit_path = coreclr_args.diff_jit_path From 8b1aecb7d89fdd7ff0319aa57001315da3d1a6a4 Mon Sep 17 00:00:00 2001 From: mikelle-rogers <45022607+mikelle-rogers@users.noreply.github.com> Date: Tue, 9 Aug 2022 18:50:55 -0600 Subject: [PATCH 36/41] Change GetCpuUtilization return from int to double and corresponding counter payload (#66743) --- .../Unix/System.Native/Interop.GetCpuUtilization.cs | 2 +- .../src/System/Diagnostics/Tracing/RuntimeEventSource.cs | 2 +- .../Diagnostics/Tracing/RuntimeEventSourceHelper.Unix.cs | 2 +- .../Tracing/RuntimeEventSourceHelper.Windows.cs | 6 +++--- .../PortableThreadPool.CpuUtilizationReader.Unix.cs | 2 +- .../src/System/Threading/PortableThreadPool.GateThread.cs | 2 +- src/native/libs/System.Native/pal_time.c | 8 ++++---- src/native/libs/System.Native/pal_time.h | 2 +- 8 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/libraries/Common/src/Interop/Unix/System.Native/Interop.GetCpuUtilization.cs b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.GetCpuUtilization.cs index da8fca2c75d9a7..244aa3a903152a 100644 --- a/src/libraries/Common/src/Interop/Unix/System.Native/Interop.GetCpuUtilization.cs +++ b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.GetCpuUtilization.cs @@ -16,6 +16,6 @@ internal struct ProcessCpuInformation } [LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_GetCpuUtilization")] - internal static partial int GetCpuUtilization(ref ProcessCpuInformation previousCpuInfo); + internal static partial double GetCpuUtilization(ref ProcessCpuInformation previousCpuInfo); } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/RuntimeEventSource.cs b/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/RuntimeEventSource.cs index 5340fe8de8325a..e358c49f15028a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/RuntimeEventSource.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/RuntimeEventSource.cs @@ -88,7 +88,7 @@ protected override void OnEventCommand(EventCommandEventArgs command) // overhead by at all times even when counters aren't enabled. // On disable, PollingCounters will stop polling for values so it should be fine to leave them around. - _cpuTimeCounter ??= new PollingCounter("cpu-usage", this, () => RuntimeEventSourceHelper.GetCpuUsage()) { DisplayName = "CPU Usage", DisplayUnits = "%" }; + _cpuTimeCounter ??= new PollingCounter("cpu-usage", this, RuntimeEventSourceHelper.GetCpuUsage) { DisplayName = "CPU Usage", DisplayUnits = "%" }; _workingSetCounter ??= new PollingCounter("working-set", this, () => ((double)Environment.WorkingSet / 1_000_000)) { DisplayName = "Working Set", DisplayUnits = "MB" }; _gcHeapSizeCounter ??= new PollingCounter("gc-heap-size", this, () => ((double)GC.GetTotalMemory(false) / 1_000_000)) { DisplayName = "GC Heap Size", DisplayUnits = "MB" }; _gen0GCCounter ??= new IncrementingPollingCounter("gen-0-gc-count", this, () => GC.CollectionCount(0)) { DisplayName = "Gen 0 GC Count", DisplayRateTimeScale = new TimeSpan(0, 1, 0) }; diff --git a/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/RuntimeEventSourceHelper.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/RuntimeEventSourceHelper.Unix.cs index abc5ddd19477d1..fd58b336a4395b 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/RuntimeEventSourceHelper.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/RuntimeEventSourceHelper.Unix.cs @@ -7,7 +7,7 @@ internal static class RuntimeEventSourceHelper { private static Interop.Sys.ProcessCpuInformation s_cpuInfo; - internal static int GetCpuUsage() => + internal static double GetCpuUsage() => Interop.Sys.GetCpuUtilization(ref s_cpuInfo) / Environment.ProcessorCount; } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/RuntimeEventSourceHelper.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/RuntimeEventSourceHelper.Windows.cs index 7dc8b40e0a99ad..8c797c7f9fcea3 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/RuntimeEventSourceHelper.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/RuntimeEventSourceHelper.Windows.cs @@ -10,11 +10,11 @@ internal static class RuntimeEventSourceHelper private static long s_prevSystemUserTime; private static long s_prevSystemKernelTime; - internal static int GetCpuUsage() + internal static double GetCpuUsage() { // Returns the current process' CPU usage as a percentage - int cpuUsage = 0; + double cpuUsage = 0.0; if (Interop.Kernel32.GetProcessTimes(Interop.Kernel32.GetCurrentProcess(), out _, out _, out long procKernelTime, out long procUserTime) && Interop.Kernel32.GetSystemTimes(out _, out long systemUserTime, out long systemKernelTime)) @@ -25,7 +25,7 @@ internal static int GetCpuUsage() if (s_prevSystemUserTime != 0 && s_prevSystemKernelTime != 0 && // These may be 0 when we report CPU usage for the first time, in which case we should just return 0. totalSystemTime != 0) { - cpuUsage = (int)(totalProcTime * 100 / totalSystemTime); + cpuUsage = (totalProcTime * 100.0 / totalSystemTime); } s_prevProcUserTime = procUserTime; diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.CpuUtilizationReader.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.CpuUtilizationReader.Unix.cs index fe1eaa37d7577b..ea754f9185e9bf 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.CpuUtilizationReader.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.CpuUtilizationReader.Unix.cs @@ -9,7 +9,7 @@ private struct CpuUtilizationReader { private Interop.Sys.ProcessCpuInformation _cpuInfo; - public int CurrentUtilization => + public double CurrentUtilization => Interop.Sys.GetCpuUtilization(ref _cpuInfo) / Environment.ProcessorCount; } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.GateThread.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.GateThread.cs index 2843f99347ca50..26512a4e81c636 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.GateThread.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.GateThread.cs @@ -102,7 +102,7 @@ private static void GateThreadStart() (uint)threadPoolInstance.GetAndResetHighWatermarkCountOfThreadsProcessingUserCallbacks()); } - int cpuUtilization = cpuUtilizationReader.CurrentUtilization; + int cpuUtilization = (int)cpuUtilizationReader.CurrentUtilization; threadPoolInstance._cpuUtilization = cpuUtilization; bool needGateThreadForRuntime = ThreadPool.PerformRuntimeSpecificGateActivities(cpuUtilization); diff --git a/src/native/libs/System.Native/pal_time.c b/src/native/libs/System.Native/pal_time.c index cffe378a83cbac..588277afdf5925 100644 --- a/src/native/libs/System.Native/pal_time.c +++ b/src/native/libs/System.Native/pal_time.c @@ -95,7 +95,7 @@ uint64_t SystemNative_GetTimestamp() #endif } -int32_t SystemNative_GetCpuUtilization(ProcessCpuInformation* previousCpuInfo) +double SystemNative_GetCpuUtilization(ProcessCpuInformation* previousCpuInfo) { uint64_t kernelTime = 0; uint64_t userTime = 0; @@ -109,7 +109,7 @@ int32_t SystemNative_GetCpuUtilization(ProcessCpuInformation* previousCpuInfo) else { kernelTime = - ((uint64_t)(resUsage.ru_stime.tv_sec) * SecondsToNanoSeconds) + + ((uint64_t)(resUsage.ru_stime.tv_sec) * SecondsToNanoSeconds) + ((uint64_t)(resUsage.ru_stime.tv_usec) * MicroSecondsToNanoSeconds); userTime = ((uint64_t)(resUsage.ru_utime.tv_sec) * SecondsToNanoSeconds) + @@ -134,10 +134,10 @@ int32_t SystemNative_GetCpuUtilization(ProcessCpuInformation* previousCpuInfo) cpuBusyTime = (userTime - lastRecordedUserTime) + (kernelTime - lastRecordedKernelTime); } - int32_t cpuUtilization = 0; + double cpuUtilization = 0.0; if (cpuTotalTime > 0 && cpuBusyTime > 0) { - cpuUtilization = (int32_t)(cpuBusyTime * 100 / cpuTotalTime); + cpuUtilization = ((double)cpuBusyTime * 100.0 / (double)cpuTotalTime); } previousCpuInfo->lastRecordedCurrentTime = currentTime; diff --git a/src/native/libs/System.Native/pal_time.h b/src/native/libs/System.Native/pal_time.h index c7ef77da1e5bd5..8660902f4761f5 100644 --- a/src/native/libs/System.Native/pal_time.h +++ b/src/native/libs/System.Native/pal_time.h @@ -48,4 +48,4 @@ PALEXPORT uint64_t SystemNative_GetTimestamp(void); * returned is sum of utilization across all processors, e.g. this function will * return 200 when two cores are running at 100%. */ -PALEXPORT int32_t SystemNative_GetCpuUtilization(ProcessCpuInformation* previousCpuInfo); +PALEXPORT double SystemNative_GetCpuUtilization(ProcessCpuInformation* previousCpuInfo); From 66165e636df578ff0c0ea7a658a1a5ebbb7129a9 Mon Sep 17 00:00:00 2001 From: Jiri Formacek Date: Wed, 10 Aug 2022 03:48:58 +0200 Subject: [PATCH 37/41] Ldap search crashes on Linux when timeout specified (#72676) --- .../Interop/Linux/OpenLdap/Interop.Ldap.cs | 2 +- .../Protocols/Interop/LdapPal.Linux.cs | 15 ++++++++++++-- .../tests/DirectoryServicesProtocolsTests.cs | 20 +++++++++++++++++++ 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/src/libraries/Common/src/Interop/Linux/OpenLdap/Interop.Ldap.cs b/src/libraries/Common/src/Interop/Linux/OpenLdap/Interop.Ldap.cs index faf43785254ec4..cfde1935a1f65e 100644 --- a/src/libraries/Common/src/Interop/Linux/OpenLdap/Interop.Ldap.cs +++ b/src/libraries/Common/src/Interop/Linux/OpenLdap/Interop.Ldap.cs @@ -122,7 +122,7 @@ public static partial int ldap_search( [MarshalAs(UnmanagedType.Bool)] bool attributeOnly, IntPtr servercontrol, IntPtr clientcontrol, - int timelimit, + in LDAP_TIMEVAL timelimit, int sizelimit, ref int messageNumber); diff --git a/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/Interop/LdapPal.Linux.cs b/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/Interop/LdapPal.Linux.cs index cb4423b53d603b..c751636ac85ba4 100644 --- a/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/Interop/LdapPal.Linux.cs +++ b/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/Interop/LdapPal.Linux.cs @@ -90,9 +90,20 @@ internal static int RenameDirectoryEntry(ConnectionHandle ldapHandle, string dn, internal static int ResultToErrorCode(ConnectionHandle ldapHandle, IntPtr result, int freeIt) => Interop.Ldap.ldap_result2error(ldapHandle, result, freeIt); - internal static int SearchDirectory(ConnectionHandle ldapHandle, string dn, int scope, string filter, IntPtr attributes, bool attributeOnly, IntPtr servercontrol, IntPtr clientcontrol, int timelimit, int sizelimit, ref int messageNumber) => - Interop.Ldap.ldap_search(ldapHandle, dn, scope, filter, attributes, attributeOnly, servercontrol, clientcontrol, timelimit, sizelimit, ref messageNumber); + internal static int SearchDirectory(ConnectionHandle ldapHandle, string dn, int scope, string filter, IntPtr attributes, bool attributeOnly, IntPtr servercontrol, IntPtr clientcontrol, int timelimit, int sizelimit, ref int messageNumber) + { + LDAP_TIMEVAL searchTimeout = new LDAP_TIMEVAL + { + tv_sec = timelimit + }; + //zero must not be passed otherwise libldap runtime returns LDAP_PARAM_ERROR + if (searchTimeout.tv_sec < 1) + //-1 means no time limit + searchTimeout.tv_sec = -1; + + return Interop.Ldap.ldap_search(ldapHandle, dn, scope, filter, attributes, attributeOnly, servercontrol, clientcontrol, searchTimeout, sizelimit, ref messageNumber); + } internal static int SetBoolOption(ConnectionHandle ld, LdapOption option, bool value) => Interop.Ldap.ldap_set_option_bool(ld, option, value); // This option is not supported in Linux, so it would most likely throw. diff --git a/src/libraries/System.DirectoryServices.Protocols/tests/DirectoryServicesProtocolsTests.cs b/src/libraries/System.DirectoryServices.Protocols/tests/DirectoryServicesProtocolsTests.cs index 229b6117da1b22..c32078fe4f16fe 100644 --- a/src/libraries/System.DirectoryServices.Protocols/tests/DirectoryServicesProtocolsTests.cs +++ b/src/libraries/System.DirectoryServices.Protocols/tests/DirectoryServicesProtocolsTests.cs @@ -74,6 +74,26 @@ public void TestUnavailableNonCriticalExtension() _ = (SearchResponse) connection.SendRequest(searchRequest); // Does not throw } + [InlineData(60)] + [InlineData(0)] + [InlineData(-60)] + [ConditionalTheory(nameof(IsLdapConfigurationExist))] + public void TestSearchWithTimeLimit(int timeLimit) + { + using LdapConnection connection = GetConnection(); + + var searchRequest = new SearchRequest(LdapConfiguration.Configuration.SearchDn, "(objectClass=*)", SearchScope.Subtree); + if (timeLimit < 0) + { + Assert.Throws(() => searchRequest.TimeLimit = TimeSpan.FromSeconds(timeLimit)); + } + else + { + searchRequest.TimeLimit = TimeSpan.FromSeconds(timeLimit); + _ = (SearchResponse)connection.SendRequest(searchRequest); + // Shall succeed + } + } [ConditionalFact(nameof(IsLdapConfigurationExist))] public void TestAddingOU() From a3fb0d383adf98ee2fb2ab816c28735dc1caaba0 Mon Sep 17 00:00:00 2001 From: Tom McDonald Date: Tue, 9 Aug 2022 21:54:59 -0400 Subject: [PATCH 38/41] Support managed debugging when CET is enabled (#73572) * Support managed debugging when CET is enabled * Use DataTarget ReadVirtual function to read data Remove RS GetLiveContext/SetLiveContext implementations Update debugger contract * Clean up contracts and add extra validation in SetThreadContextNeeded message * Add OUT_OF_PROCESS_SETTHREADCONTEXT ifdef * Add todo comment to merge ICorDebugMutableDataTarget::SetThreadContext with HandleSetThreadContextNeeded implementation --- src/coreclr/clrdefinitions.cmake | 4 + src/coreclr/debug/di/process.cpp | 166 ++++++++++++++++++++++ src/coreclr/debug/di/rspriv.h | 4 + src/coreclr/debug/ee/amd64/dbghelpers.asm | 8 +- src/coreclr/debug/ee/debugger.cpp | 139 ++++++++++++++++-- src/coreclr/debug/ee/debugger.h | 18 ++- src/coreclr/debug/ee/rcthread.cpp | 12 +- src/coreclr/debug/inc/dbgipcevents.h | 1 + src/coreclr/inc/clrconfigvalues.h | 2 + src/coreclr/vm/dbginterface.h | 10 +- src/coreclr/vm/encee.cpp | 16 ++- 11 files changed, 355 insertions(+), 25 deletions(-) diff --git a/src/coreclr/clrdefinitions.cmake b/src/coreclr/clrdefinitions.cmake index 19adf122925686..bcb533d0e2051d 100644 --- a/src/coreclr/clrdefinitions.cmake +++ b/src/coreclr/clrdefinitions.cmake @@ -62,6 +62,10 @@ if(CLR_CMAKE_TARGET_ARCH_AMD64 OR CLR_CMAKE_TARGET_ARCH_I386 OR CLR_CMAKE_TARGET endif(CLR_CMAKE_TARGET_WIN32) endif(CLR_CMAKE_TARGET_ARCH_AMD64 OR CLR_CMAKE_TARGET_ARCH_I386 OR CLR_CMAKE_TARGET_ARCH_ARM64) +if(CLR_CMAKE_TARGET_WIN32 AND CLR_CMAKE_TARGET_ARCH_AMD64) +add_compile_definitions(OUT_OF_PROCESS_SETTHREADCONTEXT) +endif(CLR_CMAKE_TARGET_WIN32) + # Features - please keep them alphabetically sorted if(CLR_CMAKE_TARGET_WIN32) if(NOT CLR_CMAKE_TARGET_ARCH_I386) diff --git a/src/coreclr/debug/di/process.cpp b/src/coreclr/debug/di/process.cpp index b325d122062949..465936b2febcb2 100644 --- a/src/coreclr/debug/di/process.cpp +++ b/src/coreclr/debug/di/process.cpp @@ -7661,6 +7661,8 @@ HRESULT CordbProcess::GetRuntimeOffsets() m_runtimeOffsets.m_debuggerWordTLSIndex)); #endif // FEATURE_INTEROP_DEBUGGING + LOG((LF_CORDB, LL_INFO10000, " m_setThreadContextNeededAddr= 0x%p\n", + m_runtimeOffsets.m_setThreadContextNeededAddr)); LOG((LF_CORDB, LL_INFO10000, " m_TLSIndex= 0x%08x\n", m_runtimeOffsets.m_TLSIndex)); LOG((LF_CORDB, LL_INFO10000, " m_EEThreadStateOffset= 0x%08x\n", @@ -7719,6 +7721,7 @@ HRESULT CordbProcess::GetRuntimeOffsets() m_runtimeOffsets.m_signalHijackCompleteBPAddr, m_runtimeOffsets.m_excepNotForRuntimeBPAddr, m_runtimeOffsets.m_notifyRSOfSyncCompleteBPAddr, + m_runtimeOffsets.m_setThreadContextNeededAddr, }; const int NumFlares = ARRAY_SIZE(flares); @@ -11152,7 +11155,162 @@ void CordbProcess::FilterClrNotification( } } +#ifdef OUT_OF_PROCESS_SETTHREADCONTEXT +void CordbProcess::HandleSetThreadContextNeeded(DWORD dwThreadId) +{ + LOG((LF_CORDB, LL_INFO10000, "RS HandleSetThreadContextNeeded\n")); + +#if defined(TARGET_WINDOWS) && defined(TARGET_AMD64) + HandleHolder hThread = OpenThread( + THREAD_GET_CONTEXT | THREAD_SET_CONTEXT | THREAD_QUERY_INFORMATION | THREAD_SUSPEND_RESUME, + FALSE, // thread handle is not inheritable. + dwThreadId); + + if (hThread == NULL) + { + LOG((LF_CORDB, LL_INFO10000, "RS HandleSetThreadContextNeeded - Unexpected result from OpenThread\n")); + ThrowHR(E_UNEXPECTED); + } + + DWORD previousSuspendCount = ::SuspendThread(hThread); + if (previousSuspendCount == (DWORD)-1) + { + LOG((LF_CORDB, LL_INFO10000, "RS HandleSetThreadContextNeeded - Unexpected result from SuspendThread\n")); + ThrowHR(HRESULT_FROM_GetLastError()); + } + + CONTEXT context = { 0 }; + context.ContextFlags = CONTEXT_FULL; + + HRESULT hr = GetDataTarget()->GetThreadContext(dwThreadId, CONTEXT_FULL, sizeof(CONTEXT), reinterpret_cast (&context)); + IfFailThrow(hr); + + TADDR lsContextAddr = (TADDR)context.Rcx; + DWORD contextSize = (DWORD)context.Rdx; + + TADDR expectedRip = (TADDR)context.R8; + TADDR expectedRsp = (TADDR)context.R9; + + if (contextSize == 0 || contextSize > sizeof(CONTEXT) + 25000) + { + _ASSERTE(!"Corrupted HandleSetThreadContextNeeded message received"); + + LOG((LF_CORDB, LL_INFO10000, "RS HandleSetThreadContextNeeded - Corrupted HandleSetThreadContextNeeded message received\n")); + + ThrowHR(E_UNEXPECTED); + } + + PCONTEXT pContext = (PCONTEXT)_alloca(contextSize); + ULONG32 cbRead; + hr = GetDataTarget()->ReadVirtual(lsContextAddr, reinterpret_cast(pContext), contextSize, &cbRead); + if (FAILED(hr)) + { + _ASSERTE(!"ReadVirtual failed"); + + LOG((LF_CORDB, LL_INFO10000, "RS HandleSetThreadContextNeeded - ReadVirtual (error: 0x%X).\n", hr)); + + ThrowHR(CORDBG_E_READVIRTUAL_FAILURE); + } + + if (cbRead != contextSize) + { + _ASSERTE(!"ReadVirtual context size mismatch"); + LOG((LF_CORDB, LL_INFO10000, "RS HandleSetThreadContextNeeded - ReadVirtual context size mismatch\n")); + + ThrowHR(ERROR_PARTIAL_COPY); + } + + if (pContext->Rip != expectedRip || pContext->Rsp != expectedRsp) + { + _ASSERTE(!"ReadVirtual unexpectedly returned mismatched Rip and Rsp registers"); + + LOG((LF_CORDB, LL_INFO10000, "RS HandleSetThreadContextNeeded - ReadVirtual unexpectedly returned mismatched Rip and Rsp registers\n")); + + ThrowHR(E_UNEXPECTED); + } + + // TODO: Ideally we would use ICorDebugMutableDataTarget::SetThreadContext however this API currently only handles the legacy context. + // We should combine the following code with the shared implementation + + // The initialize call should fail but return contextSize + contextSize = 0; + DWORD contextFlags = pContext->ContextFlags; + BOOL success = InitializeContext(NULL, contextFlags, NULL, &contextSize); + + if(success || GetLastError() != ERROR_INSUFFICIENT_BUFFER) + { + _ASSERTE(!"InitializeContext unexpectedly succeeded or didn't return ERROR_INSUFFICIENT_BUFFER"); + + LOG((LF_CORDB, LL_INFO10000, "RS HandleSetThreadContextNeeded - InitializeContext unexpectedly succeeded or didn't return ERROR_INSUFFICIENT_BUFFER\n")); + + ThrowHR(E_UNEXPECTED); + } + + LOG((LF_CORDB, LL_INFO10000, "RS HandleSetThreadContextNeeded - InitializeContext ContextSize %d\n", contextSize)); + + PVOID pBuffer = _alloca(contextSize); + PCONTEXT pFrameContext = NULL; + success = InitializeContext(pBuffer, contextFlags, &pFrameContext, &contextSize); + if (!success) + { + HRESULT hr = HRESULT_FROM_WIN32(GetLastError()); + _ASSERTE(!"InitializeContext failed"); + + LOG((LF_CORDB, LL_INFO10000, "RS HandleSetThreadContextNeeded - Unexpected result from InitializeContext (error: 0x%X [%d]).\n", hr, GetLastError())); + + ThrowHR(hr); + } + + _ASSERTE((BYTE*)pFrameContext == pBuffer); + + success = CopyContext(pFrameContext, contextFlags, pContext); + LOG((LF_CORDB, LL_INFO10000, "RS HandleSetThreadContextNeeded - CopyContext=%s %d\n", success?"SUCCESS":"FAIL", GetLastError())); + if (!success) + { + HRESULT hr = HRESULT_FROM_WIN32(GetLastError()); + _ASSERTE(!"CopyContext failed"); + + LOG((LF_CORDB, LL_INFO10000, "RS HandleSetThreadContextNeeded - Unexpected result from CopyContext (error: 0x%X [%d]).\n", hr, GetLastError())); + + ThrowHR(hr); + } + + LOG((LF_CORDB, LL_INFO10000, "RS HandleSetThreadContextNeeded - Set Thread Context - ID = 0x%X, SS enabled = %d\n", dwThreadId, /*(uint64_t)hThread,*/ (pContext->EFlags & 0x100) != 0)); + + DWORD lastError = 0; + + success = ::SetThreadContext(hThread, pFrameContext); + if (!success) + { + lastError = ::GetLastError(); + } + + LOG((LF_CORDB, LL_INFO10000, "RS HandleSetThreadContextNeeded - Set Thread Context Completed: Success=%d GetLastError=%d hr=0x%X\n", success, lastError, HRESULT_FROM_WIN32(lastError))); + _ASSERTE(success); + + DWORD suspendCount = ::ResumeThread(hThread); + if (suspendCount == (DWORD)-1) + { + LOG((LF_CORDB, LL_INFO10000, "RS HandleSetThreadContextNeeded - Unexpected result from ResumeThread\n")); + ThrowHR(HRESULT_FROM_GetLastError()); + } + if (suspendCount != previousSuspendCount + 1) + { + LOG((LF_CORDB, LL_INFO10000, "RS HandleSetThreadContextNeeded - Unexpected result from ResumeThread\n")); + ThrowHR(E_UNEXPECTED); + } + + if (!success) + { + LOG((LF_CORDB, LL_INFO10000, "RS HandleSetThreadContextNeeded - Unexpected result from SetThreadContext\n")); + ThrowHR(HRESULT_FROM_WIN32(lastError)); + } +#else + #error Platform not supported +#endif +} +#endif // OUT_OF_PROCESS_SETTHREADCONTEXT // // If the thread has an unhandled managed exception, hijack it. @@ -11377,7 +11535,15 @@ HRESULT CordbProcess::Filter( // holder will invoke DeleteIPCEventHelper(pManagedEvent). } +#ifdef OUT_OF_PROCESS_SETTHREADCONTEXT + else if (dwFirstChance && pRecord->ExceptionCode == STATUS_BREAKPOINT && pRecord->ExceptionAddress == m_runtimeOffsets.m_setThreadContextNeededAddr) + { + // this is a request to set the thread context out of process + HandleSetThreadContextNeeded(dwThreadId); + *pContinueStatus = DBG_CONTINUE; + } +#endif } PUBLIC_API_END(hr); // we may not find the correct mscordacwks so fail gracefully diff --git a/src/coreclr/debug/di/rspriv.h b/src/coreclr/debug/di/rspriv.h index 30e474a2fdc097..bcad293e339bf5 100644 --- a/src/coreclr/debug/di/rspriv.h +++ b/src/coreclr/debug/di/rspriv.h @@ -3280,6 +3280,10 @@ class CordbProcess : #endif } +#ifdef OUT_OF_PROCESS_SETTHREADCONTEXT + void HandleSetThreadContextNeeded(DWORD dwThreadId); +#endif + // // Shim callbacks to simulate fake attach events. // diff --git a/src/coreclr/debug/ee/amd64/dbghelpers.asm b/src/coreclr/debug/ee/amd64/dbghelpers.asm index 49f01283829da5..2ec3483f68538a 100644 --- a/src/coreclr/debug/ee/amd64/dbghelpers.asm +++ b/src/coreclr/debug/ee/amd64/dbghelpers.asm @@ -157,7 +157,13 @@ LEAF_ENTRY NotifyRightSideOfSyncCompleteFlare, _TEXT ret LEAF_END NotifyRightSideOfSyncCompleteFlare, _TEXT - +; Flare for setting the context out of process +LEAF_ENTRY SetThreadContextNeededFlare, _TEXT + int 3 + ; make sure that the basic block is unique + test rax,7 + ret +LEAF_END SetThreadContextNeededFlare, _TEXT ; This goes at the end of the assembly file end diff --git a/src/coreclr/debug/ee/debugger.cpp b/src/coreclr/debug/ee/debugger.cpp index f404ef4570d92b..df05ee034a0d96 100644 --- a/src/coreclr/debug/ee/debugger.cpp +++ b/src/coreclr/debug/ee/debugger.cpp @@ -971,7 +971,12 @@ Debugger::Debugger() // as data structure layouts change, add a new version number // and comment the changes m_mdDataStructureVersion = 1; - + m_fOutOfProcessSetContextEnabled = +#if defined(OUT_OF_PROCESS_SETTHREADCONTEXT) && !defined(DACCESS_COMPILE) + Thread::AreCetShadowStacksEnabled() || CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_OutOfProcessSetContext) != 0; +#else + FALSE; +#endif } /****************************************************************************** @@ -5504,7 +5509,8 @@ bool Debugger::IsJMCMethod(Module* pModule, mdMethodDef tkMethod) bool Debugger::FirstChanceNativeException(EXCEPTION_RECORD *exception, CONTEXT *context, DWORD code, - Thread *thread) + Thread *thread, + BOOL fIsVEH) { // @@@ @@ -5537,19 +5543,27 @@ bool Debugger::FirstChanceNativeException(EXCEPTION_RECORD *exception, bool retVal; - // Don't stop for native debugging anywhere inside our inproc-Filters. - CantStopHolder hHolder; - - if (!CORDBUnrecoverableError(this)) { - retVal = DebuggerController::DispatchNativeException(exception, context, - code, thread); + // Don't stop for native debugging anywhere inside our inproc-Filters. + CantStopHolder hHolder; + + if (!CORDBUnrecoverableError(this)) + { + retVal = DebuggerController::DispatchNativeException(exception, context, + code, thread); + } + else + { + retVal = false; + } } - else + +#if defined(OUT_OF_PROCESS_SETTHREADCONTEXT) && !defined(DACCESS_COMPILE) + if (retVal && fIsVEH) { - retVal = false; + SendSetThreadContextNeeded(context); } - +#endif return retVal; } @@ -13146,7 +13160,7 @@ void STDCALL ExceptionHijackWorker( break; case EHijackReason::kFirstChanceSuspend: _ASSERTE(pData == NULL); - g_pDebugger->FirstChanceSuspendHijackWorker(pContext, pRecord); + g_pDebugger->FirstChanceSuspendHijackWorker(pContext, pRecord, FALSE); break; case EHijackReason::kGenericHijack: _ASSERTE(pData == NULL); @@ -13463,7 +13477,8 @@ VOID Debugger::M2UHandoffHijackWorker(CONTEXT *pContext, okay = g_pDebugger->FirstChanceNativeException(pExceptionRecord, pContext, pExceptionRecord->ExceptionCode, - pEEThread); + pEEThread, + FALSE); _ASSERTE(okay == true); LOG((LF_CORDB, LL_INFO1000, "D::M2UHHW: FirstChanceNativeException returned\n")); } @@ -13502,7 +13517,8 @@ VOID Debugger::M2UHandoffHijackWorker(CONTEXT *pContext, // - this thread is not in cooperative mode. //----------------------------------------------------------------------------- LONG Debugger::FirstChanceSuspendHijackWorker(CONTEXT *pContext, - EXCEPTION_RECORD *pExceptionRecord) + EXCEPTION_RECORD *pExceptionRecord, + BOOL fIsVEH) { // if we aren't set up to do interop debugging this function should just bail out if(m_pRCThread == NULL) @@ -13638,6 +13654,12 @@ LONG Debugger::FirstChanceSuspendHijackWorker(CONTEXT *pContext, if (pFcd->action == HIJACK_ACTION_EXIT_HANDLED) { SPEW(fprintf(stderr, "0x%x D::FCHF: exiting with CONTINUE_EXECUTION\n", tid)); +#if defined(OUT_OF_PROCESS_SETTHREADCONTEXT) && !defined(DACCESS_COMPILE) + if (fIsVEH) + { + SendSetThreadContextNeeded(pContext); + } +#endif return EXCEPTION_CONTINUE_EXECUTION; } else @@ -16631,4 +16653,93 @@ void Debugger::StartCanaryThread() } #endif // DACCESS_COMPILE +#ifndef DACCESS_COMPILE +#ifdef OUT_OF_PROCESS_SETTHREADCONTEXT +void Debugger::SendSetThreadContextNeeded(CONTEXT *context) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + + if (!m_fOutOfProcessSetContextEnabled) + return; + +#if defined(TARGET_WINDOWS) && defined(TARGET_AMD64) + DWORD contextFlags = context->ContextFlags; + DWORD contextSize = 0; + + // determine the context size + BOOL success = InitializeContext(NULL, contextFlags, NULL, &contextSize); + if (success || GetLastError() != ERROR_INSUFFICIENT_BUFFER || contextSize == 0) + { + // The initialize call should fail but return contextSize + _ASSERTE(!"InitializeContext unexpectedly failed\n"); + return; + } + + // allocate a temp buffer for the context + BYTE *pBuffer = (BYTE*)_alloca(contextSize); + if (pBuffer == NULL) + { + _ASSERTE(!"Failed to allocate context buffer"); + LOG((LF_CORDB, LL_INFO10000, "D::SSTCN Failed to allocate context buffer\n")); + return; + } + + // make a copy of the context + PCONTEXT pContext = NULL; + success = InitializeContext(pBuffer, contextFlags, &pContext, &contextSize); + if (!success) + { + _ASSERTE(!"InitializeContext failed"); + LOG((LF_CORDB, LL_INFO10000, "D::SSTCN Unexpected result from InitializeContext (error: %d).\n", GetLastError())); + return; + } + + success = CopyContext(pContext, contextFlags, context); + if (!success) + { + _ASSERTE(!"CopyContext failed"); + LOG((LF_CORDB, LL_INFO10000, "D::SSTCN Unexpected result from CopyContext (error: %d).\n", GetLastError())); + return; + } + + // adjust context size if the context pointer is not aligned with the buffer we allocated + contextSize -= (DWORD)((BYTE*)pContext-(BYTE*)pBuffer); + + // send the context to the right side + LOG((LF_CORDB, LL_INFO10000, "D::SSTCN ContextFlags=0x%X contextSize=%d..\n", contextFlags, contextSize)); + EX_TRY + { + SetThreadContextNeededFlare((TADDR)pContext, contextSize, pContext->Rip, pContext->Rsp); + } + EX_CATCH + { + } + EX_END_CATCH(SwallowAllExceptions); +#else + #error Platform not supported +#endif + + LOG((LF_CORDB, LL_INFO10000, "D::SSTCN SetThreadContextNeededFlare returned\n")); + _ASSERTE(!"We failed to SetThreadContext from out of process!"); +} + +BOOL Debugger::IsOutOfProcessSetContextEnabled() +{ + return m_fOutOfProcessSetContextEnabled; +} +#else +void Debugger::SendSetThreadContextNeeded(CONTEXT* context) +{ + _ASSERTE(!"SendSetThreadContextNeeded is not enabled on this platform"); +} + +BOOL Debugger::IsOutOfProcessSetContextEnabled() +{ + return FALSE; +} +#endif // OUT_OF_PROCESS_SETTHREADCONTEXT +#endif // DACCESS_COMPILE + #endif //DEBUGGING_SUPPORTED + diff --git a/src/coreclr/debug/ee/debugger.h b/src/coreclr/debug/ee/debugger.h index 8b5a79543461f2..695b88aaef1ba4 100644 --- a/src/coreclr/debug/ee/debugger.h +++ b/src/coreclr/debug/ee/debugger.h @@ -1751,6 +1751,13 @@ extern "C" void __stdcall SignalHijackCompleteFlare(void); extern "C" void __stdcall ExceptionNotForRuntimeFlare(void); extern "C" void __stdcall NotifyRightSideOfSyncCompleteFlare(void); extern "C" void __stdcall NotifySecondChanceReadyForDataFlare(void); +#ifdef OUT_OF_PROCESS_SETTHREADCONTEXT +#if defined(TARGET_WINDOWS) && defined(TARGET_AMD64) +extern "C" void __stdcall SetThreadContextNeededFlare(TADDR pContext, DWORD size, TADDR Rip, TADDR Rsp); +#else +#error Platform not supported +#endif +#endif // OUT_OF_PROCESS_SETTHREADCONTEXT /* ------------------------------------------------------------------------ * * Debugger class @@ -1900,7 +1907,8 @@ class Debugger : public DebugInterface bool FirstChanceNativeException(EXCEPTION_RECORD *exception, T_CONTEXT *context, DWORD code, - Thread *thread); + Thread *thread, + BOOL fIsVEH = TRUE); bool IsJMCMethod(Module* pModule, mdMethodDef tkMethod); @@ -2346,7 +2354,8 @@ class Debugger : public DebugInterface LONG FirstChanceSuspendHijackWorker( T_CONTEXT *pContext, - EXCEPTION_RECORD *pExceptionRecord); + EXCEPTION_RECORD *pExceptionRecord, + BOOL fIsVEH = TRUE); static void GenericHijackFunc(void); static void SecondChanceHijackFunc(void); static void SecondChanceHijackFuncWorker(void); @@ -2917,6 +2926,11 @@ class Debugger : public DebugInterface private: HANDLE GetGarbageCollectionBlockerEvent() { return GetLazyData()->m_garbageCollectionBlockerEvent; } +private: + BOOL m_fOutOfProcessSetContextEnabled; +public: + void SendSetThreadContextNeeded(CONTEXT *context); + BOOL IsOutOfProcessSetContextEnabled(); }; diff --git a/src/coreclr/debug/ee/rcthread.cpp b/src/coreclr/debug/ee/rcthread.cpp index 24644e11cd6bd0..23a61151de4368 100644 --- a/src/coreclr/debug/ee/rcthread.cpp +++ b/src/coreclr/debug/ee/rcthread.cpp @@ -476,7 +476,7 @@ HRESULT DebuggerRCThread::SetupRuntimeOffsets(DebuggerIPCControlBlock * pDebugge // Fill out the struct. #ifdef FEATURE_INTEROP_DEBUGGING pDebuggerRuntimeOffsets->m_genericHijackFuncAddr = Debugger::GenericHijackFunc; - // Set flares - these only exist for interop debugging. + // the following 6 flares only exist for interop debugging. pDebuggerRuntimeOffsets->m_signalHijackStartedBPAddr = (void*) SignalHijackStartedFlare; pDebuggerRuntimeOffsets->m_excepForRuntimeHandoffStartBPAddr = (void*) ExceptionForRuntimeHandoffStartFlare; pDebuggerRuntimeOffsets->m_excepForRuntimeHandoffCompleteBPAddr = (void*) ExceptionForRuntimeHandoffCompleteFlare; @@ -486,6 +486,16 @@ HRESULT DebuggerRCThread::SetupRuntimeOffsets(DebuggerIPCControlBlock * pDebugge pDebuggerRuntimeOffsets->m_debuggerWordTLSIndex = g_debuggerWordTLSIndex; #endif // FEATURE_INTEROP_DEBUGGING +#ifdef OUT_OF_PROCESS_SETTHREADCONTEXT +#ifdef TARGET_WINDOWS + pDebuggerRuntimeOffsets->m_setThreadContextNeededAddr = (void*) SetThreadContextNeededFlare; +#else + #error Platform not supported +#endif +#else + pDebuggerRuntimeOffsets->m_setThreadContextNeededAddr = NULL; +#endif + pDebuggerRuntimeOffsets->m_pPatches = DebuggerController::GetPatchTable(); pDebuggerRuntimeOffsets->m_pPatchTableValid = (BOOL*)DebuggerController::GetPatchTableValidAddr(); pDebuggerRuntimeOffsets->m_offRgData = DebuggerPatchTable::GetOffsetOfEntries(); diff --git a/src/coreclr/debug/inc/dbgipcevents.h b/src/coreclr/debug/inc/dbgipcevents.h index 6dbd7fe25a7a70..f40eae3c6a4276 100644 --- a/src/coreclr/debug/inc/dbgipcevents.h +++ b/src/coreclr/debug/inc/dbgipcevents.h @@ -153,6 +153,7 @@ struct MSLAYOUT DebuggerIPCRuntimeOffsets SIZE_T m_cbOpcode; // Max size of opcode SIZE_T m_offTraceType; // Offset of the trace.type within a patch DWORD m_traceTypeUnmanaged; // TRACE_UNMANAGED + void *m_setThreadContextNeededAddr; // Address of SetThreadContextNeededFlare DebuggerIPCRuntimeOffsets() { diff --git a/src/coreclr/inc/clrconfigvalues.h b/src/coreclr/inc/clrconfigvalues.h index 747685b9767b6f..ce7d1836a363e9 100644 --- a/src/coreclr/inc/clrconfigvalues.h +++ b/src/coreclr/inc/clrconfigvalues.h @@ -232,6 +232,8 @@ RETAIL_CONFIG_DWORD_INFO(INTERNAL_MiniMdBufferCapacity, W("MiniMdBufferCapacity" CONFIG_DWORD_INFO(INTERNAL_DbgNativeCodeBpBindsAcrossVersions, W("DbgNativeCodeBpBindsAcrossVersions"), 0, "If non-zero causes native breakpoints at offset 0 to bind in all tiered compilation versions of the given method") RETAIL_CONFIG_DWORD_INFO(UNSUPPORTED_RichDebugInfo, W("RichDebugInfo"), 0, "If non-zero store some additional debug information for each jitted method") +RETAIL_CONFIG_DWORD_INFO(EXTERNAL_OutOfProcessSetContext, W("OutOfProcessSetContext"), 0, "If enabled the debugger will not modify thread contexts in-process. Enabled by default when CET is enabled for the process.") + /// /// Diagnostics (internal general-purpose) /// diff --git a/src/coreclr/vm/dbginterface.h b/src/coreclr/vm/dbginterface.h index 9a30c5876ae090..25e4bf81722aca 100644 --- a/src/coreclr/vm/dbginterface.h +++ b/src/coreclr/vm/dbginterface.h @@ -108,7 +108,8 @@ class DebugInterface virtual bool FirstChanceNativeException(EXCEPTION_RECORD *exception, CONTEXT *context, DWORD code, - Thread *thread) = 0; + Thread *thread, + BOOL fIsVEH = TRUE) = 0; // pThread is thread that exception is on. // currentSP is stack frame of the throw site. @@ -193,6 +194,11 @@ class DebugInterface SIZE_T ilOffset, TADDR nativeFnxStart, SIZE_T *nativeOffset) = 0; + + + // Used by FixContextAndResume + virtual void SendSetThreadContextNeeded(CONTEXT *context) = 0; + virtual BOOL IsOutOfProcessSetContextEnabled() = 0; #endif // EnC_SUPPORTED // Get debugger variable information for a specific version of a method @@ -389,7 +395,7 @@ class DebugInterface virtual BOOL FallbackJITAttachPrompt() = 0; #ifdef FEATURE_INTEROP_DEBUGGING - virtual LONG FirstChanceSuspendHijackWorker(PCONTEXT pContext, PEXCEPTION_RECORD pExceptionRecord) = 0; + virtual LONG FirstChanceSuspendHijackWorker(PCONTEXT pContext, PEXCEPTION_RECORD pExceptionRecord, BOOL fIsVEH = TRUE) = 0; #endif // Helper method for cleaning up transport socket diff --git a/src/coreclr/vm/encee.cpp b/src/coreclr/vm/encee.cpp index f81ade62be7aed..44e2a3512c3986 100644 --- a/src/coreclr/vm/encee.cpp +++ b/src/coreclr/vm/encee.cpp @@ -751,9 +751,6 @@ NOINLINE void EditAndContinueModule::FixContextAndResume( STATIC_CONTRACT_GC_TRIGGERS; // Sends IPC event STATIC_CONTRACT_THROWS; -#if defined(TARGET_WINDOWS) && defined(TARGET_AMD64) - DWORD64 ssp = GetSSP(pContext); -#endif // Create local copies of all structs passed as arguments to prevent them from being overwritten CONTEXT context; memcpy(&context, pContext, sizeof(CONTEXT)); @@ -832,13 +829,22 @@ NOINLINE void EditAndContinueModule::FixContextAndResume( // and return because we are potentially writing new vars onto the stack. pCurThread->SetFilterContext( NULL ); +#ifdef OUT_OF_PROCESS_SETTHREADCONTEXT + if (g_pDebugInterface->IsOutOfProcessSetContextEnabled()) + { + g_pDebugInterface->SendSetThreadContextNeeded(pContext); + } + else + { +#endif // OUT_OF_PROCESS_SETTHREADCONTEXT #if defined(TARGET_X86) ResumeAtJit(pContext, oldSP); -#elif defined(TARGET_WINDOWS) && defined(TARGET_AMD64) - ClrRestoreNonvolatileContextWorker(pContext, ssp); #else ClrRestoreNonvolatileContext(pContext); #endif +#ifdef OUT_OF_PROCESS_SETTHREADCONTEXT + } +#endif // OUT_OF_PROCESS_SETTHREADCONTEXT // At this point we shouldn't have failed, so this is genuinely erroneous. LOG((LF_ENC, LL_ERROR, "**Error** EnCModule::ResumeInUpdatedFunction returned from ResumeAtJit")); From 44d0cfed5a4dfc69a9bc6b0a481aeb761e072854 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Tue, 9 Aug 2022 22:43:50 -0700 Subject: [PATCH 39/41] Initialize main thread to MTA by default in NativeAOT (#73659) --- .../IL/Stubs/StartupCode/StartupCodeMainMethod.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/IL/Stubs/StartupCode/StartupCodeMainMethod.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/IL/Stubs/StartupCode/StartupCodeMainMethod.cs index 435198bf162aa9..1741ffdd61dd7c 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/IL/Stubs/StartupCode/StartupCodeMainMethod.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/IL/Stubs/StartupCode/StartupCodeMainMethod.cs @@ -77,7 +77,7 @@ public override MethodIL EmitIL() codeStream.Emit(ILOpcode.call, emitter.NewToken(method)); } } - + MetadataType startup = Context.GetOptionalHelperType("StartupCodeHelpers"); // Initialize command line args if the class library supports this @@ -110,8 +110,9 @@ public override MethodIL EmitIL() codeStream.EmitLdc((int)System.Threading.ApartmentState.STA); codeStream.Emit(ILOpcode.call, emitter.NewToken(initApartmentState)); } - if (_mainMethod.WrappedMethod.HasCustomAttribute("System", "MTAThreadAttribute")) + else { + // Initialize to MTA by default codeStream.EmitLdc((int)System.Threading.ApartmentState.MTA); codeStream.Emit(ILOpcode.call, emitter.NewToken(initApartmentState)); } From 7b63201cea47a299f28a2a0b743f4dc3a94c1736 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Wed, 10 Aug 2022 15:26:23 +0900 Subject: [PATCH 40/41] [NativeAot] Triage another 14 libs tests (#73442) --- .../tests/MemoryCacheSetAndRemoveTests.cs | 5 +++- .../tests/XmlConfigurationTest.cs | 1 + .../ConsoleLoggerExtensionsTests.cs | 1 + .../SimpleConsoleFormatterTests.cs | 1 + .../tests/EventSourceLoggerTest.cs | 3 +++ .../tests/EventLogMessagesTests.cs | 6 +++-- .../System.Diagnostics.EventLog.Tests.csproj | 1 + .../tests/ProcessModuleTests.Windows.cs | 2 +- .../tests/ProcessModuleTests.cs | 3 ++- .../tests/ProcessTests.cs | 2 +- .../System.Diagnostics.Process.Tests.csproj | 5 +++- .../tests/SortRequestControlTests.cs | 2 +- .../tests/Dataflow/DebugAttributeTests.cs | 4 +++- src/libraries/tests.proj | 23 ++++++++----------- 14 files changed, 36 insertions(+), 23 deletions(-) diff --git a/src/libraries/Microsoft.Extensions.Caching.Memory/tests/MemoryCacheSetAndRemoveTests.cs b/src/libraries/Microsoft.Extensions.Caching.Memory/tests/MemoryCacheSetAndRemoveTests.cs index 15ff4a158bd5fd..e7ac1edb71ece7 100644 --- a/src/libraries/Microsoft.Extensions.Caching.Memory/tests/MemoryCacheSetAndRemoveTests.cs +++ b/src/libraries/Microsoft.Extensions.Caching.Memory/tests/MemoryCacheSetAndRemoveTests.cs @@ -219,7 +219,10 @@ public void DisposingCacheEntryReleasesScope(bool trackLinkedCacheEntries) { object GetScope(ICacheEntry entry) { - return entry.GetType() + // Use Type.GetType so that trimming can know what type we operate on + Type cacheEntryType = Type.GetType("Microsoft.Extensions.Caching.Memory.CacheEntry, Microsoft.Extensions.Caching.Memory"); + Assert.Equal(cacheEntryType, entry.GetType()); + return cacheEntryType .GetField("_previous", BindingFlags.NonPublic | BindingFlags.Instance) .GetValue(entry); } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Xml/tests/XmlConfigurationTest.cs b/src/libraries/Microsoft.Extensions.Configuration.Xml/tests/XmlConfigurationTest.cs index 96550f57d07789..9efd7cd087351d 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Xml/tests/XmlConfigurationTest.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Xml/tests/XmlConfigurationTest.cs @@ -704,6 +704,7 @@ public void XmlConfiguration_Does_Not_Throw_On_Optional_Configuration() } [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/73432", typeof(PlatformDetection), nameof(PlatformDetection.IsBuiltWithAggressiveTrimming))] [ActiveIssue("https://github.com/dotnet/runtime/issues/37669", TestPlatforms.Browser)] public void LoadKeyValuePairsFromValidEncryptedXml() { diff --git a/src/libraries/Microsoft.Extensions.Logging.Console/tests/Microsoft.Extensions.Logging.Console.Tests/ConsoleLoggerExtensionsTests.cs b/src/libraries/Microsoft.Extensions.Logging.Console/tests/Microsoft.Extensions.Logging.Console.Tests/ConsoleLoggerExtensionsTests.cs index a89c53f5322a88..de5dd9310cd781 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Console/tests/Microsoft.Extensions.Logging.Console.Tests/ConsoleLoggerExtensionsTests.cs +++ b/src/libraries/Microsoft.Extensions.Logging.Console/tests/Microsoft.Extensions.Logging.Console.Tests/ConsoleLoggerExtensionsTests.cs @@ -271,6 +271,7 @@ public void AddSystemdConsole_OutsideConfig_TakesProperty() } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/73436", typeof(PlatformDetection), nameof(PlatformDetection.IsNativeAot))] public void AddJsonConsole_ChangeProperties_IsReadFromLoggingConfiguration() { var configuration = new ConfigurationBuilder().AddInMemoryCollection(new[] { diff --git a/src/libraries/Microsoft.Extensions.Logging.Console/tests/Microsoft.Extensions.Logging.Console.Tests/SimpleConsoleFormatterTests.cs b/src/libraries/Microsoft.Extensions.Logging.Console/tests/Microsoft.Extensions.Logging.Console.Tests/SimpleConsoleFormatterTests.cs index 46c88511870324..4192809f7e913b 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Console/tests/Microsoft.Extensions.Logging.Console.Tests/SimpleConsoleFormatterTests.cs +++ b/src/libraries/Microsoft.Extensions.Logging.Console/tests/Microsoft.Extensions.Logging.Console.Tests/SimpleConsoleFormatterTests.cs @@ -15,6 +15,7 @@ public class SimpleConsoleFormatterTests : ConsoleFormatterTests [InlineData(LoggerColorBehavior.Disabled)] [ActiveIssue("https://github.com/dotnet/runtime/issues/50575", TestPlatforms.Android)] [ActiveIssue("https://github.com/dotnet/runtime/issues/51398", TestPlatforms.iOS | TestPlatforms.tvOS | TestPlatforms.MacCatalyst)] + [ActiveIssue("https://github.com/dotnet/runtime/issues/73436", typeof(PlatformDetection), nameof(PlatformDetection.IsNativeAot))] public void Log_WritingScopes_LogsWithCorrectColorsWhenColorEnabled(LoggerColorBehavior colorBehavior) { // Arrange diff --git a/src/libraries/Microsoft.Extensions.Logging.EventSource/tests/EventSourceLoggerTest.cs b/src/libraries/Microsoft.Extensions.Logging.EventSource/tests/EventSourceLoggerTest.cs index 6a54f4f5e43b23..343036cca5b5ce 100644 --- a/src/libraries/Microsoft.Extensions.Logging.EventSource/tests/EventSourceLoggerTest.cs +++ b/src/libraries/Microsoft.Extensions.Logging.EventSource/tests/EventSourceLoggerTest.cs @@ -162,6 +162,7 @@ public void FilterSpecs_IncreaseLoggingLevelForOneCategory_DisablesExistingRules } [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/73438", typeof(PlatformDetection), nameof(PlatformDetection.IsNativeAot))] public void Logs_AsExpected_WithDefaults() { using (var testListener = new TestEventListener()) @@ -195,6 +196,7 @@ public void Logs_AsExpected_WithDefaults() } [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/73438", typeof(PlatformDetection), nameof(PlatformDetection.IsNativeAot))] public void Logs_AsExpected_WithDefaults_EnabledEarly() { using (var testListener = new TestEventListener()) @@ -301,6 +303,7 @@ public void Logs_OnlyJson_IfKeywordSet() } [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/73438", typeof(PlatformDetection), nameof(PlatformDetection.IsNativeAot))] public void Logs_OnlyMessage_IfKeywordSet() { using (var testListener = new TestEventListener()) diff --git a/src/libraries/System.Diagnostics.EventLog/tests/EventLogMessagesTests.cs b/src/libraries/System.Diagnostics.EventLog/tests/EventLogMessagesTests.cs index ad205ced6c2f6b..410b7920aa019a 100644 --- a/src/libraries/System.Diagnostics.EventLog/tests/EventLogMessagesTests.cs +++ b/src/libraries/System.Diagnostics.EventLog/tests/EventLogMessagesTests.cs @@ -20,7 +20,7 @@ public void EventLogMessagesContainsNoTypes() Assert.Empty(messageAssembly.GetTypes()); } - [Theory] + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.HasAssemblyFiles))] [InlineData(0)] [InlineData(1)] [InlineData(65535)] @@ -50,7 +50,9 @@ public unsafe void CanFormatMessage(uint messageId) } } - [ConditionalFact(typeof(Helpers), nameof(Helpers.IsElevatedAndSupportsEventLogs))] + public static bool HasAssemblyFilesIsElevatedAndSupportsEventLogs => PlatformDetection.HasAssemblyFiles && Helpers.IsElevatedAndSupportsEventLogs; + + [ConditionalFact(nameof(HasAssemblyFilesIsElevatedAndSupportsEventLogs))] public void CanReadAndWriteMessages() { string messageDllPath = Path.Combine(Path.GetDirectoryName(typeof(EventLog).Assembly.Location), "System.Diagnostics.EventLog.Messages.dll"); diff --git a/src/libraries/System.Diagnostics.EventLog/tests/System.Diagnostics.EventLog.Tests.csproj b/src/libraries/System.Diagnostics.EventLog/tests/System.Diagnostics.EventLog.Tests.csproj index f96d3e38fa1a36..6b2270e38c71eb 100644 --- a/src/libraries/System.Diagnostics.EventLog/tests/System.Diagnostics.EventLog.Tests.csproj +++ b/src/libraries/System.Diagnostics.EventLog/tests/System.Diagnostics.EventLog.Tests.csproj @@ -44,5 +44,6 @@ + diff --git a/src/libraries/System.Diagnostics.Process/tests/ProcessModuleTests.Windows.cs b/src/libraries/System.Diagnostics.Process/tests/ProcessModuleTests.Windows.cs index 5e3a8cc45f806c..0b4796c5048a31 100644 --- a/src/libraries/System.Diagnostics.Process/tests/ProcessModuleTests.Windows.cs +++ b/src/libraries/System.Diagnostics.Process/tests/ProcessModuleTests.Windows.cs @@ -20,7 +20,7 @@ public void LongModuleFileNamesAreSupported() const string libraryName = "LongPath.dll"; const int minPathLength = 261; - string testBinPath = Path.GetDirectoryName(typeof(ProcessModuleTests).Assembly.Location); + string testBinPath = AppContext.BaseDirectory; string libraryToCopy = Path.Combine(testBinPath, libraryName); Assert.True(File.Exists(libraryToCopy), $"{libraryName} was not present in bin folder '{testBinPath}'"); diff --git a/src/libraries/System.Diagnostics.Process/tests/ProcessModuleTests.cs b/src/libraries/System.Diagnostics.Process/tests/ProcessModuleTests.cs index 1b05b2fa84f55b..a5376fce094160 100644 --- a/src/libraries/System.Diagnostics.Process/tests/ProcessModuleTests.cs +++ b/src/libraries/System.Diagnostics.Process/tests/ProcessModuleTests.cs @@ -38,7 +38,8 @@ public void Modules_Get_ContainsHostFileName() Assert.Contains(modules.Cast(), m => m.FileName.Contains(RemoteExecutor.HostRunnerName)); } - [Fact] + // Single-file executables don't have libcoreclr or libSystem.Native + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.HasAssemblyFiles))] [PlatformSpecific(TestPlatforms.Linux)] // OSX only includes the main module public void TestModulesContainsUnixNativeLibs() { diff --git a/src/libraries/System.Diagnostics.Process/tests/ProcessTests.cs b/src/libraries/System.Diagnostics.Process/tests/ProcessTests.cs index a13d097bd671a8..6fa4ee16248878 100644 --- a/src/libraries/System.Diagnostics.Process/tests/ProcessTests.cs +++ b/src/libraries/System.Diagnostics.Process/tests/ProcessTests.cs @@ -1827,7 +1827,7 @@ static void SetPrivateFieldsToNonDefaultValues(Process process) SetPrivateFieldValue(process, "_haveExitTime", true); SetPrivateFieldValue(process, "_havePriorityBoostEnabled", true); - SetPrivateFieldValue(process, "_processInfo", typeof(Process).Assembly.GetType("System.Diagnostics.ProcessInfo").GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, Array.Empty()).Invoke(null)); + SetPrivateFieldValue(process, "_processInfo", Type.GetType("System.Diagnostics.ProcessInfo, System.Diagnostics.Process").GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, Array.Empty()).Invoke(null)); SetPrivateFieldValue(process, "_threads", new ProcessThreadCollection(Array.Empty())); SetPrivateFieldValue(process, "_modules", new ProcessModuleCollection(Array.Empty())); diff --git a/src/libraries/System.Diagnostics.Process/tests/System.Diagnostics.Process.Tests.csproj b/src/libraries/System.Diagnostics.Process/tests/System.Diagnostics.Process.Tests.csproj index 396b584abd2a6f..e23bb4a94a71c6 100644 --- a/src/libraries/System.Diagnostics.Process/tests/System.Diagnostics.Process.Tests.csproj +++ b/src/libraries/System.Diagnostics.Process/tests/System.Diagnostics.Process.Tests.csproj @@ -64,6 +64,9 @@ - + + Content + PreserveNewest + diff --git a/src/libraries/System.DirectoryServices.Protocols/tests/SortRequestControlTests.cs b/src/libraries/System.DirectoryServices.Protocols/tests/SortRequestControlTests.cs index 480b537bab85b6..50185cf8d52ef0 100644 --- a/src/libraries/System.DirectoryServices.Protocols/tests/SortRequestControlTests.cs +++ b/src/libraries/System.DirectoryServices.Protocols/tests/SortRequestControlTests.cs @@ -101,7 +101,7 @@ public void SortKeys_SetValid_GetReturnsExpected() public void SortKeys_GetNull_ReturnsEmptyArray() { var control = new SortRequestControl(); - FieldInfo field = control.GetType().GetField("_keys", BindingFlags.NonPublic | BindingFlags.Instance); + FieldInfo field = typeof(SortRequestControl).GetField("_keys", BindingFlags.NonPublic | BindingFlags.Instance); field.SetValue(control, null); Assert.Empty(control.SortKeys); } diff --git a/src/libraries/System.Threading.Tasks.Dataflow/tests/Dataflow/DebugAttributeTests.cs b/src/libraries/System.Threading.Tasks.Dataflow/tests/Dataflow/DebugAttributeTests.cs index 9b757ce5c9e62f..75f45c48a260ad 100644 --- a/src/libraries/System.Threading.Tasks.Dataflow/tests/Dataflow/DebugAttributeTests.cs +++ b/src/libraries/System.Threading.Tasks.Dataflow/tests/Dataflow/DebugAttributeTests.cs @@ -8,7 +8,9 @@ namespace System.Threading.Tasks.Dataflow.Tests { public class DebugAttributeTests { - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + public static bool IsThreadingAndDebuggerAttributesSupported => PlatformDetection.IsThreadingSupported && PlatformDetection.IsDebuggerTypeProxyAttributeSupported; + + [ConditionalFact(nameof(IsThreadingAndDebuggerAttributesSupported))] public void TestDebuggerDisplaysAndTypeProxies() { // Test both canceled and non-canceled diff --git a/src/libraries/tests.proj b/src/libraries/tests.proj index c153884d978452..d1a1ffe538402e 100644 --- a/src/libraries/tests.proj +++ b/src/libraries/tests.proj @@ -413,6 +413,10 @@ + + + + @@ -464,6 +468,8 @@ + + @@ -471,6 +477,9 @@ + + + @@ -483,16 +492,10 @@ - - - - - - @@ -507,13 +510,8 @@ - - - - - @@ -522,15 +520,12 @@ - - - From 7bc07c2433edf8ea9ef2c62505c13bba182c9a7f Mon Sep 17 00:00:00 2001 From: Buyaa Namnan Date: Tue, 9 Aug 2022 23:36:32 -0700 Subject: [PATCH 41/41] Revert "Generic attributes handling in CustomAttributeDecoder" (#73671) This reverts commit b947dd6e1bb45b064e30159c9251add5c80914af. --- .../Ecma335/CustomAttributeDecoder.cs | 155 +------- .../Decoding/CustomAttributeDecoderTests.cs | 364 +----------------- 2 files changed, 18 insertions(+), 501 deletions(-) diff --git a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/CustomAttributeDecoder.cs b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/CustomAttributeDecoder.cs index 45deeee7853047..a81396cbdef16c 100644 --- a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/CustomAttributeDecoder.cs +++ b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/CustomAttributeDecoder.cs @@ -22,7 +22,6 @@ public CustomAttributeDecoder(ICustomAttributeTypeProvider provider, Meta public CustomAttributeValue DecodeValue(EntityHandle constructor, BlobHandle value) { BlobHandle signature; - BlobHandle attributeOwningTypeSpec = default; switch (constructor.Kind) { case HandleKind.MethodDefinition: @@ -33,13 +32,6 @@ public CustomAttributeValue DecodeValue(EntityHandle constructor, BlobHan case HandleKind.MemberReference: MemberReference reference = _reader.GetMemberReference((MemberReferenceHandle)constructor); signature = reference.Signature; - - // If this is a generic attribute, we'll need its instantiation to decode the signatures - if (reference.Parent.Kind == HandleKind.TypeSpecification) - { - TypeSpecification genericOwner = _reader.GetTypeSpecification((TypeSpecificationHandle)reference.Parent); - attributeOwningTypeSpec = genericOwner.Signature; - } break; default: @@ -68,38 +60,12 @@ public CustomAttributeValue DecodeValue(EntityHandle constructor, BlobHan throw new BadImageFormatException(); } - BlobReader genericContextReader = default; - if (!attributeOwningTypeSpec.IsNil) - { - // If this is a generic attribute, grab the instantiation arguments so that we can - // interpret the constructor signature, should it refer to the generic context. - genericContextReader = _reader.GetBlobReader(attributeOwningTypeSpec); - if (genericContextReader.ReadSignatureTypeCode() == SignatureTypeCode.GenericTypeInstance) - { - int kind = genericContextReader.ReadCompressedInteger(); - if (kind != (int)SignatureTypeKind.Class && kind != (int)SignatureTypeKind.ValueType) - { - throw new BadImageFormatException(); - } - - genericContextReader.ReadTypeHandle(); - - // At this point, the reader points to the "GenArgCount Type Type*" part of the signature. - } - else - { - // Some other invalid TypeSpec. Don't accidentally allow resolving generic parameters - // from the constructor signature into a broken blob. - genericContextReader = default; - } - } - - ImmutableArray> fixedArguments = DecodeFixedArguments(ref signatureReader, ref valueReader, parameterCount, genericContextReader); + ImmutableArray> fixedArguments = DecodeFixedArguments(ref signatureReader, ref valueReader, parameterCount); ImmutableArray> namedArguments = DecodeNamedArguments(ref valueReader); return new CustomAttributeValue(fixedArguments, namedArguments); } - private ImmutableArray> DecodeFixedArguments(ref BlobReader signatureReader, ref BlobReader valueReader, int count, BlobReader genericContextReader) + private ImmutableArray> DecodeFixedArguments(ref BlobReader signatureReader, ref BlobReader valueReader, int count) { if (count == 0) { @@ -110,7 +76,7 @@ private ImmutableArray> DecodeFixedArguments for (int i = 0; i < count; i++) { - ArgumentTypeInfo info = DecodeFixedArgumentType(ref signatureReader, genericContextReader); + ArgumentTypeInfo info = DecodeFixedArgumentType(ref signatureReader); arguments.Add(DecodeArgument(ref valueReader, info)); } @@ -158,7 +124,7 @@ private struct ArgumentTypeInfo // better perf-wise, but even more important is that we can't actually reason about // a method signature with opaque TType values without adding some unnecessary chatter // with the provider. - private ArgumentTypeInfo DecodeFixedArgumentType(ref BlobReader signatureReader, BlobReader genericContextReader, bool isElementType = false) + private ArgumentTypeInfo DecodeFixedArgumentType(ref BlobReader signatureReader, bool isElementType = false) { SignatureTypeCode signatureTypeCode = signatureReader.ReadSignatureTypeCode(); @@ -204,33 +170,12 @@ private ArgumentTypeInfo DecodeFixedArgumentType(ref BlobReader signatureReader, throw new BadImageFormatException(); } - var elementInfo = DecodeFixedArgumentType(ref signatureReader, genericContextReader, isElementType: true); + var elementInfo = DecodeFixedArgumentType(ref signatureReader, isElementType: true); info.ElementType = elementInfo.Type; info.ElementTypeCode = elementInfo.TypeCode; info.Type = _provider.GetSZArrayType(info.ElementType); break; - case SignatureTypeCode.GenericTypeParameter: - if (genericContextReader.Length == 0) - { - throw new BadImageFormatException(); - } - - int parameterIndex = signatureReader.ReadCompressedInteger(); - int numGenericParameters = genericContextReader.ReadCompressedInteger(); - if (parameterIndex >= numGenericParameters) - { - throw new BadImageFormatException(); - } - - while (parameterIndex > 0) - { - SkipType(ref genericContextReader); - parameterIndex--; - } - - return DecodeFixedArgumentType(ref genericContextReader, default, isElementType); - default: throw new BadImageFormatException(); } @@ -418,95 +363,5 @@ private TType GetTypeFromHandle(EntityHandle handle) => HandleKind.TypeReference => _provider.GetTypeFromReference(_reader, (TypeReferenceHandle)handle, 0), _ => throw new BadImageFormatException(SR.NotTypeDefOrRefHandle), }; - - private static void SkipType(ref BlobReader blobReader) - { - int typeCode = blobReader.ReadCompressedInteger(); - - switch (typeCode) - { - case (int)SignatureTypeCode.Boolean: - case (int)SignatureTypeCode.Char: - case (int)SignatureTypeCode.SByte: - case (int)SignatureTypeCode.Byte: - case (int)SignatureTypeCode.Int16: - case (int)SignatureTypeCode.UInt16: - case (int)SignatureTypeCode.Int32: - case (int)SignatureTypeCode.UInt32: - case (int)SignatureTypeCode.Int64: - case (int)SignatureTypeCode.UInt64: - case (int)SignatureTypeCode.Single: - case (int)SignatureTypeCode.Double: - case (int)SignatureTypeCode.IntPtr: - case (int)SignatureTypeCode.UIntPtr: - case (int)SignatureTypeCode.Object: - case (int)SignatureTypeCode.String: - case (int)SignatureTypeCode.Void: - case (int)SignatureTypeCode.TypedReference: - return; - - case (int)SignatureTypeCode.Pointer: - case (int)SignatureTypeCode.ByReference: - case (int)SignatureTypeCode.Pinned: - case (int)SignatureTypeCode.SZArray: - SkipType(ref blobReader); - return; - - case (int)SignatureTypeCode.FunctionPointer: - SignatureHeader header = blobReader.ReadSignatureHeader(); - if (header.IsGeneric) - { - blobReader.ReadCompressedInteger(); // arity - } - - int paramCount = blobReader.ReadCompressedInteger(); - SkipType(ref blobReader); - for (int i = 0; i < paramCount; i++) - SkipType(ref blobReader); - return; - - case (int)SignatureTypeCode.Array: - SkipType(ref blobReader); - blobReader.ReadCompressedInteger(); // rank - int boundsCount = blobReader.ReadCompressedInteger(); - for (int i = 0; i < boundsCount; i++) - { - blobReader.ReadCompressedInteger(); - } - int lowerBoundsCount = blobReader.ReadCompressedInteger(); - for (int i = 0; i < lowerBoundsCount; i++) - { - blobReader.ReadCompressedSignedInteger(); - } - return; - - case (int)SignatureTypeCode.RequiredModifier: - case (int)SignatureTypeCode.OptionalModifier: - blobReader.ReadTypeHandle(); - SkipType(ref blobReader); - return; - - case (int)SignatureTypeCode.GenericTypeInstance: - SkipType(ref blobReader); - int count = blobReader.ReadCompressedInteger(); - for (int i = 0; i < count; i++) - { - SkipType(ref blobReader); - } - return; - - case (int)SignatureTypeCode.GenericTypeParameter: - blobReader.ReadCompressedInteger(); - return; - - case (int)SignatureTypeKind.Class: - case (int)SignatureTypeKind.ValueType: - SkipType(ref blobReader); - break; - - default: - throw new BadImageFormatException(); - } - } } } diff --git a/src/libraries/System.Reflection.Metadata/tests/Metadata/Decoding/CustomAttributeDecoderTests.cs b/src/libraries/System.Reflection.Metadata/tests/Metadata/Decoding/CustomAttributeDecoderTests.cs index a1f6355201053a..f0aafa09ed66f9 100644 --- a/src/libraries/System.Reflection.Metadata/tests/Metadata/Decoding/CustomAttributeDecoderTests.cs +++ b/src/libraries/System.Reflection.Metadata/tests/Metadata/Decoding/CustomAttributeDecoderTests.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Collections.Generic; using System.Collections.Immutable; using System.IO; using System.Reflection.Metadata.Tests; @@ -12,7 +11,7 @@ namespace System.Reflection.Metadata.Decoding.Tests { public class CustomAttributeDecoderTests { - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.HasAssemblyFiles), nameof(PlatformDetection.IsMonoRuntime))] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.HasAssemblyFiles))] [ActiveIssue("https://github.com/dotnet/runtime/issues/60579", TestPlatforms.iOS | TestPlatforms.tvOS)] public void TestCustomAttributeDecoder() { @@ -23,6 +22,7 @@ public void TestCustomAttributeDecoder() var provider = new CustomAttributeTypeProvider(); TypeDefinitionHandle typeDefHandle = TestMetadataResolver.FindTestType(reader, typeof(HasAttributes)); + int i = 0; foreach (CustomAttributeHandle attributeHandle in reader.GetCustomAttributes(typeDefHandle)) { @@ -75,274 +75,16 @@ public void TestCustomAttributeDecoder() break; default: - // TODO: https://github.com/dotnet/runtime/issues/73593 - // This method only tests first 3 attriubtes because the complete test 'TestCustomAttributeDecoderUsingReflection' fails on mono - // Leaving this hard coded test only for mono, until the issue fixed on mono + // TODO: https://github.com/dotnet/runtime/issues/16552 + // The other cases are missing corresponding assertions. This needs some refactoring to + // be data-driven. A better approach would probably be to generically compare reflection + // CustomAttributeData to S.R.M CustomAttributeValue for every test attribute applied. break; } } } } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.HasAssemblyFiles))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/73593", TestRuntimes.Mono)] - public void TestCustomAttributeDecoderUsingReflection() - { - Type type = typeof(HasAttributes); - using (FileStream stream = File.OpenRead(AssemblyPathHelper.GetAssemblyLocation(type.GetTypeInfo().Assembly))) - using (PEReader peReader = new PEReader(stream)) - { - MetadataReader reader = peReader.GetMetadataReader(); - CustomAttributeTypeProvider provider = new CustomAttributeTypeProvider(); - TypeDefinitionHandle typeDefHandle = TestMetadataResolver.FindTestType(reader, type); - - IList attributes = type.GetCustomAttributesData(); - - int i = 0; - foreach (CustomAttributeHandle attributeHandle in reader.GetCustomAttributes(typeDefHandle)) - { - CustomAttribute attribute = reader.GetCustomAttribute(attributeHandle); - CustomAttributeValue value = attribute.DecodeValue(provider); - CustomAttributeData reflectionAttribute = attributes[i++]; - - Assert.Equal(reflectionAttribute.ConstructorArguments.Count, value.FixedArguments.Length); - Assert.Equal(reflectionAttribute.NamedArguments.Count, value.NamedArguments.Length); - - int j = 0; - foreach (CustomAttributeTypedArgument arguments in value.FixedArguments) - { - Type t = reflectionAttribute.ConstructorArguments[j].ArgumentType; - Assert.Equal(TypeToString(t), arguments.Type); - if (t.IsArray && arguments.Value is not null) - { - ImmutableArray> array = (ImmutableArray>)(arguments.Value); - IList refArray = (IList)reflectionAttribute.ConstructorArguments[j].Value; - int k = 0; - foreach (CustomAttributeTypedArgument element in array) - { - if (refArray[k].ArgumentType.IsArray) - { - ImmutableArray> innerArray = (ImmutableArray>)(element.Value); - IList refInnerArray = (IList)refArray[k].Value; - int a = 0; - foreach (CustomAttributeTypedArgument el in innerArray) - { - if (refInnerArray[a].Value?.ToString() != el.Value?.ToString()) - { - Assert.Equal(refInnerArray[a].Value, el.Value); - } - a++; - } - } - else if (refArray[k].Value?.ToString() != element.Value?.ToString()) - { - if (refArray[k].ArgumentType == typeof(Type)) // TODO: check if it is expected - { - Assert.Contains(refArray[k].Value.ToString(), element.Value.ToString()); - } - else - { - Assert.Equal(refArray[k].Value, element.Value); - } - } - k++; - } - } - else if (reflectionAttribute.ConstructorArguments[j].Value?.ToString() != arguments.Value?.ToString()) - { - if (reflectionAttribute.ConstructorArguments[j].ArgumentType == typeof(Type)) - { - Assert.Contains(reflectionAttribute.ConstructorArguments[j].Value.ToString(), arguments.Value.ToString()); - } - else - { - Assert.Equal(reflectionAttribute.ConstructorArguments[j].Value, arguments.Value); - } - } - j++; - } - j = 0; - foreach (CustomAttributeNamedArgument arguments in value.NamedArguments) - { - Type t = reflectionAttribute.NamedArguments[j].TypedValue.ArgumentType; - Assert.Equal(TypeToString(t), arguments.Type); - if (t.IsArray && arguments.Value is not null) - { - ImmutableArray> array = (ImmutableArray>)(arguments.Value); - IList refArray = (IList)reflectionAttribute.NamedArguments[j].TypedValue.Value; - int k = 0; - foreach (CustomAttributeTypedArgument element in array) - { - if (refArray[k].Value?.ToString() != element.Value?.ToString()) - { - Assert.Equal(refArray[k].Value, element.Value); - } - k++; - } - } - else if (reflectionAttribute.NamedArguments[j].TypedValue.Value?.ToString() != arguments.Value?.ToString()) - { - if (reflectionAttribute.NamedArguments[j].TypedValue.ArgumentType == typeof(Type)) // typeof operator used for named parameter, like [Test(TypeField = typeof(string))], check if it is expected - { - Assert.Contains(reflectionAttribute.NamedArguments[j].TypedValue.Value.ToString(), arguments.Value.ToString()); - } - else - { - Assert.Equal(reflectionAttribute.NamedArguments[j].TypedValue.Value, arguments.Value); - } - } - j++; - } - } - } - } - -#if NETCOREAPP // Generic attribute is not supported on .NET Framework. - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.HasAssemblyFiles))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/60579", TestPlatforms.iOS | TestPlatforms.tvOS)] - public void TestCustomAttributeDecoderGenericUsingReflection() - { - Type type = typeof(HasGenericAttributes); - using (FileStream stream = File.OpenRead(AssemblyPathHelper.GetAssemblyLocation(type.GetTypeInfo().Assembly))) - using (PEReader peReader = new PEReader(stream)) - { - MetadataReader reader = peReader.GetMetadataReader(); - CustomAttributeTypeProvider provider = new CustomAttributeTypeProvider(); - TypeDefinitionHandle typeDefHandle = TestMetadataResolver.FindTestType(reader, type); - - IList attributes= type.GetCustomAttributesData(); - - int i = 0; - foreach (CustomAttributeHandle attributeHandle in reader.GetCustomAttributes(typeDefHandle)) - { - CustomAttribute attribute = reader.GetCustomAttribute(attributeHandle); - CustomAttributeValue value = attribute.DecodeValue(provider); - CustomAttributeData reflectionAttribute = attributes[i++]; - - Assert.Equal(reflectionAttribute.ConstructorArguments.Count, value.FixedArguments.Length); - Assert.Equal(reflectionAttribute.NamedArguments.Count, value.NamedArguments.Length); - - int j = 0; - foreach (CustomAttributeTypedArgument arguments in value.FixedArguments) - { - Assert.Equal(TypeToString(reflectionAttribute.ConstructorArguments[j].ArgumentType), arguments.Type); - if (reflectionAttribute.ConstructorArguments[j].Value.ToString() != arguments.Value.ToString()) - { - Assert.Equal(reflectionAttribute.ConstructorArguments[j].Value, arguments.Value); - } - j++; - } - j = 0; - foreach (CustomAttributeNamedArgument arguments in value.NamedArguments) - { - Assert.Equal(TypeToString(reflectionAttribute.NamedArguments[j].TypedValue.ArgumentType), arguments.Type); - if (reflectionAttribute.NamedArguments[j].TypedValue.Value.ToString() != arguments.Value.ToString()) - { - Assert.Equal(reflectionAttribute.NamedArguments[j].TypedValue.Value, arguments.Value); - } - j++; - } - } - } - } - - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.HasAssemblyFiles))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/60579", TestPlatforms.iOS | TestPlatforms.tvOS)] - public void TestCustomAttributeDecoderGenericArray() - { - Type type = typeof(HasGenericArrayAttributes); - using (FileStream stream = File.OpenRead(AssemblyPathHelper.GetAssemblyLocation(type.GetTypeInfo().Assembly))) - using (PEReader peReader = new PEReader(stream)) - { - MetadataReader reader = peReader.GetMetadataReader(); - CustomAttributeTypeProvider provider = new CustomAttributeTypeProvider(); - TypeDefinitionHandle typeDefHandle = TestMetadataResolver.FindTestType(reader, type); - - IList attributes = type.GetCustomAttributesData(); - - foreach (CustomAttributeHandle attributeHandle in reader.GetCustomAttributes(typeDefHandle)) - { - CustomAttribute attribute = reader.GetCustomAttribute(attributeHandle); - CustomAttributeValue value = attribute.DecodeValue(provider); - - if (value.FixedArguments.Length == 2) - { - Assert.Equal(2, value.FixedArguments.Length); - ImmutableArray> array1 = (ImmutableArray>)(value.FixedArguments[0].Value); - Assert.Equal("int32[]", value.FixedArguments[0].Type); - Assert.Equal(1, array1[0].Value); - Assert.Equal(3, array1[2].Value); - ImmutableArray> array2 = (ImmutableArray>)(value.FixedArguments[1].Value); - Assert.Equal("uint8[]", value.FixedArguments[1].Type); - Assert.Equal((byte)4, array2[0].Value); - Assert.Equal((byte)5, array2[1].Value); - - Assert.Empty(value.NamedArguments); - } - else - { - Assert.Equal(1, value.FixedArguments.Length); - - Assert.Equal("uint8", value.FixedArguments[0].Type); - Assert.Equal((byte)1, value.FixedArguments[0].Value); - - Assert.Equal(2, value.NamedArguments.Length); - - Assert.Equal("uint8", value.NamedArguments[0].Type); - Assert.Equal((byte)2, value.NamedArguments[0].Value); - - ImmutableArray> array = (ImmutableArray>)(value.NamedArguments[1].Value); - Assert.Equal("uint8[]", value.NamedArguments[1].Type); - Assert.Equal((byte)3, array[0].Value); - } - } - } - } - - [GenericAttribute] - [GenericAttribute("Hello")] - [GenericAttribute(12)] - [GenericAttribute("Hello", 12, TProperty = "Bye")] - [GenericAttribute(1, TProperty = 2)] - [GenericAttribute2(true, 13)] - // [GenericAttribute(MyEnum.Property)] TODO: https://github.com/dotnet/runtime/issues/16552 - [GenericAttribute(typeof(HasAttributes))] - [GenericAttribute(TProperty = typeof(HasAttributes))] - public class HasGenericAttributes { } - - [GenericAttribute2(new int[] { 1, 2, 3 }, new byte[] { 4, 5 })] - [GenericAttribute(1, TProperty = 2, TArrayProperty = new byte[] { 3, 4 })] - public class HasGenericArrayAttributes { } - - [AttributeUsage(AttributeTargets.All, AllowMultiple = true)] - internal class GenericAttribute : Attribute - { - public GenericAttribute() { } - public GenericAttribute(T value) - { - Field = value; - } - public GenericAttribute(T value, int count) - { - Field = value; - } - public T TProperty { get; set; } - public T[] TArrayProperty { get; set; } - public T Field; - } - - [AttributeUsage(AttributeTargets.All, AllowMultiple = true)] - internal class GenericAttribute2 : Attribute - { - public GenericAttribute2() { } - public GenericAttribute2(K key) { } - public GenericAttribute2(K key, V value) { } - public K Key { get; set; } - public V Value { get; set; } - public K[] ArrayProperty { get; set; } - } -#endif - // no arguments [Test] @@ -378,17 +120,17 @@ public GenericAttribute2(K key, V value) { } [Test(true)] [Test(false)] [Test(typeof(string))] - /* [Test(SByteEnum.Value)] // The FullName is (System.Reflection.Metadata.Decoding.Tests.CustomAttributeDecoderTests+SByteEnum) - [Test(Int16Enum.Value)] // but some enums '+' is replaced with '/' and causing inconsistency - [Test(Int32Enum.Value)] // Updaated https://github.com/dotnet/runtime/issues/16552 to resolve this scenario later + [Test(SByteEnum.Value)] + [Test(Int16Enum.Value)] + [Test(Int32Enum.Value)] [Test(Int64Enum.Value)] [Test(ByteEnum.Value)] [Test(UInt16Enum.Value)] [Test(UInt32Enum.Value)] - [Test(UInt64Enum.Value)]*/ + [Test(UInt64Enum.Value)] [Test(new string[] { })] [Test(new string[] { "x", "y", "z", null })] - // [Test(new Int32Enum[] { Int32Enum.Value })] TODO: https://github.com/dotnet/runtime/issues/16552 + [Test(new Int32Enum[] { Int32Enum.Value })] // same single fixed arguments as above, typed as object [Test((object)("string"))] @@ -434,7 +176,7 @@ public GenericAttribute2(K key, V value) { } (uint)4, true, false, - typeof(string), // check if the produced value is expected + typeof(string), SByteEnum.Value, Int16Enum.Value, Int32Enum.Value, @@ -475,7 +217,7 @@ public GenericAttribute2(K key, V value) { } [Test(UInt64EnumField = UInt64Enum.Value)] [Test(new string[] { })] [Test(new string[] { "x", "y", "z", null })] - // [Test(new Int32Enum[] { Int32Enum.Value })] TODO: https://github.com/dotnet/runtime/issues/16552 + [Test(new Int32Enum[] { Int32Enum.Value })] // null named arguments [Test(ObjectField = null)] @@ -591,83 +333,6 @@ public TestAttribute(UInt64Enum[] value) { } public UInt64Enum[] UInt64EnumArrayProperty { get; set; } } - private string TypeToString(Type type) - { - if (type == typeof(Type)) - return $"[{MetadataReaderTestHelpers.RuntimeAssemblyName}]System.Type"; - - if (type.IsArray) - { - if (type.GetElementType().IsEnum) - { - Type el = type.GetElementType(); - return type.FullName; - } - return GetPrimitiveType(type.GetElementType()) + "[]"; - } - - if (type.IsEnum) - return type.FullName; - - return GetPrimitiveType(type); - } - - private static string GetPrimitiveType(Type type) - { - switch (Type.GetTypeCode(type)) - { - case TypeCode.Boolean: - return "bool"; - - case TypeCode.Byte: - return "uint8"; - - case TypeCode.Char: - return "char"; - - case TypeCode.Double: - return "float64"; - - case TypeCode.Int16: - return "int16"; - - case TypeCode.Int32: - return "int32"; - - case TypeCode.Int64: - return "int64"; - - case TypeCode.Object: - return "object"; - - case TypeCode.SByte: - return "int8"; - - case TypeCode.Single: - return "float32"; - - case TypeCode.String: - return "string"; - - case TypeCode.UInt16: - return "uint16"; - - case TypeCode.UInt32: - return "uint32"; - - case TypeCode.UInt64: - return "uint64"; - - default: - throw new ArgumentOutOfRangeException(nameof(type)); - } - } - - public enum MyEnum - { - Ctor, - Property - } private class CustomAttributeTypeProvider : DisassemblingTypeProvider, ICustomAttributeTypeProvider { @@ -715,9 +380,6 @@ public PrimitiveTypeCode GetUnderlyingEnumType(string type) if (runtimeType == typeof(UInt64Enum)) return PrimitiveTypeCode.UInt64; - if (runtimeType == typeof(MyEnum)) - return PrimitiveTypeCode.Byte; - throw new ArgumentOutOfRangeException(); } }