Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions src/fsharp/IlxGen.fs
Original file line number Diff line number Diff line change
Expand Up @@ -967,6 +967,9 @@ and IlxGenEnv =

/// Are we inside of a recursive let binding, while loop, or a for loop?
isInLoop: bool

/// Indicates that the .locals init flag should be set on a method and all its nested methods and lambdas
initLocals: bool
}

override _.ToString() = "<IlxGenEnv>"
Expand Down Expand Up @@ -2105,7 +2108,7 @@ let CodeGenMethod cenv mgbuf (entryPointInfo, methodName, eenv, alreadyUsedArgs,
let maxStack = maxStack + 2

// Build an Abstract IL method
instrs, mkILMethodBody (true, locals, maxStack, code, sourceRange)
instrs, mkILMethodBody (eenv.initLocals, locals, maxStack, code, sourceRange)

let StartDelayedLocalScope nm cgbuf =
let startScope = CG.GenerateDelayMark cgbuf ("start_" + nm)
Expand Down Expand Up @@ -5609,7 +5612,8 @@ and GenBindingAfterDebugPoint cenv cgbuf eenv sp (TBind(vspec, rhsExpr, _)) star
| Some _, Some e -> cgbuf.mgbuf.AddReflectedDefinition(vspec, e)
| _ -> ()

let eenv = {eenv with letBoundVars= (mkLocalValRef vspec) :: eenv.letBoundVars}
let eenv = { eenv with letBoundVars = (mkLocalValRef vspec) :: eenv.letBoundVars;
initLocals = eenv.initLocals && (match vspec.ApparentEnclosingEntity with Parent ref -> not (HasFSharpAttribute g g.attrib_SkipLocalsInitAttribute ref.Attribs) | _ -> true) }

let access = ComputeMethodAccessRestrictedBySig eenv vspec

Expand Down Expand Up @@ -6181,6 +6185,7 @@ and GenMethodForBinding
let eenvForMeth = eenvForMeth |> AddStorageForLocalWitnesses (methLambdaWitnessInfos |> List.mapi (fun i w -> (w, Arg (numArgsUsed+i))))
let numArgsUsed = numArgsUsed + methLambdaWitnessInfos.Length
let eenvForMeth = eenvForMeth |> AddStorageForLocalVals cenv.g (List.mapi (fun i v -> (v, Arg (numArgsUsed+i))) nonUnitNonSelfMethodVars)
let eenvForMeth = if eenvForMeth.initLocals && HasFSharpAttribute g g.attrib_SkipLocalsInitAttribute v.Attribs then { eenvForMeth with initLocals = false } else eenvForMeth
eenvForMeth

let tailCallInfo =
Expand Down Expand Up @@ -6953,7 +6958,7 @@ and GenModuleBinding cenv (cgbuf: CodeGenBuffer) (qname: QualifiedNameOfFile) la

let eenvinner =
if mspec.IsNamespace then eenv else
{eenv with cloc = CompLocForFixedModule cenv.opts.fragName qname.Text mspec }
{ eenv with cloc = CompLocForFixedModule cenv.opts.fragName qname.Text mspec; initLocals = eenv.initLocals && not (HasFSharpAttribute cenv.g cenv.g.attrib_SkipLocalsInitAttribute mspec.Attribs) }

// Create the class to hold the contents of this module. No class needed if
// we're compiling it as a namespace.
Expand Down Expand Up @@ -8044,7 +8049,8 @@ let GetEmptyIlxGenEnv (g: TcGlobals) ccu =
innerVals = []
sigToImplRemapInfo = [] (* "module remap info" *)
withinSEH = false
isInLoop = false }
isInLoop = false
initLocals = true }

