Skip to content

Commit

Permalink
[dotnet-linker] Trim away the static constructor for protocol interfa…
Browse files Browse the repository at this point in the history
…ces if we're registering protocols in the static registrar.

Pending:

* Update docs
* Tests
* Stats for size improvements

This might be the fix for #21002.
  • Loading branch information
rolfbjarne authored and vs-mobiletools-engineering-service2 committed Sep 2, 2024
1 parent fd384c5 commit bf8efd2
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/bgen/Generator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5055,6 +5055,7 @@ void GenerateProtocolTypes (Type type, string class_visibility, string TypeName,
foreach (var docId in docIds) {
print ($"[DynamicDependencyAttribute (\"{docId}\")]");
}
print ("[BindingImpl (BindingImplOptions.GeneratedCode | BindingImplOptions.Optimizable)]");
print ($"static I{TypeName} ()");
print ("{");
print ("\tGC.KeepAlive (null);"); // need to do _something_ (doesn't seem to matter what), otherwise the static cctor (and the DynamicDependency attributes) are trimmed away.
Expand Down
77 changes: 77 additions & 0 deletions tools/linker/CoreOptimizeGeneratedCode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -755,6 +755,9 @@ protected override void Process (MethodDefinition method)
return; // nothing else to do here.
}

if (ProcessProtocolInterfaceStaticConstructor (method))
return;

var instructions = method.Body.Instructions;
for (int i = 0; i < instructions.Count; i++) {
var ins = instructions [i];
Expand Down Expand Up @@ -1191,6 +1194,19 @@ static Instruction GetPreviousSkippingNops (Instruction ins)
return ins;
}

static Instruction SkipNops (Instruction ins)
{
if (ins is null)
return null;

while (ins.OpCode == OpCodes.Nop) {
if (ins.Next is null)
return null;
ins = ins.Next;
}
return ins;
}

int ProcessIsARM64CallingConvention (MethodDefinition caller, Instruction ins)
{
const string operation = "inline Runtime.IsARM64CallingConvention";
Expand Down Expand Up @@ -1345,5 +1361,66 @@ MethodReference GetBlockLiteralConstructor (MethodDefinition caller, Instruction
}
return caller.Module.ImportReference (block_ctor_def);
}

bool ProcessProtocolInterfaceStaticConstructor (MethodDefinition method)
{
// The static cctor in protocol interfaces exists only to preserve the protocol's members, for inspection by the registrar(s).
// If we're registering protocols, then we don't need to preserve protocol members, because the registrar
// already knows everything about it => we can remove the static cctor.

if (!(method.DeclaringType.IsInterface && method.DeclaringType.IsInterface && method.IsStatic && method.IsConstructor && method.HasBody))
return false;

if (Optimizations.RegisterProtocols != true) {
Driver.Log (4, "Did not optimize static constructor in the protocol interface {0}: the 'register-protocols' optimization is disabled.", method.DeclaringType.FullName);
return false;
}

if (!method.DeclaringType.HasCustomAttributes || !method.DeclaringType.CustomAttributes.Any (v => v.AttributeType.Is ("Foundation", "ProtocolAttribute"))) {
Driver.Log (4, "Did not optimize static constructor in the protocol interface {0}: no Protocol attribute found.", method.DeclaringType.FullName);
return false;
}

var ins = SkipNops (method.Body.Instructions.First ());
if (ins is null) {
ErrorHelper.Show (ErrorHelper.CreateWarning (LinkContext.App, 2112, method, ins, Errors.MX2112_A /* Could not optimize the static constructor in the interface {0} because it did not have the expected instruction sequence (found end of method too soon). */, method.DeclaringType.FullName));
return false;
} else if (ins.OpCode != OpCodes.Ldnull) {
ErrorHelper.Show (ErrorHelper.CreateWarning (LinkContext.App, 2112, method, ins, Errors.MX2112_B /* Could not optimize the static constructor in the interface {0} because it had an unexpected instruction {1} at offset {2}. */, method.DeclaringType.FullName, ins.OpCode, ins.Offset));
return false;
}

ins = SkipNops (ins.Next);
var callGCKeepAlive = ins;
if (ins is null) {
ErrorHelper.Show (ErrorHelper.CreateWarning (LinkContext.App, 2112, method, ins, Errors.MX2112_A /* Could not optimize the static constructor in the interface {0} because it did not have the expected instruction sequence (found end of method too soon). */, method.DeclaringType.FullName));
return false;
} else if (callGCKeepAlive.OpCode != OpCodes.Call || !(callGCKeepAlive.Operand is MethodReference methodOperand) || methodOperand.Name != "KeepAlive" || !methodOperand.DeclaringType.Is ("System", "GC")) {
ErrorHelper.Show (ErrorHelper.CreateWarning (LinkContext.App, 2112, method, ins, Errors.MX2112_B /* Could not optimize the static constructor in the interface {0} because it had an unexpected instruction {1} at offset {2}. */, method.DeclaringType.FullName, ins.OpCode, ins.Offset));
return false;
}

ins = SkipNops (ins.Next);
if (ins is null) {
ErrorHelper.Show (ErrorHelper.CreateWarning (LinkContext.App, 2112, method, ins, Errors.MX2112_A /* Could not optimize the static constructor in the interface {0} because it did not have the expected instruction sequence (found end of method too soon). */, method.DeclaringType.FullName));
return false;
} else if (ins.OpCode != OpCodes.Ret) {
ErrorHelper.Show (ErrorHelper.CreateWarning (LinkContext.App, 2112, method, ins, Errors.MX2112_B /* Could not optimize the static constructor in the interface {0} because it had an unexpected instruction {1} at offset {2}. */, method.DeclaringType.FullName, ins.OpCode, ins.Offset));
return false;
}

ins = SkipNops (ins.Next);
if (ins is not null) {
ErrorHelper.Show (ErrorHelper.CreateWarning (LinkContext.App, 2112, method, ins, Errors.MX2112_B /* Could not optimize the static constructor in the interface {0} because it had an unexpected instruction {1} at offset {2}. */, method.DeclaringType.FullName, ins.OpCode, ins.Offset));
return false;
}

// We can just remove the entire method
Driver.Log (4, "Optimized static constructor in the protocol interface {0} (static constructor was cleared and custom attributes removed)", method.DeclaringType.FullName);
method.Body.Instructions.Clear ();
method.Body.Instructions.Add (Instruction.Create (OpCodes.Ret));
method.CustomAttributes.Clear ();
return true;
}
}
}
18 changes: 18 additions & 0 deletions tools/mtouch/Errors.designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions tools/mtouch/Errors.resx
Original file line number Diff line number Diff line change
Expand Up @@ -1327,6 +1327,13 @@
</value>
</data>

<data name="MX2112_A" xml:space="preserve">
<value>Could not optimize the static constructor in the interface {0} because it did not have the expected instruction sequence (found end of method too soon).</value>
</data>

<data name="MX2112_B" xml:space="preserve">
<value>Could not optimize the static constructor in the interface {0} because it had an unexpected instruction {1} at offset {2}.</value>
</data>
<!-- 2200 -> 2299 is used by/reserved for ExceptionalSubStep subclasses in the linker -->
<!-- 220x: PreserveSmartEnumConversionsSubStep -->
<!-- 221x: RemoveBitcodeIncompatibleCodeStep -->
Expand Down

0 comments on commit bf8efd2

Please sign in to comment.