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.)
@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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With