Browse Source

Make AvaloniaXamlIncludeTransformer a group transformer and remove old code with manual IL emit

pull/9537/head
Max Katz 4 years ago
parent
commit
47b9112138
  1. 11
      src/Avalonia.Base/Platform/AssetLoader.cs
  2. 139
      src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
  3. 3
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs
  4. 133
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlIncludeGroupTransformer.cs
  5. 93
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlAssetIncludeTransformer.cs
  6. 24
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlConstructorServiceProviderTransformer.cs
  7. 10
      src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs

11
src/Avalonia.Base/Platform/AssetLoader.cs

@ -3,16 +3,22 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
#if !BUILDTASK
using Avalonia.Platform.Internal;
using Avalonia.Utilities;
#endif
namespace Avalonia.Platform
{
/// <summary>
/// Loads assets compiled into the application binary.
/// </summary>
public class AssetLoader : IAssetLoader
public class AssetLoader
#if !BUILDTASK
: IAssetLoader
#endif
{
#if !BUILDTASK
private static IAssemblyDescriptorResolver s_assemblyDescriptorResolver = new AssemblyDescriptorResolver();
private AssemblyDescriptor? _defaultResmAssembly;
@ -206,7 +212,8 @@ namespace Avalonia.Platform
return null;
}
#endif
public static void RegisterResUriParsers()
{
if (!UriParser.IsKnownScheme("avares"))

139
src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs

@ -231,8 +231,6 @@ namespace Avalonia.Build.Tasks
});
asm.MainModule.Types.Add(typeDef);
var builder = typeSystem.CreateTypeBuilder(typeDef);
var populateMethodsToTransform = new List<(MethodDefinition populateMethod, string resourceFilePath)>();
IReadOnlyCollection<XamlDocumentResource> parsedXamlDocuments = new List<XamlDocumentResource>();
foreach (var res in group.Resources.Where(CheckXamlName).OrderBy(x => x.FilePath.ToLowerInvariant()))
@ -347,23 +345,11 @@ namespace Avalonia.Build.Tasks
res.Uri, res
);
var compiledPopulateMethod = typeSystem.GetTypeReference(populateBuilder).Resolve()
.Methods.First(m => m.Name == populateName);
// Include populate method and all nested methods/closures used in the populate method,
// So we can replace style/resource includes in all of them.
populateMethodsToTransform.Add((compiledPopulateMethod, res.FilePath));
populateMethodsToTransform.AddRange(compiledPopulateMethod.Body.Instructions
.Where(b => b.OpCode == OpCodes.Call || b.OpCode == OpCodes.Callvirt || b.OpCode == OpCodes.Ldftn)
.Select(b => b.Operand)
.OfType<MethodReference>()
.Where(m => compiledPopulateMethod.DeclaringType.NestedTypes.Contains(m.DeclaringType))
.Select(m => m.Resolve())
.Where(m => m.HasBody)
.Select(m => (m, res.FilePath)));
if (classTypeDefinition != null)
{
var compiledPopulateMethod = typeSystem.GetTypeReference(populateBuilder).Resolve()
.Methods.First(m => m.Name == document.PopulateMethod.Name);
var designLoaderFieldType = typeSystem
.GetType("System.Action`1")
.MakeGenericType(typeSystem.GetType("System.Object"));
@ -512,14 +498,6 @@ namespace Avalonia.Build.Tasks
res.Remove();
}
foreach (var (populateMethod, resourceFilePath) in populateMethodsToTransform)
{
if (!TransformXamlIncludes(engine, typeSystem, populateMethod, resourceFilePath, createRootServiceProviderMethod))
{
return false;
}
}
// Technically that's a hack, but it fixes corert incompatibility caused by deterministic builds
int dupeCounter = 1;
foreach (var grp in typeDef.NestedTypes.GroupBy(x => x.Name))
@ -596,116 +574,5 @@ namespace Avalonia.Build.Tasks
return true;
}
private static bool TransformXamlIncludes(
IBuildEngine engine, CecilTypeSystem typeSystem,
MethodDefinition populateMethod, string resourceFilePath,
MethodReference createRootServiceProviderMethod)
{
var asm = typeSystem.TargetAssemblyDefinition;
foreach (var instruction in populateMethod.Body.Instructions.ToArray())
{
const string resolveStyleIncludeName = "ResolveStyleInclude";
const string resolveResourceInclude = "ResolveResourceInclude";
if (instruction.OpCode == OpCodes.Call
&& instruction.Operand is MethodReference
{
Name: resolveStyleIncludeName or resolveResourceInclude,
DeclaringType: { Name: "XamlIlRuntimeHelpers" }
})
{
int lineNumber = 0, linePosition = 0;
bool instructionsModified = false;
try
{
var assetSource = (string)instruction.Previous.Previous.Previous.Operand;
lineNumber = GetConstValue(instruction.Previous.Previous);
linePosition = GetConstValue(instruction.Previous);
var index = populateMethod.Body.Instructions.IndexOf(instruction);
assetSource = assetSource.Replace("avares://", "");
var assemblyNameSeparator = assetSource.IndexOf('/');
var fileNameSeparator = assetSource.LastIndexOf('/');
if (assemblyNameSeparator < 0 || fileNameSeparator < 0)
{
throw new InvalidProgramException(
$"Invalid asset source \"{assetSource}\". It must contain assembly name and relative path.");
}
var assetAssemblyName = assetSource.Substring(0, assemblyNameSeparator);
var assetAssembly = typeSystem.FindAssembly(assetAssemblyName)
?? throw new InvalidProgramException(
$"Unable to resolve assembly \"{assetAssemblyName}\"");
var fileName = Path.GetFileNameWithoutExtension(assetSource.Replace('/', '.'));
if (assetAssembly.FindType(fileName) is { } type
&& type.FindConstructor() is { } ctor)
{
var ctorMethod =
asm.MainModule.ImportReference(typeSystem.GetMethodReference(ctor));
instructionsModified = true;
populateMethod.Body.Instructions[index - 3] = Instruction.Create(OpCodes.Nop);
populateMethod.Body.Instructions[index - 2] = Instruction.Create(OpCodes.Nop);
populateMethod.Body.Instructions[index - 1] = Instruction.Create(OpCodes.Nop);
populateMethod.Body.Instructions[index] = Instruction.Create(OpCodes.Newobj, ctorMethod);
}
else
{
var resources = assetAssembly.FindType("CompiledAvaloniaXaml.!AvaloniaResources")
?? throw new InvalidOperationException(
$"Unable to resolve \"!AvaloniaResources\" type on \"{assetAssemblyName}\" assembly");
var relativeName = "Build:" + assetSource.Substring(assemblyNameSeparator);
var buildMethod = resources.FindMethod(m => m.Name == relativeName)
?? throw new InvalidOperationException(
$"Unable to resolve build method \"{relativeName}\" resource on the \"{assetAssemblyName}\" assembly");
var methodReference = asm.MainModule.ImportReference(typeSystem.GetMethodReference(buildMethod));
instructionsModified = true;
populateMethod.Body.Instructions[index - 3] = Instruction.Create(OpCodes.Nop);
populateMethod.Body.Instructions[index - 2] = Instruction.Create(OpCodes.Nop);
populateMethod.Body.Instructions[index - 1] =
Instruction.Create(OpCodes.Call, createRootServiceProviderMethod);
populateMethod.Body.Instructions[index] = Instruction.Create(OpCodes.Call, methodReference);
}
}
catch (Exception e)
{
if (instructionsModified)
{
engine.LogErrorEvent(new BuildErrorEventArgs("Avalonia", "XAMLIL", resourceFilePath,
lineNumber, linePosition, lineNumber, linePosition,
e.Message, "", "Avalonia"));
return false;
}
else
{
engine.LogWarningEvent(new BuildWarningEventArgs("Avalonia", "XAMLIL",
resourceFilePath,
lineNumber, linePosition, lineNumber, linePosition,
e.Message, "", "Avalonia"));
}
}
static int GetConstValue(Instruction instruction)
{
if (instruction.OpCode is { Code : >= Code.Ldc_I4_0 and <= Code.Ldc_I4_8 })
{
return instruction.OpCode.Code - Code.Ldc_I4_0;
}
if (instruction.Operand is not null)
{
return Convert.ToInt32(instruction.Operand);
}
return 0;
}
}
}
return true;
}
}
}

