Skip to content

Commit 8ce221c

Browse files
[Xamarin.Android.Build.Tasks] Avoid br.s in generated lookup tables (#9969)
Context: 8d739f4 Context: #9962 Context: https://ecma-international.org/wp-content/uploads/ECMA-335_6th_edition_june_2012.pdf `ManagedMarshalMethodsLookupGenerator` (8d739f4) generates IL of a switch statement [^0] which looks like this: .method public hidebysig static native int '<JI>GetFunctionPointer' ( int32 methodIndex ) cil managed { // Method begins at RVA 0xac270 // Header size: 1 // Code size: 59 (0x3b) .maxstack 8 IL_0000: ldarg methodIndex IL_0004: switch (IL_001b, IL_0022, IL_0029, IL_0030) IL_0019: br.s IL_0037 IL_001b: ldftn void Android.App.Application::n_OnCreate_mm_wrapper(native int, native int) IL_0021: ret IL_0022: ldftn void Android.App.Application::n_OnLowMemory_mm_wrapper(native int, native int) IL_0028: ret IL_0029: ldftn void Android.App.Application::n_OnTrimMemory_I_mm_wrapper(native int, native int, int32) IL_002f: ret IL_0030: ldftn void Android.App.Application::n_OnConfigurationChanged_Landroid_content_res_Configuration__mm_wrapper(native int, native int, native int) IL_0036: ret IL_0037: nop IL_0038: ldc.i4.m1 IL_0039: conv.i IL_003a: ret } // end of method Application::'<JI>GetFunctionPointer' If the `switch` instruction doesn't jump to any of the target offsets, it continues on the next instruction. This instruction used to be a `br.s` [^1] which would jump over all the individual cases to the default case. When the switch gets too large (19 cases), the `br.s` becomes insufficient, as the offset for `br.s` is a signed 8-bit value. The code can be simplified by removing the branching operation completely and simply return from the default case. The newly generated code will look like this: .method public hidebysig static native int '<JI>GetFunctionPointer' ( int32 methodIndex ) cil managed { // Method begins at RVA 0xac1a8 // Header size: 1 // Code size: 56 (0x38) .maxstack 8 IL_0000: ldarg methodIndex IL_0004: switch (IL_001c, IL_0023, IL_002a, IL_0031) IL_0019: ldc.i4.m1 IL_001a: conv.i IL_001b: ret IL_001c: ldftn void Android.App.Application::n_OnCreate_mm_wrapper(native int, native int) IL_0022: ret IL_0023: ldftn void Android.App.Application::n_OnLowMemory_mm_wrapper(native int, native int) IL_0029: ret IL_002a: ldftn void Android.App.Application::n_OnTrimMemory_I_mm_wrapper(native int, native int, int32) IL_0030: ret IL_0031: ldftn void Android.App.Application::n_OnConfigurationChanged_Landroid_content_res_Configuration__mm_wrapper(native int, native int, native int) IL_0037: ret } // end of method Application::'<JI>GetFunctionPointer' [^0]: IL `switch` is in ECMA-335 §III.3.66, page 392 (page numbers) or page 418 (Go to page…) [^1]: IL `br.s` is in ECMA-335 §III.3.15, page 338 (page numbers) or page 364 (Go to page…)
1 parent bc9f07c commit 8ce221c

File tree

1 file changed

+15
-28
lines changed

1 file changed

+15
-28
lines changed

src/Xamarin.Android.Build.Tasks/Utilities/ManagedMarshalMethodsLookupGenerator.cs

Lines changed: 15 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -74,25 +74,20 @@ MethodDefinition GenerateGetFunctionPointer (ManagedMarshalMethodsLookupInfo.Cla
7474
targets [methodLookup.Index] = Instruction.Create (OpCodes.Ldftn, methodLookup.NativeCallbackWrapper);
7575
}
7676

77-
var invalidUcoTarget = Instruction.Create (OpCodes.Nop);
78-
7977
var il = getFunctionPointerMethod.Body.GetILProcessor ();
8078
il.Emit (OpCodes.Ldarg, methodIndexParameter);
8179
il.Emit (OpCodes.Switch, targets);
8280

83-
il.Emit (OpCodes.Br_S, invalidUcoTarget);
81+
// The default target
82+
il.Emit (OpCodes.Ldc_I4_M1);
83+
il.Emit (OpCodes.Conv_I);
84+
il.Emit (OpCodes.Ret);
8485

8586
for (var k = 0; k < targets.Length; k++) {
8687
il.Append (targets [k]);
8788
il.Emit (OpCodes.Ret);
8889
}
8990

90-
// no hit? this shouldn't happen
91-
il.Append (invalidUcoTarget);
92-
il.Emit (OpCodes.Ldc_I4_M1);
93-
il.Emit (OpCodes.Conv_I);
94-
il.Emit (OpCodes.Ret);
95-
9691
// in the case of private/private protected/protected nested types, we need to generate proxy method(s) in the parent type(s)
9792
// so that we can call the actual GetFunctionPointer method from our assembly-level GetFunctionPointer method
9893
while (declaringType.IsNested && (declaringType.IsNestedPrivate || declaringType.IsNestedFamily || declaringType.IsNestedFamilyAndAssembly)) {
@@ -153,21 +148,17 @@ MethodDefinition GenerateGetFunctionPointerPerAssembly (ManagedMarshalMethodsLoo
153148
il.Emit (OpCodes.Ldarg, classIndexParameter);
154149
il.Emit (OpCodes.Switch, targets);
155150

156-
var defaultTarget = Instruction.Create (OpCodes.Nop);
157-
il.Emit (OpCodes.Br, defaultTarget);
151+
// "Default target"
152+
il.Emit (OpCodes.Pop); // methodIndex
153+
il.Emit (OpCodes.Ldc_I4_M1);
154+
il.Emit (OpCodes.Conv_I);
155+
il.Emit (OpCodes.Ret);
158156

159157
for (int i = 0; i < targets.Length; i++) {
160158
il.Append (targets [i]); // call
161159
il.Emit (OpCodes.Ret);
162160
}
163161

164-
// no hit? this shouldn't happen
165-
il.Append (defaultTarget);
166-
il.Emit (OpCodes.Pop); // methodIndex
167-
il.Emit (OpCodes.Ldc_I4_M1);
168-
il.Emit (OpCodes.Conv_I);
169-
il.Emit (OpCodes.Ret);
170-
171162
return getFunctionPointerMethod;
172163
}
173164

@@ -196,21 +187,17 @@ void GenerateGlobalGetFunctionPointerMethod ()
196187
il.Emit (OpCodes.Ldarg_0); // assemblyIndex
197188
il.Emit (OpCodes.Switch, targets);
198189

199-
var defaultTarget = Instruction.Create (OpCodes.Nop);
200-
il.Emit (OpCodes.Br_S, defaultTarget);
201-
202-
for (int i = 0; i < targets.Length; i++) {
203-
il.Append (targets [i]); // call
204-
il.Emit (OpCodes.Ret);
205-
}
206-
207-
// no hit? this shouldn't happen
208-
il.Append (defaultTarget);
190+
// The default target
209191
il.Emit (OpCodes.Pop); // methodIndex
210192
il.Emit (OpCodes.Pop); // classIndex
211193
il.Emit (OpCodes.Ldc_I4_M1);
212194
il.Emit (OpCodes.Conv_I);
213195
il.Emit (OpCodes.Ret);
196+
197+
for (int i = 0; i < targets.Length; i++) {
198+
il.Append (targets [i]); // call
199+
il.Emit (OpCodes.Ret);
200+
}
214201
}
215202

216203
TypeReference ImportReference (ModuleDefinition module, Type type)

0 commit comments

Comments
 (0)