Skip to content

Commit

Permalink
[xamlc] remove LoadInSeparateAppDomain and static state (#11982)
Browse files Browse the repository at this point in the history
Fixes: #11885

Looking at the JITStats report in PerfView (for MSBuild.exe):

| Name                                    | JitTime (ms) |
| ---                                     |          --: |
| Microsoft.Maui.Controls.Build.Tasks.dll |        214.0 |
| Mono.Cecil                              |        119.0 |

It appears that `Microsoft.Maui.Controls.Build.Tasks.dll` spends a lot
of time in the JIT. What is confusing, is this was an incremental
build where everything should already be loaded. The JIT's work should
be done already? I don't see the same behavior for Android MSBuild
tasks or Windows App SDK tasks.

The cause appears to be usage of `[LoadInSeparateAppDomain]` in .NET MAUI.

However, we can't just *remove this* as there would be complications...

`[LoadInSeparateAppDomain]` also conveniently resets all `static`
state when `<XamlCTask/>` runs again. Meaning that future incremental
builds would potentially use old (garbage) values. There are several
places that cache Mono.Cecil objects for performance reasons. Really
weird bugs would result if we didn't address this.

To make things correct *and* remove `[LoadInSeparateAppDomain]`:

* Move all `static` state to a `XamlCache` class as instance values.

* Each `ILContext` has an instance of `XamlCache`.

* Any child `ILContext`'s such as with `DataTemplate` are passed the
  parent's `XamlCache`.

* Various `static` extension methods now require a `XamlCache` to be
  passed in. This allows unit tests to do this as well.

* Other `*Visitor` or `*Converter` types have an instance of
  `ILContext`, so they can access `ILContext.Cache` as needed.

Unfortunately, I had to make small changes to lots of lines for this.

Testing these changes inside Visual Studio with a `dotnet new maui`
project template:

    Before:
    XamlCTask = 743 ms
    XamlCTask = 706 ms
    XamlCTask = 692 ms
    After:
    XamlCTask = 128 ms
    XamlCTask = 134 ms
    XamlCTask = 117 ms

This saves about ~587ms on incremental builds on all platforms, an 82%
improvement. This will help even more on large solutions with multiple
.NET MAUI projects, where `<XamlCTask/>` runs multiple times.
  • Loading branch information
jonathanpeppers committed Dec 20, 2022
1 parent 44575de commit 73a06de
Show file tree
Hide file tree
Showing 54 changed files with 651 additions and 635 deletions.
24 changes: 12 additions & 12 deletions src/Controls/src/Build.Tasks/BindablePropertyReferenceExtensions.cs
Expand Up @@ -9,48 +9,48 @@ namespace Microsoft.Maui.Controls.Build.Tasks
{
static class BindablePropertyReferenceExtensions
{
public static TypeReference GetBindablePropertyType(this FieldReference bpRef, IXmlLineInfo iXmlLineInfo, ModuleDefinition module)
public static TypeReference GetBindablePropertyType(this FieldReference bpRef, XamlCache cache, IXmlLineInfo iXmlLineInfo, ModuleDefinition module)
{
if (!bpRef.Name.EndsWith("Property", StringComparison.InvariantCulture))
throw new BuildException(BuildExceptionCode.BPName, iXmlLineInfo, null, bpRef.Name);
var bpName = bpRef.Name.Substring(0, bpRef.Name.Length - 8);
var owner = bpRef.DeclaringType;

var getter = owner.GetProperty(pd => pd.Name == bpName, out TypeReference declaringTypeRef)?.GetMethod;
var getter = owner.GetProperty(cache, pd => pd.Name == bpName, out TypeReference declaringTypeRef)?.GetMethod;
if (getter == null || getter.IsStatic || !getter.IsPublic)
getter = null;
getter = getter ?? owner.GetMethods(md => md.Name == $"Get{bpName}" &&
getter = getter ?? owner.GetMethods(cache, md => md.Name == $"Get{bpName}" &&
md.IsStatic &&
md.IsPublic &&
md.Parameters.Count == 1 &&
md.Parameters[0].ParameterType.InheritsFromOrImplements(module.ImportReference(("Microsoft.Maui.Controls", "Microsoft.Maui.Controls", "BindableObject"))), module).FirstOrDefault()?.Item1;
md.Parameters[0].ParameterType.InheritsFromOrImplements(cache, module.ImportReference(cache, ("Microsoft.Maui.Controls", "Microsoft.Maui.Controls", "BindableObject"))), module).FirstOrDefault()?.Item1;
if (getter == null)
throw new BuildException(BuildExceptionCode.BPName, iXmlLineInfo, null, bpName, bpRef.DeclaringType);

return getter.ResolveGenericReturnType(declaringTypeRef, module);
}

public static TypeReference GetBindablePropertyTypeConverter(this FieldReference bpRef, ModuleDefinition module)
public static TypeReference GetBindablePropertyTypeConverter(this FieldReference bpRef, XamlCache cache, ModuleDefinition module)
{
var owner = bpRef.DeclaringType;
var bpName = bpRef.Name.EndsWith("Property", StringComparison.Ordinal) ? bpRef.Name.Substring(0, bpRef.Name.Length - 8) : bpRef.Name;
var property = owner.GetProperty(pd => pd.Name == bpName, out TypeReference propertyDeclaringType);
var property = owner.GetProperty(cache, pd => pd.Name == bpName, out TypeReference propertyDeclaringType);
var propertyType = property?.PropertyType?.ResolveGenericParameters(propertyDeclaringType);
var staticGetter = owner.GetMethods(md => md.Name == $"Get{bpName}" &&
var staticGetter = owner.GetMethods(cache, md => md.Name == $"Get{bpName}" &&
md.IsStatic &&
md.IsPublic &&
md.Parameters.Count == 1 &&
md.Parameters[0].ParameterType.InheritsFromOrImplements(module.ImportReference(("Microsoft.Maui.Controls", "Microsoft.Maui.Controls", "BindableObject"))), module).FirstOrDefault()?.Item1;
md.Parameters[0].ParameterType.InheritsFromOrImplements(cache, module.ImportReference(cache, ("Microsoft.Maui.Controls", "Microsoft.Maui.Controls", "BindableObject"))), module).FirstOrDefault()?.Item1;

var attributes = new List<CustomAttribute>();
if (property != null && property.HasCustomAttributes)
attributes.AddRange(property.CustomAttributes);
if (propertyType != null && propertyType.ResolveCached().HasCustomAttributes)
attributes.AddRange(propertyType.ResolveCached().CustomAttributes);
if (propertyType != null && propertyType.ResolveCached(cache).HasCustomAttributes)
attributes.AddRange(propertyType.ResolveCached(cache).CustomAttributes);
if (staticGetter != null && staticGetter.HasCustomAttributes)
attributes.AddRange(staticGetter.CustomAttributes);
if (staticGetter != null && staticGetter.ReturnType.ResolveGenericParameters(bpRef.DeclaringType).ResolveCached().HasCustomAttributes)
attributes.AddRange(staticGetter.ReturnType.ResolveGenericParameters(bpRef.DeclaringType).ResolveCached().CustomAttributes);
if (staticGetter != null && staticGetter.ReturnType.ResolveGenericParameters(bpRef.DeclaringType).ResolveCached(cache).HasCustomAttributes)
attributes.AddRange(staticGetter.ReturnType.ResolveGenericParameters(bpRef.DeclaringType).ResolveCached(cache).CustomAttributes);

if (attributes.FirstOrDefault(cad => cad.AttributeType.FullName == "System.ComponentModel.TypeConverterAttribute")?.ConstructorArguments[0].Value is TypeReference typeConverter)
return typeConverter;
Expand Down
Expand Up @@ -21,11 +21,11 @@ public IEnumerable<Instruction> ConvertFromString(string value, ILContext contex
yield return Instruction.Create(OpCodes.Ldnull);
yield break;
}
var bpRef = GetBindablePropertyFieldReference(value, module, node);
var bpRef = GetBindablePropertyFieldReference(value, context, module, node);
yield return Instruction.Create(OpCodes.Ldsfld, bpRef);
}

public FieldReference GetBindablePropertyFieldReference(string value, ModuleDefinition module, BaseNode node)
public FieldReference GetBindablePropertyFieldReference(string value, ILContext context, ModuleDefinition module, BaseNode node)
{
FieldReference bpRef = null;
string typeName = null, propertyName = null;
Expand Down Expand Up @@ -70,11 +70,11 @@ public FieldReference GetBindablePropertyFieldReference(string value, ModuleDefi
if (typeName == null || propertyName == null)
throw new BuildException(Conversion, node, null, value, typeof(BindableProperty));

var typeRef = XmlTypeExtensions.GetTypeReference(typeName, module, node);
var typeRef = XmlTypeExtensions.GetTypeReference(context.Cache, typeName, module, node);
if (typeRef == null)
throw new BuildException(TypeResolution, node, null, typeName);

bpRef = GetBindablePropertyFieldReference(typeRef, propertyName, module);
bpRef = GetBindablePropertyFieldReference(context.Cache, typeRef, propertyName, module);
if (bpRef == null)
throw new BuildException(PropertyResolution, node, null, propertyName, typeRef.Name);
return bpRef;
Expand Down Expand Up @@ -103,10 +103,10 @@ static string FindTypeNameForVisualState(IElementNode parent, IXmlLineInfo lineI
return target.XmlType.Name;
}

public static FieldReference GetBindablePropertyFieldReference(TypeReference typeRef, string propertyName, ModuleDefinition module)
public static FieldReference GetBindablePropertyFieldReference(XamlCache cache, TypeReference typeRef, string propertyName, ModuleDefinition module)
{
TypeReference declaringTypeReference;
FieldReference bpRef = typeRef.GetField(fd => fd.Name == $"{propertyName}Property" && fd.IsStatic && fd.IsPublic, out declaringTypeReference);
FieldReference bpRef = typeRef.GetField(cache, fd => fd.Name == $"{propertyName}Property" && fd.IsStatic && fd.IsPublic, out declaringTypeReference);
if (bpRef != null)
{
bpRef = module.ImportReference(bpRef.ResolveGenericParameters(declaringTypeReference));
Expand Down
Expand Up @@ -53,10 +53,10 @@ public IEnumerable<Instruction> ConvertFromString(string value, ILContext contex
if (!hasX || !hasY || !hasW || !hasH)
throw new BuildException(BuildExceptionCode.Conversion, node, null, value, typeof(Rect));

return GenerateIL(x, y, w, h, module);
return GenerateIL(context, x, y, w, h, module);
}

IEnumerable<Instruction> GenerateIL(double x, double y, double w, double h, ModuleDefinition module)
IEnumerable<Instruction> GenerateIL(ILContext context, double x, double y, double w, double h, ModuleDefinition module)
{
// IL_0000: ldc.r8 3.1000000000000001
// IL_0009: ldc.r8 4.2000000000000002
Expand All @@ -68,7 +68,7 @@ IEnumerable<Instruction> GenerateIL(double x, double y, double w, double h, Modu
yield return Instruction.Create(OpCodes.Ldc_R8, y);
yield return Instruction.Create(OpCodes.Ldc_R8, w);
yield return Instruction.Create(OpCodes.Ldc_R8, h);
yield return Instruction.Create(OpCodes.Newobj, module.ImportCtorReference(("Microsoft.Maui.Graphics", "Microsoft.Maui.Graphics", "Rect"), parameterTypes: new[] {
yield return Instruction.Create(OpCodes.Newobj, module.ImportCtorReference(context.Cache, ("Microsoft.Maui.Graphics", "Microsoft.Maui.Graphics", "Rect"), parameterTypes: new[] {
("mscorlib", "System", "Double"),
("mscorlib", "System", "Double"),
("mscorlib", "System", "Double"),
Expand Down
Expand Up @@ -22,13 +22,14 @@ public IEnumerable<Instruction> ConvertFromString(string value, ILContext contex
foreach (var instruction in colorConverter.ConvertFromString(value, context, node))
yield return instruction;

yield return Instruction.Create(OpCodes.Newobj, module.ImportCtorReference(("Microsoft.Maui.Controls", "Microsoft.Maui.Controls", "SolidColorBrush"), parameterTypes: new[] {
yield return Instruction.Create(OpCodes.Newobj, module.ImportCtorReference(context.Cache, ("Microsoft.Maui.Controls", "Microsoft.Maui.Controls", "SolidColorBrush"), parameterTypes: new[] {
("Microsoft.Maui.Graphics", "Microsoft.Maui.Graphics", "Color")}));

yield break;
}

var propertyGetterReference = module.ImportPropertyGetterReference(("Microsoft.Maui.Controls", "Microsoft.Maui.Controls", "Brush"),
var propertyGetterReference = module.ImportPropertyGetterReference(context.Cache,
("Microsoft.Maui.Controls", "Microsoft.Maui.Controls", "Brush"),
value,
isStatic: true,
caseSensitive: false);
Expand Down
Expand Up @@ -29,7 +29,7 @@ public virtual IEnumerable<Instruction> ConvertFromString(string value, ILContex
yield return Instruction.Create(OpCodes.Ldc_R4, color.Blue);
yield return Instruction.Create(OpCodes.Ldc_R4, color.Alpha);

yield return Instruction.Create(OpCodes.Newobj, module.ImportCtorReference(("Microsoft.Maui.Graphics", "Microsoft.Maui.Graphics", "Color"), parameterTypes: new[] {
yield return Instruction.Create(OpCodes.Newobj, module.ImportCtorReference(context.Cache, ("Microsoft.Maui.Graphics", "Microsoft.Maui.Graphics", "Color"), parameterTypes: new[] {
("mscorlib", "System", "Single"),
("mscorlib", "System", "Single"),
("mscorlib", "System", "Single"),
Expand All @@ -49,7 +49,7 @@ public virtual IEnumerable<Instruction> ConvertFromString(string value, ILContex
yield break;
}

var fieldReference = module.ImportFieldReference(("Microsoft.Maui.Graphics", "Microsoft.Maui.Graphics", "Colors"),
var fieldReference = module.ImportFieldReference(context.Cache, ("Microsoft.Maui.Graphics", "Microsoft.Maui.Graphics", "Colors"),
color,
isStatic: true,
caseSensitive: false);
Expand All @@ -60,7 +60,7 @@ public virtual IEnumerable<Instruction> ConvertFromString(string value, ILContex
yield break;
}

var propertyGetterReference = module.ImportPropertyGetterReference(("Microsoft.Maui.Graphics", "Microsoft.Maui.Graphics", "Colors"),
var propertyGetterReference = module.ImportPropertyGetterReference(context.Cache, ("Microsoft.Maui.Graphics", "Microsoft.Maui.Graphics", "Colors"),
color,
isStatic: true,
caseSensitive: false);
Expand Down
Expand Up @@ -20,19 +20,19 @@ public IEnumerable<Instruction> ConvertFromString(string value, ILContext contex
var parts = value.Split(',');

yield return Create(Ldc_I4, parts.Length);
yield return Create(Newarr, module.ImportReference(("Microsoft.Maui.Controls", "Microsoft.Maui.Controls", "ColumnDefinition")));
yield return Create(Newarr, module.ImportReference(context.Cache, ("Microsoft.Maui.Controls", "Microsoft.Maui.Controls", "ColumnDefinition")));
for (var i = 0; i < parts.Length; i++)
{
yield return Create(Dup);
yield return Create(Ldc_I4, i);
foreach (var instruction in gridlengthconverter.ConvertFromString(parts[i], context, node))
yield return instruction;
yield return Create(Newobj, module.ImportCtorReference(
yield return Create(Newobj, module.ImportCtorReference(context.Cache,
type: ("Microsoft.Maui.Controls", "Microsoft.Maui.Controls", "ColumnDefinition"),
parameterTypes: new[] { ("Microsoft.Maui", "Microsoft.Maui", "GridLength") }));
yield return Create(Stelem_Ref);
}
yield return Create(Newobj, module.ImportCtorReference(
yield return Create(Newobj, module.ImportCtorReference(context.Cache,
type: ("Microsoft.Maui.Controls", "Microsoft.Maui.Controls", "ColumnDefinitionCollection"),
paramCount: 1));
yield break;
Expand Down
Expand Up @@ -20,7 +20,7 @@ public IEnumerable<Instruction> ConvertFromString(string value, ILContext contex
throw new BuildException(BuildExceptionCode.Conversion, node, null, value, typeof(Compatibility.Constraint));

yield return Create(Ldc_R8, size);
yield return Create(Call, module.ImportMethodReference(("Microsoft.Maui.Controls", "Microsoft.Maui.Controls.Compatibility", "Constraint"),
yield return Create(Call, module.ImportMethodReference(context.Cache, ("Microsoft.Maui.Controls", "Microsoft.Maui.Controls.Compatibility", "Constraint"),
methodName: "Constant",
parameterTypes: new[] { ("mscorlib", "System", "Double") },
isStatic: true));
Expand Down
Expand Up @@ -22,25 +22,25 @@ public IEnumerable<Instruction> ConvertFromString(string value, ILContext contex
{
case 1:
if (double.TryParse(cornerradius[0], NumberStyles.Number, CultureInfo.InvariantCulture, out l))
return GenerateIL(module, l);
return GenerateIL(context, module, l);
break;
case 4:
if (double.TryParse(cornerradius[0], NumberStyles.Number, CultureInfo.InvariantCulture, out tl)
&& double.TryParse(cornerradius[1], NumberStyles.Number, CultureInfo.InvariantCulture, out tr)
&& double.TryParse(cornerradius[2], NumberStyles.Number, CultureInfo.InvariantCulture, out bl)
&& double.TryParse(cornerradius[3], NumberStyles.Number, CultureInfo.InvariantCulture, out br))
return GenerateIL(module, tl, tr, bl, br);
return GenerateIL(context, module, tl, tr, bl, br);
break;
}
}
throw new BuildException(BuildExceptionCode.Conversion, node, null, value, typeof(CornerRadius));
}

IEnumerable<Instruction> GenerateIL(ModuleDefinition module, params double[] args)
IEnumerable<Instruction> GenerateIL(ILContext context, ModuleDefinition module, params double[] args)
{
foreach (var d in args)
yield return Instruction.Create(OpCodes.Ldc_R8, d);
yield return Instruction.Create(OpCodes.Newobj, module.ImportCtorReference(("Microsoft.Maui", "Microsoft.Maui", "CornerRadius"), parameterTypes: args.Select(a => ("mscorlib", "System", "Double")).ToArray()));
yield return Instruction.Create(OpCodes.Newobj, module.ImportCtorReference(context.Cache, ("Microsoft.Maui", "Microsoft.Maui", "CornerRadius"), parameterTypes: args.Select(a => ("mscorlib", "System", "Double")).ToArray()));
}
}

Expand Down
Expand Up @@ -25,7 +25,7 @@ public IEnumerable<Instruction> ConvertFromString(string value, ILContext contex
var assemblyTypeInfo = ("Microsoft.Maui.Controls", "Microsoft.Maui.Controls", nameof(Easing));

var module = context.Body.Method.Module;
var fieldReference = module.ImportFieldReference(assemblyTypeInfo, value, isStatic: true, caseSensitive: false);
var fieldReference = module.ImportFieldReference(context.Cache, assemblyTypeInfo, value, isStatic: true, caseSensitive: false);

if (fieldReference != null)
{
Expand Down
Expand Up @@ -19,7 +19,7 @@ public IEnumerable<Instruction> ConvertFromString(string value, ILContext contex
if (value == "Auto")
{
yield return Instruction.Create(OpCodes.Ldsfld,
module.ImportFieldReference(("Microsoft.Maui", "Microsoft.Maui.Layouts", "FlexBasis"),
module.ImportFieldReference(context.Cache, ("Microsoft.Maui", "Microsoft.Maui.Layouts", "FlexBasis"),
"Auto",
isStatic: true));
yield break;
Expand All @@ -29,7 +29,7 @@ public IEnumerable<Instruction> ConvertFromString(string value, ILContext contex
{
yield return Instruction.Create(OpCodes.Ldc_R4, (float)(relflex / 100));
yield return Instruction.Create(OpCodes.Ldc_I4_1); //isRelative: true
yield return Instruction.Create(OpCodes.Newobj, module.ImportCtorReference(("Microsoft.Maui", "Microsoft.Maui.Layouts", "FlexBasis"), parameterTypes: new[] {
yield return Instruction.Create(OpCodes.Newobj, module.ImportCtorReference(context.Cache, ("Microsoft.Maui", "Microsoft.Maui.Layouts", "FlexBasis"), parameterTypes: new[] {
("mscorlib", "System", "Single"),
("mscorlib", "System", "Boolean")}));
yield break;
Expand All @@ -38,7 +38,7 @@ public IEnumerable<Instruction> ConvertFromString(string value, ILContext contex
{
yield return Instruction.Create(OpCodes.Ldc_R4, flex);
yield return Instruction.Create(OpCodes.Ldc_I4_0); //isRelative: false
yield return Instruction.Create(OpCodes.Newobj, module.ImportCtorReference(("Microsoft.Maui", "Microsoft.Maui.Layouts", "FlexBasis"), parameterTypes: new[] {
yield return Instruction.Create(OpCodes.Newobj, module.ImportCtorReference(context.Cache, ("Microsoft.Maui", "Microsoft.Maui.Layouts", "FlexBasis"), parameterTypes: new[] {
("mscorlib", "System", "Single"),
("mscorlib", "System", "Boolean")}));
yield break;
Expand Down
Expand Up @@ -32,19 +32,22 @@ public IEnumerable<Instruction> ConvertFromString(string value, ILContext contex
{
yield return Instruction.Create(OpCodes.Ldloc, context.Variables[parent]);
yield return Instruction.Create(OpCodes.Callvirt, module.ImportMethodReference(
context.Cache,
module.TypeSystem.Object,
methodName: "GetType"));
}
else
{
yield return Instruction.Create(OpCodes.Ldtoken, module.ImportReference(("Microsoft.Maui.Controls", "Microsoft.Maui.Controls", "Label")));
yield return Instruction.Create(OpCodes.Ldtoken, module.ImportReference(context.Cache, ("Microsoft.Maui.Controls", "Microsoft.Maui.Controls", "Label")));
yield return Instruction.Create(OpCodes.Call, module.ImportMethodReference(
context.Cache,
("mscorlib", "System", "Type"),
methodName: "GetTypeFromHandle",
parameterTypes: new[] { ("mscorlib", "System", "RuntimeTypeHandle") },
isStatic: true));
}
yield return Instruction.Create(OpCodes.Call, module.ImportMethodReference(
context.Cache,
("Microsoft.Maui.Controls", "Microsoft.Maui.Controls", "Device"),
methodName: "GetNamedSize",
parameterTypes: new[] { ("Microsoft.Maui.Controls", "Microsoft.Maui.Controls", "NamedSize"), ("System.Runtime", "System", "Type") },
Expand Down

0 comments on commit 73a06de

Please sign in to comment.