Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Avoid using br.s when generating managed UCO lookup tables #9969

Merged
merged 1 commit into from
Mar 26, 2025

Conversation

simonrozsival
Copy link
Member

@simonrozsival simonrozsival commented Mar 26, 2025

Contributes to #9962

The ManagedMarshalMethodsLookupGenerator generates IL of a switch statement 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 which would jump over all the individual cases to the default case. When the switch got too large (19 cases), the br.s became insufficient (the offset for br.s is a signed 8-bit number).

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'

@ivanpovazan
Copy link
Member

I know it is not related to this PR, but should we also cleanup - remove these lines:

<!-- NOTE: temporarily disable for CoreCLR for now, until we have an implementation that works
with the new runtime -->
<AndroidEnableMarshalMethods Condition=" '$(AndroidEnableMarshalMethods)' == '' and '$(_AndroidRuntime)' == 'CoreCLR' ">False</AndroidEnableMarshalMethods>

As they are not relevant anymore?

Line:

<AndroidEnableMarshalMethods Condition=" '$(AndroidEnableMarshalMethods)' == '' and '$(_AndroidRuntime)' != 'NativeAOT' ">True</AndroidEnableMarshalMethods>

already sets AndroidEnableMarshalMethods=true for non-NativeAOT runtimes

@simonrozsival
Copy link
Member Author

@ivanpovazan that should definitely be changed. I think we should do it in a separate PR though.

@jonpryor jonpryor merged commit 8ce221c into main Mar 26, 2025
57 of 59 checks passed
@jonpryor jonpryor deleted the dev/simonrozsival/fix-managed-uco-lookup-brs branch March 26, 2025 18:11
jonathanpeppers pushed a commit that referenced this pull request Mar 27, 2025
…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…)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants