Skip to content

Commit bc88b51

Browse files
authored
Add attribute to prevent fsc inlining but allow JIT inlining (#14235)
* Add attribute to prevent fsc inlining but allow JIT inlining * Work around IL checker shortcomings * Update surface area * Rename NoFscInliningAttribute
1 parent 947f2bb commit bc88b51

File tree

7 files changed

+153
-11
lines changed

7 files changed

+153
-11
lines changed

src/Compiler/Checking/CheckExpressions.fs

+19-11
Original file line numberDiff line numberDiff line change
@@ -2204,18 +2204,23 @@ module GeneralizationHelpers =
22042204
// ComputeInlineFlag
22052205
//-------------------------------------------------------------------------
22062206

2207-
let ComputeInlineFlag (memFlagsOption: SynMemberFlags option) isInline isMutable m =
2207+
let ComputeInlineFlag (memFlagsOption: SynMemberFlags option) isInline isMutable hasNoCompilerInliningAttribute m =
22082208
let inlineFlag =
2209+
let isCtorOrAbstractSlot =
2210+
match memFlagsOption with
2211+
| None -> false
2212+
| Some x -> (x.MemberKind = SynMemberKind.Constructor) || x.IsDispatchSlot || x.IsOverrideOrExplicitImpl
2213+
22092214
// Mutable values may never be inlined
22102215
// Constructors may never be inlined
22112216
// Calls to virtual/abstract slots may never be inlined
2212-
if isMutable ||
2213-
(match memFlagsOption with
2214-
| None -> false
2215-
| Some x -> (x.MemberKind = SynMemberKind.Constructor) || x.IsDispatchSlot || x.IsOverrideOrExplicitImpl)
2216-
then ValInline.Never
2217-
elif isInline then ValInline.Always
2218-
else ValInline.Optional
2217+
// Values marked with NoCompilerInliningAttribute may never be inlined
2218+
if isMutable || isCtorOrAbstractSlot || hasNoCompilerInliningAttribute then
2219+
ValInline.Never
2220+
elif isInline then
2221+
ValInline.Always
2222+
else
2223+
ValInline.Optional
22192224

22202225
if isInline && (inlineFlag <> ValInline.Always) then
22212226
errorR(Error(FSComp.SR.tcThisValueMayNotBeInlined(), m))
@@ -10281,8 +10286,9 @@ and TcNormalizedBinding declKind (cenv: cenv) env tpenv overallTy safeThisValOpt
1028110286
retAttribs, valAttribs, valSynData
1028210287

1028310288
let isVolatile = HasFSharpAttribute g g.attrib_VolatileFieldAttribute valAttribs
10289+
let hasNoCompilerInliningAttribute = HasFSharpAttribute g g.attrib_NoCompilerInliningAttribute valAttribs
1028410290

10285-
let inlineFlag = ComputeInlineFlag memberFlagsOpt isInline isMutable mBinding
10291+
let inlineFlag = ComputeInlineFlag memberFlagsOpt isInline isMutable hasNoCompilerInliningAttribute mBinding
1028610292

1028710293
let argAttribs =
1028810294
spatsL |> List.map (SynInfo.InferSynArgInfoFromSimplePats >> List.map (SynInfo.AttribsOfArgData >> TcAttrs AttributeTargets.Parameter false))
@@ -11403,8 +11409,9 @@ and AnalyzeAndMakeAndPublishRecursiveValue
1140311409

1140411410
// Allocate the type inference variable for the inferred type
1140511411
let ty = NewInferenceType g
11412+
let hasNoCompilerInliningAttribute = HasFSharpAttribute g g.attrib_NoCompilerInliningAttribute bindingAttribs
1140611413

11407-
let inlineFlag = ComputeInlineFlag memberFlagsOpt isInline isMutable mBinding
11414+
let inlineFlag = ComputeInlineFlag memberFlagsOpt isInline isMutable hasNoCompilerInliningAttribute mBinding
1140811415

1140911416
if isMutable then errorR(Error(FSComp.SR.tcOnlyRecordFieldsAndSimpleLetCanBeMutable(), mBinding))
1141011417

@@ -12020,6 +12027,7 @@ let TcAndPublishValSpec (cenv: cenv, env, containerInfo: ContainerInfo, declKind
1202012027

1202112028
let attrs = TcAttributes cenv env attrTgt synAttrs
1202212029
let newOk = if canInferTypars then NewTyparsOK else NoNewTypars
12030+
let hasNoCompilerInliningAttribute = HasFSharpAttribute g g.attrib_NoCompilerInliningAttribute attrs
1202312031

1202412032
let valinfos, tpenv = TcValSpec cenv env declKind newOk containerInfo memFlagsOpt None tpenv synValSig attrs
1202512033
let denv = env.DisplayEnv
@@ -12028,7 +12036,7 @@ let TcAndPublishValSpec (cenv: cenv, env, containerInfo: ContainerInfo, declKind
1202812036

1202912037
let (ValSpecResult (altActualParent, memberInfoOpt, id, enclosingDeclaredTypars, declaredTypars, ty, prelimValReprInfo, declKind)) = valSpecResult
1203012038

12031-
let inlineFlag = ComputeInlineFlag (memberInfoOpt |> Option.map (fun (PrelimMemberInfo(memberInfo, _, _)) -> memberInfo.MemberFlags)) isInline mutableFlag m
12039+
let inlineFlag = ComputeInlineFlag (memberInfoOpt |> Option.map (fun (PrelimMemberInfo(memberInfo, _, _)) -> memberInfo.MemberFlags)) isInline mutableFlag hasNoCompilerInliningAttribute m
1203212040

1203312041
let freeInType = freeInTypeLeftToRight g false ty
1203412042

src/Compiler/TypedTree/TcGlobals.fs

+1
Original file line numberDiff line numberDiff line change
@@ -1427,6 +1427,7 @@ type TcGlobals(
14271427
member val attrib_MeasureAttribute = mk_MFCore_attrib "MeasureAttribute"
14281428
member val attrib_MeasureableAttribute = mk_MFCore_attrib "MeasureAnnotatedAbbreviationAttribute"
14291429
member val attrib_NoDynamicInvocationAttribute = mk_MFCore_attrib "NoDynamicInvocationAttribute"
1430+
member val attrib_NoCompilerInliningAttribute = mk_MFCore_attrib "NoCompilerInliningAttribute"
14301431
member val attrib_SecurityAttribute = tryFindSysAttrib "System.Security.Permissions.SecurityAttribute"
14311432
member val attrib_SecurityCriticalAttribute = findSysAttrib "System.Security.SecurityCriticalAttribute"
14321433
member val attrib_SecuritySafeCriticalAttribute = findSysAttrib "System.Security.SecuritySafeCriticalAttribute"

src/FSharp.Core/prim-types.fs

+5
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,11 @@ namespace Microsoft.FSharp.Core
369369
type ValueAsStaticPropertyAttribute() =
370370
inherit Attribute()
371371

372+
[<AttributeUsage(AttributeTargets.Method ||| AttributeTargets.Property, AllowMultiple=false)>]
373+
[<Sealed>]
374+
type NoCompilerInliningAttribute() =
375+
inherit Attribute()
376+
372377
[<MeasureAnnotatedAbbreviation>] type float<[<Measure>] 'Measure> = float
373378
[<MeasureAnnotatedAbbreviation>] type float32<[<Measure>] 'Measure> = float32
374379
[<MeasureAnnotatedAbbreviation>] type decimal<[<Measure>] 'Measure> = decimal

src/FSharp.Core/prim-types.fsi

+13
Original file line numberDiff line numberDiff line change
@@ -937,6 +937,19 @@ namespace Microsoft.FSharp.Core
937937
/// or an enclosing module opened.</summary>
938938
member Path: string
939939

940+
/// <summary>Indicates a value or a function that must not be inlined by the F# compiler,
941+
/// but may be inlined by the JIT compiler.</summary>
942+
///
943+
/// <category>Attributes</category>
944+
[<AttributeUsage(AttributeTargets.Method ||| AttributeTargets.Property, AllowMultiple=false)>]
945+
[<Sealed>]
946+
type NoCompilerInliningAttribute =
947+
inherit Attribute
948+
949+
/// <summary>Creates an instance of the attribute</summary>
950+
/// <returns>NoCompilerInliningAttribute</returns>
951+
new: unit -> NoCompilerInliningAttribute
952+
940953
/// <summary>The type of double-precision floating point numbers, annotated with a unit of measure.
941954
/// The unit of measure is erased in compiled code and when values of this type
942955
/// are analyzed using reflection. The type is representationally equivalent to
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.
2+
3+
namespace FSharp.Compiler.ComponentTests.EmittedIL
4+
5+
open Xunit
6+
open FSharp.Test.Compiler
7+
8+
module ``NoCompilerInlining`` =
9+
[<Fact>]
10+
let ``Function marked with NoCompilerInlining is not inlined by the compiler``() =
11+
FSharp """
12+
module NoCompilerInlining
13+
14+
let functionInlined () = 3
15+
16+
[<NoCompilerInliningAttribute>]
17+
let functionNotInlined () = 3
18+
19+
let x () = functionInlined () + functionNotInlined ()
20+
"""
21+
|> compile
22+
|> shouldSucceed
23+
|> verifyIL ["""
24+
.custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 07 00 00 00 00 00 )
25+
.method public static int32 functionInlined() cil managed
26+
{
27+
28+
.maxstack 8
29+
IL_0000: ldc.i4.3
30+
IL_0001: ret
31+
}"""
32+
33+
"""
34+
.method public static int32 functionNotInlined() cil managed
35+
{
36+
.custom instance void [FSharp.Core]Microsoft.FSharp.Core.NoCompilerInliningAttribute::.ctor() = ( 01 00 00 00 )
37+
38+
.maxstack 8
39+
IL_0000: ldc.i4.3
40+
IL_0001: ret
41+
}"""
42+
43+
"""
44+
.method public static int32 x() cil managed
45+
{
46+
47+
.maxstack 8
48+
IL_0000: ldc.i4.3
49+
IL_0001: call int32 NoCompilerInlining::functionNotInlined()
50+
IL_0006: add
51+
IL_0007: ret
52+
}"""]
53+
54+
[<Fact>]
55+
let ``Value marked with NoCompilerInlining is not inlined by the compiler``() =
56+
FSharp """
57+
module NoCompilerInlining
58+
59+
let valueInlined = 3
60+
61+
[<NoCompilerInliningAttribute>]
62+
let valueNotInlined = 3
63+
64+
let x () = valueInlined + valueNotInlined
65+
"""
66+
|> compile
67+
|> shouldSucceed
68+
|> verifyIL ["""
69+
get_valueInlined() cil managed
70+
{
71+
.custom instance void [runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 )
72+
.custom instance void [runtime]System.Diagnostics.DebuggerNonUserCodeAttribute::.ctor() = ( 01 00 00 00 )
73+
74+
.maxstack 8
75+
IL_0000: ldc.i4.3
76+
IL_0001: ret
77+
}"""
78+
79+
"""
80+
get_valueNotInlined() cil managed
81+
{
82+
.custom instance void [runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 )
83+
.custom instance void [runtime]System.Diagnostics.DebuggerNonUserCodeAttribute::.ctor() = ( 01 00 00 00 )
84+
85+
.maxstack 8
86+
IL_0000: ldc.i4.3
87+
IL_0001: ret
88+
}"""
89+
90+
"""
91+
.method public static int32 x() cil managed
92+
{
93+
94+
.maxstack 8
95+
IL_0000: ldc.i4.3
96+
IL_0001: call int32 NoCompilerInlining::get_valueNotInlined()
97+
IL_0006: add
98+
IL_0007: ret
99+
}"""
100+
101+
"""
102+
.property int32 valueInlined()
103+
{
104+
.get int32 NoCompilerInlining::get_valueInlined()
105+
}"""
106+
107+
"""
108+
.property int32 valueNotInlined()
109+
{
110+
.custom instance void [FSharp.Core]Microsoft.FSharp.Core.NoCompilerInliningAttribute::.ctor() = ( 01 00 00 00 )
111+
.get int32 NoCompilerInlining::get_valueNotInlined()
112+
}
113+
"""]

tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj

+1
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@
102102
<Compile Include="EmittedIL\CompilerGeneratedAttributeOnAccessors.fs" />
103103
<Compile Include="EmittedIL\EmptyArray.fs" />
104104
<Compile Include="EmittedIL\Literals.fs" />
105+
<Compile Include="EmittedIL\NoCompilerInlining.fs" />
105106
<Compile Include="EmittedIL\SkipLocalsInit.fs" />
106107
<Compile Include="EmittedIL\StringFormatAndInterpolation.fs" />
107108
<Compile Include="EmittedIL\StructGettersReadOnly.fs" />

tests/FSharp.Core.UnitTests/SurfaceArea.fs

+1
Original file line numberDiff line numberDiff line change
@@ -1533,6 +1533,7 @@ Microsoft.FSharp.Core.MeasureAttribute: Void .ctor()
15331533
Microsoft.FSharp.Core.NoComparisonAttribute: Void .ctor()
15341534
Microsoft.FSharp.Core.NoDynamicInvocationAttribute: Void .ctor()
15351535
Microsoft.FSharp.Core.NoEqualityAttribute: Void .ctor()
1536+
Microsoft.FSharp.Core.NoCompilerInliningAttribute: Void .ctor()
15361537
Microsoft.FSharp.Core.NumericLiterals+NumericLiteralI: System.Object FromInt64Dynamic(Int64)
15371538
Microsoft.FSharp.Core.NumericLiterals+NumericLiteralI: System.Object FromStringDynamic(System.String)
15381539
Microsoft.FSharp.Core.NumericLiterals+NumericLiteralI: T FromInt32[T](Int32)

0 commit comments

Comments
 (0)