Applying MethodImplOptions.AggressiveInlining to F# functions
Asked Answered
G

1

14

The attribute System.Runtime.CompilerServices.MethodImplAttribute can be used to give hints to the JIT compiler about how to handle the decorated method. In particular, the option MethodImplOptions.AggressiveInlining instructs the compiler to inline the affected method if possible. Unfortunately the F# compiler seems to simply ignore this attribute when generating IL.

Example: The following C# code

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int Inc(int x) => x + 1;

is translated to

.method public hidebysig static int32  Inc(int32 x) cil managed aggressiveinlining
{     
    .maxstack  8
    IL_0000:  ldarg.0
    IL_0001:  ldc.i4.1
    IL_0002:  add
    IL_0003:  ret
}

Note the "aggressiveinlining" flag.

This F# code however

[<MethodImpl(MethodImplOptions.AggressiveInlining)>]
let inc x = x + 1

becomes

.method public static int32  inc(int32 x) cil managed
{
    .maxstack  8
    IL_0000:  nop
    IL_0001:  ldarg.0
    IL_0002:  ldc.i4.1
    IL_0003:  add
    IL_0004:  ret
}

No "aggressiveinlining". I also tried to apply the attribute to static and non-static methods of proper classes (type ...), but the result is the same.

If however I apply it to a custom indexer, like so

type Dummy =
    member self.Item
        with [<MethodImpl(MethodImplOptions.AggressiveInlining)>] get x = x + 1

the resulting IL is

.method public hidebysig specialname instance int32 get_Item(int32 x) cil managed
{
    .custom instance void [mscorlib]System.Runtime.CompilerServices.MethodImplAttribute::.ctor(valuetype [mscorlib]System.Runtime.CompilerServices.MethodImplOptions) = ( 01 00 00 01 00 00 00 00 ) 
    .maxstack  8
    IL_0000:  nop
    IL_0001:  ldarg.1
    IL_0002:  ldc.i4.1
    IL_0003:  add
    IL_0004:  ret
}

... though I'm not sure whether that is equivalent to the "aggressiveinling" flag generated by the C# compiler.

Is that behavior desired/expected? Is it a bug in the F# compiler?

(Note: I'm aware of the F# inline keyword, but that only works for F# clients of my library, not C# consumers.)

Genuflection answered 18/10, 2016 at 9:37 Comment(3)
AFAIK this attribute is used by the JIT:er to hint that the method should be inlined. So if that is correct you should check the generate machine code. Note; it's not as simple as viewing disassembly in .NET as with debugger running the JIT:er is much less aggressive.Mediative
Awww, I see what you mean. It seems to missing in the IL meta data. Let me check this later.Mediative
It looks like only PreserveSig, Synchronized, and NoInlining are respected - see ComputeMethodImplAttribs in IlxGen.fsProtonema
M
6

@kvb is correct that it seems the F# compiler seems to strip out the MethodImpl.

ComputeMethodImplAttribs in IlxGen.fs is called to compute the method attributes.

and ComputeMethodImplAttribs cenv (_v:Val) attrs =
    let implflags = 
        match TryFindFSharpAttribute cenv.g cenv.g.attrib_MethodImplAttribute attrs with
        | Some (Attrib(_,_,[ AttribInt32Arg flags ],_,_,_,_))  -> flags
        | _ -> 0x0

    let hasPreserveSigAttr = 
        match TryFindFSharpAttributeOpt cenv.g cenv.g.attrib_PreserveSigAttribute attrs with
        | Some _ -> true
        | _ -> false

    // strip the MethodImpl pseudo-custom attribute    
    // The following method implementation flags are used here
    // 0x80 - hasPreserveSigImplFlag
    // 0x20 - synchronize
    // (See ECMA 335, Partition II, section 23.1.11 - Flags for methods [MethodImplAttributes]) 
    let attrs = attrs 
                    |> List.filter (IsMatchingFSharpAttribute cenv.g cenv.g.attrib_MethodImplAttribute >> not) 
                        |> List.filter (IsMatchingFSharpAttributeOpt cenv.g cenv.g.attrib_PreserveSigAttribute >> not)
    let hasPreserveSigImplFlag = ((implflags &&& 0x80) <> 0x0) || hasPreserveSigAttr
    let hasSynchronizedImplFlag = (implflags &&& 0x20) <> 0x0
    let hasNoInliningImplFlag = (implflags &&& 0x08) <> 0x0
    hasPreserveSigImplFlag, hasSynchronizedImplFlag, hasNoInliningImplFlag, attrs

Looking more closely around row: 4990:

    let attrs = attrs 
                    |> List.filter (IsMatchingFSharpAttribute cenv.g cenv.g.attrib_MethodImplAttribute >> not) 
                        |> List.filter (IsMatchingFSharpAttributeOpt cenv.g cenv.g.attrib_PreserveSigAttribute >> not)

The first filter filters away MethodImplAttribute.

Now, I was looking a bit trying to find the rationale but this code stems back to latkin initial commit. I do think it's probably wrong to strip away the MethodImpl especially for AggressiveInlining which I believe affects the JIT:ing therefore it needs to be in the assembly.

I would recommend registering an issue. Perhaps you can get an explaination at least.

Mediative answered 18/10, 2016 at 17:4 Comment(4)
Filtering out the attribute makes sense because it's really a pseudo-custom-attribute and shouldn't be present as an actual custom attribute in the metadata (see e.g. weblog.ikvm.net/2008/11/25/PseudoCustomAttributes.aspx).Protonema
I'll open an issue on the GitHub repo.Genuflection
That doesn't seem like the correct place to put the attribute. #20517603Mawkin
The issue on GitHub can be found at: github.com/dotnet/fsharp/issues/1637Alleviator

© 2022 - 2024 — McMap. All rights reserved.