3
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs

@ -56,8 +56,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
new AvaloniaXamlIlSetterTransformer(),
new AvaloniaXamlIlConstructorServiceProviderTransformer(),
new AvaloniaXamlIlTransitionsTypeMetadataTransformer(),
new AvaloniaXamlIlResolveByNameMarkupExtensionReplacer(),
new AvaloniaXamlIlAssetIncludeTransformer()
new AvaloniaXamlIlResolveByNameMarkupExtensionReplacer()
);
InsertBefore<ConvertPropertyValuesToAssignmentsTransformer>(
new AvaloniaXamlIlOptionMarkupExtensionTransformer());

133
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlIncludeGroupTransformer.cs

@ -0,0 +1,133 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
using Avalonia.Platform;
using XamlX;
using XamlX.Ast;
using XamlX.Emit;
using XamlX.IL;
using XamlX.Transform;
using XamlX.Transform.Transformers;
using XamlX.TypeSystem;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.GroupTransformers;
internal class AvaloniaXamlIncludeTransformer : IXamlAstGroupTransformer
{
public IXamlAstNode Transform(AstGroupTransformationContext context, IXamlAstNode node)
{
if (node is not XamlValueWithManipulationNode valueNode
|| valueNode.Value is not XamlAstNewClrObjectNode objectNode
|| (objectNode.Type.GetClrType() != context.GetAvaloniaTypes().StyleInclude
&& objectNode.Type.GetClrType() != context.GetAvaloniaTypes().ResourceInclude))
{
return node;
}
var nodeTypeName = objectNode.Type.GetClrType().Name;
if (valueNode.Manipulation is not XamlObjectInitializationNode
{
Manipulation: XamlPropertyAssignmentNode { Property: { Name: "Source" } } sourceProperty
})
{
return context.ParseError($"Source property must be set on the \"{nodeTypeName}\" node.", node);
}
if (sourceProperty.Values.OfType<XamlAstNewClrObjectNode>().FirstOrDefault() is not { } sourceUriNode
|| sourceUriNode.Type.GetClrType() != context.GetAvaloniaTypes().Uri
|| sourceUriNode.Arguments.FirstOrDefault() is not XamlConstantNode { Constant: string originalAssetPath })
{
// TODO: make it a compiler warning
// Source value can be set with markup extension instead of the Uri object node, we don't support it here yet.
return node;
}
if (originalAssetPath.StartsWith("avares://"))
{
}
else if (originalAssetPath.StartsWith("/"))
{
var baseUrl = context.CurrentDocument.Uri ?? throw new InvalidOperationException("CurrentDocument URI is null.");
originalAssetPath = baseUrl.Substring(0, baseUrl.LastIndexOf('/')) + originalAssetPath;
}
else
{
return context.ParseError(
$"Avalonia supports only \"avares://\" sources or relative sources starting with \"/\" on the \"{nodeTypeName}\" node.",
node);
}
AssetLoader.RegisterResUriParsers();
originalAssetPath = Uri.UnescapeDataString(new Uri(originalAssetPath).AbsoluteUri);
var assetPath = originalAssetPath.Replace("avares://", "");
var assemblyNameSeparator = assetPath.IndexOf('/');
var assembly = assetPath.Substring(0, assemblyNameSeparator);
var fullTypeName = Path.GetFileNameWithoutExtension(assetPath.Replace('/', '.'));
if (context.Documents.FirstOrDefault(d => string.Equals(d.Uri, originalAssetPath, StringComparison.InvariantCultureIgnoreCase)) is {} targetDocument)
{
if (targetDocument.ClassType is not null)
{
return FromType(context, targetDocument.ClassType, node);
}
if (targetDocument.BuildMethod is null)
{
return context.ParseError($"\"{originalAssetPath}\" cannot be instantiated.", node);
}
return FromMethod(context, targetDocument.BuildMethod, node);
}
if (context.Configuration.TypeSystem.FindAssembly(assembly) is not { } assetAssembly)
{
return context.ParseError($"Assembly \"{assembly}\" was not found from the \"{originalAssetPath}\" source.", node);
}
if (assetAssembly.FindType(fullTypeName) is { } type
&& type.FindMethod(m => m.Name == "!XamlIlPopulate") is not null)
{
return FromType(context, type, node);
}
else
{
var avaResType = assetAssembly.FindType("CompiledAvaloniaXaml.!AvaloniaResources");
if (avaResType is null)
{
return context.ParseError(
$"Unable to resolve \"!AvaloniaResources\" type on \"{assembly}\" assembly.", node);
}
var relativeName = "Build:" + assetPath.Substring(assemblyNameSeparator);
var buildMethod = avaResType.FindMethod(m => m.Name == relativeName);
if (buildMethod is null)
{
return context.ParseError(
$"Unable to resolve build method \"{relativeName}\" resource on the \"{assembly}\" assembly.",
node);
}
return FromMethod(context, buildMethod, node);
}
}
private static IXamlAstNode FromType(AstTransformationContext context, IXamlType type, IXamlLineInfo li)
{
IXamlAstNode newObjNode = new XamlAstObjectNode(li, new XamlAstClrTypeReference(li, type, false));
newObjNode = new AvaloniaXamlIlConstructorServiceProviderTransformer().Transform(context, newObjNode);
newObjNode = new ConstructableObjectTransformer().Transform(context, newObjNode);
return new NewObjectTransformer().Transform(context, newObjNode);
}
private static IXamlAstNode FromMethod(AstTransformationContext context, IXamlMethod method, IXamlLineInfo li)
{
var sp = context.Configuration.TypeMappings.ServiceProvider;
return new XamlStaticOrTargetedReturnMethodCallNode(li, method,
new[] { new AvaloniaXamlIlConstructorServiceProviderTransformer.InjectServiceProviderNode(sp, li, false) });
}
}

93
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlAssetIncludeTransformer.cs

@ -1,93 +0,0 @@
using System.Linq;
using XamlX;
using XamlX.Ast;
using XamlX.Emit;
using XamlX.IL;
using XamlX.Transform;
using XamlX.TypeSystem;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
internal class AvaloniaXamlIlAssetIncludeTransformer : IXamlAstTransformer
{
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
{
if (node is not XamlAstObjectNode objectNode
|| (objectNode.Type.GetClrType() != context.GetAvaloniaTypes().StyleInclude
&& objectNode.Type.GetClrType() != context.GetAvaloniaTypes().ResourceInclude))
{
return node;
}
var nodeTypeName = objectNode.Type.GetClrType().Name;
var sourceProperty = objectNode.Children.OfType<XamlAstXamlPropertyValueNode>().FirstOrDefault(n => n.Property.GetClrProperty().Name == "Source");
var directives = objectNode.Children.OfType<XamlAstXmlDirective>().ToList();
if (sourceProperty is null
|| objectNode.Children.Count != (directives.Count + 1))
{
throw new XamlParseException($"Unexpected property on the {nodeTypeName} node", node);
}
if (sourceProperty.Values.OfType<XamlAstTextNode>().FirstOrDefault() is not { } sourceTextNode)
{
// TODO: make it a compiler warning
// Source value can be set with markup extension instead of a text node, we don't support it here yet.
return node;
}
var originalAssetPath = sourceTextNode.Text;
if (!(originalAssetPath.StartsWith("avares://") || originalAssetPath.StartsWith("/")))
{
return node;
}
var runtimeHelpers = context.GetAvaloniaTypes().RuntimeHelpers;
var markerMethodName = "Resolve" + nodeTypeName;
var markerMethod = runtimeHelpers.FindMethod(m => m.Name == markerMethodName && m.Parameters.Count == 3);
if (markerMethod is null)
{
throw new XamlParseException($"Marker method \"{markerMethodName}\" was not found for the \"{nodeTypeName}\" node", node);
}
return new XamlValueWithManipulationNode(
node,
new AssetIncludeMethodNode(node, markerMethod, originalAssetPath),
new XamlManipulationGroupNode(node, directives));
}
private class AssetIncludeMethodNode : XamlAstNode, IXamlAstValueNode, IXamlAstILEmitableNode
{
private readonly IXamlMethod _method;
private readonly string _originalAssetPath;
public AssetIncludeMethodNode(
IXamlAstNode original, IXamlMethod method, string originalAssetPath)
: base(original)
{
_method = method;
_originalAssetPath = originalAssetPath;
}
public IXamlAstTypeReference Type => new XamlAstClrTypeReference(this, _method.ReturnType, false);
public XamlILNodeEmitResult Emit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
{
var absoluteSource = _originalAssetPath;
if (absoluteSource.StartsWith("/"))
{
// Avoid Uri class here to avoid potential problems with escaping.
// Keeping string as close to the original as possible.
var absoluteBaseUrl = context.RuntimeContext.BaseUrl;
absoluteSource = absoluteBaseUrl.Substring(0, absoluteBaseUrl.LastIndexOf('/')) + absoluteSource;
}
codeGen.Ldstr(absoluteSource);
codeGen.Ldc_I4(Line);
codeGen.Ldc_I4(Position);
codeGen.EmitCall(_method);
return XamlILNodeEmitResult.Type(0, _method.ReturnType);
}
}
}

