From 99ae8f8ca6a431789a47442aa72a6a63d7a4105f Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Fri, 9 Aug 2024 09:23:59 +0200 Subject: [PATCH 1/5] feat: x:Shared --- .../Controls/INotSharedDeferredContent.cs | 8 + .../Controls/ResourceDictionary.cs | 19 +- .../XamlNotSharedDeferredContentNode.cs | 219 ++++++++++++++++++ .../AvaloniaXamlIlCompiler.cs | 2 + ...aloniaXamlIlDeferredResourceTransformer.cs | 8 + .../AvaloniaXamlIlWellKnownTypes.cs | 8 + .../Transformers/XSharedTransformer.cs | 172 ++++++++++++++ .../Visitors/NotSharedVisitor.cs | 55 +++++ .../XamlIl/Runtime/XamlIlRuntimeHelpers.cs | 30 +++ 9 files changed, 519 insertions(+), 2 deletions(-) create mode 100644 src/Avalonia.Base/Controls/INotSharedDeferredContent.cs create mode 100644 src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AstNodes/XamlNotSharedDeferredContentNode.cs create mode 100644 src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/XSharedTransformer.cs create mode 100644 src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Visitors/NotSharedVisitor.cs diff --git a/src/Avalonia.Base/Controls/INotSharedDeferredContent.cs b/src/Avalonia.Base/Controls/INotSharedDeferredContent.cs new file mode 100644 index 00000000000..198ecc616f1 --- /dev/null +++ b/src/Avalonia.Base/Controls/INotSharedDeferredContent.cs @@ -0,0 +1,8 @@ +namespace Avalonia.Controls; + +/// +/// Represents a not shared deferred content. +/// +public interface INotSharedDeferredContent: IDeferredContent +{ +} diff --git a/src/Avalonia.Base/Controls/ResourceDictionary.cs b/src/Avalonia.Base/Controls/ResourceDictionary.cs index 70c3e884e88..5392c332a25 100644 --- a/src/Avalonia.Base/Controls/ResourceDictionary.cs +++ b/src/Avalonia.Base/Controls/ResourceDictionary.cs @@ -147,6 +147,12 @@ public void AddDeferred(object key, Func factory) public void AddDeferred(object key, IDeferredContent deferredContent) => Add(key, deferredContent); + public void AddNotShared(object key, Func factory) + => Add(key, new NotSharedDeferredItem(factory)); + + public void AddNotShared(object key, INotSharedDeferredContent notSharedDeferredContent) + => Add(key, notSharedDeferredContent); + public void Clear() { if (_inner?.Count > 0) @@ -236,12 +242,16 @@ public bool TryGetValue(object key, out object? value) try { _lastDeferredItemKey = key; - _inner[key] = value = deferred.Build(null) switch + value = deferred.Build(null) switch { ITemplateResult t => t.Result, { } v => v, _ => null, }; + if (deferred is not INotSharedDeferredContent) + { + _inner[key] = value; + } } finally { @@ -250,7 +260,6 @@ public bool TryGetValue(object key, out object? value) } return true; } - value = null; return false; } @@ -376,5 +385,11 @@ private sealed class DeferredItem : IDeferredContent public DeferredItem(Func factory) => _factory = factory; public object? Build(IServiceProvider? serviceProvider) => _factory(serviceProvider); } + + private sealed class NotSharedDeferredItem(Func factory) : INotSharedDeferredContent + { + private readonly Func _factory = factory; + public object? Build(IServiceProvider? serviceProvider) => _factory(serviceProvider); + } } } diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AstNodes/XamlNotSharedDeferredContentNode.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AstNodes/XamlNotSharedDeferredContentNode.cs new file mode 100644 index 00000000000..90a5cf7fc17 --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AstNodes/XamlNotSharedDeferredContentNode.cs @@ -0,0 +1,219 @@ +using System.Linq; +using XamlX.Ast; +using XamlX.Emit; +using XamlX.IL; +using XamlX.Transform; +using XamlX.TypeSystem; + +namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.AstNodes; + +class XamlNotSharedDeferredContentNode : XamlAstNode, IXamlAstValueNode, IXamlAstEmitableNode +{ + private readonly IXamlMethod? _deferredContentCustomization; + private readonly IXamlType? _deferredContentCustomizationTypeParameter; + private readonly IXamlType _funcType; + + public IXamlAstValueNode Value { get; set; } + public IXamlAstTypeReference Type { get; } + + public XamlNotSharedDeferredContentNode(IXamlAstValueNode value, + IXamlMethod? deferredContentCustomization, + IXamlType? deferredContentCustomizationTypeParameter, + TransformerConfiguration config) : base(value) + { + _deferredContentCustomization = deferredContentCustomization; + _deferredContentCustomizationTypeParameter = deferredContentCustomizationTypeParameter; + Value = value; + + _funcType = config.TypeSystem + .GetType("System.Func`2") + .MakeGenericType(config.TypeMappings.ServiceProvider, config.WellKnownTypes.Object); + + var returnType = _deferredContentCustomization?.ReturnType ?? _funcType; + Type = new XamlAstClrTypeReference(value, returnType, false); + } + + public override void VisitChildren(IXamlAstVisitor visitor) + { + Value = (IXamlAstValueNode)Value.Visit(visitor); + } + + void CompileBuilder(ILEmitContext context, XamlClosureInfo xamlClosure) + { + var il = context.Emitter; + // Initialize the context + il + .Ldarg_0() + .EmitCall(xamlClosure.CreateRuntimeContextMethod) + .Stloc(context.ContextLocal); + + context.Emit(Value, context.Emitter, context.Configuration.WellKnownTypes.Object); + il.Ret(); + + context.ExecuteAfterEmitCallbacks(); + } + + public XamlILNodeEmitResult Emit(XamlEmitContext context, IXamlILEmitter codeGen) + { + var so = context.Configuration.WellKnownTypes.Object; + var isp = context.Configuration.TypeMappings.ServiceProvider; + + if (!context.TryGetItem(out XamlClosureInfo? closureInfo)) + { + var closureType = context.DeclaringType.DefineSubType( + so, + "XamlClosure_" + context.Configuration.IdentifierGenerator.GenerateIdentifierPart(), + XamlVisibility.Private); + + closureInfo = new XamlClosureInfo(closureType, context); + context.AddAfterEmitCallbacks(() => closureType.CreateType()); + context.SetItem(closureInfo); + } + + var counter = ++closureInfo.BuildMethodCounter; + + var buildMethod = closureInfo.Type.DefineMethod( + so, + new[] { isp }, + $"Build_{counter}", + XamlVisibility.Public, + true, + false); + + var subContext = new ILEmitContext( + buildMethod.Generator, + context.Configuration, + context.EmitMappings, + context.RuntimeContext, + buildMethod.Generator.DefineLocal(context.RuntimeContext.ContextType), + closureInfo.Type, + context.File, + context.Emitters); + + subContext.SetItem(closureInfo); + + CompileBuilder(subContext, closureInfo); + + var customization = _deferredContentCustomization; + + if (_deferredContentCustomizationTypeParameter is not null) + customization = customization?.MakeGenericMethod(new[] { _deferredContentCustomizationTypeParameter }); + + if (customization is not null && IsFunctionPointerLike(customization.Parameters[0])) + { + // &Build + codeGen + .Ldftn(buildMethod); + } + else + { + // new Func(null, &Build); + codeGen + .Ldnull() + .Ldftn(buildMethod) + .Newobj(_funcType.Constructors.First(ct => + ct.Parameters.Count == 2 && + ct.Parameters[0].Equals(context.Configuration.WellKnownTypes.Object))); + } + + // Allow to save values from the parent context, pass own service provider, etc, etc + if (customization is not null) + { + codeGen + .Ldloc(context.ContextLocal) + .EmitCall(customization); + } + + return XamlILNodeEmitResult.Type(0, Type.GetClrType()); + } + + private static bool IsFunctionPointerLike(IXamlType xamlType) + => xamlType.IsFunctionPointer // Cecil, SRE with .NET 8 + || xamlType.FullName == "System.IntPtr"; // SRE with .NET < 8 or .NET Standard + + private sealed class XamlClosureInfo + { + private readonly XamlEmitContext _parentContext; + private IXamlMethod? _createRuntimeContextMethod; + + public IXamlTypeBuilder Type { get; } + + public IXamlMethod CreateRuntimeContextMethod + => _createRuntimeContextMethod ??= BuildCreateRuntimeContextMethod(); + + public int BuildMethodCounter { get; set; } + + public XamlClosureInfo( + IXamlTypeBuilder type, + XamlEmitContext parentContext) + { + Type = type; + _parentContext = parentContext; + } + + private IXamlMethod BuildCreateRuntimeContextMethod() + { + var method = Type.DefineMethod( + _parentContext.RuntimeContext.ContextType, + new[] { _parentContext.Configuration.TypeMappings.ServiceProvider }, + "CreateContext", + XamlVisibility.Public, + true, + false); + + var context = new ILEmitContext( + method.Generator, + _parentContext.Configuration, + _parentContext.EmitMappings, + _parentContext.RuntimeContext, + method.Generator.DefineLocal(_parentContext.RuntimeContext.ContextType), + Type, + _parentContext.File, + _parentContext.Emitters); + + var il = context.Emitter; + + // context = new Context(arg0, ...) + il.Ldarg_0(); + context.RuntimeContext.Factory(il); + + if (context.Configuration.TypeMappings.RootObjectProvider is { } rootObjectProviderType) + { + // Attempt to get the root object from parent service provider + var noRoot = il.DefineLabel(); + using var loc = context.GetLocalOfType(context.Configuration.WellKnownTypes.Object); + il + .Stloc(context.ContextLocal) + // if(arg == null) goto noRoot; + .Ldarg_0() + .Brfalse(noRoot) + // var loc = arg.GetService(typeof(IRootObjectProvider)) + .Ldarg_0() + .Ldtype(rootObjectProviderType) + .EmitCall(context.Configuration.TypeMappings.ServiceProvider + .GetMethod(m => m.Name == "GetService")) + .Stloc(loc.Local) + // if(loc == null) goto noRoot; + .Ldloc(loc.Local) + .Brfalse(noRoot) + // loc = ((IRootObjectProvider)loc).RootObject + .Ldloc(loc.Local) + .Castclass(rootObjectProviderType) + .EmitCall(rootObjectProviderType + .GetMethod(m => m.Name == "get_RootObject")) + .Stloc(loc.Local) + // contextLocal.RootObject = loc; + .Ldloc(context.ContextLocal) + .Ldloc(loc.Local) + .Castclass(context.RuntimeContext.ContextType.GenericArguments[0]) + .Stfld(context.RuntimeContext.RootObjectField!) + .MarkLabel(noRoot) + .Ldloc(context.ContextLocal); + } + + il.Ret(); + + return method; + } + } +} diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs index 9e46e5f76fd..10a88a2227a 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs @@ -88,6 +88,8 @@ void InsertBeforeMany(Type[] types, params IXamlAstTransformer[] t) InsertBefore(new AvaloniaXamlIlTransformRoutedEvent()); + InsertAfter(new XSharedTransformer()); + Transformers.Add(new AvaloniaXamlIlControlTemplatePriorityTransformer()); Transformers.Add(new AvaloniaXamlIlMetadataRemover()); Transformers.Add(new AvaloniaXamlIlEnsureResourceDictionaryCapacityTransformer()); diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDeferredResourceTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDeferredResourceTransformer.cs index 84ca68f4973..ac3bb46f8c8 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDeferredResourceTransformer.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDeferredResourceTransformer.cs @@ -58,6 +58,14 @@ private static bool ShouldBeDeferred(IXamlAstValueNode node) return false; } + var sharedVisitor = new NotSharedVisitor(); + _ = sharedVisitor.Visit(node); + // Ingnore if Element is marked with x:Shared="false" + if (sharedVisitor.Count > 0) + { + return false; + } + // Do not defer resources, if it has any x:Name registration, as it cannot be delayed. // This visitor will count x:Name registrations, ignoring nested NestedScopeMetadataNode scopes. // We set target scope level to 0, assuming that this resource node is a scope of itself. diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs index 8fc4cfff76c..baa38586ea8 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs @@ -118,6 +118,7 @@ sealed class AvaloniaXamlIlWellKnownTypes public IXamlType IResourceDictionary { get; } public IXamlType ResourceDictionary { get; } public IXamlMethod ResourceDictionaryDeferredAdd { get; } + public IXamlMethod ResourceDictionaryNotSharedAdd { get; } public IXamlMethod ResourceDictionaryEnsureCapacity { get; } public IXamlMethod ResourceDictionaryGetCount { get; } public IXamlType IThemeVariantProvider { get; } @@ -129,6 +130,7 @@ sealed class AvaloniaXamlIlWellKnownTypes public IXamlType IReadOnlyListOfT { get; } public IXamlType ControlTemplate { get; } public IXamlType EventHandlerT { get; } + public IXamlMethod? NotSharedDeferredContentExecutorCustomization { get; set; } sealed internal class InteractivityWellKnownTypes { @@ -312,6 +314,9 @@ public AvaloniaXamlIlWellKnownTypes(TransformerConfiguration cfg) ResourceDictionary = cfg.TypeSystem.GetType("Avalonia.Controls.ResourceDictionary"); ResourceDictionaryDeferredAdd = ResourceDictionary.GetMethod("AddDeferred", XamlIlTypes.Void, true, XamlIlTypes.Object, cfg.TypeSystem.GetType("Avalonia.Controls.IDeferredContent")); + ResourceDictionaryNotSharedAdd = ResourceDictionary.GetMethod("AddNotShared", XamlIlTypes.Void, true, XamlIlTypes.Object, + cfg.TypeSystem.GetType("Avalonia.Controls.INotSharedDeferredContent")); + ResourceDictionaryEnsureCapacity = ResourceDictionary.GetMethod("EnsureCapacity", XamlIlTypes.Void, true, XamlIlTypes.Int32); ResourceDictionaryGetCount = ResourceDictionary.GetMethod("get_Count", XamlIlTypes.Int32, true); IThemeVariantProvider = cfg.TypeSystem.GetType("Avalonia.Controls.IThemeVariantProvider"); @@ -323,6 +328,9 @@ public AvaloniaXamlIlWellKnownTypes(TransformerConfiguration cfg) IReadOnlyListOfT = cfg.TypeSystem.GetType("System.Collections.Generic.IReadOnlyList`1"); EventHandlerT = cfg.TypeSystem.GetType("System.EventHandler`1"); Interactivity = new InteractivityWellKnownTypes(cfg); + var runtimeHelpers = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.XamlIl.Runtime.XamlIlRuntimeHelpers"); + NotSharedDeferredContentExecutorCustomization = + runtimeHelpers.FindMethod(m => m.Name == "NotSharedDeferredTransformationFactoryV0"); } } diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/XSharedTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/XSharedTransformer.cs new file mode 100644 index 00000000000..b4c7f0f6f14 --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/XSharedTransformer.cs @@ -0,0 +1,172 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.AstNodes; +using XamlX; +using XamlX.Ast; +using XamlX.Emit; +using XamlX.IL; +using XamlX.Transform; +using XamlX.TypeSystem; + +namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers; + +class XSharedTransformer : IXamlAstTransformer +{ + public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node) + { + var types = context.GetAvaloniaTypes(); + switch (node) + { + case XamlPropertyAssignmentNode { Values.Count: 2 } pa when + pa.Property.Name == "Content" + && pa.Property.DeclaringType == types.ResourceDictionary + && pa.Values[1] is XamlAstConstructableObjectNode co + && TryGetSharedDirective(co, out var directive) + : + { + co.Children.Remove(directive.Node); + if (!directive.Value) + { + pa.Values[1] = new XamlNotSharedDeferredContentNode(pa.Values[1] + , types.NotSharedDeferredContentExecutorCustomization + , types.XamlIlTypes.Object + , context.Configuration); + pa.PossibleSetters = new List + { + new XamlDirectCallPropertySetter(types.ResourceDictionaryNotSharedAdd), + }; + } + } + break; + case XamlPropertyAssignmentNode { Values.Count: 2 } pa when + pa.Property.Name == "Resources" + && pa.Property.Getter?.ReturnType.Equals(types.IResourceDictionary) == true + && pa.Values[1] is XamlAstConstructableObjectNode co + && TryGetSharedDirective(co, out var directive) + : + { + co.Children.Remove(directive.Node); + if (!directive.Value) + { + pa.Values[1] = new XamlNotSharedDeferredContentNode(pa.Values[1] + , types.NotSharedDeferredContentExecutorCustomization + , types.XamlIlTypes.Object + , context.Configuration); + pa.PossibleSetters = new List + { + new AdderSetter(pa.Property.Getter, types.ResourceDictionaryNotSharedAdd), + }; + + } + } + break; + default: + break; + } + + return node; + + bool TryGetSharedDirective(XamlAstConstructableObjectNode co, out (IXamlAstNode Node, bool Value) directive) + { + directive = default; + if (co.Children.Find(d => d is XamlAstXmlDirective { Namespace: XamlNamespaces.Xaml2006, Name: "Shared" }) is XamlAstXmlDirective sharedDirective) + { + if (sharedDirective.Values.Count == 1 && sharedDirective.Values[0] is XamlAstTextNode text) + { + if (bool.TryParse(text.Text, out var value)) + { + directive = (sharedDirective, value); + return true; + } + else + { + context?.ReportTransformError("Invalid argument type for x:Shared directive.", node); + } + } + else + { + context?.ReportTransformError("Invalid number of arguments for x:Shared directive.", node); + } + } + return false; + } + } + + class AdderSetter : IXamlILOptimizedEmitablePropertySetter, IEquatable + { + private readonly IXamlMethod _getter; + private readonly IXamlMethod _adder; + + public AdderSetter(IXamlMethod getter, IXamlMethod adder) + { + _getter = getter; + _adder = adder; + TargetType = getter.DeclaringType; + Parameters = adder.ParametersWithThis().Skip(1).ToList(); + + bool allowNull = Parameters.Last().AcceptsNull(); + BinderParameters = new PropertySetterBinderParameters + { + AllowMultiple = true, + AllowXNull = allowNull, + AllowRuntimeNull = allowNull, + AllowAttributeSyntax = false, + }; + } + + public IXamlType TargetType { get; } + + public PropertySetterBinderParameters BinderParameters { get; } + + public IReadOnlyList Parameters { get; } + public IReadOnlyList CustomAttributes => _adder.CustomAttributes; + + public void Emit(IXamlILEmitter emitter) + { + var locals = new Stack(); + // Save all "setter" parameters + for (var c = Parameters.Count - 1; c >= 0; c--) + { + var loc = emitter.LocalsPool.GetLocal(Parameters[c]); + locals.Push(loc); + emitter.Stloc(loc.Local); + } + + emitter.EmitCall(_getter); + while (locals.Count > 0) + using (var loc = locals.Pop()) + emitter.Ldloc(loc.Local); + emitter.EmitCall(_adder, true); + } + + public void EmitWithArguments( + XamlEmitContextWithLocals context, + IXamlILEmitter emitter, + IReadOnlyList arguments) + { + emitter.EmitCall(_getter); + + for (var i = 0; i < arguments.Count; ++i) + context.Emit(arguments[i], emitter, Parameters[i]); + + emitter.EmitCall(_adder, true); + } + + public bool Equals(AdderSetter? other) + { + if (ReferenceEquals(null, other)) + return false; + if (ReferenceEquals(this, other)) + return true; + + return _getter.Equals(other._getter) && _adder.Equals(other._adder); + } + + public override bool Equals(object? obj) + => Equals(obj as AdderSetter); + + public override int GetHashCode() + => (_getter.GetHashCode() * 397) ^ _adder.GetHashCode(); + } +} diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Visitors/NotSharedVisitor.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Visitors/NotSharedVisitor.cs new file mode 100644 index 00000000000..8a85ab84120 --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Visitors/NotSharedVisitor.cs @@ -0,0 +1,55 @@ +using XamlX; +using XamlX.Ast; + +namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Visitors; + +class NotSharedVisitor : IXamlAstVisitor +{ + public int Count { get; private set; } + private int level = 0; + + public void Pop() + { + level--; + } + + public void Push(IXamlAstNode node) + { + level++; + } + + public IXamlAstNode Visit(IXamlAstNode node) + { + if (TryGetXShared(node, out var isShared)) + { + if (!isShared) + { + Count++; + return node; + } + } + else if (level < 3) + { + node.VisitChildren(this); + } + return node; + } + + private static bool TryGetXShared(IXamlAstNode node, out bool isShared) + { + isShared = false; + if (node is XamlAstXmlDirective { Namespace: XamlNamespaces.Xaml2006, Name: "Shared" } sharedDirective) + { + if (sharedDirective.Values.Count == 1 && sharedDirective.Values[0] is XamlAstTextNode text) + { + if (bool.TryParse(text.Text, out var value)) + { + isShared = value; + return true; + } + } + } + return false; + } +} + diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs index 5b86116106b..a6b0508bf3f 100644 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs +++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs @@ -51,6 +51,20 @@ public static unsafe IDeferredContent DeferredTransformationFactoryV3( return new PointerDeferredContent(resourceNodes, rootObject, parentScope, typedBuilder); } + // The builder is typed as IntPtr instead of delegate* because Reflection.Emit has + // trouble with generic methods containing function pointers. See https://github.com/dotnet/runtime/issues/100020 + public static unsafe INotSharedDeferredContent NotSharedDeferredTransformationFactoryV0( + /*delegate**/ IntPtr builder, + IServiceProvider provider) + { + var resourceNodes = AsResourceNodesStack(provider.GetRequiredService()); + var rootObject = provider.GetRequiredService().RootObject; + var parentScope = provider.GetService(); + var typedBuilder = (delegate*)builder; + + return new PointerNotShardDeferredContent(resourceNodes, rootObject, parentScope, typedBuilder); + } + private static IResourceNode[] AsResourceNodesStack(IAvaloniaXamlIlParentStackProvider provider) { var buffer = s_resourceNodeBuffer ??= new List(8); @@ -195,6 +209,22 @@ protected override object InvokeBuilder(IServiceProvider serviceProvider) => _builder(serviceProvider); } + private sealed unsafe class PointerNotShardDeferredContent : DeferredContent,INotSharedDeferredContent + { + private readonly delegate* _builder; + + public PointerNotShardDeferredContent( + IResourceNode[] parentResourceNodes, + object rootObject, + INameScope? parentNameScope, + delegate* builder) + : base(parentResourceNodes, rootObject, parentNameScope) + => _builder = builder; + + protected override object InvokeBuilder(IServiceProvider serviceProvider) + => _builder(serviceProvider); + } + private sealed class DelegateDeferredContent : DeferredContent { private readonly Func _builder; From 5cd0f611faa9d43bba9b69bcb4fa107e3c1c5bc2 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Fri, 9 Aug 2024 15:40:54 +0200 Subject: [PATCH 2/5] test: Add x:Shared test --- .../Xaml/XSharedDirectiveTests.cs | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XSharedDirectiveTests.cs diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XSharedDirectiveTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XSharedDirectiveTests.cs new file mode 100644 index 00000000000..ce83fdd094a --- /dev/null +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XSharedDirectiveTests.cs @@ -0,0 +1,54 @@ +using Avalonia.Controls; +using Avalonia.UnitTests; +using Xunit; + +namespace Avalonia.Markup.Xaml.UnitTests.Xaml; + +public class XSharedDirectiveTests : XamlTestBase +{ + [Fact] + public void Should_Create_New_Instance_Where_x_Share_Is_False() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + const string xaml = $$""" + + + + + + + + + + + + + + + """; + var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); + window.ApplyTemplate(); + + var implicitSharedInstance1 = window.FindResource("ImplicitSharedResource"); + Assert.NotNull(implicitSharedInstance1); + var implicitSharedInstance2 = window.FindResource("ImplicitSharedResource"); + Assert.NotNull(implicitSharedInstance2); + + Assert.True(ReferenceEquals(implicitSharedInstance1, implicitSharedInstance2)); + + var notSharedResource1 = window.FindResource("NotSharedResource"); + Assert.NotNull(notSharedResource1); + + var notSharedResource2 = window.FindResource("NotSharedResource"); + Assert.NotNull(notSharedResource2); + + Assert.True(!ReferenceEquals(notSharedResource1, notSharedResource2)); + + Assert.Equal(notSharedResource1.ToString(), notSharedResource2.ToString()); + } + } +} From a56325d1c01fdaaacd882faf12eabf890cce5bff Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Sat, 10 Aug 2024 10:33:28 +0200 Subject: [PATCH 3/5] fix: Address review --- src/Avalonia.Base/Controls/ResourceDictionary.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Avalonia.Base/Controls/ResourceDictionary.cs b/src/Avalonia.Base/Controls/ResourceDictionary.cs index 5392c332a25..1598946c654 100644 --- a/src/Avalonia.Base/Controls/ResourceDictionary.cs +++ b/src/Avalonia.Base/Controls/ResourceDictionary.cs @@ -147,9 +147,6 @@ public void AddDeferred(object key, Func factory) public void AddDeferred(object key, IDeferredContent deferredContent) => Add(key, deferredContent); - public void AddNotShared(object key, Func factory) - => Add(key, new NotSharedDeferredItem(factory)); - public void AddNotShared(object key, INotSharedDeferredContent notSharedDeferredContent) => Add(key, notSharedDeferredContent); From 242a467fd707356f033058e1e4ecc9481264306e Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Mon, 12 Aug 2024 11:54:43 +0200 Subject: [PATCH 4/5] fix: Address review --- .../Controls/INotSharedDeferredContent.cs | 2 +- .../Controls/ResourceDictionary.cs | 10 +- .../XamlNotSharedDeferredContentNode.cs | 219 ------------------ .../AvaloniaXamlIlCompiler.cs | 2 - ...aloniaXamlIlDeferredResourceTransformer.cs | 53 ++++- .../AvaloniaXamlIlWellKnownTypes.cs | 8 +- .../Transformers/XSharedTransformer.cs | 172 -------------- .../Visitors/NotSharedVisitor.cs | 55 ----- .../XamlIl/Runtime/XamlIlRuntimeHelpers.cs | 30 --- .../Xaml/XSharedDirectiveTests.cs | 4 +- 10 files changed, 52 insertions(+), 503 deletions(-) delete mode 100644 src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AstNodes/XamlNotSharedDeferredContentNode.cs delete mode 100644 src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/XSharedTransformer.cs delete mode 100644 src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Visitors/NotSharedVisitor.cs diff --git a/src/Avalonia.Base/Controls/INotSharedDeferredContent.cs b/src/Avalonia.Base/Controls/INotSharedDeferredContent.cs index 198ecc616f1..7adf7f70c09 100644 --- a/src/Avalonia.Base/Controls/INotSharedDeferredContent.cs +++ b/src/Avalonia.Base/Controls/INotSharedDeferredContent.cs @@ -3,6 +3,6 @@ /// /// Represents a not shared deferred content. /// -public interface INotSharedDeferredContent: IDeferredContent +internal interface INotSharedDeferredContent: IDeferredContent { } diff --git a/src/Avalonia.Base/Controls/ResourceDictionary.cs b/src/Avalonia.Base/Controls/ResourceDictionary.cs index 1598946c654..3dce4c6d985 100644 --- a/src/Avalonia.Base/Controls/ResourceDictionary.cs +++ b/src/Avalonia.Base/Controls/ResourceDictionary.cs @@ -147,8 +147,8 @@ public void AddDeferred(object key, Func factory) public void AddDeferred(object key, IDeferredContent deferredContent) => Add(key, deferredContent); - public void AddNotShared(object key, INotSharedDeferredContent notSharedDeferredContent) - => Add(key, notSharedDeferredContent); + public void AddNotSharedDeferred(object key, IDeferredContent deferredContent) + => Add(key, new NotSharedDeferredItem(deferredContent)); public void Clear() { @@ -383,10 +383,10 @@ private sealed class DeferredItem : IDeferredContent public object? Build(IServiceProvider? serviceProvider) => _factory(serviceProvider); } - private sealed class NotSharedDeferredItem(Func factory) : INotSharedDeferredContent + private sealed class NotSharedDeferredItem(IDeferredContent deferredContent) : INotSharedDeferredContent { - private readonly Func _factory = factory; - public object? Build(IServiceProvider? serviceProvider) => _factory(serviceProvider); + private readonly IDeferredContent _deferredContent = deferredContent ; + public object? Build(IServiceProvider? serviceProvider) => _deferredContent.Build(serviceProvider); } } } diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AstNodes/XamlNotSharedDeferredContentNode.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AstNodes/XamlNotSharedDeferredContentNode.cs deleted file mode 100644 index 90a5cf7fc17..00000000000 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AstNodes/XamlNotSharedDeferredContentNode.cs +++ /dev/null @@ -1,219 +0,0 @@ -using System.Linq; -using XamlX.Ast; -using XamlX.Emit; -using XamlX.IL; -using XamlX.Transform; -using XamlX.TypeSystem; - -namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.AstNodes; - -class XamlNotSharedDeferredContentNode : XamlAstNode, IXamlAstValueNode, IXamlAstEmitableNode -{ - private readonly IXamlMethod? _deferredContentCustomization; - private readonly IXamlType? _deferredContentCustomizationTypeParameter; - private readonly IXamlType _funcType; - - public IXamlAstValueNode Value { get; set; } - public IXamlAstTypeReference Type { get; } - - public XamlNotSharedDeferredContentNode(IXamlAstValueNode value, - IXamlMethod? deferredContentCustomization, - IXamlType? deferredContentCustomizationTypeParameter, - TransformerConfiguration config) : base(value) - { - _deferredContentCustomization = deferredContentCustomization; - _deferredContentCustomizationTypeParameter = deferredContentCustomizationTypeParameter; - Value = value; - - _funcType = config.TypeSystem - .GetType("System.Func`2") - .MakeGenericType(config.TypeMappings.ServiceProvider, config.WellKnownTypes.Object); - - var returnType = _deferredContentCustomization?.ReturnType ?? _funcType; - Type = new XamlAstClrTypeReference(value, returnType, false); - } - - public override void VisitChildren(IXamlAstVisitor visitor) - { - Value = (IXamlAstValueNode)Value.Visit(visitor); - } - - void CompileBuilder(ILEmitContext context, XamlClosureInfo xamlClosure) - { - var il = context.Emitter; - // Initialize the context - il - .Ldarg_0() - .EmitCall(xamlClosure.CreateRuntimeContextMethod) - .Stloc(context.ContextLocal); - - context.Emit(Value, context.Emitter, context.Configuration.WellKnownTypes.Object); - il.Ret(); - - context.ExecuteAfterEmitCallbacks(); - } - - public XamlILNodeEmitResult Emit(XamlEmitContext context, IXamlILEmitter codeGen) - { - var so = context.Configuration.WellKnownTypes.Object; - var isp = context.Configuration.TypeMappings.ServiceProvider; - - if (!context.TryGetItem(out XamlClosureInfo? closureInfo)) - { - var closureType = context.DeclaringType.DefineSubType( - so, - "XamlClosure_" + context.Configuration.IdentifierGenerator.GenerateIdentifierPart(), - XamlVisibility.Private); - - closureInfo = new XamlClosureInfo(closureType, context); - context.AddAfterEmitCallbacks(() => closureType.CreateType()); - context.SetItem(closureInfo); - } - - var counter = ++closureInfo.BuildMethodCounter; - - var buildMethod = closureInfo.Type.DefineMethod( - so, - new[] { isp }, - $"Build_{counter}", - XamlVisibility.Public, - true, - false); - - var subContext = new ILEmitContext( - buildMethod.Generator, - context.Configuration, - context.EmitMappings, - context.RuntimeContext, - buildMethod.Generator.DefineLocal(context.RuntimeContext.ContextType), - closureInfo.Type, - context.File, - context.Emitters); - - subContext.SetItem(closureInfo); - - CompileBuilder(subContext, closureInfo); - - var customization = _deferredContentCustomization; - - if (_deferredContentCustomizationTypeParameter is not null) - customization = customization?.MakeGenericMethod(new[] { _deferredContentCustomizationTypeParameter }); - - if (customization is not null && IsFunctionPointerLike(customization.Parameters[0])) - { - // &Build - codeGen - .Ldftn(buildMethod); - } - else - { - // new Func(null, &Build); - codeGen - .Ldnull() - .Ldftn(buildMethod) - .Newobj(_funcType.Constructors.First(ct => - ct.Parameters.Count == 2 && - ct.Parameters[0].Equals(context.Configuration.WellKnownTypes.Object))); - } - - // Allow to save values from the parent context, pass own service provider, etc, etc - if (customization is not null) - { - codeGen - .Ldloc(context.ContextLocal) - .EmitCall(customization); - } - - return XamlILNodeEmitResult.Type(0, Type.GetClrType()); - } - - private static bool IsFunctionPointerLike(IXamlType xamlType) - => xamlType.IsFunctionPointer // Cecil, SRE with .NET 8 - || xamlType.FullName == "System.IntPtr"; // SRE with .NET < 8 or .NET Standard - - private sealed class XamlClosureInfo - { - private readonly XamlEmitContext _parentContext; - private IXamlMethod? _createRuntimeContextMethod; - - public IXamlTypeBuilder Type { get; } - - public IXamlMethod CreateRuntimeContextMethod - => _createRuntimeContextMethod ??= BuildCreateRuntimeContextMethod(); - - public int BuildMethodCounter { get; set; } - - public XamlClosureInfo( - IXamlTypeBuilder type, - XamlEmitContext parentContext) - { - Type = type; - _parentContext = parentContext; - } - - private IXamlMethod BuildCreateRuntimeContextMethod() - { - var method = Type.DefineMethod( - _parentContext.RuntimeContext.ContextType, - new[] { _parentContext.Configuration.TypeMappings.ServiceProvider }, - "CreateContext", - XamlVisibility.Public, - true, - false); - - var context = new ILEmitContext( - method.Generator, - _parentContext.Configuration, - _parentContext.EmitMappings, - _parentContext.RuntimeContext, - method.Generator.DefineLocal(_parentContext.RuntimeContext.ContextType), - Type, - _parentContext.File, - _parentContext.Emitters); - - var il = context.Emitter; - - // context = new Context(arg0, ...) - il.Ldarg_0(); - context.RuntimeContext.Factory(il); - - if (context.Configuration.TypeMappings.RootObjectProvider is { } rootObjectProviderType) - { - // Attempt to get the root object from parent service provider - var noRoot = il.DefineLabel(); - using var loc = context.GetLocalOfType(context.Configuration.WellKnownTypes.Object); - il - .Stloc(context.ContextLocal) - // if(arg == null) goto noRoot; - .Ldarg_0() - .Brfalse(noRoot) - // var loc = arg.GetService(typeof(IRootObjectProvider)) - .Ldarg_0() - .Ldtype(rootObjectProviderType) - .EmitCall(context.Configuration.TypeMappings.ServiceProvider - .GetMethod(m => m.Name == "GetService")) - .Stloc(loc.Local) - // if(loc == null) goto noRoot; - .Ldloc(loc.Local) - .Brfalse(noRoot) - // loc = ((IRootObjectProvider)loc).RootObject - .Ldloc(loc.Local) - .Castclass(rootObjectProviderType) - .EmitCall(rootObjectProviderType - .GetMethod(m => m.Name == "get_RootObject")) - .Stloc(loc.Local) - // contextLocal.RootObject = loc; - .Ldloc(context.ContextLocal) - .Ldloc(loc.Local) - .Castclass(context.RuntimeContext.ContextType.GenericArguments[0]) - .Stfld(context.RuntimeContext.RootObjectField!) - .MarkLabel(noRoot) - .Ldloc(context.ContextLocal); - } - - il.Ret(); - - return method; - } - } -} diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs index 10a88a2227a..9e46e5f76fd 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs @@ -88,8 +88,6 @@ void InsertBeforeMany(Type[] types, params IXamlAstTransformer[] t) InsertBefore(new AvaloniaXamlIlTransformRoutedEvent()); - InsertAfter(new XSharedTransformer()); - Transformers.Add(new AvaloniaXamlIlControlTemplatePriorityTransformer()); Transformers.Add(new AvaloniaXamlIlMetadataRemover()); Transformers.Add(new AvaloniaXamlIlEnsureResourceDictionaryCapacityTransformer()); diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDeferredResourceTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDeferredResourceTransformer.cs index ac3bb46f8c8..5c59f0c1f0f 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDeferredResourceTransformer.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDeferredResourceTransformer.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Visitors; +using XamlX; using XamlX.Ast; using XamlX.Emit; using XamlX.IL; @@ -22,23 +23,61 @@ public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode nod if (pa.Property.DeclaringType == types.ResourceDictionary && pa.Property.Name == "Content" && ShouldBeDeferred(pa.Values[1])) { + IXamlMethod addMethod = TryGetSharedValue(pa.Values[1], out var isShared) && !isShared + ? types.ResourceDictionaryNotSharedDeferedAdd + : types.ResourceDictionaryDeferredAdd; + pa.Values[1] = new XamlDeferredContentNode(pa.Values[1], types.XamlIlTypes.Object, context.Configuration); pa.PossibleSetters = new List { - new XamlDirectCallPropertySetter(types.ResourceDictionaryDeferredAdd), + new XamlDirectCallPropertySetter(addMethod), }; } else if (pa.Property.Name == "Resources" && pa.Property.Getter?.ReturnType.Equals(types.IResourceDictionary) == true && ShouldBeDeferred(pa.Values[1])) { + IXamlMethod addMethod = TryGetSharedValue(pa.Values[1], out var isShared) && !isShared + ? types.ResourceDictionaryNotSharedDeferedAdd + : types.ResourceDictionaryDeferredAdd; + pa.Values[1] = new XamlDeferredContentNode(pa.Values[1], types.XamlIlTypes.Object, context.Configuration); pa.PossibleSetters = new List { - new AdderSetter(pa.Property.Getter, types.ResourceDictionaryDeferredAdd), + new AdderSetter(pa.Property.Getter, addMethod), }; } return node; + + bool TryGetSharedValue(IXamlAstValueNode valueNode, out bool value) + { + value = default; + if (valueNode is XamlAstConstructableObjectNode co) + { + // Try find x:Share directive + if (co.Children.Find(d => d is XamlAstXmlDirective { Namespace: XamlNamespaces.Xaml2006, Name: "Shared" }) is XamlAstXmlDirective sharedDirective) + { + if (sharedDirective.Values.Count == 1 && sharedDirective.Values[0] is XamlAstTextNode text) + { + if (bool.TryParse(text.Text, out var parseValue)) + { + // If the parser succeeds, remove the x:Share directive + co.Children.Remove(sharedDirective); + return true; + } + else + { + context.ReportTransformError("Invalid argument type for x:Shared directive.", node); + } + } + else + { + context.ReportTransformError("Invalid number of arguments for x:Shared directive.", node); + } + } + } + return false; + } } private static bool ShouldBeDeferred(IXamlAstValueNode node) @@ -58,14 +97,6 @@ private static bool ShouldBeDeferred(IXamlAstValueNode node) return false; } - var sharedVisitor = new NotSharedVisitor(); - _ = sharedVisitor.Visit(node); - // Ingnore if Element is marked with x:Shared="false" - if (sharedVisitor.Count > 0) - { - return false; - } - // Do not defer resources, if it has any x:Name registration, as it cannot be delayed. // This visitor will count x:Name registrations, ignoring nested NestedScopeMetadataNode scopes. // We set target scope level to 0, assuming that this resource node is a scope of itself. @@ -79,7 +110,7 @@ private static bool ShouldBeDeferred(IXamlAstValueNode node) return true; } - + class AdderSetter : IXamlILOptimizedEmitablePropertySetter, IEquatable { private readonly IXamlMethod _getter; diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs index baa38586ea8..bfc73035095 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs @@ -118,7 +118,7 @@ sealed class AvaloniaXamlIlWellKnownTypes public IXamlType IResourceDictionary { get; } public IXamlType ResourceDictionary { get; } public IXamlMethod ResourceDictionaryDeferredAdd { get; } - public IXamlMethod ResourceDictionaryNotSharedAdd { get; } + public IXamlMethod ResourceDictionaryNotSharedDeferedAdd { get; } public IXamlMethod ResourceDictionaryEnsureCapacity { get; } public IXamlMethod ResourceDictionaryGetCount { get; } public IXamlType IThemeVariantProvider { get; } @@ -130,7 +130,6 @@ sealed class AvaloniaXamlIlWellKnownTypes public IXamlType IReadOnlyListOfT { get; } public IXamlType ControlTemplate { get; } public IXamlType EventHandlerT { get; } - public IXamlMethod? NotSharedDeferredContentExecutorCustomization { get; set; } sealed internal class InteractivityWellKnownTypes { @@ -314,7 +313,7 @@ public AvaloniaXamlIlWellKnownTypes(TransformerConfiguration cfg) ResourceDictionary = cfg.TypeSystem.GetType("Avalonia.Controls.ResourceDictionary"); ResourceDictionaryDeferredAdd = ResourceDictionary.GetMethod("AddDeferred", XamlIlTypes.Void, true, XamlIlTypes.Object, cfg.TypeSystem.GetType("Avalonia.Controls.IDeferredContent")); - ResourceDictionaryNotSharedAdd = ResourceDictionary.GetMethod("AddNotShared", XamlIlTypes.Void, true, XamlIlTypes.Object, + ResourceDictionaryNotSharedDeferedAdd = ResourceDictionary.GetMethod("AddNotSharedDeferred", XamlIlTypes.Void, true, XamlIlTypes.Object, cfg.TypeSystem.GetType("Avalonia.Controls.INotSharedDeferredContent")); ResourceDictionaryEnsureCapacity = ResourceDictionary.GetMethod("EnsureCapacity", XamlIlTypes.Void, true, XamlIlTypes.Int32); @@ -328,9 +327,6 @@ public AvaloniaXamlIlWellKnownTypes(TransformerConfiguration cfg) IReadOnlyListOfT = cfg.TypeSystem.GetType("System.Collections.Generic.IReadOnlyList`1"); EventHandlerT = cfg.TypeSystem.GetType("System.EventHandler`1"); Interactivity = new InteractivityWellKnownTypes(cfg); - var runtimeHelpers = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.XamlIl.Runtime.XamlIlRuntimeHelpers"); - NotSharedDeferredContentExecutorCustomization = - runtimeHelpers.FindMethod(m => m.Name == "NotSharedDeferredTransformationFactoryV0"); } } diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/XSharedTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/XSharedTransformer.cs deleted file mode 100644 index b4c7f0f6f14..00000000000 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/XSharedTransformer.cs +++ /dev/null @@ -1,172 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.AstNodes; -using XamlX; -using XamlX.Ast; -using XamlX.Emit; -using XamlX.IL; -using XamlX.Transform; -using XamlX.TypeSystem; - -namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers; - -class XSharedTransformer : IXamlAstTransformer -{ - public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node) - { - var types = context.GetAvaloniaTypes(); - switch (node) - { - case XamlPropertyAssignmentNode { Values.Count: 2 } pa when - pa.Property.Name == "Content" - && pa.Property.DeclaringType == types.ResourceDictionary - && pa.Values[1] is XamlAstConstructableObjectNode co - && TryGetSharedDirective(co, out var directive) - : - { - co.Children.Remove(directive.Node); - if (!directive.Value) - { - pa.Values[1] = new XamlNotSharedDeferredContentNode(pa.Values[1] - , types.NotSharedDeferredContentExecutorCustomization - , types.XamlIlTypes.Object - , context.Configuration); - pa.PossibleSetters = new List - { - new XamlDirectCallPropertySetter(types.ResourceDictionaryNotSharedAdd), - }; - } - } - break; - case XamlPropertyAssignmentNode { Values.Count: 2 } pa when - pa.Property.Name == "Resources" - && pa.Property.Getter?.ReturnType.Equals(types.IResourceDictionary) == true - && pa.Values[1] is XamlAstConstructableObjectNode co - && TryGetSharedDirective(co, out var directive) - : - { - co.Children.Remove(directive.Node); - if (!directive.Value) - { - pa.Values[1] = new XamlNotSharedDeferredContentNode(pa.Values[1] - , types.NotSharedDeferredContentExecutorCustomization - , types.XamlIlTypes.Object - , context.Configuration); - pa.PossibleSetters = new List - { - new AdderSetter(pa.Property.Getter, types.ResourceDictionaryNotSharedAdd), - }; - - } - } - break; - default: - break; - } - - return node; - - bool TryGetSharedDirective(XamlAstConstructableObjectNode co, out (IXamlAstNode Node, bool Value) directive) - { - directive = default; - if (co.Children.Find(d => d is XamlAstXmlDirective { Namespace: XamlNamespaces.Xaml2006, Name: "Shared" }) is XamlAstXmlDirective sharedDirective) - { - if (sharedDirective.Values.Count == 1 && sharedDirective.Values[0] is XamlAstTextNode text) - { - if (bool.TryParse(text.Text, out var value)) - { - directive = (sharedDirective, value); - return true; - } - else - { - context?.ReportTransformError("Invalid argument type for x:Shared directive.", node); - } - } - else - { - context?.ReportTransformError("Invalid number of arguments for x:Shared directive.", node); - } - } - return false; - } - } - - class AdderSetter : IXamlILOptimizedEmitablePropertySetter, IEquatable - { - private readonly IXamlMethod _getter; - private readonly IXamlMethod _adder; - - public AdderSetter(IXamlMethod getter, IXamlMethod adder) - { - _getter = getter; - _adder = adder; - TargetType = getter.DeclaringType; - Parameters = adder.ParametersWithThis().Skip(1).ToList(); - - bool allowNull = Parameters.Last().AcceptsNull(); - BinderParameters = new PropertySetterBinderParameters - { - AllowMultiple = true, - AllowXNull = allowNull, - AllowRuntimeNull = allowNull, - AllowAttributeSyntax = false, - }; - } - - public IXamlType TargetType { get; } - - public PropertySetterBinderParameters BinderParameters { get; } - - public IReadOnlyList Parameters { get; } - public IReadOnlyList CustomAttributes => _adder.CustomAttributes; - - public void Emit(IXamlILEmitter emitter) - { - var locals = new Stack(); - // Save all "setter" parameters - for (var c = Parameters.Count - 1; c >= 0; c--) - { - var loc = emitter.LocalsPool.GetLocal(Parameters[c]); - locals.Push(loc); - emitter.Stloc(loc.Local); - } - - emitter.EmitCall(_getter); - while (locals.Count > 0) - using (var loc = locals.Pop()) - emitter.Ldloc(loc.Local); - emitter.EmitCall(_adder, true); - } - - public void EmitWithArguments( - XamlEmitContextWithLocals context, - IXamlILEmitter emitter, - IReadOnlyList arguments) - { - emitter.EmitCall(_getter); - - for (var i = 0; i < arguments.Count; ++i) - context.Emit(arguments[i], emitter, Parameters[i]); - - emitter.EmitCall(_adder, true); - } - - public bool Equals(AdderSetter? other) - { - if (ReferenceEquals(null, other)) - return false; - if (ReferenceEquals(this, other)) - return true; - - return _getter.Equals(other._getter) && _adder.Equals(other._adder); - } - - public override bool Equals(object? obj) - => Equals(obj as AdderSetter); - - public override int GetHashCode() - => (_getter.GetHashCode() * 397) ^ _adder.GetHashCode(); - } -} diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Visitors/NotSharedVisitor.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Visitors/NotSharedVisitor.cs deleted file mode 100644 index 8a85ab84120..00000000000 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Visitors/NotSharedVisitor.cs +++ /dev/null @@ -1,55 +0,0 @@ -using XamlX; -using XamlX.Ast; - -namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Visitors; - -class NotSharedVisitor : IXamlAstVisitor -{ - public int Count { get; private set; } - private int level = 0; - - public void Pop() - { - level--; - } - - public void Push(IXamlAstNode node) - { - level++; - } - - public IXamlAstNode Visit(IXamlAstNode node) - { - if (TryGetXShared(node, out var isShared)) - { - if (!isShared) - { - Count++; - return node; - } - } - else if (level < 3) - { - node.VisitChildren(this); - } - return node; - } - - private static bool TryGetXShared(IXamlAstNode node, out bool isShared) - { - isShared = false; - if (node is XamlAstXmlDirective { Namespace: XamlNamespaces.Xaml2006, Name: "Shared" } sharedDirective) - { - if (sharedDirective.Values.Count == 1 && sharedDirective.Values[0] is XamlAstTextNode text) - { - if (bool.TryParse(text.Text, out var value)) - { - isShared = value; - return true; - } - } - } - return false; - } -} - diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs index a6b0508bf3f..5b86116106b 100644 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs +++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs @@ -51,20 +51,6 @@ public static unsafe IDeferredContent DeferredTransformationFactoryV3( return new PointerDeferredContent(resourceNodes, rootObject, parentScope, typedBuilder); } - // The builder is typed as IntPtr instead of delegate* because Reflection.Emit has - // trouble with generic methods containing function pointers. See https://github.com/dotnet/runtime/issues/100020 - public static unsafe INotSharedDeferredContent NotSharedDeferredTransformationFactoryV0( - /*delegate**/ IntPtr builder, - IServiceProvider provider) - { - var resourceNodes = AsResourceNodesStack(provider.GetRequiredService()); - var rootObject = provider.GetRequiredService().RootObject; - var parentScope = provider.GetService(); - var typedBuilder = (delegate*)builder; - - return new PointerNotShardDeferredContent(resourceNodes, rootObject, parentScope, typedBuilder); - } - private static IResourceNode[] AsResourceNodesStack(IAvaloniaXamlIlParentStackProvider provider) { var buffer = s_resourceNodeBuffer ??= new List(8); @@ -209,22 +195,6 @@ protected override object InvokeBuilder(IServiceProvider serviceProvider) => _builder(serviceProvider); } - private sealed unsafe class PointerNotShardDeferredContent : DeferredContent,INotSharedDeferredContent - { - private readonly delegate* _builder; - - public PointerNotShardDeferredContent( - IResourceNode[] parentResourceNodes, - object rootObject, - INameScope? parentNameScope, - delegate* builder) - : base(parentResourceNodes, rootObject, parentNameScope) - => _builder = builder; - - protected override object InvokeBuilder(IServiceProvider serviceProvider) - => _builder(serviceProvider); - } - private sealed class DelegateDeferredContent : DeferredContent { private readonly Func _builder; diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XSharedDirectiveTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XSharedDirectiveTests.cs index ce83fdd094a..2e04588c9f7 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XSharedDirectiveTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XSharedDirectiveTests.cs @@ -38,7 +38,7 @@ public void Should_Create_New_Instance_Where_x_Share_Is_False() var implicitSharedInstance2 = window.FindResource("ImplicitSharedResource"); Assert.NotNull(implicitSharedInstance2); - Assert.True(ReferenceEquals(implicitSharedInstance1, implicitSharedInstance2)); + Assert.Same(implicitSharedInstance1, implicitSharedInstance2); var notSharedResource1 = window.FindResource("NotSharedResource"); Assert.NotNull(notSharedResource1); @@ -46,7 +46,7 @@ public void Should_Create_New_Instance_Where_x_Share_Is_False() var notSharedResource2 = window.FindResource("NotSharedResource"); Assert.NotNull(notSharedResource2); - Assert.True(!ReferenceEquals(notSharedResource1, notSharedResource2)); + Assert.NotSame(notSharedResource1, notSharedResource2); Assert.Equal(notSharedResource1.ToString(), notSharedResource2.ToString()); } From 5d863a9f8e0cd48e27f403e400413a2a3e7a45cc Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Fri, 16 Aug 2024 11:38:55 +0200 Subject: [PATCH 5/5] fix: Address review --- src/Avalonia.Base/Controls/INotSharedDeferredContent.cs | 8 -------- src/Avalonia.Base/Controls/ResourceDictionary.cs | 4 ++-- .../AvaloniaXamlIlDeferredResourceTransformer.cs | 4 ++-- .../Transformers/AvaloniaXamlIlWellKnownTypes.cs | 6 +++--- 4 files changed, 7 insertions(+), 15 deletions(-) delete mode 100644 src/Avalonia.Base/Controls/INotSharedDeferredContent.cs diff --git a/src/Avalonia.Base/Controls/INotSharedDeferredContent.cs b/src/Avalonia.Base/Controls/INotSharedDeferredContent.cs deleted file mode 100644 index 7adf7f70c09..00000000000 --- a/src/Avalonia.Base/Controls/INotSharedDeferredContent.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Avalonia.Controls; - -/// -/// Represents a not shared deferred content. -/// -internal interface INotSharedDeferredContent: IDeferredContent -{ -} diff --git a/src/Avalonia.Base/Controls/ResourceDictionary.cs b/src/Avalonia.Base/Controls/ResourceDictionary.cs index 3dce4c6d985..748882a4508 100644 --- a/src/Avalonia.Base/Controls/ResourceDictionary.cs +++ b/src/Avalonia.Base/Controls/ResourceDictionary.cs @@ -245,7 +245,7 @@ public bool TryGetValue(object key, out object? value) { } v => v, _ => null, }; - if (deferred is not INotSharedDeferredContent) + if (deferred is not NotSharedDeferredItem) { _inner[key] = value; } @@ -383,7 +383,7 @@ private sealed class DeferredItem : IDeferredContent public object? Build(IServiceProvider? serviceProvider) => _factory(serviceProvider); } - private sealed class NotSharedDeferredItem(IDeferredContent deferredContent) : INotSharedDeferredContent + private sealed class NotSharedDeferredItem(IDeferredContent deferredContent) : IDeferredContent { private readonly IDeferredContent _deferredContent = deferredContent ; public object? Build(IServiceProvider? serviceProvider) => _deferredContent.Build(serviceProvider); diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDeferredResourceTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDeferredResourceTransformer.cs index 5c59f0c1f0f..81a174c6e24 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDeferredResourceTransformer.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDeferredResourceTransformer.cs @@ -24,7 +24,7 @@ public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode nod && ShouldBeDeferred(pa.Values[1])) { IXamlMethod addMethod = TryGetSharedValue(pa.Values[1], out var isShared) && !isShared - ? types.ResourceDictionaryNotSharedDeferedAdd + ? types.ResourceDictionaryNotSharedDeferredAdd : types.ResourceDictionaryDeferredAdd; pa.Values[1] = new XamlDeferredContentNode(pa.Values[1], types.XamlIlTypes.Object, context.Configuration); @@ -37,7 +37,7 @@ public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode nod && ShouldBeDeferred(pa.Values[1])) { IXamlMethod addMethod = TryGetSharedValue(pa.Values[1], out var isShared) && !isShared - ? types.ResourceDictionaryNotSharedDeferedAdd + ? types.ResourceDictionaryNotSharedDeferredAdd : types.ResourceDictionaryDeferredAdd; pa.Values[1] = new XamlDeferredContentNode(pa.Values[1], types.XamlIlTypes.Object, context.Configuration); diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs index bfc73035095..679938e504d 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs @@ -118,7 +118,7 @@ sealed class AvaloniaXamlIlWellKnownTypes public IXamlType IResourceDictionary { get; } public IXamlType ResourceDictionary { get; } public IXamlMethod ResourceDictionaryDeferredAdd { get; } - public IXamlMethod ResourceDictionaryNotSharedDeferedAdd { get; } + public IXamlMethod ResourceDictionaryNotSharedDeferredAdd { get; } public IXamlMethod ResourceDictionaryEnsureCapacity { get; } public IXamlMethod ResourceDictionaryGetCount { get; } public IXamlType IThemeVariantProvider { get; } @@ -313,8 +313,8 @@ public AvaloniaXamlIlWellKnownTypes(TransformerConfiguration cfg) ResourceDictionary = cfg.TypeSystem.GetType("Avalonia.Controls.ResourceDictionary"); ResourceDictionaryDeferredAdd = ResourceDictionary.GetMethod("AddDeferred", XamlIlTypes.Void, true, XamlIlTypes.Object, cfg.TypeSystem.GetType("Avalonia.Controls.IDeferredContent")); - ResourceDictionaryNotSharedDeferedAdd = ResourceDictionary.GetMethod("AddNotSharedDeferred", XamlIlTypes.Void, true, XamlIlTypes.Object, - cfg.TypeSystem.GetType("Avalonia.Controls.INotSharedDeferredContent")); + ResourceDictionaryNotSharedDeferredAdd = ResourceDictionary.GetMethod("AddNotSharedDeferred", XamlIlTypes.Void, true, XamlIlTypes.Object, + cfg.TypeSystem.GetType("Avalonia.Controls.IDeferredContent")); ResourceDictionaryEnsureCapacity = ResourceDictionary.GetMethod("EnsureCapacity", XamlIlTypes.Void, true, XamlIlTypes.Int32); ResourceDictionaryGetCount = ResourceDictionary.GetMethod("get_Count", XamlIlTypes.Int32, true);