Skip to content

Commit c1912ce

Browse files
vzarytovskii0101github-actions[bot]
authored
Fix #17903 (#18025)
Co-authored-by: vzarytovskii <1260985+vzarytovskii@users.noreply.github.com> Co-authored-by: Petr Pokorny <petr@innit.cz> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent 3aa3f72 commit c1912ce

File tree

7 files changed

+137
-29
lines changed

7 files changed

+137
-29
lines changed

docs/release-notes/.FSharp.Compiler.Service/9.0.200.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,16 @@
1111
* Fix internal error when analyzing incomplete inherit member ([PR #17905](https://github.com/dotnet/fsharp/pull/17905))
1212
* Add warning when downcasting from nullable type to non-nullable ([PR #17965](https://github.com/dotnet/fsharp/pull/17965))
1313
* Fix missing nullness warning in case of method resolution multiple candidates ([PR #17917](https://github.com/dotnet/fsharp/pull/17918))
14-
* Fix failure to use bound values in `when` clauses of `try-with` in `seq` expressions ([# 17990](https://github.com/dotnet/fsharp/pull/17990))
14+
* Fix failure to use bound values in `when` clauses of `try-with` in `seq` expressions ([PR #17990](https://github.com/dotnet/fsharp/pull/17990))
15+
* Fix locals allocating for the special `copyOfStruct` defensive copy ([PR #18025](https://github.com/dotnet/fsharp/pull/18025))
1516

1617
### Added
1718

1819
* Let `dotnet fsi --help` print a link to the documentation website. ([PR #18006](https://github.com/dotnet/fsharp/pull/18006))
1920
* Deprecate places where `seq` can be omitted. ([Language suggestion #1033](https://github.com/fsharp/fslang-suggestions/issues/1033), [PR #17772](https://github.com/dotnet/fsharp/pull/17772))
2021
* Support literal attribute on decimals ([PR #17769](https://github.com/dotnet/fsharp/pull/17769))
21-
* Added type conversions cache, only enabled for compiler runs, guarded by language version preview ([PR#17668](https://github.com/dotnet/fsharp/pull/17668))
22-
* Added project property ParallelCompilation which turns on graph based type checking, parallel ILXGen and parallel optimization. By default on for users of langversion=preview ([PR#17948](https://github.com/dotnet/fsharp/pull/17948))
22+
* Added type conversions cache, only enabled for compiler runs, guarded by language version preview ([PR #17668](https://github.com/dotnet/fsharp/pull/17668))
23+
* Added project property ParallelCompilation which turns on graph based type checking, parallel ILXGen and parallel optimization. By default on for users of langversion=preview ([PR #17948](https://github.com/dotnet/fsharp/pull/17948))
2324

2425
### Changed
2526

src/Compiler/CodeGen/IlxGen.fs

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2461,7 +2461,7 @@ let FeeFeeInstr (cenv: cenv) doc =
24612461
type CodeGenBuffer(m: range, mgbuf: AssemblyBuilder, methodName, alreadyUsedArgs: int) =
24622462

24632463
let g = mgbuf.cenv.g
2464-
let locals = ResizeArray<(string * (Mark * Mark)) list * ILType * bool>(10)
2464+
let locals = ResizeArray<(string * (Mark * Mark)) list * ILType * bool * bool>(10)
24652465
let codebuf = ResizeArray<ILInstr>(200)
24662466
let exnSpecs = ResizeArray<ILExceptionSpec>(10)
24672467

@@ -2651,18 +2651,18 @@ type CodeGenBuffer(m: range, mgbuf: AssemblyBuilder, methodName, alreadyUsedArgs
26512651

26522652
member _.PreallocatedArgCount = alreadyUsedArgs
26532653

2654-
member _.AllocLocal(ranges, ty, isFixed) =
2654+
member _.AllocLocal(ranges, ty, isFixed, canBeReallocd) =
26552655
let j = locals.Count
2656-
locals.Add((ranges, ty, isFixed))
2656+
locals.Add((ranges, ty, isFixed, canBeReallocd))
26572657
j
26582658

2659-
member cgbuf.ReallocLocal(cond, ranges, ty, isFixed) =
2659+
member cgbuf.ReallocLocal(cond, ranges, ty, isFixed, canBeReallocd) =
26602660
match ResizeArray.tryFindIndexi cond locals with
26612661
| Some j ->
2662-
let prevRanges, _, isFixed = locals[j]
2663-
locals[j] <- ((ranges @ prevRanges), ty, isFixed)
2662+
let prevRanges, _, isFixed, _ = locals[j]
2663+
locals[j] <- ((ranges @ prevRanges), ty, isFixed, canBeReallocd)
26642664
j, true
2665-
| None -> cgbuf.AllocLocal(ranges, ty, isFixed), false
2665+
| None -> cgbuf.AllocLocal(ranges, ty, isFixed, canBeReallocd), false
26662666

26672667
member _.Close() =
26682668

@@ -2772,7 +2772,10 @@ let CodeGenThen (cenv: cenv) mgbuf (entryPointInfo, methodName, eenv, alreadyUse
27722772
&& not cenv.options.localOptimizationsEnabled
27732773
->
27742774
let ilTy = selfArg.Type |> GenType cenv m eenv.tyenv
2775-
let idx = cgbuf.AllocLocal([ (selfArg.LogicalName, (start, finish)) ], ilTy, false)
2775+
2776+
let idx =
2777+
cgbuf.AllocLocal([ (selfArg.LogicalName, (start, finish)) ], ilTy, false, true)
2778+
27762779
cgbuf.EmitStartOfHiddenCode()
27772780
CG.EmitInstrs cgbuf (pop 0) Push0 [ mkLdarg0; I_stloc(uint16 idx) ]
27782781
| _ -> ()
@@ -2792,7 +2795,7 @@ let CodeGenThen (cenv: cenv) mgbuf (entryPointInfo, methodName, eenv, alreadyUse
27922795

27932796
let localDebugSpecs: ILLocalDebugInfo list =
27942797
locals
2795-
|> List.mapi (fun i (nms, _, _isFixed) -> List.map (fun nm -> (i, nm)) nms)
2798+
|> List.mapi (fun i (nms, _, _isFixed, _canBeReallocd) -> List.map (fun nm -> (i, nm)) nms)
27962799
|> List.concat
27972800
|> List.map (fun (i, (nm, (start, finish))) ->
27982801
{
@@ -2802,7 +2805,7 @@ let CodeGenThen (cenv: cenv) mgbuf (entryPointInfo, methodName, eenv, alreadyUse
28022805

28032806
let ilLocals =
28042807
locals
2805-
|> List.map (fun (infos, ty, isFixed) ->
2808+
|> List.map (fun (infos, ty, isFixed, _canBeReallocd) ->
28062809
let loc =
28072810
// in interactive environment, attach name and range info to locals to improve debug experience
28082811
if cenv.options.isInteractive && cenv.options.generateDebugSymbols then
@@ -3441,7 +3444,7 @@ and GenLinearExpr cenv cgbuf eenv expr sequel preSteps (contf: FakeUnit -> FakeU
34413444
contf Fake
34423445

34433446
| Expr.Let(bind, body, _, _) ->
3444-
// Process the debug point and see if there's a replacement technique to process this expression
3447+
34453448
if preSteps && GenExprPreSteps cenv cgbuf eenv expr sequel then
34463449
contf Fake
34473450
else
@@ -3837,7 +3840,7 @@ and UnionCodeGen (cgbuf: CodeGenBuffer) =
38373840
CG.GenerateDelayMark cgbuf "unionCodeGenMark"
38383841

38393842
member _.GenLocal ilTy =
3840-
cgbuf.AllocLocal([], ilTy, false) |> uint16
3843+
cgbuf.AllocLocal([], ilTy, false, true) |> uint16
38413844

38423845
member _.SetMarkToHere m = CG.SetMarkToHere cgbuf m
38433846

@@ -4673,7 +4676,7 @@ and GenIndirectCall cenv cgbuf eenv (funcTy, tyargs, curriedArgs, m) sequel =
46734676
let instrs =
46744677
EraseClosures.mkCallFunc
46754678
cenv.ilxPubCloEnv
4676-
(fun ty -> cgbuf.AllocLocal([], ty, false) |> uint16)
4679+
(fun ty -> cgbuf.AllocLocal([], ty, false, true) |> uint16)
46774680
eenv.tyenv.Count
46784681
isTailCall
46794682
ilxClosureApps
@@ -9822,21 +9825,24 @@ and GenGetLocalVRef cenv cgbuf eenv m (vref: ValRef) storeSequel =
98229825
and GenStoreVal cgbuf eenv m (vspec: Val) =
98239826
GenSetStorage vspec.Range cgbuf (StorageForVal m vspec eenv)
98249827

9828+
and CanRealloc isFixed eenv ty i (_, ty2, isFixed2, canBeReallocd) =
9829+
canBeReallocd
9830+
&& not isFixed2
9831+
&& not isFixed
9832+
&& not (IntMap.mem i eenv.liveLocals)
9833+
&& (ty = ty2)
9834+
98259835
/// Allocate IL locals
98269836
and AllocLocal cenv cgbuf eenv compgen (v, ty, isFixed) (scopeMarks: Mark * Mark) : int * _ * _ =
98279837
// The debug range for the local
98289838
let ranges = if compgen then [] else [ (v, scopeMarks) ]
98299839
// Get an index for the local
98309840
let j, realloc =
98319841
if cenv.options.localOptimizationsEnabled then
9832-
cgbuf.ReallocLocal(
9833-
(fun i (_, ty2, isFixed2) -> not isFixed2 && not isFixed && not (IntMap.mem i eenv.liveLocals) && (ty = ty2)),
9834-
ranges,
9835-
ty,
9836-
isFixed
9837-
)
9842+
let canBeReallocd = not (v = WellKnownNames.CopyOfStruct)
9843+
cgbuf.ReallocLocal(CanRealloc isFixed eenv ty, ranges, ty, isFixed, canBeReallocd)
98389844
else
9839-
cgbuf.AllocLocal(ranges, ty, isFixed), false
9845+
cgbuf.AllocLocal(ranges, ty, isFixed, false), false
98409846

98419847
j,
98429848
realloc,

src/Compiler/TypedTree/TypedTree.fs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ open FSharp.Compiler.TypeProviders
3232
open FSharp.Core.CompilerServices
3333
#endif
3434

35+
[<RequireQualifiedAccess>]
36+
module WellKnownNames =
37+
/// Special name for the defensive copy of a struct, we use it in situations like when we get an address of a field in ax-assembly scenario.
38+
let [<Literal>] CopyOfStruct = "copyOfStruct"
39+
3540
type Stamp = int64
3641

3742
type StampMap<'T> = Map<Stamp, 'T>

src/Compiler/TypedTree/TypedTree.fsi

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@ open FSharp.Compiler.TypeProviders
1717
open FSharp.Compiler.Xml
1818
open FSharp.Core.CompilerServices
1919

20+
[<RequireQualifiedAccess>]
21+
module WellKnownNames =
22+
/// Special name for the defensive copy of a struct, we use it in situations like when we get an address of a field in ax-assembly scenario.
23+
[<Literal>]
24+
val CopyOfStruct: string = "copyOfStruct"
25+
2026
val getNameOfScopeRef: sref: ILScopeRef -> string
2127

2228
type Stamp = int64

src/Compiler/TypedTree/TypedTreeOps.fs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6015,14 +6015,14 @@ and remapExprImpl (ctxt: RemapContext) (compgen: ValCopyFlag) (tmenv: Remap) exp
60156015
// This is "ok", in the sense that it is always valid to fix these up to be uses
60166016
// of a temporary local, e.g.
60176017
// &(E.RF) --> let mutable v = E.RF in &v
6018-
6018+
60196019
| Expr.Op (TOp.ValFieldGetAddr (rfref, readonly), tinst, [arg], m) when
60206020
not rfref.RecdField.IsMutable &&
60216021
not (entityRefInThisAssembly ctxt.g.compilingFSharpCore rfref.TyconRef) ->
60226022

60236023
let tinst = remapTypes tmenv tinst
60246024
let arg = remapExprImpl ctxt compgen tmenv arg
6025-
let tmp, _ = mkMutableCompGenLocal m "copyOfStruct" (actualTyOfRecdFieldRef rfref tinst)
6025+
let tmp, _ = mkMutableCompGenLocal m WellKnownNames.CopyOfStruct (actualTyOfRecdFieldRef rfref tinst)
60266026
mkCompGenLet m tmp (mkRecdFieldGetViaExprAddr (arg, rfref, tinst, m)) (mkValAddr m readonly (mkLocalValRef tmp))
60276027

60286028
| Expr.Op (TOp.UnionCaseFieldGetAddr (uref, cidx, readonly), tinst, [arg], m) when
@@ -6031,7 +6031,7 @@ and remapExprImpl (ctxt: RemapContext) (compgen: ValCopyFlag) (tmenv: Remap) exp
60316031

60326032
let tinst = remapTypes tmenv tinst
60336033
let arg = remapExprImpl ctxt compgen tmenv arg
6034-
let tmp, _ = mkMutableCompGenLocal m "copyOfStruct" (actualTyOfUnionFieldRef uref cidx tinst)
6034+
let tmp, _ = mkMutableCompGenLocal m WellKnownNames.CopyOfStruct (actualTyOfUnionFieldRef uref cidx tinst)
60356035
mkCompGenLet m tmp (mkUnionCaseFieldGetProvenViaExprAddr (arg, uref, tinst, cidx, m)) (mkValAddr m readonly (mkLocalValRef tmp))
60366036

60376037
| Expr.Op (op, tinst, args, m) ->
@@ -7252,8 +7252,8 @@ let rec mkExprAddrOfExprAux g mustTakeAddress useReadonlyForGenericArrayAddress
72527252
// Take a defensive copy
72537253
let tmp, _ =
72547254
match mut with
7255-
| NeverMutates -> mkCompGenLocal m "copyOfStruct" ty
7256-
| _ -> mkMutableCompGenLocal m "copyOfStruct" ty
7255+
| NeverMutates -> mkCompGenLocal m WellKnownNames.CopyOfStruct ty
7256+
| _ -> mkMutableCompGenLocal m WellKnownNames.CopyOfStruct ty
72577257

72587258
// This local is special in that it ignore byref scoping rules.
72597259
tmp.SetIgnoresByrefScope()

tests/FSharp.Compiler.ComponentTests/EmittedIL/AssemblyBoundary/AssemblyBoundary.fs

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,3 +88,93 @@ module AssemblyBoundary =
8888
compilation
8989
|> withReferences [typeLib02]
9090
|> verifyCompileAndExecution
91+
92+
[<Fact>]
93+
let ``copyOfStruct doesn't reallocate local in case of cross-assembly inlining`` () =
94+
let library =
95+
FSharp """
96+
namespace Library
97+
98+
#nowarn "346"
99+
100+
[<Struct>]
101+
type ID (value : string) =
102+
member _.Value = value
103+
member inline this.Hello(other: ID) = System.Console.WriteLine(this.Value + " " + other.Value)
104+
105+
type ID2 = { Value : ID } with
106+
member inline this.Hello(other: ID2) = this.Value.Hello other.Value
107+
"""
108+
|> asLibrary
109+
|> withName "Library"
110+
|> withOptimize
111+
112+
let program =
113+
FSharp """
114+
open Library
115+
116+
[<EntryPoint>]
117+
let main _ =
118+
119+
let aBar = { Value = ID "first" }
120+
let bBar = { Value = ID "second" }
121+
122+
aBar.Hello(bBar)
123+
124+
0
125+
"""
126+
|> withReferences [library]
127+
|> withOptimize
128+
129+
let compilation =
130+
program
131+
|> asExe
132+
|> compile
133+
134+
compilation
135+
|> shouldSucceed
136+
|> run
137+
|> shouldSucceed
138+
|> verifyOutputContains [| "first second" |]
139+
|> verifyIL
140+
[ """
141+
.method public static int32 main(string[] _arg1) cil managed
142+
{
143+
.entrypoint
144+
.custom instance void [FSharp.Core]Microsoft.FSharp.Core.EntryPointAttribute::.ctor() = ( 01 00 00 00 )
145+
146+
.maxstack 5
147+
.locals init (class [Library]Library.ID2 V_0,
148+
class [Library]Library.ID2 V_1,
149+
valuetype [Library]Library.ID& V_2,
150+
valuetype [Library]Library.ID V_3,
151+
valuetype [Library]Library.ID V_4)
152+
IL_0000: ldstr "first"
153+
IL_0005: newobj instance void [Library]Library.ID::.ctor(string)
154+
IL_000a: newobj instance void [Library]Library.ID2::.ctor(valuetype [Library]Library.ID)
155+
IL_000f: stloc.0
156+
IL_0010: ldstr "second"
157+
IL_0015: newobj instance void [Library]Library.ID::.ctor(string)
158+
IL_001a: newobj instance void [Library]Library.ID2::.ctor(valuetype [Library]Library.ID)
159+
IL_001f: stloc.1
160+
IL_0020: ldloc.0
161+
IL_0021: call instance valuetype [Library]Library.ID [Library]Library.ID2::get_Value()
162+
IL_0026: stloc.3
163+
IL_0027: ldloca.s V_3
164+
IL_0029: stloc.2
165+
IL_002a: ldloc.1
166+
IL_002b: call instance valuetype [Library]Library.ID [Library]Library.ID2::get_Value()
167+
IL_0030: stloc.s V_4
168+
IL_0032: ldloc.2
169+
IL_0033: call instance string [Library]Library.ID::get_Value()
170+
IL_0038: ldstr " "
171+
IL_003d: ldloca.s V_4
172+
IL_003f: call instance string [Library]Library.ID::get_Value()
173+
IL_0044: call string [runtime]System.String::Concat(string,
174+
string,
175+
string)
176+
IL_0049: call void [runtime]System.Console::WriteLine(string)
177+
IL_004e: ldc.i4.0
178+
IL_004f: ret
179+
}
180+
"""]

tests/ILVerify/ilverify_FSharp.Compiler.Service_Release_netstandard2.0.bsl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
[IL]: Error [StackUnexpected]: : <StartupCode$FSharp-Compiler-Service>.$FSharpCheckerResults+GetReferenceResolutionStructuredToolTipText@2205::Invoke([FSharp.Core]Microsoft.FSharp.Core.Unit)][offset 0x00000076][found Char] Unexpected type on the stack.
3434
[IL]: Error [StackUnexpected]: : FSharp.Compiler.EditorServices.AssemblyContent+traverseMemberFunctionAndValues@176::Invoke([FSharp.Compiler.Service]FSharp.Compiler.Symbols.FSharpMemberOrFunctionOrValue)][offset 0x0000002B][found Char] Unexpected type on the stack.
3535
[IL]: Error [StackUnexpected]: : FSharp.Compiler.EditorServices.AssemblyContent+traverseEntity@218::GenerateNext([S.P.CoreLib]System.Collections.Generic.IEnumerable`1<FSharp.Compiler.EditorServices.AssemblySymbol>&)][offset 0x000000BB][found Char] Unexpected type on the stack.
36-
[IL]: Error [StackUnexpected]: : FSharp.Compiler.EditorServices.ParsedInput+visitor@1423-11::VisitExpr([FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1<FSharp.Compiler.Syntax.SyntaxNode>, [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<FSharp.Compiler.Syntax.SynExpr,Microsoft.FSharp.Core.FSharpOption`1<FSharp.Compiler.EditorServices.CompletionContext>>, [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<FSharp.Compiler.Syntax.SynExpr,Microsoft.FSharp.Core.FSharpOption`1<FSharp.Compiler.EditorServices.CompletionContext>>, [FSharp.Compiler.Service]FSharp.Compiler.Syntax.SynExpr)][offset 0x00000618][found Char] Unexpected type on the stack.
36+
[IL]: Error [StackUnexpected]: : FSharp.Compiler.EditorServices.ParsedInput+visitor@1423-11::VisitExpr([FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1<FSharp.Compiler.Syntax.SyntaxNode>, [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<FSharp.Compiler.Syntax.SynExpr,Microsoft.FSharp.Core.FSharpOption`1<FSharp.Compiler.EditorServices.CompletionContext>>, [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<FSharp.Compiler.Syntax.SynExpr,Microsoft.FSharp.Core.FSharpOption`1<FSharp.Compiler.EditorServices.CompletionContext>>, [FSharp.Compiler.Service]FSharp.Compiler.Syntax.SynExpr)][offset 0x00000620][found Char] Unexpected type on the stack.
3737
[IL]: Error [StackUnexpected]: : <StartupCode$FSharp-Compiler-Service>.$ServiceLexing+clo@921-525::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<System.Tuple`3<FSharp.Compiler.Parser+token,int32,int32>,Microsoft.FSharp.Core.Unit>)][offset 0x00000032][found Char] Unexpected type on the stack.
3838
[IL]: Error [StackUnexpected]: : <StartupCode$FSharp-Compiler-Service>.$ServiceLexing+clo@921-525::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<System.Tuple`3<FSharp.Compiler.Parser+token,int32,int32>,Microsoft.FSharp.Core.Unit>)][offset 0x0000003B][found Char] Unexpected type on the stack.
3939
[IL]: Error [StackUnexpected]: : <StartupCode$FSharp-Compiler-Service>.$ServiceLexing+clo@921-525::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<System.Tuple`3<FSharp.Compiler.Parser+token,int32,int32>,Microsoft.FSharp.Core.Unit>)][offset 0x00000064][found Char] Unexpected type on the stack.

0 commit comments

Comments
 (0)