24
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlConstructorServiceProviderTransformer.cs

@ -21,7 +21,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
c.IsPublic && !c.IsStatic && c.Parameters.Count == 1 && c.Parameters[0]
.Equals(sp)))
{
on.Arguments.Add(new InjectServiceProviderNode(sp, on));
on.Arguments.Add(new InjectServiceProviderNode(sp, on, true));
}
}
}
@ -29,19 +29,33 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
return node;
}
class InjectServiceProviderNode : XamlAstNode, IXamlAstValueNode,IXamlAstNodeNeedsParentStack,
internal class InjectServiceProviderNode : XamlAstNode, IXamlAstValueNode,IXamlAstNodeNeedsParentStack,
IXamlAstEmitableNode<IXamlILEmitter, XamlILNodeEmitResult>
{
public InjectServiceProviderNode(IXamlType type, IXamlLineInfo lineInfo) : base(lineInfo)
private readonly bool _inheritContext;
public InjectServiceProviderNode(IXamlType type, IXamlLineInfo lineInfo, bool inheritContext) : base(lineInfo)
{
_inheritContext = inheritContext;
Type = new XamlAstClrTypeReference(lineInfo, type, false);
}
public IXamlAstTypeReference Type { get; }
public bool NeedsParentStack => true;
public bool NeedsParentStack => _inheritContext;
public XamlILNodeEmitResult Emit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
{
codeGen.Ldloc(context.ContextLocal);
if (_inheritContext)
{
codeGen.Ldloc(context.ContextLocal);
}
else
{
var method = context.GetAvaloniaTypes().RuntimeHelpers
.FindMethod(m => m.Name == "CreateRootServiceProviderV2");
codeGen.EmitCall(method);
context.RuntimeContext.Factory(codeGen);
}
return XamlILNodeEmitResult.Type(0, Type.GetClrType());
}
}

10
src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs

@ -17,16 +17,6 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime
{
public static class XamlIlRuntimeHelpers
{
public static IStyle ResolveStyleInclude(string absoluteSource, int line, int position)
{
return new StyleInclude((Uri)null) { Source = new Uri(absoluteSource) }.Loaded;
}
public static IResourceDictionary ResolveResourceInclude(string absoluteSource, int line, int position)
{
return new ResourceInclude((Uri)null) { Source = new Uri(absoluteSource) }.Loaded;
}
public static Func<IServiceProvider, object> DeferredTransformationFactoryV1(Func<IServiceProvider, object> builder,
IServiceProvider provider)
{

Loading…
Cancel
Save