type IlxGenResults =
{ ilTypeDefs: ILTypeDef list
Expand Down
1 change: 1 addition & 0 deletions src/fsharp/TcGlobals.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1193,6 +1193,7 @@ type public TcGlobals(compilingFslib: bool, ilg:ILGlobals, fslibCcu: CcuThunk, d
member val attrib_CallerLineNumberAttribute = findSysAttrib "System.Runtime.CompilerServices.CallerLineNumberAttribute"
member val attrib_CallerFilePathAttribute = findSysAttrib "System.Runtime.CompilerServices.CallerFilePathAttribute"
member val attrib_CallerMemberNameAttribute = findSysAttrib "System.Runtime.CompilerServices.CallerMemberNameAttribute"
member val attrib_SkipLocalsInitAttribute = findSysAttrib "System.Runtime.CompilerServices.SkipLocalsInitAttribute"
Copy link
Contributor Author

@kerams kerams Apr 3, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not quite sure if this shouldn't be tryFindSysAttrib. Attributes above use both find and try find and I can't tell what the deciding factor is. It does not throw even on net472, so I guess it's fine?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fine. You can check to see if you can deref it. If you can't, then it couldn't find it.


member val attrib_ProjectionParameterAttribute = mk_MFCore_attrib "ProjectionParameterAttribute"
member val attrib_CustomOperationAttribute = mk_MFCore_attrib "CustomOperationAttribute"
Expand Down
2 changes: 1 addition & 1 deletion src/fsharp/ilx/EraseClosures.fs
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@ let convILMethodBody (thisClo, boxReturnTy) (il: ILMethodBody) =
match boxReturnTy with
| None -> code
| Some ty -> morphILInstrsInILCode (convReturnInstr ty) code
{il with MaxStack=newMax; IsZeroInit=true; Code= code }
{ il with MaxStack = newMax; Code = code }

let convMethodBody thisClo = function
| MethodBody.IL il ->
Expand Down
133 changes: 133 additions & 0 deletions tests/FSharp.Compiler.ComponentTests/EmittedIL/SkipLocalsInit.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.

namespace FSharp.Compiler.ComponentTests.EmittedIL

open Xunit
open FSharp.Test.Utilities.Compiler

#if NETCOREAPP
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That should be good testing wise, I'll see what we can do with the x-framework IL validation.

Copy link
Contributor Author

@kerams kerams May 6, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be great if you could pass TFM to compile because the .NET Framework version of the compiler needs to be able to compile code like this, so it makes sense to test it too, but I understand a bit of infrastructure plumbing work may be required. Plus it will also help with cases where binaries for different TFMs end up with slightly different IL, making these tests tricky, as I've mentioned elsewhere.

module ``SkipLocalsInit`` =
[<Fact>]
let ``Init in function and closure not emitted when applied on function``() =
FSharp """
module SkipLocalsInit
[<System.Runtime.CompilerServices.SkipLocalsInitAttribute>]
let x () =
[||] |> Array.filter (fun x -> let y = "".Length in y + y = x) |> ignore
"""
|> compile
|> shouldSucceed
|> verifyIL ["""
.method public static void x() cil managed
{
.custom instance void [runtime]System.Runtime.CompilerServices.SkipLocalsInitAttribute::.ctor() = ( 01 00 00 00 )
.maxstack 4
.locals (int32[] V_0)
"""

"""
.method public strict virtual instance bool
Invoke(int32 x) cil managed
{
.maxstack 6
.locals (int32 V_0)"""]

[<Fact>]
let ``Init in static method not emitted when applied on class``() =
FSharp """
module SkipLocalsInit
[<System.Runtime.CompilerServices.SkipLocalsInitAttribute>]
type X () =
static member Y () =
let x = "ssa".Length
x + x
"""
|> compile
|> shouldSucceed
|> verifyIL ["""
.custom instance void [runtime]System.Runtime.CompilerServices.SkipLocalsInitAttribute::.ctor() = ( 01 00 00 00 )
.custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 03 00 00 00 00 00 )
"""

"""
.method public static int32 Y() cil managed
{
.maxstack 4
.locals (int32 V_0)"""]

[<Fact>]
let ``Init in static method and function not emitted when applied on module``() =
FSharp """
[<System.Runtime.CompilerServices.SkipLocalsInitAttribute>]
module SkipLocalsInit
let x () =
let x = "ssa".Length
x + x
type X () =
static member Y () =
let x = "ssa".Length
x + x
"""
|> compile
|> shouldSucceed
|> verifyIL ["""
.custom instance void [runtime]System.Runtime.CompilerServices.SkipLocalsInitAttribute::.ctor() = ( 01 00 00 00 )
.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 )
"""

"""
.method public static int32 x() cil managed
{
.maxstack 4
.locals (int32 V_0)
"""

"""
.method public static int32 Y() cil managed
{
.maxstack 4
.locals (int32 V_0)"""]

[<Fact>]
let ``Init in method and closure not emitted when applied on method``() =
FSharp """
module SkipLocalsInit
type X () =
[<System.Runtime.CompilerServices.SkipLocalsInitAttribute>]
member _.Y () =
[||] |> Array.filter (fun x -> let y = "".Length in y + y = x) |> ignore
let x = "ssa".Length
x + x
"""
|> compile
|> shouldSucceed
|> verifyIL ["""
.method public hidebysig instance int32
Y() cil managed
{
.custom instance void [runtime]System.Runtime.CompilerServices.SkipLocalsInitAttribute::.ctor() = ( 01 00 00 00 )
.maxstack 4
.locals (int32[] V_0,
int32 V_1)
"""

"""
.method public strict virtual instance bool
Invoke(int32 x) cil managed
{
.maxstack 6
.locals (int32 V_0)
"""]
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
<Compile Include="EmittedIL\Operators.fs" />
<Compile Include="EmittedIL\Literals.fs" />
<Compile Include="EmittedIL\Misc.fs" />
<Compile Include="EmittedIL\SkipLocalsInit.fs" />
<Compile Include="EmittedIL\TailCalls.fs" />
<Compile Include="EmittedIL\TupleElimination.fs" />
<Compile Include="ErrorMessages\TypeEqualsMissingTests.fs" />
Expand Down